Bug 1137816 - Add a test for interfaces exposed to service workers; r=baku
authorEhsan Akhgari <ehsan@mozilla.com>
Fri, 06 Mar 2015 08:27:24 -0500
changeset 232228 f4a651c0953d79e0867026ed76d21e426018d1bd
parent 232227 f827206d78403d80266d1082cc87e15875aac62f
child 232229 c20505bb8fa0ef804faaa1e0906f6aa0513805b1
push id56491
push usereakhgari@mozilla.com
push dateFri, 06 Mar 2015 13:28:06 +0000
treeherdermozilla-inbound@f4a651c0953d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1137816
milestone39.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 1137816 - Add a test for interfaces exposed to service workers; r=baku
dom/workers/test/serviceworkers/message_receiver.html
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/serviceworker_wrapper.js
dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/message_receiver.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.onmessage = function(e) {
+    window.parent.postMessage(e.data, "*");
+  };
+</script>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -21,16 +21,19 @@ support-files =
   worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
   match_all_properties_worker.js
   match_all_clients/match_all_controlled.html
+  test_serviceworker_interfaces.js
+  serviceworker_wrapper.js
+  message_receiver.html
   close_test.js
 
 [test_unregister.html]
 skip-if = true # Bug 1133805
 [test_installation_simple.html]
 [test_fetch_event.html]
 [test_match_all.html]
 [test_install_event.html]
@@ -40,8 +43,9 @@ skip-if = true # Bug 1133805
 [test_workerUpdate.html]
 skip-if = true # Bug 1133805
 [test_workerUnregister.html]
 skip-if = true # Bug 1133805
 [test_post_message.html]
 [test_post_message_advanced.html]
 [test_match_all_client_properties.html]
 [test_close.html]
+[test_serviceworker_interfaces.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
@@ -0,0 +1,131 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// ServiceWorker equivalent of worker_wrapper.js.
+
+var client;
+
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + ": " + msg + "\n");
+  client.postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + ": " + msg + "\n");
+  client.postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function workerTestArrayEquals(a, b) {
+  if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) {
+    return false;
+  }
+  for (var i = 0, n = a.length; i < n; ++i) {
+    if (a[i] !== b[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function workerTestDone() {
+  client.postMessage({ type: 'finish' });
+}
+
+function workerTestGetPrefs(prefs, cb) {
+  addEventListener('message', function workerTestGetPrefsCB(e) {
+    if (e.data.type != 'returnPrefs' ||
+        !workerTestArrayEquals(prefs, e.data.prefs)) {
+      return;
+    }
+    removeEventListener('message', workerTestGetPrefsCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getPrefs',
+    prefs: prefs
+  });
+}
+
+function workerTestGetPermissions(permissions, cb) {
+  addEventListener('message', function workerTestGetPermissionsCB(e) {
+    if (e.data.type != 'returnPermissions' ||
+        !workerTestArrayEquals(permissions, e.data.permissions)) {
+      return;
+    }
+    removeEventListener('message', workerTestGetPermissionsCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getPermissions',
+    permissions: permissions
+  });
+}
+
+function workerTestGetVersion(cb) {
+  addEventListener('message', function workerTestGetVersionCB(e) {
+    if (e.data.type !== 'returnVersion') {
+      return;
+    }
+    removeEventListener('message', workerTestGetVersionCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getVersion'
+  });
+}
+
+function workerTestGetUserAgent(cb) {
+  addEventListener('message', function workerTestGetUserAgentCB(e) {
+    if (e.data.type !== 'returnUserAgent') {
+      return;
+    }
+    removeEventListener('message', workerTestGetUserAgentCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getUserAgent'
+  });
+}
+
+function workerTestGetOSCPU(cb) {
+  addEventListener('message', function workerTestGetOSCPUCB(e) {
+    if (e.data.type !== 'returnOSCPU') {
+      return;
+    }
+    removeEventListener('message', workerTestGetOSCPUCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getOSCPU'
+  });
+}
+
+function workerTestGetIsB2G(cb) {
+  addEventListener('message', function workerTestGetIsB2GCB(e) {
+    if (e.data.type !== 'returnIsB2G') {
+      return;
+    }
+    removeEventListener('message', workerTestGetIsB2GCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getIsB2G'
+  });
+}
+
+addEventListener('message', function workerWrapperOnMessage(e) {
+  removeEventListener('message', workerWrapperOnMessage);
+  var data = e.data;
+  self.clients.matchAll().then(function(clients) {
+    client = clients[0];
+    try {
+      importScripts(data.script);
+    } catch(e) {
+      client.postMessage({
+        type: 'status',
+        status: false,
+        msg: 'worker failed to import ' + data.script + "; error: " + e.message
+      });
+    }
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -0,0 +1,113 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Validate Interfaces Exposed to Service Workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="../worker_driver.js"></script>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+  function setupSW(registration) {
+    var worker = registration.waiting ||
+                 registration.active;
+    window.onmessage = function(event) {
+      if (event.data.type == 'finish') {
+        registration.unregister().then(function(success) {
+          ok(success, "The service worker should be unregistered successfully");
+
+          SimpleTest.finish();
+        });
+      } else if (event.data.type == 'status') {
+        ok(event.data.status, event.data.msg);
+
+      } else if (event.data.type == 'getPrefs') {
+        var result = {};
+        event.data.prefs.forEach(function(pref) {
+          result[pref] = SpecialPowers.Services.prefs.getBoolPref(pref);
+        });
+        worker.postMessage({
+          type: 'returnPrefs',
+          prefs: event.data.prefs,
+          result: result
+        });
+
+      } else if (event.data.type == 'getPermissions') {
+        var result = {};
+        event.data.permissions.forEach(function(permission) {
+          result[permission] = SpecialPowers.hasPermission(permission, window.document);
+        });
+        worker.postMessage({
+          type: 'returnPermissions',
+          permissions: event.data.permissions,
+          result: result
+        });
+
+      } else if (event.data.type == 'getVersion') {
+        var result = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+        worker.postMessage({
+          type: 'returnVersion',
+          result: result
+        });
+
+      } else if (event.data.type == 'getUserAgent') {
+        worker.postMessage({
+          type: 'returnUserAgent',
+          result: navigator.userAgent
+        });
+      } else if (event.data.type == 'getOSCPU') {
+        worker.postMessage({
+          type: 'returnOSCPU',
+          result: navigator.oscpu
+        });
+      } else if (event.data.type == 'getIsB2G') {
+        worker.postMessage({
+          type: 'returnIsB2G',
+          result: SpecialPowers.isB2G
+        });
+      }
+    }
+
+    worker.onerror = function(event) {
+      ok(false, 'Worker had an error: ' + event.data);
+      SimpleTest.finish();
+    };
+
+    var iframe = document.createElement("iframe");
+    iframe.src = "message_receiver.html";
+    iframe.onload = function() {
+      worker.postMessage({ script: "test_serviceworker_interfaces.js" });
+    };
+    document.body.appendChild(iframe);
+  }
+
+  function runTest() {
+    navigator.serviceWorker.register("serviceworker_wrapper.js", {scope: "."})
+      .then(function(registration) {
+        if (registration.installing) {
+          registration.installing.onstatechange = function(e) {
+            setupSW(registration);
+            e.target.onstatechange = null;
+          };
+        } else {
+          setupSW(registration);
+        }
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.fetch.enabled", true],
+      ["dom.caches.enabled", true]
+    ]}, runTest);
+  };
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -0,0 +1,278 @@
+// This is a list of all interfaces that are exposed to workers.
+// Please only add things to this list with great care and proper review
+// from the associated module peers.
+
+// This file lists global interfaces we want exposed and verifies they
+// are what we intend. Each entry in the arrays below can either be a
+// simple string with the interface name, or an object with a 'name'
+// property giving the interface name as a string, and additional
+// properties which qualify the exposure of that interface. For example:
+//
+// [
+//   "AGlobalInterface",
+//   {name: "ExperimentalThing", release: false},
+//   {name: "OptionalThing", pref: "some.thing.enabled"},
+// ];
+//
+// See createInterfaceMap() below for a complete list of properties.
+
+// IMPORTANT: Do not change this list without review from
+//            a JavaScript Engine peer!
+var ecmaGlobals =
+  [
+    "Array",
+    "ArrayBuffer",
+    "Boolean",
+    "DataView",
+    "Date",
+    "Error",
+    "EvalError",
+    "Float32Array",
+    "Float64Array",
+    "Function",
+    "Infinity",
+    "Int16Array",
+    "Int32Array",
+    "Int8Array",
+    "InternalError",
+    {name: "Intl", b2g: false, android: false},
+    "Iterator",
+    "JSON",
+    "Map",
+    "Math",
+    "NaN",
+    "Number",
+    "Object",
+    "Proxy",
+    "RangeError",
+    "ReferenceError",
+    "RegExp",
+    "Set",
+    {name: "SharedArrayBuffer", nightly: true},
+    {name: "SharedInt8Array", nightly: true},
+    {name: "SharedUint8Array", nightly: true},
+    {name: "SharedUint8ClampedArray", nightly: true},
+    {name: "SharedInt16Array", nightly: true},
+    {name: "SharedUint16Array", nightly: true},
+    {name: "SharedInt32Array", nightly: true},
+    {name: "SharedUint32Array", nightly: true},
+    {name: "SharedFloat32Array", nightly: true},
+    {name: "SharedFloat64Array", nightly: true},
+    {name: "SIMD", nightly: true},
+    {name: "Atomics", nightly: true},
+    "StopIteration",
+    "String",
+    "Symbol",
+    "SyntaxError",
+    {name: "TypedObject", nightly: true},
+    "TypeError",
+    "Uint16Array",
+    "Uint32Array",
+    "Uint8Array",
+    "Uint8ClampedArray",
+    "URIError",
+    "WeakMap",
+    "WeakSet",
+  ];
+// IMPORTANT: Do not change the list above without review from
+//            a JavaScript Engine peer!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var interfaceNamesInGlobalScope =
+  [
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Blob",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "Cache", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "CacheStorage", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Client",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Clients",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "DataStore", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "DataStoreCursor", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMError",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMException",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMStringList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Event",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "EventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ExtendableEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "FetchEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "File",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "FileReaderSync",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "Headers", pref: "dom.fetch.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBDatabase",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBFactory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBIndex",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBKeyRange",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBObjectStore",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBOpenDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBTransaction",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBVersionChangeEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "InstallEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "MessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "MessagePort",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Promise",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Request",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Response",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ServiceWorker",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ServiceWorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "TextDecoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "TextEncoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequestEventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequestUpload",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "URL",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "URLSearchParams",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+   { name: "WebSocket", pref: "dom.workers.websocket.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerLocation",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerNavigator",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+  ];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+function createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G) {
+  var isNightly = version.endsWith("a1");
+  var isRelease = !version.contains("a");
+  var isDesktop = !/Mobile|Tablet/.test(userAgent);
+  var isAndroid = !!navigator.userAgent.contains("Android");
+
+  var interfaceMap = {};
+
+  function addInterfaces(interfaces)
+  {
+    for (var entry of interfaces) {
+      if (typeof(entry) === "string") {
+        interfaceMap[entry] = true;
+      } else if ((entry.nightly === !isNightly) ||
+                 (entry.desktop === !isDesktop) ||
+                 (entry.android === !isAndroid) ||
+                 (entry.b2g === !isB2G) ||
+                 (entry.release === !isRelease) ||
+                 (entry.pref && !prefMap[entry.pref])  ||
+                 (entry.permission && !permissionMap[entry.permission])) {
+        interfaceMap[entry.name] = false;
+      } else {
+        interfaceMap[entry.name] = true;
+      }
+    }
+  }
+
+  addInterfaces(ecmaGlobals);
+  addInterfaces(interfaceNamesInGlobalScope);
+
+  return interfaceMap;
+}
+
+function runTest(prefMap, permissionMap, version, userAgent, isB2G) {
+  var interfaceMap = createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G);
+  for (var name of Object.getOwnPropertyNames(self)) {
+    // An interface name should start with an upper case character.
+    if (!/^[A-Z]/.test(name)) {
+      continue;
+    }
+    ok(interfaceMap[name],
+       "If this is failing: DANGER, are you sure you want to expose the new interface " + name +
+       " to all webpages as a property on the service worker? Do not make a change to this file without a " +
+       " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)");
+    delete interfaceMap[name];
+  }
+  for (var name of Object.keys(interfaceMap)) {
+    ok(name in self === interfaceMap[name],
+       name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the global scope");
+    if (!interfaceMap[name]) {
+      delete interfaceMap[name];
+    }
+  }
+  is(Object.keys(interfaceMap).length, 0,
+     "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
+}
+
+function appendPrefs(prefs, interfaces) {
+  for (var entry of interfaces) {
+    if (entry.pref !== undefined && prefs.indexOf(entry.pref) === -1) {
+      prefs.push(entry.pref);
+    }
+  }
+}
+
+var prefs = [];
+appendPrefs(prefs, ecmaGlobals);
+appendPrefs(prefs, interfaceNamesInGlobalScope);
+
+function appendPermissions(permissions, interfaces) {
+  for (var entry of interfaces) {
+    if (entry.permission !== undefined &&
+        permissions.indexOf(entry.permission) === -1) {
+      permissions.push(entry.permission);
+    }
+  }
+}
+
+var permissions = [];
+appendPermissions(permissions, ecmaGlobals);
+appendPermissions(permissions, interfaceNamesInGlobalScope);
+
+workerTestGetPrefs(prefs, function(prefMap) {
+  workerTestGetPermissions(permissions, function(permissionMap) {
+    workerTestGetVersion(function(version) {
+      workerTestGetUserAgent(function(userAgent) {
+        workerTestGetIsB2G(function(isB2G) {
+          runTest(prefMap, permissionMap, version, userAgent, isB2G);
+          workerTestDone();
+	});
+      });
+    });
+  });
+});