Bug 1731940 - Web Manifest: implement id processing r=saschanaz

spec change https://github.com/w3c/manifest/pull/988

Differential Revision: https://phabricator.services.mozilla.com/D126331
This commit is contained in:
Marcos Cáceres 2021-10-14 04:26:28 +00:00
Родитель 362e26da3b
Коммит fd25c8573e
6 изменённых файлов: 213 добавлений и 11 удалений

Просмотреть файл

@ -212,6 +212,8 @@ ServiceWorkerGraceTimeoutTermination=Terminating ServiceWorker for scope %1$S
# LOCALIZATION NOTE (ServiceWorkerNoFetchHandler): Do not translate "Fetch".
ServiceWorkerNoFetchHandler=Fetch event handlers must be added during the worker scripts initial evaluation.
ExecCommandCutCopyDeniedNotInputDriven=document.execCommand(cut/copy) was denied because it was not called from inside a short running user-generated event handler.
ManifestIdIsInvalid=The id member did not resolve to a valid URL.
ManifestIdNotSameOrigin=The id member must have the same origin as the start_url member.
ManifestShouldBeObject=Manifest should be an object.
ManifestScopeURLInvalid=The scope URL is invalid.
ManifestScopeNotSameOrigin=The scope URL must be same origin as document.

Просмотреть файл

@ -124,6 +124,7 @@ var ManifestProcessor = {
background_color: processBackgroundColorMember(),
};
processedManifest.scope = processScopeMember();
processedManifest.id = processIdMember();
if (checkConformance) {
processedManifest.moz_validation = errors;
processedManifest.moz_manifest_url = manifestURL.href;
@ -258,10 +259,10 @@ var ManifestProcessor = {
expectedType: "string",
trim: false,
};
let result = new URL(docURL).href;
const defaultStartURL = new URL(docURL).href;
const value = extractor.extractValue(spec);
if (value === undefined || value === "") {
return result;
return defaultStartURL;
}
let potentialResult;
try {
@ -269,17 +270,16 @@ var ManifestProcessor = {
} catch (e) {
const warn = domBundle.GetStringFromName("ManifestStartURLInvalid");
errors.push({ warn });
return result;
return defaultStartURL;
}
if (potentialResult.origin !== docURL.origin) {
const warn = domBundle.GetStringFromName(
"ManifestStartURLShouldBeSameOrigin"
);
errors.push({ warn });
} else {
result = potentialResult.href;
return defaultStartURL;
}
return result;
return potentialResult.href;
}
function processThemeColorMember() {
@ -314,6 +314,42 @@ var ManifestProcessor = {
};
return extractor.extractLanguageValue(spec);
}
function processIdMember() {
// the start_url serves as the fallback, in case the id is not specified
// or in error. A start_url is assured.
const startURL = new URL(processedManifest.start_url);
const spec = {
objectName: "manifest",
object: rawManifest,
property: "id",
expectedType: "string",
trim: false,
};
const extractedValue = extractor.extractValue(spec);
if (typeof extractedValue !== "string" || extractedValue === "") {
return startURL.href;
}
let appId;
try {
appId = new URL(extractedValue, startURL.origin);
} catch {
const warn = domBundle.GetStringFromName("ManifestIdIsInvalid");
errors.push({ warn });
return startURL.href;
}
if (appId.origin !== startURL.origin) {
const warn = domBundle.GetStringFromName("ManifestIdNotSameOrigin");
errors.push({ warn });
return startURL.href;
}
return appId.href;
}
},
};
var EXPORTED_SYMBOLS = ["ManifestProcessor"];

Просмотреть файл

@ -13,6 +13,7 @@ support-files =
[test_ManifestProcessor_dir.html]
[test_ManifestProcessor_display.html]
[test_ManifestProcessor_icons.html]
[test_ManifestProcessor_id.html]
[test_ManifestProcessor_JSON.html]
[test_ManifestProcessor_lang.html]
[test_ManifestProcessor_name_and_short_name.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>

Просмотреть файл

@ -40,9 +40,11 @@ data.jsonText = JSON.stringify({
result = processor.process(data);
is(result.start_url.toString(), docURL.toString(), expected);
// Resolve URLs relative to manfiest
var URLs = ["path", "/path", "../../path",
// Resolve URLs relative to manifest
var URLs = [
"path",
"/path",
"../../path",
`${whiteSpace}path${whiteSpace}`,
`${whiteSpace}/path`,
`${whiteSpace}../../path`,
@ -58,5 +60,24 @@ URLs.forEach((url) => {
is(result.start_url.toString(), absURL, expected);
});
</script>
</head>
// 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>

Просмотреть файл

@ -112,6 +112,25 @@ const options = {...data, checkConformance: true } ;
},
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);