summaryrefslogtreecommitdiffstats
path: root/dom/manifest/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/manifest/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/manifest/test')
-rw-r--r--dom/manifest/test/blue-150.pngbin0 -> 534 bytes
-rw-r--r--dom/manifest/test/browser.toml23
-rw-r--r--dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js89
-rw-r--r--dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js66
-rw-r--r--dom/manifest/test/browser_ManifestObtainer_credentials.js45
-rw-r--r--dom/manifest/test/browser_ManifestObtainer_obtain.js268
-rw-r--r--dom/manifest/test/browser_Manifest_install.js58
-rw-r--r--dom/manifest/test/common.js139
-rw-r--r--dom/manifest/test/cookie_checker.sjs24
-rw-r--r--dom/manifest/test/cookie_setter.html8
-rw-r--r--dom/manifest/test/cookie_setter_with_credentials.html6
-rw-r--r--dom/manifest/test/cookie_setter_with_credentials_cross_origin.html14
-rw-r--r--dom/manifest/test/file_testserver.sjs56
-rw-r--r--dom/manifest/test/icon.pngbin0 -> 8156 bytes
-rw-r--r--dom/manifest/test/manifestLoader.html13
-rw-r--r--dom/manifest/test/mochitest.toml43
-rw-r--r--dom/manifest/test/red-50.pngbin0 -> 141 bytes
-rw-r--r--dom/manifest/test/resource.sjs85
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_purpose.html120
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_sizes.html88
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_src.html110
-rw-r--r--dom/manifest/test/test_ImageObjectProcessor_type.html57
-rw-r--r--dom/manifest/test/test_ManifestProcessor_JSON.html48
-rw-r--r--dom/manifest/test/test_ManifestProcessor_background_color.html22
-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_id.html123
-rw-r--r--dom/manifest/test/test_ManifestProcessor_lang.html139
-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.html113
-rw-r--r--dom/manifest/test/test_ManifestProcessor_start_url.html83
-rw-r--r--dom/manifest/test/test_ManifestProcessor_theme_color.html22
-rw-r--r--dom/manifest/test/test_ManifestProcessor_warnings.html149
-rw-r--r--dom/manifest/test/test_link_relList_supports_manifest.html47
36 files changed, 2388 insertions, 0 deletions
diff --git a/dom/manifest/test/blue-150.png b/dom/manifest/test/blue-150.png
new file mode 100644
index 0000000000..f4a62faddf
--- /dev/null
+++ b/dom/manifest/test/blue-150.png
Binary files differ
diff --git a/dom/manifest/test/browser.toml b/dom/manifest/test/browser.toml
new file mode 100644
index 0000000000..f5c28fcfb3
--- /dev/null
+++ b/dom/manifest/test/browser.toml
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files = [
+ "cookie_setter_with_credentials_cross_origin.html",
+ "cookie_setter_with_credentials.html",
+ "blue-150.png",
+ "cookie_checker.sjs",
+ "cookie_setter.html",
+ "file_testserver.sjs",
+ "manifestLoader.html",
+ "red-50.png",
+ "resource.sjs",
+]
+
+["browser_ManifestFinder_browserHasManifestLink.js"]
+
+["browser_ManifestIcons_browserFetchIcon.js"]
+
+["browser_ManifestObtainer_credentials.js"]
+
+["browser_ManifestObtainer_obtain.js"]
+
+["browser_Manifest_install.js"]
+skip-if = ["verify"]
diff --git a/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
new file mode 100644
index 0000000000..360c98220b
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js
@@ -0,0 +1,89 @@
+"use strict";
+const { ManifestFinder } = ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestFinder.sys.mjs"
+);
+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(async function () {
+ const expected = "Invalid types should throw a TypeError.";
+ for (let invalidValue of [undefined, null, 1, {}, "test"]) {
+ try {
+ await ManifestFinder.contentManifestLink(invalidValue);
+ ok(false, expected);
+ } catch (e) {
+ is(e.name, "TypeError", expected);
+ }
+ try {
+ await ManifestFinder.browserManifestLink(invalidValue);
+ ok(false, expected);
+ } catch (e) {
+ is(e.name, "TypeError", expected);
+ }
+ }
+});
+
+add_task(async function () {
+ const runningTests = tests
+ .map(test => ({
+ gBrowser,
+ test,
+ url: makeTestURL(test),
+ }))
+ .map(tabOptions =>
+ BrowserTestUtils.withNewTab(tabOptions, async function (browser) {
+ const result = await ManifestFinder.browserHasManifestLink(browser);
+ tabOptions.test.run(result);
+ })
+ );
+ await Promise.all(runningTests);
+});
diff --git a/dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js b/dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js
new file mode 100644
index 0000000000..e56e1e25c2
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestIcons_browserFetchIcon.js
@@ -0,0 +1,66 @@
+"use strict";
+
+Services.prefs.setBoolPref("dom.manifest.enabled", true);
+
+const { ManifestIcons } = ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestIcons.sys.mjs"
+);
+const { ManifestObtainer } = ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestObtainer.sys.mjs"
+);
+
+const defaultURL = new URL(
+ "https://example.org/browser/dom/manifest/test/resource.sjs"
+);
+defaultURL.searchParams.set("Content-Type", "application/manifest+json");
+
+const manifestMock = JSON.stringify({
+ icons: [
+ {
+ sizes: "50x50",
+ src: "red-50.png?Content-type=image/png",
+ },
+ {
+ sizes: "150x150",
+ src: "blue-150.png?Content-type=image/png",
+ },
+ ],
+});
+
+function makeTestURL() {
+ const url = new URL(defaultURL);
+ const body = `<link rel="manifest" href='${defaultURL}&body=${manifestMock}'>`;
+ url.searchParams.set("Content-Type", "text/html; charset=utf-8");
+ url.searchParams.set("body", encodeURIComponent(body));
+ return url.href;
+}
+
+function getIconColor(icon) {
+ return new Promise((resolve, reject) => {
+ const canvas = content.document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ const image = new content.Image();
+ image.onload = function () {
+ ctx.drawImage(image, 0, 0);
+ resolve(ctx.getImageData(1, 1, 1, 1).data);
+ };
+ image.onerror = function () {
+ reject(new Error("could not create image"));
+ };
+ image.src = icon;
+ });
+}
+
+add_task(async function () {
+ const tabOptions = { gBrowser, url: makeTestURL() };
+ await BrowserTestUtils.withNewTab(tabOptions, async function (browser) {
+ const manifest = await ManifestObtainer.browserObtainManifest(browser);
+ let icon = await ManifestIcons.browserFetchIcon(browser, manifest, 25);
+ let color = await SpecialPowers.spawn(browser, [icon], getIconColor);
+ is(color[0], 255, "Fetched red icon");
+
+ icon = await ManifestIcons.browserFetchIcon(browser, manifest, 500);
+ color = await SpecialPowers.spawn(browser, [icon], getIconColor);
+ is(color[2], 255, "Fetched blue icon");
+ });
+});
diff --git a/dom/manifest/test/browser_ManifestObtainer_credentials.js b/dom/manifest/test/browser_ManifestObtainer_credentials.js
new file mode 100644
index 0000000000..826ec24fc0
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestObtainer_credentials.js
@@ -0,0 +1,45 @@
+"use strict";
+
+Services.prefs.setBoolPref("dom.manifest.enabled", true);
+
+const { ManifestObtainer } = ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestObtainer.sys.mjs"
+);
+
+// Don't send cookies
+add_task(async function () {
+ const testPath = "/browser/dom/manifest/test/cookie_setter.html";
+ const tabURL = `https://example.com${testPath}`;
+ const browser = BrowserTestUtils.addTab(gBrowser, tabURL).linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+ const { short_name } = await ManifestObtainer.browserObtainManifest(browser);
+ is(short_name, "no cookie");
+ const tab = gBrowser.getTabForBrowser(browser);
+ gBrowser.removeTab(tab);
+});
+
+// Send cookies
+add_task(async function () {
+ const testPath =
+ "/browser/dom/manifest/test/cookie_setter_with_credentials.html";
+ const tabURL = `https://example.com${testPath}`;
+ const browser = BrowserTestUtils.addTab(gBrowser, tabURL).linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+ const { short_name } = await ManifestObtainer.browserObtainManifest(browser);
+ is(short_name, "🍪");
+ const tab = gBrowser.getTabForBrowser(browser);
+ gBrowser.removeTab(tab);
+});
+
+// Cross origin - we go from example.com to example.org
+add_task(async function () {
+ const testPath =
+ "/browser/dom/manifest/test/cookie_setter_with_credentials_cross_origin.html";
+ const tabURL = `https://example.com${testPath}`;
+ const browser = BrowserTestUtils.addTab(gBrowser, tabURL).linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+ const { short_name } = await ManifestObtainer.browserObtainManifest(browser);
+ is(short_name, "no cookie");
+ const tab = gBrowser.getTabForBrowser(browser);
+ gBrowser.removeTab(tab);
+});
diff --git a/dom/manifest/test/browser_ManifestObtainer_obtain.js b/dom/manifest/test/browser_ManifestObtainer_obtain.js
new file mode 100644
index 0000000000..a6d1dd6f23
--- /dev/null
+++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js
@@ -0,0 +1,268 @@
+"use strict";
+
+Services.prefs.setBoolPref("dom.manifest.enabled", true);
+Services.prefs.setBoolPref("dom.security.https_first", false);
+
+const { ManifestObtainer } = ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestObtainer.sys.mjs"
+);
+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: `<!-- no manifest in document -->`,
+ run(manifest) {
+ is(manifest, null, "Manifest without a href yields a null manifest.");
+ },
+ },
+ {
+ body: `<link rel="manifest">`,
+ run(manifest) {
+ is(manifest, null, "Manifest without a href yields a null manifest.");
+ },
+ },
+ {
+ 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(async 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;
+ }, []);
+
+ await Promise.all(promises);
+
+ function testObtainingManifest(aTest) {
+ return async function (aBrowser) {
+ try {
+ const manifest = await ManifestObtainer.browserObtainManifest(aBrowser);
+ aTest.run(manifest);
+ } catch (e) {
+ aTest.run(e);
+ }
+ };
+ }
+});
+
+add_task(async () => {
+ // This loads a generic html page.
+ const url = new URL(defaultURL);
+ // The body get injected into the page on the server.
+ const body = `<link rel="manifest" href='resource.sjs?body={"name": "conformance check"}'>`;
+ url.searchParams.set("body", encodeURIComponent(body));
+
+ // Let's open a tab!
+ const tabOpts = {
+ gBrowser,
+ url: url.href,
+ };
+ // Let's do the test
+ await BrowserTestUtils.withNewTab(tabOpts, async aBrowser => {
+ const obtainerOpts = {
+ checkConformance: true, // gives us back "moz_manifest_url" member
+ };
+ const manifest = await ManifestObtainer.browserObtainManifest(
+ aBrowser,
+ obtainerOpts
+ );
+ is(manifest.name, "conformance check");
+ ok("moz_manifest_url" in manifest, "Has a moz_manifest_url member");
+ const testString = defaultURL.origin + defaultURL.pathname;
+ ok(
+ manifest.moz_manifest_url.startsWith(testString),
+ `Expect to start with with the testString, but got ${manifest.moz_manifest_url} instead,`
+ );
+ // Clean up!
+ gBrowser.removeTab(gBrowser.getTabForBrowser(aBrowser));
+ });
+});
+
+/*
+ * e10s race condition tests
+ * Open a bunch of tabs and load manifests
+ * in each tab. They should all return pass.
+ */
+add_task(async 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 = tabURLs.map(
+ url => BrowserTestUtils.addTab(gBrowser, url).linkedBrowser
+ );
+
+ // Once all the pages have loaded, run a bunch of tests in "parallel".
+ await Promise.all(
+ (function* () {
+ for (let browser of browsers) {
+ yield BrowserTestUtils.browserLoaded(browser);
+ }
+ })()
+ );
+ // Flood random browsers with requests. Once promises settle, check that
+ // responses all pass.
+ const results = await Promise.all(
+ (function* () {
+ for (let browser of randBrowsers(browsers, 50)) {
+ yield 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_Manifest_install.js b/dom/manifest/test/browser_Manifest_install.js
new file mode 100644
index 0000000000..d3b949be19
--- /dev/null
+++ b/dom/manifest/test/browser_Manifest_install.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { Manifests } = ChromeUtils.importESModule(
+ "resource://gre/modules/Manifest.sys.mjs"
+);
+
+const defaultURL = new URL(
+ "http://example.org/browser/dom/manifest/test/resource.sjs"
+);
+defaultURL.searchParams.set("Content-Type", "application/manifest+json");
+
+const manifestMock = JSON.stringify({
+ short_name: "hello World",
+ scope: "/browser/",
+});
+const manifestUrl = `${defaultURL}&body=${manifestMock}`;
+
+function makeTestURL() {
+ const url = new URL(defaultURL);
+ const body = `<link rel="manifest" href='${manifestUrl}'>`;
+ url.searchParams.set("Content-Type", "text/html; charset=utf-8");
+ url.searchParams.set("body", encodeURIComponent(body));
+ return url.href;
+}
+
+add_task(async function () {
+ const tabOptions = { gBrowser, url: makeTestURL() };
+
+ await BrowserTestUtils.withNewTab(tabOptions, async function (browser) {
+ let manifest = await Manifests.getManifest(browser, manifestUrl);
+ is(manifest.installed, false, "We haven't installed this manifest yet");
+
+ await manifest.install(browser);
+ is(manifest.name, "hello World", "Manifest has correct name");
+ is(manifest.installed, true, "Manifest is installed");
+ is(manifest.url, manifestUrl, "has correct url");
+ is(manifest.browser, browser, "has correct browser");
+
+ manifest = await Manifests.getManifest(browser, manifestUrl);
+ is(manifest.installed, true, "New instances are installed");
+
+ manifest = await Manifests.getManifest(browser);
+ is(manifest.installed, true, "Will find manifest without being given url");
+
+ let foundManifest = Manifests.findManifestUrl(
+ "http://example.org/browser/dom/"
+ );
+ is(foundManifest, manifestUrl, "Finds manifests within scope");
+
+ foundManifest = Manifests.findManifestUrl("http://example.org/");
+ is(foundManifest, null, "Does not find manifests outside scope");
+ });
+ // Get the cached one now
+ await BrowserTestUtils.withNewTab(tabOptions, async browser => {
+ const manifest = await Manifests.getManifest(browser, manifestUrl);
+ is(manifest.browser, browser, "has updated browser object");
+ });
+});
diff --git a/dom/manifest/test/common.js b/dom/manifest/test/common.js
new file mode 100644
index 0000000000..50f27c119a
--- /dev/null
+++ b/dom/manifest/test/common.js
@@ -0,0 +1,139 @@
+/**
+ * Common infrastructure for manifest tests.
+ **/
+"use strict";
+const { ManifestProcessor } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/ManifestProcessor.sys.mjs"
+);
+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,
+ docURL,
+};
+
+const validThemeColors = [
+ ["maroon", "#800000ff"],
+ ["#f00", "#ff0000ff"],
+ ["#ff0000", "#ff0000ff"],
+ ["rgb(255,0,0)", "#ff0000ff"],
+ ["rgb(255,0,0,1)", "#ff0000ff"],
+ ["rgb(255,0,0,1.0)", "#ff0000ff"],
+ ["rgb(255,0,0,100%)", "#ff0000ff"],
+ ["rgb(255 0 0)", "#ff0000ff"],
+ ["rgb(255 0 0 / 1)", "#ff0000ff"],
+ ["rgb(255 0 0 / 1.0)", "#ff0000ff"],
+ ["rgb(255 0 0 / 100%)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%, 1)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%, 1.0)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%, 100%)", "#ff0000ff"],
+ ["rgb(100% 0% 0%)", "#ff0000ff"],
+ ["rgb(100% 0% 0% / 1)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%, 1.0)", "#ff0000ff"],
+ ["rgb(100%, 0%, 0%, 100%)", "#ff0000ff"],
+ ["rgb(300,0,0)", "#ff0000ff"],
+ ["rgb(300 0 0)", "#ff0000ff"],
+ ["rgb(255,-10,0)", "#ff0000ff"],
+ ["rgb(110%, 0%, 0%)", "#ff0000ff"],
+ ["rgba(255,0,0)", "#ff0000ff"],
+ ["rgba(255,0,0,1)", "#ff0000ff"],
+ ["rgba(255 0 0 / 1)", "#ff0000ff"],
+ ["rgba(100%,0%,0%,1)", "#ff0000ff"],
+ ["rgba(0,0,255,0.5)", "#0000ff80"],
+ ["rgba(100%, 50%, 0%, 0.1)", "#ff80001a"],
+ ["hsl(120, 100%, 50%)", "#00ff00ff"],
+ ["hsl(120 100% 50%)", "#00ff00ff"],
+ ["hsl(120, 100%, 50%, 1.0)", "#00ff00ff"],
+ ["hsl(120 100% 50% / 1.0)", "#00ff00ff"],
+ ["hsla(120, 100%, 50%)", "#00ff00ff"],
+ ["hsla(120 100% 50%)", "#00ff00ff"],
+ ["hsla(120, 100%, 50%, 1.0)", "#00ff00ff"],
+ ["hsla(120 100% 50% / 1.0)", "#00ff00ff"],
+ ["hsl(120deg, 100%, 50%)", "#00ff00ff"],
+ ["hsl(133.33333333grad, 100%, 50%)", "#00ff00ff"],
+ ["hsl(2.0943951024rad, 100%, 50%)", "#00ff00ff"],
+ ["hsl(0.3333333333turn, 100%, 50%)", "#00ff00ff"],
+];
+
+function setupManifest(key, value) {
+ const manifest = {};
+ manifest[key] = value;
+ data.jsonText = JSON.stringify(manifest);
+}
+
+function testValidColors(key) {
+ validThemeColors.forEach(item => {
+ const [manifest_color, parsed_color] = item;
+ setupManifest(key, manifest_color);
+ const result = processor.process(data);
+
+ is(
+ result[key],
+ parsed_color,
+ `Expect ${key} to be returned for ${manifest_color}`
+ );
+ });
+
+ // Trim tests
+ validThemeColors.forEach(item => {
+ const [manifest_color, parsed_color] = item;
+ const expandedThemeColor = `${seperators}${lineTerminators}${manifest_color}${lineTerminators}${seperators}`;
+ setupManifest(key, expandedThemeColor);
+ const result = processor.process(data);
+
+ is(
+ result[key],
+ parsed_color,
+ `Expect trimmed ${key} to be returned for ${manifest_color}`
+ );
+ });
+}
+
+const 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}",
+];
+
+function testInvalidColors(key) {
+ typeTests.forEach(type => {
+ setupManifest(key, type);
+ const result = processor.process(data);
+
+ is(
+ result[key],
+ undefined,
+ `Expect non-string ${key} to be undefined: ${typeof type}.`
+ );
+ });
+
+ invalidThemeColors.forEach(manifest_color => {
+ setupManifest(key, manifest_color);
+ const result = processor.process(data);
+
+ is(
+ result[key],
+ undefined,
+ `Expect ${key} to be undefined: ${manifest_color}.`
+ );
+ });
+}
diff --git a/dom/manifest/test/cookie_checker.sjs b/dom/manifest/test/cookie_checker.sjs
new file mode 100644
index 0000000000..5405a6207b
--- /dev/null
+++ b/dom/manifest/test/cookie_checker.sjs
@@ -0,0 +1,24 @@
+"use strict";
+let { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200);
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/json", false);
+
+ // CORS stuff
+ const origin = request.hasHeader("Origin")
+ ? request.getHeader("Origin")
+ : null;
+ if (origin) {
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+ }
+ const short_name = request.hasHeader("Cookie")
+ ? request.getHeader("Cookie")
+ : "no cookie";
+ response.write(JSON.stringify({ short_name }));
+}
diff --git a/dom/manifest/test/cookie_setter.html b/dom/manifest/test/cookie_setter.html
new file mode 100644
index 0000000000..d8f412fbcb
--- /dev/null
+++ b/dom/manifest/test/cookie_setter.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ document.cookie = "🍪";
+</script>
+<link
+ rel="manifest"
+ href="cookie_checker.sjs">
diff --git a/dom/manifest/test/cookie_setter_with_credentials.html b/dom/manifest/test/cookie_setter_with_credentials.html
new file mode 100644
index 0000000000..0d721b55ef
--- /dev/null
+++ b/dom/manifest/test/cookie_setter_with_credentials.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ document.cookie = "🍪";
+</script>
+<link rel="manifest" href="cookie_checker.sjs" crossorigin="use-credentials"`>
diff --git a/dom/manifest/test/cookie_setter_with_credentials_cross_origin.html b/dom/manifest/test/cookie_setter_with_credentials_cross_origin.html
new file mode 100644
index 0000000000..6d1c4045c3
--- /dev/null
+++ b/dom/manifest/test/cookie_setter_with_credentials_cross_origin.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<h1>Cross origin cookie sender</h1>
+<script>
+ document.cookie = "🍪";
+</script>
+<!--
+ This is loaded from "example.com", which then loads
+ the manifest from "example.org".
+-->
+<link
+ rel="manifest"
+ href="https://example.org/browser/dom/manifest/test/cookie_checker.sjs"
+ crossorigin="use-credentials">
diff --git a/dom/manifest/test/file_testserver.sjs b/dom/manifest/test/file_testserver.sjs
new file mode 100644
index 0000000000..aec29ee144
--- /dev/null
+++ b/dom/manifest/test/file_testserver.sjs
@@ -0,0 +1,56 @@
+"use strict";
+let { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+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 = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+
+ const testHTMLFileStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+
+ path
+ .split("/")
+ .filter(path_1 => path_1)
+ .reduce((file, path_2) => {
+ testHTMLFile.append(path_2);
+ 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/icon.png b/dom/manifest/test/icon.png
new file mode 100644
index 0000000000..bb87f783b7
--- /dev/null
+++ b/dom/manifest/test/icon.png
Binary files differ
diff --git a/dom/manifest/test/manifestLoader.html b/dom/manifest/test/manifestLoader.html
new file mode 100644
index 0000000000..e244260902
--- /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.toml b/dom/manifest/test/mochitest.toml
new file mode 100644
index 0000000000..e674c3229e
--- /dev/null
+++ b/dom/manifest/test/mochitest.toml
@@ -0,0 +1,43 @@
+[DEFAULT]
+support-files = [
+ "common.js",
+ "resource.sjs",
+ "manifestLoader.html",
+ "file_testserver.sjs",
+]
+
+["test_ImageObjectProcessor_purpose.html"]
+
+["test_ImageObjectProcessor_sizes.html"]
+
+["test_ImageObjectProcessor_src.html"]
+
+["test_ImageObjectProcessor_type.html"]
+
+["test_ManifestProcessor_JSON.html"]
+
+["test_ManifestProcessor_background_color.html"]
+
+["test_ManifestProcessor_dir.html"]
+
+["test_ManifestProcessor_display.html"]
+
+["test_ManifestProcessor_icons.html"]
+
+["test_ManifestProcessor_id.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_link_relList_supports_manifest.html"]
diff --git a/dom/manifest/test/red-50.png b/dom/manifest/test/red-50.png
new file mode 100644
index 0000000000..e2ce365c3c
--- /dev/null
+++ b/dom/manifest/test/red-50.png
Binary files differ
diff --git a/dom/manifest/test/resource.sjs b/dom/manifest/test/resource.sjs
new file mode 100644
index 0000000000..eadac95b64
--- /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";
+
+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_purpose.html b/dom/manifest/test/test_ImageObjectProcessor_purpose.html
new file mode 100644
index 0000000000..386c4566d4
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_purpose.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+ /**
+ * Image object's purpose member
+ * https://w3c.github.io/manifest/#purpose-member
+ **/
+
+ "use strict";
+ const testManifest = {
+ icons: [{
+ src: "test",
+ }],
+ };
+
+ const invalidPurposeTypes = [
+ [],
+ 123,
+ {},
+ null,
+ ]
+
+ invalidPurposeTypes.forEach(invalidType => {
+ const expected = `Invalid types get treated as 'any'.`;
+ testManifest.icons[0].purpose = invalidType;
+ data.jsonText = JSON.stringify(testManifest);
+ const result = processor.process(data);
+ is(result.icons.length, 1, expected);
+ is(result.icons[0].purpose.length, 1, expected);
+ is(result.icons[0].purpose[0], "any", expected);
+ });
+
+ const invalidPurposes = [
+ "not-known-test-purpose",
+ "invalid-purpose invalid-purpose",
+ "no-purpose invalid-purpose some-other-non-valid-purpose",
+ ];
+
+ invalidPurposes.forEach(invalidPurpose => {
+ const expected = `Expect invalid purposes to invalidate the icon.`;
+ testManifest.icons[0].purpose = invalidPurpose;
+ data.jsonText = JSON.stringify(testManifest);
+ const result = processor.process(data);
+ is(result.icons.length, 0, expected);
+ });
+
+ const mixedMaskableAndInvalidPurposes = [
+ "not-known-test-purpose maskable",
+ "maskable invalid-purpose invalid-purpose",
+ "no-purpose invalid-purpose maskable some-other-non-valid-purpose",
+ ];
+
+ mixedMaskableAndInvalidPurposes.forEach(mixedPurpose => {
+ const expected = `Expect on 'maskable' to remain.`;
+ testManifest.icons[0].purpose = mixedPurpose;
+ data.jsonText = JSON.stringify(testManifest);
+ const result = processor.process(data);
+ is(result.icons.length, 1, expected);
+ is(result.icons[0].purpose.join(), "maskable", expected);
+ });
+
+ const mixedMonochromeAndInvalidPurposes = [
+ "not-known-test-purpose monochrome",
+ "monochrome invalid-purpose invalid-purpose",
+ "no-purpose invalid-purpose monochrome some-other-non-valid-purpose",
+ ];
+
+ mixedMonochromeAndInvalidPurposes.forEach(mixedPurpose => {
+ const expected = `Expect on 'monochrome' to remain.`;
+ testManifest.icons[0].purpose = mixedPurpose;
+ data.jsonText = JSON.stringify(testManifest);
+ const result = processor.process(data);
+ is(result.icons.length, 1, expected);
+ is(result.icons[0].purpose.join(), "monochrome", expected);
+ });
+
+ const validPurposes = [
+ "maskable",
+ "monochrome",
+ "any",
+ "any maskable",
+ "maskable any",
+ "any monochrome",
+ "monochrome any",
+ "maskable monochrome any",
+ "monochrome maskable"
+ ];
+
+ validPurposes.forEach(purpose => {
+ testManifest.icons[0].purpose = purpose;
+ data.jsonText = JSON.stringify(testManifest);
+ var manifest = processor.process(data);
+ is(manifest.icons[0].purpose.join(" "), purpose, `Expected "${purpose}" as purpose.`);
+ });
+
+ const validWhiteSpace = [
+ "",
+ whiteSpace, // defined in common.js
+ `${whiteSpace}any`,
+ `any${whiteSpace}`,
+ `${whiteSpace}any${whiteSpace}`,
+ ];
+
+ validWhiteSpace.forEach(purpose => {
+ testManifest.icons[0].purpose = purpose;
+ data.jsonText = JSON.stringify(testManifest);
+ var manifest = processor.process(data);
+ is(manifest.icons[0].purpose.join(), "any");
+ });
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ImageObjectProcessor_sizes.html b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
new file mode 100644
index 0000000000..9418b44118
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script 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.join(" "), expect.join(" "), expected);
+});
+
+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 0000000000..8d17c3048f
--- /dev/null
+++ b/dom/manifest/test/test_ImageObjectProcessor_src.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script 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: "",
+ }],
+};
+
+expected = `Expect icons with invalid src prop to be filtered out.`;
+data.jsonText = JSON.stringify(invalidSrc);
+result = processor.process(data);
+is(result.icons.length, 0, expected);
+
+expected = `Expect icon's src to be a string.`;
+var withSrc = {
+ icons: [{
+ src: "pass",
+ }],
+};
+data.jsonText = JSON.stringify(withSrc);
+result = processor.process(data);
+is(typeof result.icons[0].src, "string", expected);
+
+
+expected = `Expect only icons with a src prop to be kept.`;
+withSrc = {
+ icons: [{
+ src: "pass",
+ }, {
+ src: "pass",
+ }, {}, {
+ foo: "foo",
+ }],
+};
+data.jsonText = JSON.stringify(withSrc);
+result = processor.process(data);
+is(result.icons.length, 2, expected);
+
+var expectedURL = new URL("pass", manifestURL);
+for (var icon of result.icons) {
+ 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) => {
+ expected = `Resolve icon src URLs relative to manifest.`;
+ data.jsonText = JSON.stringify({
+ icons: [{
+ src: url,
+ }],
+ });
+ var absURL = new URL(url, manifestURL).toString();
+ 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 0000000000..29627c5e46
--- /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 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 0000000000..b072355f20
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_JSON.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script 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;
+ data.checkConformance = true;
+ var result = processor.process(data);
+ SimpleTest.is(result.start_url, docURL.href, expected);
+ SimpleTest.ok(
+ [...result.moz_validation].find(x => x.error && x.type === "json"),
+ "A JSON error message is included");
+});
+
+var validButUnhelpful = ["1", 1, "[{}]"];
+validButUnhelpful.forEach((testString) => {
+ var expected = `Expect to recover from valid JSON: ${testString}`;
+ data.jsonText = testString;
+ var result = processor.process(data);
+ SimpleTest.is(result.start_url, docURL.href, expected);
+});
+
+// Bug 1534756 - Allow for null manifests
+var input = {
+ jsonText: "null",
+ manifestURL,
+ docURL,
+};
+var result = processor.process(input);
+SimpleTest.is(result, null, "Expected null when the input is null");
+
+ </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 0000000000..38073b8e4f
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_background_color.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195018
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1195018</title>
+ <script 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";
+
+testValidColors("background_color");
+testInvalidColors("background_color");
+ </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 0000000000..b5dc84a9e4
--- /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 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 0000000000..f5ea5fbc4f
--- /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 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 0000000000..578f49f5dc
--- /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 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);
+ SpecialPowers.unwrap(result.icons);
+ is(result.icons.length, 0, expected);
+});
+ </script>
+</head>
diff --git a/dom/manifest/test/test_ManifestProcessor_id.html b/dom/manifest/test/test_ManifestProcessor_id.html
new file mode 100644
index 0000000000..336d1d3a77
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_id.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1731940
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1731940 - implement id member</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="common.js"></script>
+ <script>
+ /**
+ * Manifest id member
+ * https://w3c.github.io/manifest/#id-member
+ **/
+ for (const type of typeTests) {
+ data.jsonText = JSON.stringify({
+ id: type,
+ });
+ const result = processor.process(data);
+ is(
+ result.id.toString(),
+ result.start_url.toString(),
+ `Expect non-string id to fall back to start_url: ${typeof type}.`
+ );
+ }
+
+ // Invalid URLs
+ const invalidURLs = [
+ "https://foo:65536",
+ "https://foo\u0000/",
+ "//invalid:65555",
+ "file:///passwords",
+ "about:blank",
+ "data:text/html,<html><script>alert('lol')<\/script></html>",
+ ];
+
+ for (const url of invalidURLs) {
+ data.jsonText = JSON.stringify({
+ id: url,
+ });
+ const result = processor.process(data);
+ is(
+ result.id.toString(),
+ result.start_url.toString(),
+ "Expect invalid id URL to fall back to start_url."
+ );
+ }
+
+ // Not same origin
+ data.jsonText = JSON.stringify({
+ id: "http://not-same-origin",
+ });
+ var result = processor.process(data);
+ is(
+ result.id.toString(),
+ result.start_url,
+ "Expect different origin id to fall back to start_url."
+ );
+
+ // Empty string test
+ data.jsonText = JSON.stringify({
+ id: "",
+ });
+ result = processor.process(data);
+ is(
+ result.id.toString(),
+ result.start_url.toString(),
+ `Expect empty string for id to use start_url.`
+ );
+
+ // Resolve URLs relative to the start_url's origin
+ const URLs = [
+ "path",
+ "/path",
+ "../../path",
+ "./path",
+ `${whiteSpace}path${whiteSpace}`,
+ `${whiteSpace}/path`,
+ `${whiteSpace}../../path`,
+ `${whiteSpace}./path`,
+ ];
+
+ for (const url of URLs) {
+ data.jsonText = JSON.stringify({
+ id: url,
+ start_url: "/path/some.html",
+ });
+ result = processor.process(data);
+ const baseOrigin = new URL(result.start_url.toString()).origin;
+ const expectedUrl = new URL(url, baseOrigin).toString();
+ is(
+ result.id.toString(),
+ expectedUrl,
+ "Expected id to be resolved relative to start_url's origin."
+ );
+ }
+
+ // Handles unicode encoded URLs
+ const specialCases = [
+ ["😀", "%F0%9F%98%80"],
+ [
+ "this/is/ok?query_is_ok=😀#keep_hash",
+ "this/is/ok?query_is_ok=%F0%9F%98%80#keep_hash",
+ ],
+ ];
+ for (const [id, expected] of specialCases) {
+ data.jsonText = JSON.stringify({
+ id,
+ start_url: "/my-app/",
+ });
+ result = processor.process(data);
+ const baseOrigin = new URL(result.start_url.toString()).origin;
+ const expectedUrl = new URL(expected, baseOrigin).toString();
+ is(
+ result.id.toString(),
+ expectedUrl,
+ `Expect id to be encoded/decoded per URL spec.`
+ );
+ }
+ </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 0000000000..568950c2ba
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_lang.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<!--
+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 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 (const type of typeTests) {
+ const expected = `Expect non-string to be undefined.`;
+ data.jsonText = JSON.stringify({
+ lang: type,
+ });
+ const 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", "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", "io",
+ "is", "it", "iu", "ja", "jv", "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", "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", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr",
+ "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk",
+ "tn", "to", "tr", "ts", "tt", "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", "sr-Cyrl", "sr-Latn",
+ "zh-TW", "en-GB-boont-posix-r-extended-sequence-x-private",
+ "yue-HK", "de-CH-x-phonebk", "az-Arab-x-aze-derbend",
+ "qaa-Qaaa-QM-x-southern",
+];
+
+
+for (var tag of validTags) {
+ const expected = `Expect lang to be "${tag}"`;
+ data.jsonText = JSON.stringify({
+ lang: tag,
+ });
+ const result = processor.process(data);
+ is(result.lang, tag, expected);
+}
+
+// Canonical form conversion... old names become new names.
+const granfatheredTags = [
+ ["bh", "bho"],
+ ["in", "id"],
+ ["iw", "he"],
+ ["ji", "yi"],
+ ["jw", "jv"],
+ ["mo", "ro"],
+ ["sh", "sr-Latn"],
+ ["tl", "fil"],
+ ["tw", "ak"],
+ ["nan-Hans-MM-variant2-variant1-t-zh-latn-u-ca-chinese-x-private",
+ "nan-Hans-MM-variant1-variant2-t-zh-latn-u-ca-chinese-x-private"],
+ ["cmn-Hans-CN", "zh-Hans-CN"],
+];
+
+for (const [oldTag, newTag] of granfatheredTags) {
+ const expected = `Expect lang to be "${newTag}"`;
+ data.jsonText = JSON.stringify({
+ lang: oldTag,
+ });
+ const result = processor.process(data);
+ is(result.lang, newTag, expected);
+}
+
+// trim tests - check that language tags get trimmed properly.
+for (tag of validTags) {
+ const expected = `Expect trimmed tag to be returned.`;
+ let expandedtag = seperators + lineTerminators + tag;
+ expandedtag += lineTerminators + seperators;
+ data.jsonText = JSON.stringify({
+ lang: expandedtag,
+ });
+ const 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) {
+ const expected = `Expect invalid tag (${item}) to be treated as undefined.`;
+ data.jsonText = JSON.stringify({
+ lang: item,
+ });
+ const result = processor.process(data);
+ 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", "sr-Cyrl", "sr-Latn",
+ "hy-Latn-IT", "zh-TW", "en-GB-boont-r-extended-sequence-x-private",
+ "yue-HK", "de-CH-x-phonebk", "az-Arab-x-aze-derbend",
+ "qaa-Qaaa-QM-x-southern",
+];
+
+for (tag of canonicalTags) {
+ var uppedTag = tag.toUpperCase();
+ const expected = `Expect tag (${uppedTag}) to be in canonical form (${tag}).`;
+ data.jsonText = JSON.stringify({
+ lang: uppedTag,
+ });
+ const result = processor.process(data);
+ 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 0000000000..e843b583e4
--- /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 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, 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 0000000000..9333eecbe3
--- /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 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 0000000000..590d76a251
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_scope.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script 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 the default";
+typeTests.forEach((type) => {
+ data.jsonText = JSON.stringify({
+ scope: type,
+ });
+ var result = processor.process(data);
+ is(result.scope, new URL(".", docURL).href, expected);
+});
+
+expected = "Expect different origin to be the default";
+data.jsonText = JSON.stringify({
+ scope: "http://not-same-origin",
+});
+var result = processor.process(data);
+is(result.scope, new URL(".", docURL).href, expected);
+
+expected = "Expect the empty string to be the default";
+data.jsonText = JSON.stringify({
+ scope: "",
+});
+result = processor.process(data);
+is(result.scope, new URL(".", docURL).href, expected);
+
+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();
+ result = processor.process(data);
+ is(result.scope, absURL, expected);
+});
+
+expected = "If start URL is not in scope, return the default.";
+data.jsonText = JSON.stringify({
+ scope: "foo",
+ start_url: "bar",
+});
+result = processor.process(data);
+let expected_start = new URL("bar", docURL);
+is(result.scope, new URL(document.location.origin).href, expected);
+
+expected = "If start URL is in scope, use the scope.";
+data.jsonText = JSON.stringify({
+ start_url: "foobar",
+ scope: "foo",
+});
+result = processor.process(data);
+is(result.scope.toString(), new URL("foo", manifestURL).toString(), expected);
+
+expected = "Expect start_url to be " + new URL("foobar", manifestURL).toString();
+is(result.start_url.toString(), new URL("foobar", manifestURL).toString(), expected);
+
+expected = "If start URL is in scope, use the scope.";
+data.jsonText = JSON.stringify({
+ start_url: "/foo/",
+ scope: "/foo/",
+});
+result = processor.process(data);
+is(result.scope.toString(), new URL("/foo/", manifestURL).toString(), expected);
+
+expected = "If start URL is in scope, use the scope.";
+data.jsonText = JSON.stringify({
+ start_url: ".././foo/",
+ scope: "../foo/",
+});
+result = processor.process(data);
+is(result.scope.toString(), new URL("/foo/", manifestURL).toString(), expected);
+
+expected = "scope member has the URL's query removed.";
+data.jsonText = JSON.stringify({
+ scope: "./test/?a=b&a=b&b=c&c=d&e",
+});
+result = processor.process(data);
+is(new URL(result.scope).search, "", expected);
+
+expected = "scope member has the URL's fragment removed.";
+data.jsonText = JSON.stringify({
+ scope: "./test/#fragment"
+});
+result = processor.process(data);
+is(new URL(result.scope).hash, "", expected);
+
+expected = "scope member has the URL's query and fragment removed.";
+data.jsonText = JSON.stringify({
+ scope: "./test/?a=b&a=b&b=c&c=d&e#fragment"
+});
+result = processor.process(data);
+is(new URL(result.scope).search, "", expected);
+is(new URL(result.scope).hash, "", 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 0000000000..1d172df555
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_start_url.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1079453</title>
+ <script 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
+expected = `Expect empty string for start_url to become document's URL.`;
+data.jsonText = JSON.stringify({
+ start_url: "",
+});
+result = processor.process(data);
+is(result.start_url.toString(), docURL.toString(), expected);
+
+// Resolve URLs relative to manifest
+var URLs = [
+ "path",
+ "/path",
+ "../../path",
+ `${whiteSpace}path${whiteSpace}`,
+ `${whiteSpace}/path`,
+ `${whiteSpace}../../path`,
+];
+
+URLs.forEach((url) => {
+ expected = `Resolve URLs relative to manifest.`;
+ data.jsonText = JSON.stringify({
+ start_url: url,
+ });
+ var absURL = new URL(url, manifestURL).toString();
+ result = processor.process(data);
+ is(result.start_url.toString(), absURL, expected);
+});
+
+// It retains the fragment
+var startURL = "./path?query=123#fragment";
+data.jsonText = JSON.stringify({
+ start_url: startURL,
+});
+var absURL = new URL(startURL, manifestURL).href;
+result = processor.process(data);
+is(result.start_url.toString(), absURL, "Retains fragment");
+
+// It retains the fragment on the document's location too.
+window.location = "#here";
+data.jsonText = JSON.stringify({});
+result = processor.process(data);
+is(
+ window.location.href,
+ result.start_url.toString(),
+ `Retains the fragment of document's location`
+);
+</script>
+ </head>
+</html>
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 0000000000..c73832748d
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_theme_color.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1195018
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1195018</title>
+ <script 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";
+
+testValidColors("theme_color");
+testInvalidColors("theme_color");
+ </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 0000000000..f2092c1dcf
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_warnings.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1086997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1086997</title>
+ <script 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 options = {...data, checkConformance: true } ;
+[
+ {
+ func: () => options.jsonText = JSON.stringify(1),
+ warn: "Manifest should be an object.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify("a string"),
+ warn: "Manifest should be an object.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ scope: "https://www.mozilla.org",
+ }),
+ warn: "The scope URL must be same origin as document.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ scope: "foo",
+ start_url: "bar",
+ }),
+ warn: "The start URL is outside the scope, so the scope is invalid.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ start_url: "https://www.mozilla.org",
+ }),
+ warn: "The start URL must be same origin as document.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ start_url: 42,
+ }),
+ warn: "Expected the manifest\u2019s start_url member to be a string.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ theme_color: "42",
+ }),
+ warn: "theme_color: 42 is not a valid CSS color.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ background_color: "42",
+ }),
+ warn: "background_color: 42 is not a valid CSS color.",
+ },
+ {
+ func: () => options.jsonText = JSON.stringify({
+ icons: [
+ { "src": "http://example.com", "sizes": "48x48"},
+ { "src": "http://:Invalid", "sizes": "48x48"},
+ ],
+ }),
+ warn: "icons item at index 1 is invalid. The src member is an invalid URL http://:Invalid",
+ },
+ // testing dom.properties: ManifestImageUnusable
+ {
+ func() {
+ return (options.jsonText = JSON.stringify({
+ icons: [
+ { src: "http://example.com", purpose: "any" }, // valid
+ { src: "http://example.com", purpose: "banana" }, // generates error
+ ],
+ }));
+ },
+ get warn() {
+ // Returns 2 warnings... array here is just to keep them organized
+ return [
+ "icons item at index 1 includes unsupported purpose(s): banana.",
+ "icons item at index 1 lacks a usable purpose. It will be ignored.",
+ ].join(" ");
+ },
+ },
+ // testing dom.properties: ManifestImageUnsupportedPurposes
+ {
+ func() {
+ return (options.jsonText = JSON.stringify({
+ icons: [
+ { src: "http://example.com", purpose: "any" }, // valid
+ { src: "http://example.com", purpose: "any foo bar baz bar bar baz" }, // generates error
+ ],
+ }));
+ },
+ warn: "icons item at index 1 includes unsupported purpose(s): foo bar baz.",
+ },
+ // testing dom.properties: ManifestImageRepeatedPurposes
+ {
+ func() {
+ return (options.jsonText = JSON.stringify({
+ icons: [
+ { src: "http://example.com", purpose: "any" }, // valid
+ {
+ src: "http://example.com",
+ purpose: "any maskable any maskable maskable", // generates error
+ },
+ ],
+ }));
+ },
+ warn: "icons item at index 1 includes repeated purpose(s): any maskable.",
+ },
+ // testing dom.properties: ManifestIdIsInvalid
+ {
+ func() {
+ return (options.jsonText = JSON.stringify({
+ id: "http://test:65536/foo",
+ }));
+ },
+ warn: "The id member did not resolve to a valid URL.",
+ },
+ // testing dom.properties ManifestIdNotSameOrigin
+ {
+ func() {
+ return (options.jsonText = JSON.stringify({
+ id: "https://other.com",
+ start_url: "/this/place"
+ }));
+ },
+ warn: "The id member must have the same origin as the start_url member.",
+ }
+].forEach((test, index) => {
+ test.func();
+ const result = processor.process(options);
+ let messages = [];
+ // Poking directly at "warn" triggers xray security wrapper.
+ for (const validationError of result.moz_validation) {
+ const { warn } = validationError;
+ messages.push(warn);
+ }
+ is(messages.join(" "), test.warn, "Check warning.");
+ options.manifestURL = manifestURL;
+ options.docURL = docURL;
+});
+
+ </script>
+</head>
diff --git a/dom/manifest/test/test_link_relList_supports_manifest.html b/dom/manifest/test/test_link_relList_supports_manifest.html
new file mode 100644
index 0000000000..2a5c0e1ee1
--- /dev/null
+++ b/dom/manifest/test/test_link_relList_supports_manifest.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1596040
+-->
+
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1596040 - Link relList support returns false for manifest</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <script>
+ "use strict";
+ SimpleTest.waitForExplicitFinish();
+
+ async function run() {
+ const prefSetting = [
+ { manifest: true, modulepreload: true },
+ { manifest: true, modulepreload: false },
+ { manifest: false, modulepreload: true },
+ { manifest: false, modulepreload: false },
+ ];
+ for (const { manifest, modulepreload } of prefSetting) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.manifest.enabled", manifest],
+ ["network.modulepreload", modulepreload],
+ ],
+ });
+ const { relList } = document.createElement("link");
+ is(
+ relList.supports("manifest"),
+ manifest,
+ `Expected manifest to be ${manifest}`
+ );
+ is(
+ relList.supports("modulepreload"),
+ modulepreload,
+ `Expected preload to be ${modulepreload}`
+ );
+ }
+ }
+ run()
+ .catch(console.error)
+ .finally(() => SimpleTest.finish());
+ </script>
+</head>