summaryrefslogtreecommitdiffstats
path: root/dom/manifest/test
diff options
context:
space:
mode:
Diffstat (limited to 'dom/manifest/test')
-rw-r--r--dom/manifest/test/browser.ini9
-rw-r--r--dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js84
-rw-r--r--dom/manifest/test/browser_ManifestObtainer_obtain.js181
-rw-r--r--dom/manifest/test/browser_fire_appinstalled_event.js49
-rw-r--r--dom/manifest/test/common.js22
-rw-r--r--dom/manifest/test/file_reg_appinstalled_event.html15
-rw-r--r--dom/manifest/test/file_testserver.sjs54
-rw-r--r--dom/manifest/test/manifestLoader.html13
-rw-r--r--dom/manifest/test/mochitest.ini23
-rw-r--r--dom/manifest/test/resource.sjs85
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_sizes.html95
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_src.html106
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_type.html57
-rw-r--r--dom/manifest/test/test_ManifestProcessor_JSON.html34
-rw-r--r--dom/manifest/test/test_ManifestProcessor_background_color.html118
-rw-r--r--dom/manifest/test/test_ManifestProcessor_dir.html57
-rw-r--r--dom/manifest/test/test_ManifestProcessor_display.html78
-rw-r--r--dom/manifest/test/test_ManifestProcessor_icons.html30
-rw-r--r--dom/manifest/test/test_ManifestProcessor_lang.html112
-rw-r--r--dom/manifest/test/test_ManifestProcessor_name_and_short_name.html79
-rw-r--r--dom/manifest/test/test_ManifestProcessor_orientation.html86
-rw-r--r--dom/manifest/test/test_ManifestProcessor_scope.html89
-rw-r--r--dom/manifest/test/test_ManifestProcessor_start_url.html59
-rw-r--r--dom/manifest/test/test_ManifestProcessor_theme_color.html118
-rw-r--r--dom/manifest/test/test_ManifestProcessor_warnings.html90
-rw-r--r--dom/manifest/test/test_window_onappinstalled_event.html98
26 files changed, 1841 insertions, 0 deletions
diff --git a/dom/manifest/test/browser.ini b/dom/manifest/test/browser.ini
new file mode 100644
index 000000000..ad98fe26c
--- /dev/null
+++ b/dom/manifest/test/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ file_reg_appinstalled_event.html
+ file_testserver.sjs
+ manifestLoader.html
+ resource.sjs
+[browser_ManifestFinder_browserHasManifestLink.js]
+[browser_ManifestObtainer_obtain.js]
+[browser_fire_appinstalled_event.js] \ No newline at end of file
diff --git a/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
new file mode 100644
index 000000000..5ec663962
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
@@ -0,0 +1,84 @@
+//Used by JSHint:
+/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+"use strict";
+const { ManifestFinder } = Cu.import("resource://gre/modules/ManifestFinder.jsm", {});
+const defaultURL = new URL("http://example.org/browser/dom/manifest/test/resource.sjs");
+defaultURL.searchParams.set("Content-Type", "text/html; charset=utf-8");
+
+const tests = [{
+ body: `
+ <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>
+ <link rel="foo bar manifest bar test" href='${defaultURL}?body={"name":"value"}'>
+ <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>
+ `,
+ run(result) {
+ ok(result, "Document has a web manifest.");
+ },
+}, {
+ body: `
+ <link rel="amanifista" href='${defaultURL}?body={"name":"fail"}'>
+ <link rel="foo bar manifesto bar test" href='${defaultURL}?body={"name":"pass-1"}'>
+ <link rel="manifesto" href='${defaultURL}?body={"name":"fail"}'>`,
+ run(result) {
+ ok(!result, "Document does not have a web manifest.");
+ },
+}, {
+ body: `
+ <link rel="manifest" href="">
+ <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`,
+ run(result) {
+ ok(!result, "Manifest link is has empty href.");
+ },
+}, {
+ body: `
+ <link rel="manifest">
+ <link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`,
+ run(result) {
+ ok(!result, "Manifest link is missing.");
+ },
+}];
+
+function makeTestURL({ body }) {
+ const url = new URL(defaultURL);
+ url.searchParams.set("body", encodeURIComponent(body));
+ return url.href;
+}
+
+/**
+ * Test basic API error conditions
+ */
+add_task(function*() {
+ const expected = "Invalid types should throw a TypeError.";
+ for (let invalidValue of [undefined, null, 1, {}, "test"]) {
+ try {
+ yield ManifestFinder.contentManifestLink(invalidValue);
+ ok(false, expected);
+ } catch (e) {
+ is(e.name, "TypeError", expected);
+ }
+ try {
+ yield ManifestFinder.browserManifestLink(invalidValue);
+ ok(false, expected);
+ } catch (e) {
+ is(e.name, "TypeError", expected);
+ }
+ }
+});
+
+add_task(function*() {
+ const runningTests = tests
+ .map(
+ test => ({
+ gBrowser,
+ test,
+ url: makeTestURL(test),
+ })
+ )
+ .map(
+ tabOptions => BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ const result = yield ManifestFinder.browserHasManifestLink(browser);
+ tabOptions.test.run(result);
+ })
+ );
+ yield Promise.all(runningTests);
+});
diff --git a/dom/manifest/test/browser_ManifestObtainer_obtain.js b/dom/manifest/test/browser_ManifestObtainer_obtain.js
new file mode 100644
index 000000000..a2e468905
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js
@@ -0,0 +1,181 @@
+//Used by JSHint:
+/*global ok, is, Cu, BrowserTestUtils, add_task, gBrowser, makeTestURL, requestLongerTimeout*/
+'use strict';
+const { ManifestObtainer } = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
+const remoteURL = 'http://mochi.test:8888/browser/dom/manifest/test/resource.sjs';
+const defaultURL = new URL('http://example.org/browser/dom/manifest/test/resource.sjs');
+defaultURL.searchParams.set('Content-Type', 'text/html; charset=utf-8');
+requestLongerTimeout(4);
+
+const tests = [
+ // Fetch tests.
+ {
+ body: `
+ <link rel="manifesto" href='resource.sjs?body={"name":"fail"}'>
+ <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-1"}'>
+ <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>`,
+ run(manifest) {
+ is(manifest.name, 'pass-1', 'Manifest is first `link` where @rel contains token manifest.');
+ }
+ }, {
+ body: `
+ <link rel="foo bar manifest bar test" href='resource.sjs?body={"name":"pass-2"}'>
+ <link rel="manifest" href='resource.sjs?body={"name":"fail"}'>
+ <link rel="manifest foo bar test" href='resource.sjs?body={"name":"fail"}'>`,
+ run(manifest) {
+ is(manifest.name, 'pass-2', 'Manifest is first `link` where @rel contains token manifest.');
+ },
+ }, {
+ body: `<link rel="manifest" href='${remoteURL}?body={"name":"pass-3"}'>`,
+ run(err) {
+ is(err.name, 'TypeError', 'By default, manifest cannot load cross-origin.');
+ },
+ },
+ // CORS Tests.
+ {
+ get body() {
+ const body = 'body={"name": "pass-4"}';
+ const CORS =
+ `Access-Control-Allow-Origin=${defaultURL.origin}`;
+ const link =
+ `<link
+ crossorigin=anonymous
+ rel="manifest"
+ href='${remoteURL}?${body}&${CORS}'>`;
+ return link;
+ },
+ run(manifest) {
+ is(manifest.name, 'pass-4', 'CORS enabled, manifest must be fetched.');
+ },
+ }, {
+ get body() {
+ const body = 'body={"name": "fail"}';
+ const CORS = 'Access-Control-Allow-Origin=http://not-here';
+ const link =
+ `<link
+ crossorigin
+ rel="manifest"
+ href='${remoteURL}?${body}&${CORS}'>`;
+ return link;
+ },
+ run(err) {
+ is(err.name, 'TypeError', 'Fetch blocked by CORS - origin does not match.');
+ },
+ }, {
+ body: `<link rel="manifest" href='about:whatever'>`,
+ run(err) {
+ is(err.name, 'TypeError', 'Trying to load from about:whatever is TypeError.');
+ },
+ }, {
+ body: `<link rel="manifest" href='file://manifest'>`,
+ run(err) {
+ is(err.name, 'TypeError', 'Trying to load from file://whatever is a TypeError.');
+ },
+ },
+ //URL parsing tests
+ {
+ body: `<link rel="manifest" href='http://[12.1212.21.21.12.21.12]'>`,
+ run(err) {
+ is(err.name, 'TypeError', 'Trying to load invalid URL is a TypeError.');
+ },
+ },
+];
+
+function makeTestURL({ body }) {
+ const url = new URL(defaultURL);
+ url.searchParams.set('body', encodeURIComponent(body));
+ return url.href;
+}
+
+add_task(function*() {
+ const promises = tests
+ .map(test => ({
+ gBrowser,
+ testRunner: testObtainingManifest(test),
+ url: makeTestURL(test)
+ }))
+ .reduce((collector, tabOpts) => {
+ const promise = BrowserTestUtils.withNewTab(tabOpts, tabOpts.testRunner);
+ collector.push(promise);
+ return collector;
+ }, []);
+
+ const results = yield Promise.all(promises);
+
+ function testObtainingManifest(aTest) {
+ return function*(aBrowser) {
+ try {
+ const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+ aTest.run(manifest);
+ } catch (e) {
+ aTest.run(e);
+ }
+ };
+ }
+});
+
+/*
+ * e10s race condition tests
+ * Open a bunch of tabs and load manifests
+ * in each tab. They should all return pass.
+ */
+add_task(function*() {
+ const defaultPath = '/browser/dom/manifest/test/manifestLoader.html';
+ const tabURLs = [
+ `http://example.com:80${defaultPath}`,
+ `http://example.org:80${defaultPath}`,
+ `http://example.org:8000${defaultPath}`,
+ `http://mochi.test:8888${defaultPath}`,
+ `http://sub1.test1.example.com:80${defaultPath}`,
+ `http://sub1.test1.example.org:80${defaultPath}`,
+ `http://sub1.test1.example.org:8000${defaultPath}`,
+ `http://sub1.test1.mochi.test:8888${defaultPath}`,
+ `http://sub1.test2.example.com:80${defaultPath}`,
+ `http://sub1.test2.example.org:80${defaultPath}`,
+ `http://sub1.test2.example.org:8000${defaultPath}`,
+ `http://sub2.test1.example.com:80${defaultPath}`,
+ `http://sub2.test1.example.org:80${defaultPath}`,
+ `http://sub2.test1.example.org:8000${defaultPath}`,
+ `http://sub2.test2.example.com:80${defaultPath}`,
+ `http://sub2.test2.example.org:80${defaultPath}`,
+ `http://sub2.test2.example.org:8000${defaultPath}`,
+ `http://sub2.xn--lt-uia.mochi.test:8888${defaultPath}`,
+ `http://test1.example.com:80${defaultPath}`,
+ `http://test1.example.org:80${defaultPath}`,
+ `http://test1.example.org:8000${defaultPath}`,
+ `http://test1.mochi.test:8888${defaultPath}`,
+ `http://test2.example.com:80${defaultPath}`,
+ `http://test2.example.org:80${defaultPath}`,
+ `http://test2.example.org:8000${defaultPath}`,
+ `http://test2.mochi.test:8888${defaultPath}`,
+ `http://test:80${defaultPath}`,
+ `http://www.example.com:80${defaultPath}`,
+ ];
+ // Open tabs an collect corresponding browsers
+ let browsers = [
+ for (url of tabURLs) gBrowser.addTab(url).linkedBrowser
+ ];
+ // Once all the pages have loaded, run a bunch of tests in "parallel".
+ yield Promise.all((
+ for (browser of browsers) BrowserTestUtils.browserLoaded(browser)
+ ));
+ // Flood random browsers with requests. Once promises settle, check that
+ // responses all pass.
+ const results = yield Promise.all((
+ for (browser of randBrowsers(browsers, 50)) ManifestObtainer.browserObtainManifest(browser)
+ ));
+ const pass = results.every(manifest => manifest.name === 'pass');
+ ok(pass, 'Expect every manifest to have name equal to `pass`.');
+ //cleanup
+ browsers
+ .map(browser => gBrowser.getTabForBrowser(browser))
+ .forEach(tab => gBrowser.removeTab(tab));
+
+ //Helper generator, spits out random browsers
+ function* randBrowsers(aBrowsers, aMax) {
+ for (let i = 0; i < aMax; i++) {
+ const randNum = Math.round(Math.random() * (aBrowsers.length - 1));
+ yield aBrowsers[randNum];
+ }
+ }
+});
diff --git a/dom/manifest/test/browser_fire_appinstalled_event.js b/dom/manifest/test/browser_fire_appinstalled_event.js
new file mode 100644
index 000000000..517b120d3
--- /dev/null
+++ b/dom/manifest/test/browser_fire_appinstalled_event.js
@@ -0,0 +1,49 @@
+//Used by JSHint:
+/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */
+"use strict";
+const { PromiseMessage } = Cu.import("resource://gre/modules/PromiseMessage.jsm", {});
+const testPath = "/browser/dom/manifest/test/file_reg_appinstalled_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);
+
+// Enable window.onappinstalled, so we can fire events at it.
+function enableOnAppInstalledPref() {
+ const ops = {
+ "set": [
+ ["dom.manifest.onappinstalled", true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+}
+
+// Send a message for the even to be fired.
+// This cause file_reg_install_event.html to be dynamically change.
+function* theTest(aBrowser) {
+ aBrowser.allowEvents = true;
+ let waitForInstall = ContentTask.spawn(aBrowser, null, function*() {
+ yield ContentTaskUtils.waitForEvent(content.window, "appinstalled");
+ });
+ const { data: { success } } = yield PromiseMessage
+ .send(aBrowser.messageManager, "DOM:Manifest:FireAppInstalledEvent");
+ ok(success, "message sent and received successfully.");
+ try {
+ yield waitForInstall;
+ ok(true, "AppInstalled event fired");
+ } catch (err) {
+ ok(false, "AppInstalled event didn't fire: " + err.message);
+ }
+}
+
+// Open a tab and run the test
+add_task(function*() {
+ yield enableOnAppInstalledPref();
+ let tabOptions = {
+ gBrowser: gBrowser,
+ url: testURL.href,
+ };
+ yield BrowserTestUtils.withNewTab(
+ tabOptions,
+ theTest
+ );
+});
diff --git a/dom/manifest/test/common.js b/dom/manifest/test/common.js
new file mode 100644
index 000000000..4f618be80
--- /dev/null
+++ b/dom/manifest/test/common.js
@@ -0,0 +1,22 @@
+/**
+ * Common infrastructure for manifest tests.
+ **/
+/*globals SpecialPowers, ManifestProcessor*/
+'use strict';
+const {
+ ManifestProcessor
+} = SpecialPowers.Cu.import('resource://gre/modules/ManifestProcessor.jsm');
+const processor = ManifestProcessor;
+const manifestURL = new URL(document.location.origin + '/manifest.json');
+const docURL = document.location;
+const seperators = '\u2028\u2029\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000';
+const lineTerminators = '\u000D\u000A\u2028\u2029';
+const whiteSpace = `${seperators}${lineTerminators}`;
+const typeTests = [1, null, {},
+ [], false
+];
+const data = {
+ jsonText: '{}',
+ manifestURL: manifestURL,
+ docURL: docURL
+};
diff --git a/dom/manifest/test/file_reg_appinstalled_event.html b/dom/manifest/test/file_reg_appinstalled_event.html
new file mode 100644
index 000000000..80ff15e11
--- /dev/null
+++ b/dom/manifest/test/file_reg_appinstalled_event.html
@@ -0,0 +1,15 @@
+<meta charset=utf-8>
+<script>
+"use strict";
+window.addEventListener("appinstalled", () => {
+ document
+ .querySelector("#output")
+ .innerHTML = "event received!";
+ // Send a custom event back to the browser
+ // to acknowledge that we got this
+ const detail = { result: true }
+ const ev = new CustomEvent("dom.manifest.onappinstalled", { detail });
+ document.dispatchEvent(ev);
+});
+</script>
+<h1 id=output>waiting for event</h1>
diff --git a/dom/manifest/test/file_testserver.sjs b/dom/manifest/test/file_testserver.sjs
new file mode 100644
index 000000000..5229de9f9
--- /dev/null
+++ b/dom/manifest/test/file_testserver.sjs
@@ -0,0 +1,54 @@
+"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 CSPRO policy encoded in the URL
+ if(query.has("cspro")){
+ response.setHeader("Content-Security-Policy-Report-Only", query.get("cspro"), 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")));
+}
diff --git a/dom/manifest/test/manifestLoader.html b/dom/manifest/test/manifestLoader.html
new file mode 100644
index 000000000..e24426090
--- /dev/null
+++ b/dom/manifest/test/manifestLoader.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<!--
+Uses resource.sjs to load a Web Manifest that can be loaded cross-origin.
+-->
+<link rel="manifest" href='resource.sjs?body={"name":"pass"}&amp;Access-Control-Allow-Origin=*'>
+<h1>Manifest loader</h1>
+<p>Uses resource.sjs to load a Web Manifest that can be loaded cross-origin. The manifest looks like this:</p>
+<pre>
+{
+ "name":"pass"
+}
+</pre>
diff --git a/dom/manifest/test/mochitest.ini b/dom/manifest/test/mochitest.ini
new file mode 100644
index 000000000..24e3b120d
--- /dev/null
+++ b/dom/manifest/test/mochitest.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ common.js
+ resource.sjs
+ manifestLoader.html
+ file_reg_appinstalled_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_onappinstalled_event.html] \ No newline at end of file
diff --git a/dom/manifest/test/resource.sjs b/dom/manifest/test/resource.sjs
new file mode 100644
index 000000000..ec7804d3f
--- /dev/null
+++ b/dom/manifest/test/resource.sjs
@@ -0,0 +1,85 @@
+/* Generic responder that composes a response from
+ * the query string of a request.
+ *
+ * It reserves some special prop names:
+ * - body: get's used as the response body
+ * - statusCode: override the 200 OK response code
+ * (response text is set automatically)
+ *
+ * Any property names it doesn't know about get converted into
+ * HTTP headers.
+ *
+ * For example:
+ * http://test/resource.sjs?Content-Type=text/html&body=<h1>hello</h1>&Hello=hi
+ *
+ * Outputs:
+ * HTTP/1.1 200 OK
+ * Content-Type: text/html
+ * Hello: hi
+ * <h1>hello</h1>
+ */
+//global handleRequest
+'use strict';
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+const HTTPStatus = new Map([
+ [100, 'Continue'],
+ [101, 'Switching Protocol'],
+ [200, 'OK'],
+ [201, 'Created'],
+ [202, 'Accepted'],
+ [203, 'Non-Authoritative Information'],
+ [204, 'No Content'],
+ [205, 'Reset Content'],
+ [206, 'Partial Content'],
+ [300, 'Multiple Choice'],
+ [301, 'Moved Permanently'],
+ [302, 'Found'],
+ [303, 'See Other'],
+ [304, 'Not Modified'],
+ [305, 'Use Proxy'],
+ [306, 'unused'],
+ [307, 'Temporary Redirect'],
+ [308, 'Permanent Redirect'],
+ [400, 'Bad Request'],
+ [401, 'Unauthorized'],
+ [402, 'Payment Required'],
+ [403, 'Forbidden'],
+ [404, 'Not Found'],
+ [405, 'Method Not Allowed'],
+ [406, 'Not Acceptable'],
+ [407, 'Proxy Authentication Required'],
+ [408, 'Request Timeout'],
+ [409, 'Conflict'],
+ [410, 'Gone'],
+ [411, 'Length Required'],
+ [412, 'Precondition Failed'],
+ [413, 'Request Entity Too Large'],
+ [414, 'Request-URI Too Long'],
+ [415, 'Unsupported Media Type'],
+ [416, 'Requested Range Not Satisfiable'],
+ [417, 'Expectation Failed'],
+ [500, 'Internal Server Error'],
+ [501, 'Not Implemented'],
+ [502, 'Bad Gateway'],
+ [503, 'Service Unavailable'],
+ [504, 'Gateway Timeout'],
+ [505, 'HTTP Version Not Supported']
+]);
+
+function handleRequest(request, response) {
+ const queryMap = new URLSearchParams(request.queryString);
+ if (queryMap.has('statusCode')) {
+ let statusCode = parseInt(queryMap.get('statusCode'));
+ let statusText = HTTPStatus.get(statusCode);
+ queryMap.delete('statusCode');
+ response.setStatusLine('1.1', statusCode, statusText);
+ }
+ if (queryMap.has('body')) {
+ let body = queryMap.get('body') || '';
+ queryMap.delete('body');
+ response.write(decodeURIComponent(body));
+ }
+ for (let [key, value] of queryMap.entries()) {
+ response.setHeader(key, value);
+ }
+}
diff --git a/dom/manifest/test/test_ImageObjectProcessor_sizes.html b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
new file mode 100644
index 000000000..82a8ef991
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Image object's sizes member
+ * https://w3c.github.io/manifest/#sizes-member
+ **/
+'use strict';
+var validSizes = [{
+ test: '16x16',
+ expect: ['16x16']
+}, {
+ test: 'hello 16x16 16x16',
+ expect: ['16x16']
+}, {
+ test: '32x32 16 48x48 12',
+ expect: ['32x32', '48x48']
+}, {
+ test: `${whiteSpace}128x128${whiteSpace}512x512 8192x8192 32768x32768${whiteSpace}`,
+ expect: ['128x128', '512x512', '8192x8192', '32768x32768']
+}, {
+ test: 'any',
+ expect: ['any']
+}, {
+ test: 'Any',
+ expect: ['Any']
+}, {
+ test: '16x32',
+ expect: ['16x32']
+}, {
+ test: '17x33',
+ expect: ['17x33']
+}, {
+ test: '32x32 32x32',
+ expect: ['32x32']
+}, {
+ test: '32X32',
+ expect: ['32X32']
+}, {
+ test: 'any 32x32',
+ expect: ['any', '32x32']
+}];
+
+var testIcon = {
+ icons: [{
+ src: 'test',
+ sizes: undefined
+ }]
+};
+
+validSizes.forEach(({test, expect}) => {
+ testIcon.icons[0].sizes = test;
+ data.jsonText = JSON.stringify(testIcon);
+ var result = processor.process(data);
+ var sizes = result.icons[0].sizes;
+ var expected = `Expect sizes to equal ${expect.join(" ")}`;
+ is(sizes, expect.join(" "), expected);
+});
+
+var testIcon = {
+ icons: [{
+ src: 'test',
+ sizes: undefined
+ }]
+};
+
+var invalidSizes = ['invalid', '', ' ', '16 x 16', '32', '21', '16xx16', '16 x x 6'];
+invalidSizes.forEach((invalidSize) => {
+ var expected = 'Expect invalid sizes to return undefined.';
+ testIcon.icons[0].sizes = invalidSize;
+ data.jsonText = JSON.stringify(testIcon);
+ var result = processor.process(data);
+ var sizes = result.icons[0].sizes;
+ is(sizes, undefined, expected);
+});
+
+typeTests.forEach((type) => {
+ var expected = `Expect non-string sizes ${typeof type} to be undefined.`;
+ testIcon.icons[0].sizes = type;
+ data.jsonText = JSON.stringify(testIcon);
+ var result = processor.process(data);
+ var sizes = result.icons[0].sizes;
+ is(sizes, undefined, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ImageObjectProcessor_src.html b/dom/manifest/test/test_ImageObjectProcessor_src.html
new file mode 100644
index 000000000..cb77af0bd
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_src.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Image object's src member
+ * https://w3c.github.io/manifest/#src-member
+ **/
+'use strict';
+var noSrc = {
+ icons: [{}, {
+ src: []
+ }, {
+ src: {}
+ }, {
+ src: null
+ }, {
+ type: 'image/jpg'
+ }, {
+ sizes: '1x1,2x2'
+ }, {
+ sizes: 'any',
+ type: 'image/jpg'
+ }]
+};
+
+var expected = `Expect icons without a src prop to be filtered out.`;
+data.jsonText = JSON.stringify(noSrc);
+var result = processor.process(data);
+is(result.icons.length, 0, expected);
+
+var invalidSrc = {
+ icons: [{
+ src: null
+ }, {
+ src: 1
+ }, {
+ src: []
+ }, {
+ src: {}
+ }, {
+ src: true
+ }, {
+ src: ''
+ }]
+};
+
+var expected = `Expect icons with invalid src prop to be filtered out.`;
+data.jsonText = JSON.stringify(noSrc);
+var result = processor.process(data);
+is(result.icons.length, 0, expected);
+
+var expected = `Expect icon's src to be a string.`;
+var withSrc = {
+ icons: [{
+ src: 'pass'
+ }]
+};
+data.jsonText = JSON.stringify(withSrc);
+var result = processor.process(data);
+is(typeof result.icons[0].src, "string", expected);
+
+var expected = `Expect only icons with a src prop to be kept.`;
+var withSrc = {
+ icons: [{
+ src: 'pass'
+ }, {
+ src: 'pass',
+ }, {}, {
+ foo: 'foo'
+ }]
+};
+data.jsonText = JSON.stringify(withSrc);
+var result = processor.process(data);
+is(result.icons.length, 2, expected);
+
+var expectedURL = new URL('pass', manifestURL);
+for (var icon of result.icons) {
+ var expected = `Expect src prop to be ${expectedURL.toString()}`;
+ is(icon.src.toString(), expectedURL.toString(), expected);
+}
+
+//Resolve URLs relative to manfiest
+var URLs = ['path', '/path', '../../path'];
+
+URLs.forEach((url) => {
+ var expected = `Resolve icon src URLs relative to manifest.`;
+ data.jsonText = JSON.stringify({
+ icons: [{
+ src: url
+ }]
+ });
+ var absURL = new URL(url, manifestURL).toString();
+ var result = processor.process(data);
+ is(result.icons[0].src.toString(), absURL, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ImageObjectProcessor_type.html b/dom/manifest/test/test_ImageObjectProcessor_type.html
new file mode 100644
index 000000000..d1b95044d
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_type.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Image object's type property
+ * https://w3c.github.io/manifest/#type-member
+ **/
+
+'use strict';
+var testIcon = {
+ icons: [{
+ src: 'test',
+ type: undefined
+ }]
+};
+
+var invalidMimeTypes = [
+ 'application / text',
+ 'test;test',
+ ';test?test',
+ 'application\\text',
+ 'image/jpeg, image/gif'
+];
+invalidMimeTypes.forEach((invalidMime) => {
+ var expected = `Expect invalid mime to be treated like undefined.`;
+ testIcon.icons[0].type = invalidMime;
+ data.jsonText = JSON.stringify(testIcon);
+ var result = processor.process(data);
+ is(result.icons[0].type, undefined, expected);
+});
+
+var validTypes = [
+ 'image/jpeg',
+ 'IMAGE/jPeG',
+ `${whiteSpace}image/jpeg${whiteSpace}`,
+ 'image/JPEG; whatever=something',
+ 'image/JPEG;whatever'
+];
+
+validTypes.forEach((validMime) => {
+ var expected = `Expect valid mime to be parsed to : image/jpeg.`;
+ testIcon.icons[0].type = validMime;
+ data.jsonText = JSON.stringify(testIcon);
+ var result = processor.process(data);
+ is(result.icons[0].type, 'image/jpeg', expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_JSON.html b/dom/manifest/test/test_ManifestProcessor_JSON.html
new file mode 100644
index 000000000..0319445eb
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_JSON.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * JSON parsing/processing tests
+ * https://w3c.github.io/manifest/#processing
+ **/
+'use strict';
+var invalidJson = ['', ` \t \n ${whiteSpace} `, '{', '{[[}'];
+invalidJson.forEach((testString) => {
+ var expected = `Expect to recover from invalid JSON: ${testString}`;
+ data.jsonText = testString;
+ var result = processor.process(data);
+ SimpleTest.is(result.start_url, docURL.href, expected);
+});
+
+var validButUnhelpful = ["1", 1, "", "[{}]", "null"];
+validButUnhelpful.forEach((testString) => {
+ var expected = `Expect to recover from invalid JSON: ${testString}`;
+ data.jsonText = testString;
+ var result = processor.process(data);
+ SimpleTest.is(result.start_url, docURL.href, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_background_color.html b/dom/manifest/test/test_ManifestProcessor_background_color.html
new file mode 100644
index 000000000..e7249df4c
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_background_color.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195018
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1195018</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * background_color member
+ * https://w3c.github.io/manifest/#background_color-member
+ **/
+'use strict';
+
+typeTests.forEach(type => {
+ data.jsonText = JSON.stringify({
+ background_color: type
+ });
+ var result = processor.process(data);
+
+ is(result.background_color, undefined, `Expect non-string background_color to be undefined: ${typeof type}.`);
+});
+
+var validThemeColors = [
+ 'maroon',
+ '#f00',
+ '#ff0000',
+ 'rgb(255,0,0)',
+ 'rgb(255,0,0,1)',
+ 'rgb(255,0,0,1.0)',
+ 'rgb(255,0,0,100%)',
+ 'rgb(255 0 0)',
+ 'rgb(255 0 0 / 1)',
+ 'rgb(255 0 0 / 1.0)',
+ 'rgb(255 0 0 / 100%)',
+ 'rgb(100%, 0%, 0%)',
+ 'rgb(100%, 0%, 0%, 1)',
+ 'rgb(100%, 0%, 0%, 1.0)',
+ 'rgb(100%, 0%, 0%, 100%)',
+ 'rgb(100% 0% 0%)',
+ 'rgb(100% 0% 0% / 1)',
+ 'rgb(100%, 0%, 0%, 1.0)',
+ 'rgb(100%, 0%, 0%, 100%)',
+ 'rgb(300,0,0)',
+ 'rgb(300 0 0)',
+ 'rgb(255,-10,0)',
+ 'rgb(110%, 0%, 0%)',
+ 'rgba(255,0,0)',
+ 'rgba(255,0,0,1)',
+ 'rgba(255 0 0 / 1)',
+ 'rgba(100%,0%,0%,1)',
+ 'rgba(0,0,255,0.5)',
+ 'rgba(100%, 50%, 0%, 0.1)',
+ 'hsl(120, 100%, 50%)',
+ 'hsl(120 100% 50%)',
+ 'hsl(120, 100%, 50%, 1.0)',
+ 'hsl(120 100% 50% / 1.0)',
+ 'hsla(120, 100%, 50%)',
+ 'hsla(120 100% 50%)',
+ 'hsla(120, 100%, 50%, 1.0)',
+ 'hsla(120 100% 50% / 1.0)',
+ 'hsl(120deg, 100%, 50%)',
+ 'hsl(133.33333333grad, 100%, 50%)',
+ 'hsl(2.0943951024rad, 100%, 50%)',
+ 'hsl(0.3333333333turn, 100%, 50%)',
+];
+
+validThemeColors.forEach(background_color => {
+ data.jsonText = JSON.stringify({
+ background_color: background_color
+ });
+ var result = processor.process(data);
+
+ is(result.background_color, background_color, `Expect background_color to be returned: ${background_color}.`);
+});
+
+var invalidThemeColors = [
+ 'marooon',
+ 'f000000',
+ '#ff00000',
+ 'rgb(100, 0%, 0%)',
+ 'rgb(255,0)',
+ 'rbg(255,-10,0)',
+ 'rgb(110, 0%, 0%)',
+ '(255,0,0) }',
+ 'rgba(255)',
+ ' rgb(100%,0%,0%) }',
+ 'hsl(120, 100%, 50)',
+ 'hsl(120, 100%, 50.0)',
+ 'hsl 120, 100%, 50%',
+ 'hsla{120, 100%, 50%, 1}',
+]
+
+invalidThemeColors.forEach(background_color => {
+ data.jsonText = JSON.stringify({
+ background_color: background_color
+ });
+ var result = processor.process(data);
+
+ is(result.background_color, undefined, `Expect background_color to be undefined: ${background_color}.`);
+});
+
+// Trim tests
+validThemeColors.forEach(background_color => {
+ var expandedThemeColor = `${seperators}${lineTerminators}${background_color}${lineTerminators}${seperators}`;
+ data.jsonText = JSON.stringify({
+ background_color: expandedThemeColor
+ });
+ var result = processor.process(data);
+
+ is(result.background_color, background_color, `Expect trimmed background_color to be returned.`);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_dir.html b/dom/manifest/test/test_ManifestProcessor_dir.html
new file mode 100644
index 000000000..1978eeca3
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_dir.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1258899
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1258899</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * dir member
+ * https://w3c.github.io/manifest/#dir-member
+ **/
+'use strict';
+//Type checks
+typeTests.forEach((type) => {
+ var expected = `Expect non - string dir to default to "auto".`;
+ data.jsonText = JSON.stringify({
+ dir: type
+ });
+ var result = processor.process(data);
+ is(result.dir, 'auto', expected);
+});
+
+/*Test valid values*/
+var validDirs = ['ltr', 'rtl', 'auto']
+validDirs.forEach((dir) => {
+ var expected = `Expect dir value to be ${dir}.`;
+ data.jsonText = JSON.stringify({dir});
+ var result = processor.process(data);
+ is(result.dir, dir, expected);
+});
+
+//trim tests
+validDirs.forEach((dir) => {
+ var expected = `Expect trimmed dir to be returned.`;
+ var expandeddir = seperators + lineTerminators + dir + lineTerminators + seperators;
+ data.jsonText = JSON.stringify({
+ dir: expandeddir
+ });
+ var result = processor.process(data);
+ is(result.dir, dir, expected);
+});
+
+//Unknown/Invalid directions
+var invalidDirs = ['LTR', 'RtL', `fooo${whiteSpace}rtl`, '', 'bar baz, some value', 'ltr rtl auto', 'AuTo'];
+invalidDirs.forEach((dir) => {
+ var expected = `Expect default dir "auto" to be returned: '${dir}'`;
+ data.jsonText = JSON.stringify({dir});
+ var result = processor.process(data);
+ is(result.dir, 'auto', expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_display.html b/dom/manifest/test/test_ManifestProcessor_display.html
new file mode 100644
index 000000000..10106465a
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_display.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * display member
+ * https://w3c.github.io/manifest/#display-member
+ **/
+'use strict';
+//Type checks
+typeTests.forEach((type) => {
+ var expected = `Expect non - string display to default to "browser".`;
+ data.jsonText = JSON.stringify({
+ display: type
+ });
+ var result = processor.process(data);
+ is(result.display, 'browser', expected);
+});
+
+/*Test valid modes - case insensitive*/
+var validModes = [
+ 'fullscreen',
+ 'standalone',
+ 'minimal-ui',
+ 'browser',
+ 'FullScreen',
+ 'standAlone',
+ 'minimal-UI',
+ 'BROWSER',
+]
+validModes.forEach((mode) => {
+ var expected = `Expect display mode to be ${mode.toLowerCase()}.`;
+ data.jsonText = JSON.stringify({
+ display: mode
+ });
+ var result = processor.process(data);
+ is(result.display, mode.toLowerCase(), expected);
+});
+
+//trim tests
+validModes.forEach((display) => {
+ var expected = `Expect trimmed display mode to be returned.`;
+ var expandedDisplay = seperators + lineTerminators + display + lineTerminators + seperators;
+ data.jsonText = JSON.stringify({
+ display: expandedDisplay
+ });
+ var result = processor.process(data);
+ is(result.display, display.toLowerCase(), expected);
+});
+
+//Unknown modes
+var invalidModes = [
+ 'foo',
+ `fooo${whiteSpace}`,
+ '',
+ 'fullscreen,standalone',
+ 'standalone fullscreen',
+ 'FULLSCreENS',
+];
+
+invalidModes.forEach((invalidMode) => {
+ var expected = `Expect default display mode "browser" to be returned: '${invalidMode}'`;
+ data.jsonText = JSON.stringify({
+ display: invalidMode
+ });
+ var result = processor.process(data);
+ is(result.display, 'browser', expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_icons.html b/dom/manifest/test/test_ManifestProcessor_icons.html
new file mode 100644
index 000000000..9bd3d90ec
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_icons.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Manifest icons member
+ * https://w3c.github.io/manifest/#icons-member
+ **/
+
+'use strict';
+
+typeTests.forEach((type) => {
+ var expected = `Expect non-array icons to be empty array: ${typeof type}.`;
+ data.jsonText = JSON.stringify({
+ icons: type
+ });
+ var result = processor.process(data);
+ var y = SpecialPowers.unwrap(result.icons);
+ is(result.icons.length, 0, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_lang.html b/dom/manifest/test/test_ManifestProcessor_lang.html
new file mode 100644
index 000000000..f5e994175
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_lang.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<!--
+Bug 1143879 - Implement lang member of Web manifest
+https://bugzilla.mozilla.org/show_bug.cgi?id=1143879
+-->
+<meta charset="utf-8">
+<title>Test for Bug 1143879 - Implement lang member of Web manifest</title>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script src="common.js"></script>
+<script>
+/**
+ * lang member
+ * https://w3c.github.io/manifest/#lang-member
+ **/
+/*globals is, typeTests, data, processor, seperators, lineTerminators, todo_is*/
+'use strict';
+// Type checks: checks that only strings are accepted.
+for (var type of typeTests) {
+ var expected = `Expect non-string to be undefined.`;
+ data.jsonText = JSON.stringify({
+ lang: type
+ });
+ var result = processor.process(data);
+ is(result.lang, undefined, expected);
+}
+
+// Test valid language tags - derived from IANA and BCP-47 spec
+// and our Intl.js implementation.
+var validTags = [
+ 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
+ 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
+ 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
+ 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
+ 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
+ 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ik', 'in', 'io',
+ 'is', 'it', 'iu', 'iw', 'ja', 'ji', 'jv', 'jw', 'ka', 'kg', 'ki', 'kj',
+ 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la',
+ 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk',
+ 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng',
+ 'nl', 'nn', 'no', 'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa',
+ 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc',
+ 'sd', 'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr',
+ 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl',
+ 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've',
+ 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu', 'en-US',
+ 'jp-JS', 'pt-PT', 'pt-BR', 'de-CH', 'de-DE-1901', 'es-419', 'sl-IT-nedis',
+ 'en-US-boont', 'mn-Cyrl-MN', 'x-fr-CH', 'sr-Cyrl', 'sr-Latn',
+ 'hy-Latn-IT-arevela', 'zh-TW', 'en-GB-boont-r-extended-sequence-x-private',
+ 'zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private',
+ 'zh-cmn-Hans-CN', 'cmn-Hans-CN', 'zh-yue-HK', 'yue-HK',
+ 'de-CH-x-phonebk', 'az-Arab-x-AZE-derbend', 'x-whatever',
+ 'qaa-Qaaa-QM-x-southern'
+];
+for (var tag of validTags) {
+ var expected = `Expect lang to be ${tag}.`;
+ data.jsonText = JSON.stringify({
+ lang: tag
+ });
+ var result = processor.process(data);
+ is(result.lang, tag, expected);
+}
+
+// trim tests - check that language tags get trimmed properly.
+for (var tag of validTags) {
+ var expected = `Expect trimmed tag to be returned.`;
+ var expandedtag = seperators + lineTerminators + tag;
+ expandedtag += lineTerminators + seperators;
+ data.jsonText = JSON.stringify({
+ lang: expandedtag
+ });
+ var result = processor.process(data);
+ is(result.lang, tag, expected);
+}
+
+//Invalid language tags, derived from BCP-47 and made up.
+var invalidTags = [
+ 'de-419-DE', ' a-DE ', 'ar-a-aaa-b-bbb-a-ccc', 'sdafsdfaadsfdsf', 'i',
+ 'i-phone', 'en US', 'EN-*-US-JP', 'JA-INVALID-TAG', '123123123'
+];
+for (var item of invalidTags) {
+ var expected = `Expect invalid tag (${item}) to be treated as undefined.`;
+ data.jsonText = JSON.stringify({
+ lang: item
+ });
+ var result = processor.process(data);
+ todo_is(result.lang, undefined, expected);
+}
+
+// Canonical form conversion tests. We convert the following tags, which are in
+// canonical form, to upper case and expect the processor to return them
+// in canonical form.
+var canonicalTags = [
+ 'jp-JS', 'pt-PT', 'pt-BR', 'de-CH', 'de-DE-1901', 'es-419', 'sl-IT-nedis',
+ 'en-US-boont', 'mn-Cyrl-MN', 'x-fr-CH', 'sr-Cyrl', 'sr-Latn',
+ 'hy-Latn-IT-arevela', 'zh-TW', 'en-GB-boont-r-extended-sequence-x-private',
+ 'zh-cmn-Hans-CN', 'cmn-Hans-CN', 'zh-yue-HK', 'yue-HK',
+ 'de-CH-x-phonebk', 'az-Arab-x-AZE-derbend', 'x-whatever',
+ 'qaa-Qaaa-QM-x-southern'
+];
+
+for (var tag of canonicalTags) {
+ var uppedTag = tag.toUpperCase();
+ var expected = `Expect tag (${uppedTag}) to be in canonical form (${tag}).`;
+ data.jsonText = JSON.stringify({
+ lang: uppedTag
+ });
+ var result = processor.process(data);
+ todo_is(result.lang, tag, expected);
+}
+
+</script>
diff --git a/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html b/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
new file mode 100644
index 000000000..682c8d225
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * name and short_name members
+ * https://w3c.github.io/manifest/#name-member
+ * https://w3c.github.io/manifest/#short_name-member
+ **/
+
+'use strict';
+
+var trimNamesTests = [
+ `${seperators}pass${seperators}`,
+ `${lineTerminators}pass${lineTerminators}`,
+ `${whiteSpace}pass${whiteSpace}`,
+ //BOM
+ `\uFEFFpass\uFEFF`
+];
+var props = ['name', 'short_name'];
+
+props.forEach((prop) => {
+ trimNamesTests.forEach((trimmableString) => {
+ var assetion = `Expecting ${prop} to be trimmed.`;
+ var obj = {};
+ obj[prop] = trimmableString;
+ data.jsonText = JSON.stringify(obj);
+ var result = processor.process(data);
+ is(result[prop], 'pass', assetion);
+ });
+});
+
+/*
+ * If the object is not a string, it becomes undefined
+ */
+props.forEach((prop) => {
+ typeTests.forEach((type) => {
+ var expected = `Expect non - string ${prop} to be undefined: ${typeof type}`;
+ var obj = {};
+ obj[prop] = type;
+ data.jsonText = JSON.stringify(obj);
+ var result = processor.process(data);
+ SimpleTest.ok(result[prop] === undefined, true, expected);
+ });
+});
+
+/**
+ * acceptable names - including long names
+ */
+var acceptableNames = [
+ 'pass',
+ `pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+ pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+ pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+ pass pass pass pass pass pass pass pass pass pass pass pass`,
+ 'これは許容できる名前です',
+ 'ນີ້ແມ່ນຊື່ທີ່ຍອມຮັບໄດ້'
+];
+
+props.forEach((prop) => {
+ acceptableNames.forEach((name) => {
+ var expected = `Expecting name to be acceptable : ${name}`;
+ var obj = {};
+ obj[prop] = name;
+ data.jsonText = JSON.stringify(obj);
+ var result = processor.process(data);
+ is(result[prop], name, expected);
+ });
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_orientation.html b/dom/manifest/test/test_ManifestProcessor_orientation.html
new file mode 100644
index 000000000..67f19a9ff
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_orientation.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * orientation member
+ * https://w3c.github.io/manifest/#orientation-member
+ **/
+'use strict';
+
+typeTests.forEach((type) => {
+ var expected = `Expect non-string orientation to be empty string : ${typeof type}.`;
+ data.jsonText = JSON.stringify({
+ orientation: type
+ });
+ var result = processor.process(data);
+ is(result.orientation, undefined, expected);
+});
+
+var validOrientations = [
+ 'any',
+ 'natural',
+ 'landscape',
+ 'portrait',
+ 'portrait-primary',
+ 'portrait-secondary',
+ 'landscape-primary',
+ 'landscape-secondary',
+ 'aNy',
+ 'NaTuRal',
+ 'LANDsCAPE',
+ 'PORTRAIT',
+ 'portrait-PRIMARY',
+ 'portrait-SECONDARY',
+ 'LANDSCAPE-primary',
+ 'LANDSCAPE-secondary',
+];
+
+validOrientations.forEach((orientation) => {
+ var expected = `Expect orientation to be returned: ${orientation}.`;
+ data.jsonText = JSON.stringify({ orientation });
+ var result = processor.process(data);
+ is(result.orientation, orientation.toLowerCase(), expected);
+});
+
+var invalidOrientations = [
+ 'all',
+ 'ANYMany',
+ 'NaTuRalle',
+ 'portrait-primary portrait-secondary',
+ 'portrait-primary,portrait-secondary',
+ 'any-natural',
+ 'portrait-landscape',
+ 'primary-portrait',
+ 'secondary-portrait',
+ 'landscape-landscape',
+ 'secondary-primary'
+];
+
+invalidOrientations.forEach((orientation) => {
+ var expected = `Expect orientation to be empty string: ${orientation}.`;
+ data.jsonText = JSON.stringify({ orientation });
+ var result = processor.process(data);
+ is(result.orientation, undefined, expected);
+});
+
+//Trim tests
+validOrientations.forEach((orientation) => {
+ var expected = `Expect trimmed orientation to be returned.`;
+ var expandedOrientation = `${seperators}${lineTerminators}${orientation}${lineTerminators}${seperators}`;
+ data.jsonText = JSON.stringify({
+ orientation: expandedOrientation
+ });
+ var result = processor.process(data);
+ is(result.orientation, orientation.toLowerCase(), expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_scope.html b/dom/manifest/test/test_ManifestProcessor_scope.html
new file mode 100644
index 000000000..b1cc9dbd1
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_scope.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Manifest scope
+ * https://w3c.github.io/manifest/#scope-member
+ **/
+'use strict';
+var expected = 'Expect non-string scope to be undefined';
+typeTests.forEach((type) => {
+ data.jsonText = JSON.stringify({
+ scope: type
+ });
+ var result = processor.process(data);
+ is(result.scope, undefined, expected);
+});
+
+var expected = 'Expect different origin to be treated as undefined';
+data.jsonText = JSON.stringify({
+ scope: 'http://not-same-origin'
+});
+var result = processor.process(data);
+is(result.scope, undefined, expected);
+
+var expected = 'Expect the empty string to be treated as undefined.';
+data.jsonText = JSON.stringify({
+ scope: ''
+});
+var result = processor.process(data);
+is(result.scope, undefined, expected);
+
+var expected = 'Resolve URLs relative to manifest.';
+var URLs = ['path', '/path', '../../path'];
+URLs.forEach((url) => {
+ data.jsonText = JSON.stringify({
+ scope: url,
+ start_url: "/path"
+ });
+ var absURL = new URL(url, manifestURL).toString();
+ var result = processor.process(data);
+ is(result.scope, absURL, expected);
+});
+
+var expected = 'If start URL is not in scope, return undefined.';
+data.jsonText = JSON.stringify({
+ scope: 'foo',
+ start_url: 'bar'
+});
+var result = processor.process(data);
+is(result.scope, undefined, expected);
+
+var expected = 'If start URL is in scope, use the scope.';
+data.jsonText = JSON.stringify({
+ start_url: 'foobar',
+ scope: 'foo'
+});
+var result = processor.process(data);
+is(result.scope.toString(), new URL('foo', manifestURL).toString(), expected);
+
+var expected = 'Expect start_url to be ' + new URL('foobar', manifestURL).toString();
+is(result.start_url.toString(), new URL('foobar', manifestURL).toString(), expected);
+
+var expected = 'If start URL is in scope, use the scope.';
+data.jsonText = JSON.stringify({
+ start_url: '/foo/',
+ scope: '/foo/'
+});
+var result = processor.process(data);
+is(result.scope.toString(), new URL('/foo/', manifestURL).toString(), expected);
+
+var expected = 'If start URL is in scope, use the scope.';
+data.jsonText = JSON.stringify({
+ start_url: '.././foo/',
+ scope: '../foo/'
+});
+var result = processor.process(data);
+is(result.scope.toString(), new URL('/foo/', manifestURL).toString(), expected);
+
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_start_url.html b/dom/manifest/test/test_ManifestProcessor_start_url.html
new file mode 100644
index 000000000..d0b381fa2
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_start_url.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * Manifest start_url
+ * https://w3c.github.io/manifest/#start_url-member
+ **/
+'use strict';
+typeTests.forEach((type) => {
+ var expected = `Expect non - string start_url to be doc's url: ${typeof type}.`;
+ data.jsonText = JSON.stringify({
+ start_url: type
+ });
+ var result = processor.process(data);
+ is(result.start_url.toString(), docURL.toString(), expected);
+});
+
+//Not same origin
+var expected = `Expect different origin URLs to become document's URL.`;
+data.jsonText = JSON.stringify({
+ start_url: 'http://not-same-origin'
+});
+var result = processor.process(data);
+is(result.start_url.toString(), docURL.toString(), expected);
+
+//Empty string test
+var expected = `Expect empty string for start_url to become document's URL.`;
+data.jsonText = JSON.stringify({
+ start_url: ''
+});
+var result = processor.process(data);
+is(result.start_url.toString(), docURL.toString(), expected);
+
+//Resolve URLs relative to manfiest
+var URLs = ['path', '/path', '../../path',
+ `${whiteSpace}path${whiteSpace}`,
+ `${whiteSpace}/path`,
+ `${whiteSpace}../../path`
+];
+URLs.forEach((url) => {
+ var expected = `Resolve URLs relative to manifest.`;
+ data.jsonText = JSON.stringify({
+ start_url: url
+ });
+ var absURL = new URL(url, manifestURL).toString();
+ var result = processor.process(data);
+ is(result.start_url.toString(), absURL, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_theme_color.html b/dom/manifest/test/test_ManifestProcessor_theme_color.html
new file mode 100644
index 000000000..c08830025
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_theme_color.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195018
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1195018</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+/**
+ * theme_color member
+ * https://w3c.github.io/manifest/#theme_color-member
+ **/
+'use strict';
+
+typeTests.forEach(type => {
+ data.jsonText = JSON.stringify({
+ theme_color: type
+ });
+ var result = processor.process(data);
+
+ is(result.theme_color, undefined, `Expect non-string theme_color to be undefined: ${typeof type}.`);
+});
+
+var validThemeColors = [
+ 'maroon',
+ '#f00',
+ '#ff0000',
+ 'rgb(255,0,0)',
+ 'rgb(255,0,0,1)',
+ 'rgb(255,0,0,1.0)',
+ 'rgb(255,0,0,100%)',
+ 'rgb(255 0 0)',
+ 'rgb(255 0 0 / 1)',
+ 'rgb(255 0 0 / 1.0)',
+ 'rgb(255 0 0 / 100%)',
+ 'rgb(100%, 0%, 0%)',
+ 'rgb(100%, 0%, 0%, 1)',
+ 'rgb(100%, 0%, 0%, 1.0)',
+ 'rgb(100%, 0%, 0%, 100%)',
+ 'rgb(100% 0% 0%)',
+ 'rgb(100% 0% 0% / 1)',
+ 'rgb(100%, 0%, 0%, 1.0)',
+ 'rgb(100%, 0%, 0%, 100%)',
+ 'rgb(300,0,0)',
+ 'rgb(300 0 0)',
+ 'rgb(255,-10,0)',
+ 'rgb(110%, 0%, 0%)',
+ 'rgba(255,0,0)',
+ 'rgba(255,0,0,1)',
+ 'rgba(255 0 0 / 1)',
+ 'rgba(100%,0%,0%,1)',
+ 'rgba(0,0,255,0.5)',
+ 'rgba(100%, 50%, 0%, 0.1)',
+ 'hsl(120, 100%, 50%)',
+ 'hsl(120 100% 50%)',
+ 'hsl(120, 100%, 50%, 1.0)',
+ 'hsl(120 100% 50% / 1.0)',
+ 'hsla(120, 100%, 50%)',
+ 'hsla(120 100% 50%)',
+ 'hsla(120, 100%, 50%, 1.0)',
+ 'hsla(120 100% 50% / 1.0)',
+ 'hsl(120deg, 100%, 50%)',
+ 'hsl(133.33333333grad, 100%, 50%)',
+ 'hsl(2.0943951024rad, 100%, 50%)',
+ 'hsl(0.3333333333turn, 100%, 50%)',
+];
+
+validThemeColors.forEach(theme_color => {
+ data.jsonText = JSON.stringify({
+ theme_color: theme_color
+ });
+ var result = processor.process(data);
+
+ is(result.theme_color, theme_color, `Expect theme_color to be returned: ${theme_color}.`);
+});
+
+var invalidThemeColors = [
+ 'marooon',
+ 'f000000',
+ '#ff00000',
+ 'rgb(100, 0%, 0%)',
+ 'rgb(255,0)',
+ 'rbg(255,-10,0)',
+ 'rgb(110, 0%, 0%)',
+ '(255,0,0) }',
+ 'rgba(255)',
+ ' rgb(100%,0%,0%) }',
+ 'hsl(120, 100%, 50)',
+ 'hsl(120, 100%, 50.0)',
+ 'hsl 120, 100%, 50%',
+ 'hsla{120, 100%, 50%, 1}',
+]
+
+invalidThemeColors.forEach(theme_color => {
+ data.jsonText = JSON.stringify({
+ theme_color: theme_color
+ });
+ var result = processor.process(data);
+
+ is(result.theme_color, undefined, `Expect theme_color to be undefined: ${theme_color}.`);
+});
+
+// Trim tests
+validThemeColors.forEach(theme_color => {
+ var expandedThemeColor = `${seperators}${lineTerminators}${theme_color}${lineTerminators}${seperators}`;
+ data.jsonText = JSON.stringify({
+ theme_color: expandedThemeColor
+ });
+ var result = processor.process(data);
+
+ is(result.theme_color, theme_color, `Expect trimmed theme_color to be returned.`);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_warnings.html b/dom/manifest/test/test_ManifestProcessor_warnings.html
new file mode 100644
index 000000000..865ef8054
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_warnings.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1086997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086997</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+'use strict';
+
+const {
+ ConsoleAPI
+} = SpecialPowers.Cu.import('resource://gre/modules/Console.jsm');
+
+var warning = null;
+
+var originalWarn = ConsoleAPI.prototype.warn;
+ConsoleAPI.prototype.warn = function(aWarning) {
+ warning = aWarning;
+};
+
+[
+ {
+ func: () => data.jsonText = JSON.stringify(1),
+ warning: 'Manifest should be an object.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify(null),
+ warning: 'Manifest should be an object.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify('a string'),
+ warning: 'Manifest should be an object.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ scope: 'https://www.mozilla.org',
+ }),
+ warning: 'The scope URL must be same origin as document.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ scope: 'foo',
+ start_url: 'bar',
+ }),
+ warning: 'The start URL is outside the scope, so the scope is invalid.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ start_url: 'https://www.mozilla.org',
+ }),
+ warning: 'The start URL must be same origin as document.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ start_url: 42,
+ }),
+ warning: 'Expected the manifest\u2019s start_url member to be a string.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ theme_color: '42',
+ }),
+ warning: 'theme_color: 42 is not a valid CSS color.',
+ },
+ {
+ func: () => data.jsonText = JSON.stringify({
+ background_color: '42',
+ }),
+ warning: 'background_color: 42 is not a valid CSS color.',
+ },
+].forEach(function(test) {
+ test.func();
+
+ processor.process(data);
+
+ is(warning, test.warning, 'Correct warning.');
+
+ warning = null;
+ data.manifestURL = manifestURL;
+ data.docURL = docURL;
+});
+
+ConsoleAPI.prototype.warn = originalWarn;
+ </script>
+</head>
diff --git a/dom/manifest/test/test_window_onappinstalled_event.html b/dom/manifest/test/test_window_onappinstalled_event.html
new file mode 100644
index 000000000..af57fbf77
--- /dev/null
+++ b/dom/manifest/test/test_window_onappinstalled_event.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265279
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1309099 - Web Manifest: Implement window.onappinstalled</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();
+ const finish = SimpleTest.finish.bind(SimpleTest);
+ enableOnAppInstalledPref()
+ .then(createIframe)
+ .then(checkImplementation)
+ .then(checkOnappInstalledEventFired)
+ .then(checkAddEventListenerFires)
+ .then(finish)
+ .catch(err => {
+ ok(false, err.stack);
+ finish();
+ });
+
+ function enableOnAppInstalledPref() {
+ const ops = {
+ "set": [
+ ["dom.manifest.onappinstalled", true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ // WebIDL conditional annotations for an interface are evaluate once per
+ // global, so we need to create an iframe to see the effects of calling
+ // enableOnAppInstalledPref().
+ function createIframe() {
+ return new Promise((resolve) => {
+ const iframe = document.createElement("iframe");
+ iframe.src = "about:blank";
+ iframe.onload = () => resolve(iframe.contentWindow);
+ document.body.appendChild(iframe);
+ });
+ }
+
+ // Check that the WebIDL is as expected.
+ function checkImplementation(ifrWindow) {
+ return new Promise((resolve, reject) => {
+ const hasOnAppInstalledProp = ifrWindow.hasOwnProperty("onappinstalled");
+ ok(hasOnAppInstalledProp, "window has own onappinstalled property");
+
+ // no point in continuing
+ if (!hasOnAppInstalledProp) {
+ const err = new Error("No 'onappinstalled' IDL attribute. Aborting early.");
+ return reject(err);
+ }
+ is(ifrWindow.onappinstalled, null, "window install is initially set to null");
+
+ // Check that enumerable, configurable, and has a getter and setter.
+ const objDescriptor = Object.getOwnPropertyDescriptor(ifrWindow, "onappinstalled");
+ ok(objDescriptor.enumerable, "is enumerable");
+ ok(objDescriptor.configurable, "is configurable");
+ ok(objDescriptor.hasOwnProperty("get"), "has getter");
+ ok(objDescriptor.hasOwnProperty("set"), "has setter");
+ resolve(ifrWindow);
+ });
+ }
+
+ // Checks that .onappinstalled receives an event.
+ function checkOnappInstalledEventFired(ifrWindow) {
+ const customEv = new CustomEvent("appinstalled");
+ return new Promise((resolve) => {
+ // Test is we receive the event on `appinstalled`
+ ifrWindow.onappinstalled = ev => {
+ ifrWindow.onappinstalled = null;
+ is(ev, customEv, "The events should be the same event object");
+ resolve(ifrWindow);
+ };
+ ifrWindow.dispatchEvent(customEv);
+ });
+ }
+
+ // Checks that .addEventListener("appinstalled") receives an event.
+ function checkAddEventListenerFires(ifrWindow) {
+ const customEv = new CustomEvent("appinstalled");
+ return new Promise((resolve) => {
+ ifrWindow.addEventListener("appinstalled", function handler(ev) {
+ ifrWindow.removeEventListener("appinstalled", handler);
+ is(ev, customEv, "The events should be the same");
+ resolve(ifrWindow);
+ });
+ ifrWindow.dispatchEvent(customEv);
+ });
+ }
+ </script>
+</head>