diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/manifest/test | |
parent | Initial commit. (diff) | |
download | firefox-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')
36 files changed, 2388 insertions, 0 deletions
diff --git a/dom/manifest/test/blue-150.png b/dom/manifest/test/blue-150.png Binary files differnew file mode 100644 index 0000000000..f4a62faddf --- /dev/null +++ b/dom/manifest/test/blue-150.png 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 Binary files differnew file mode 100644 index 0000000000..bb87f783b7 --- /dev/null +++ b/dom/manifest/test/icon.png 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"}&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 Binary files differnew file mode 100644 index 0000000000..e2ce365c3c --- /dev/null +++ b/dom/manifest/test/red-50.png 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> |