Bug 1265279 - Web Manifest: Implement window.oninstall. r=baku
authorMarcos Caceres <marcos@marcosc.com>
Mon, 30 May 2016 18:52:00 +0200
changeset 299644 9143e6d98cdf
parent 299643 b7836822d084
child 299645 6750817f09e1
push id30299
push usercbook@mozilla.com
push date2016-05-31 10:00 +0000
treeherdermozilla-central@864cdd00360c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1265279
milestone49.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 1265279 - Web Manifest: Implement window.oninstall. r=baku * Add tests for window.oninstall * Teach manifestMessages how to fire install event * Test that the install event fired from Parent process
dom/base/nsGkAtomList.h
dom/events/EventNameList.h
dom/ipc/manifestMessages.js
dom/manifest/test/browser.ini
dom/manifest/test/browser_fire_install_event.js
dom/manifest/test/file_reg_install_event.html
dom/manifest/test/file_testserver.sjs
dom/manifest/test/mochitest.ini
dom/manifest/test/test_window_oninstall_event.html
dom/webidl/Window.webidl
widget/EventMessageList.h
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -488,16 +488,17 @@ GK_ATOM(inherit, "inherit")
 GK_ATOM(inherits, "inherits")
 GK_ATOM(inheritstyle, "inheritstyle")
 GK_ATOM(initial_scale, "initial-scale")
 GK_ATOM(input, "input")
 GK_ATOM(inputmode, "inputmode")
 GK_ATOM(ins, "ins")
 GK_ATOM(insertafter, "insertafter")
 GK_ATOM(insertbefore, "insertbefore")
+GK_ATOM(install, "install")
 GK_ATOM(instanceOf, "instanceOf")
 GK_ATOM(int32, "int32")
 GK_ATOM(int64, "int64")
 GK_ATOM(integer, "integer")
 GK_ATOM(integrity, "integrity")
 GK_ATOM(intersection, "intersection")
 GK_ATOM(is, "is")
 GK_ATOM(iscontainer, "iscontainer")
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -583,16 +583,23 @@ WINDOW_ONLY_EVENT(userproximity,
                   eUserProximity,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(devicelight,
                   eDeviceLight,
                   EventNameType_None,
                   eBasicEventClass)
 
+// Install events as per W3C Manifest spec
+WINDOW_ONLY_EVENT(install,
+                  eInstall,
+                  EventNameType_None,
+                  eBasicEventClass)
+
+
 #ifdef MOZ_B2G
 WINDOW_ONLY_EVENT(moztimechange,
                   eTimeChange,
                   EventNameType_None,
                   eBasicEventClass)
 WINDOW_ONLY_EVENT(moznetworkupload,
                   eNetworkUpload,
                   EventNameType_None,
--- a/dom/ipc/manifestMessages.js
+++ b/dom/ipc/manifestMessages.js
@@ -25,16 +25,20 @@ const MessageHandler = {
     addMessageListener(
       "DOM:WebManifest:hasManifestLink",
       this.hasManifestLink.bind(this)
     );
     addMessageListener(
       "DOM:ManifestObtainer:Obtain",
       this.obtainManifest.bind(this)
     );
+    addMessageListener(
+      "DOM:Manifest:FireInstallEvent",
+      this.fireInstallEvent.bind(this)
+    );
   },
 
   /**
    * Check if the content document includes a link to a web manifest.
    * @param {Object} aMsg The IPC message, which is destructured to just
    *                      get the id.
    */
   hasManifestLink({data: {id}}) {
@@ -55,16 +59,29 @@ const MessageHandler = {
     try {
       response.result = yield ManifestObtainer.contentObtainManifest(content);
       response.success = true;
     } catch (err) {
       response.result = serializeError(err);
     }
     sendAsyncMessage("DOM:ManifestObtainer:Obtain", response);
   }),
+
+  fireInstallEvent({data: {id}}){
+    const ev = new Event("install");
+    const response = makeMsgResponse(id);
+    if (!content || content.top !== content) {
+      const msg = "Can only dispatch install event on top-level browsing contexts.";
+      response.result = serializeError(new Error(msg));
+    } else {
+      response.success = true;
+      content.dispatchEvent(ev);
+    }
+    sendAsyncMessage("DOM:Manifest:FireInstallEvent", response);
+  }
 };
 /**
  * Utility function to Serializes an JS Error, so it can be transferred over
  * the message channel.
  * FIX ME: https://bugzilla.mozilla.org/show_bug.cgi?id=1172586
  * @param  {Error} aError The error to serialize.
  * @return {Object} The serialized object.
  */
--- a/dom/manifest/test/browser.ini
+++ b/dom/manifest/test/browser.ini
@@ -1,3 +1,8 @@
 [DEFAULT]
+support-files =
+  manifestLoader.html
+  file_reg_install_event.html
+  file_testserver.sjs
 [browser_ManifestFinder_browserHasManifestLink.js]
 [browser_ManifestObtainer_obtain.js]
+[browser_fire_install_event.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/browser_fire_install_event.js
@@ -0,0 +1,33 @@
+//Used by JSHint:
+/*global Cu, BrowserTestUtils, ok, add_task, PromiseMessage, gBrowser */
+"use strict";
+const { PromiseMessage } = Cu.import("resource://gre/modules/PromiseMessage.jsm", {});
+const testPath = "/browser/dom/manifest/test/file_reg_install_event.html";
+const defaultURL = new URL("http://example.org/browser/dom/manifest/test/file_testserver.sjs");
+const testURL = new URL(defaultURL);
+testURL.searchParams.append("file", testPath);
+
+// Send a message for the even to be fired.
+// This cause file_reg_install_event.html to be dynamically change.
+function* theTest(aBrowser) {
+  const mm = aBrowser.messageManager;
+  const msgKey = "DOM:Manifest:FireInstallEvent";
+  const initialText = aBrowser.contentWindowAsCPOW.document.body.innerHTML.trim()
+  is(initialText, '<h1 id="output">waiting for event</h1>', "should be waiting for event");
+  const { data: { success } } = yield PromiseMessage.send(mm, msgKey);
+  ok(success, "message sent and received successfully.");
+  const eventText = aBrowser.contentWindowAsCPOW.document.body.innerHTML.trim();
+  is(eventText, '<h1 id="output">event received!</h1>', "text of the page should have changed.");
+};
+
+// Open a tab and run the test
+add_task(function*() {
+  let tabOptions = {
+    gBrowser: gBrowser,
+    url: testURL.href,
+  };
+  yield BrowserTestUtils.withNewTab(
+    tabOptions,
+    theTest
+  );
+});
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/file_reg_install_event.html
@@ -0,0 +1,9 @@
+<script>
+window.addEventListener("install", (ev) => {
+  document
+    .querySelector("#output")
+    .innerHTML = "event received!"
+});
+</script>
+<link rel="manifest" href="file_manifest.json">
+<h1 id=output>waiting for event</h1>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/file_testserver.sjs
@@ -0,0 +1,49 @@
+"use strict";
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function loadHTMLFromFile(path) {
+  // Load the HTML to return in the response from file.
+  // Since it's relative to the cwd of the test runner, we start there and
+  // append to get to the actual path of the file.
+  const testHTMLFile =
+    Components.classes["@mozilla.org/file/directory_service;1"].
+    getService(Components.interfaces.nsIProperties).
+    get("CurWorkD", Components.interfaces.nsILocalFile);
+
+  const testHTMLFileStream =
+    Components.classes["@mozilla.org/network/file-input-stream;1"].
+    createInstance(Components.interfaces.nsIFileInputStream);
+
+  path
+    .split("/")
+    .filter(path => path)
+    .reduce((file, path) => {
+      testHTMLFile.append(path)
+      return testHTMLFile;
+    }, testHTMLFile);
+  testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+  const isAvailable = testHTMLFileStream.available();
+  return NetUtil.readInputStreamToString(testHTMLFileStream, isAvailable);
+}
+
+function handleRequest(request, response) {
+  const query = new URLSearchParams(request.queryString);
+
+  // avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+
+  // Deliver the CSP policy encoded in the URL
+  if(query.has("csp")){
+    response.setHeader("Content-Security-Policy", query.get("csp"), false);
+  }
+
+  // Deliver the CORS header in the URL
+  if(query.has("cors")){
+    response.setHeader("Access-Control-Allow-Origin", query.get("cors"), false);
+  }
+
+  // Send HTML to test allowed/blocked behaviors
+  response.setHeader("Content-Type", "text/html", false);
+  response.write(loadHTMLFromFile(query.get("file")));
+}
--- a/dom/manifest/test/mochitest.ini
+++ b/dom/manifest/test/mochitest.ini
@@ -1,20 +1,23 @@
 [DEFAULT]
 support-files =
 	common.js
 	resource.sjs
 	manifestLoader.html
+  file_reg_install_event.html
+  file_testserver.sjs
 [test_ImageObjectProcessor_sizes.html]
 [test_ImageObjectProcessor_src.html]
 [test_ImageObjectProcessor_type.html]
 [test_ManifestProcessor_background_color.html]
 [test_ManifestProcessor_dir.html]
 [test_ManifestProcessor_display.html]
 [test_ManifestProcessor_icons.html]
 [test_ManifestProcessor_JSON.html]
 [test_ManifestProcessor_lang.html]
 [test_ManifestProcessor_name_and_short_name.html]
 [test_ManifestProcessor_orientation.html]
 [test_ManifestProcessor_scope.html]
 [test_ManifestProcessor_start_url.html]
 [test_ManifestProcessor_theme_color.html]
 [test_ManifestProcessor_warnings.html]
+[test_window_oninstall_event.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_window_oninstall_event.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265279
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1265279 - Web Manifest: Implement window.oninstall</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script>
+"use strict";
+SimpleTest.waitForExplicitFinish();
+is(window.hasOwnProperty("oninstall"), true, "window has own oninstall property");
+is(window.oninstall, null, "window install is initially set to null");
+
+// Check that enumerable, configurable, and has a getter and setter
+const objDescriptor = Object.getOwnPropertyDescriptor(window, "oninstall");
+is(objDescriptor.enumerable, true, "is enumerable");
+is(objDescriptor.configurable, true, "is configurable");
+is(objDescriptor.hasOwnProperty("get"), true, "has getter");
+is(objDescriptor.hasOwnProperty("set"), true, "has setter");
+
+// Test is we receive the event on window.install
+const customEv = new CustomEvent("install");
+window.oninstall = function handler(ev){
+  window.oninstall = null;
+  is(ev, customEv, "The events should be the same");
+  testAddEventListener();
+};
+window.dispatchEvent(customEv);
+
+// Test that it works with .addEventListener("install", f);
+function testAddEventListener(){
+  const customEv2 = new CustomEvent("install");
+  window.addEventListener("install", function handler2(ev2) {
+    window.removeEventListener("install", handler2);
+    is(ev2, customEv2, "The events should be the same");
+    SimpleTest.finish();
+  });
+  window.dispatchEvent(customEv2);
+}
+  </script>
+</head>
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -84,16 +84,22 @@ typedef any Transferable;
 
   [Throws, CrossOriginCallable] void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer);
 
   // also has obsolete members
 };
 Window implements GlobalEventHandlers;
 Window implements WindowEventHandlers;
 
+[NoInterfaceObject, Exposed=(Window)]
+interface AppInstallEventHandlersMixin {
+  attribute EventHandler oninstall;
+};
+Window implements AppInstallEventHandlersMixin;
+
 // http://www.whatwg.org/specs/web-apps/current-work/
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface WindowTimers {
   [Throws] long setTimeout(Function handler, optional long timeout = 0, any... arguments);
   [Throws] long setTimeout(DOMString handler, optional long timeout = 0, any... unused);
   void clearTimeout(optional long handle = 0);
   [Throws] long setInterval(Function handler, optional long timeout, any... arguments);
   [Throws] long setInterval(DOMString handler, optional long timeout, any... unused);
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -56,16 +56,19 @@ NS_EVENT_MESSAGE(eAfterKeyUp)
 // to indicate that an potential accesskey was not found. The parent process may
 // then respond by, for example, opening menus and processing other shortcuts.
 // It inherits its properties from a keypress event.
 NS_EVENT_MESSAGE(eAccessKeyNotFound)
 
 NS_EVENT_MESSAGE(eResize)
 NS_EVENT_MESSAGE(eScroll)
 
+// Application installation
+NS_EVENT_MESSAGE(eInstall)
+
 // A plugin was clicked or otherwise focused. ePluginActivate should be
 // used when the window is not active. ePluginFocus should be used when
 // the window is active. In the latter case, the dispatcher of the event
 // is expected to ensure that the plugin's widget is focused beforehand.
 NS_EVENT_MESSAGE(ePluginActivate)
 NS_EVENT_MESSAGE(ePluginFocus)
 
 NS_EVENT_MESSAGE(eOffline)