Bug 1171200 - Add means of checking if a document links to a manifest. r=billm

--HG--
rename : dom/manifest/ImageObjectProcessor.js => dom/manifest/ImageObjectProcessor.jsm
rename : dom/manifest/ManifestProcessor.js => dom/manifest/ManifestProcessor.jsm
rename : dom/manifest/ValueExtractor.js => dom/manifest/ValueExtractor.jsm
This commit is contained in:
Marcos Caceres 2015-07-30 11:56:12 -04:00
Родитель c1a0be69de
Коммит 2465cf3a99
17 изменённых файлов: 498 добавлений и 274 удалений

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

@ -11,112 +11,81 @@
*
* BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410
*/
/*globals content, sendAsyncMessage, addMessageListener, Components*/
'use strict';
/*globals Task, ManifestObtainer, ManifestFinder, content, sendAsyncMessage, addMessageListener, Components*/
"use strict";
const {
utils: Cu,
classes: Cc,
interfaces: Ci
} = Components;
const {
ManifestProcessor
} = Cu.import('resource://gre/modules/WebManifest.jsm', {});
const {
Task: {
spawn, async
}
} = Components.utils.import('resource://gre/modules/Task.jsm', {});
Cu.import("resource://gre/modules/ManifestObtainer.jsm");
Cu.import("resource://gre/modules/ManifestFinder.jsm");
Cu.import("resource://gre/modules/Task.jsm");
addMessageListener('DOM:ManifestObtainer:Obtain', async(function* (aMsg) {
const response = {
msgId: aMsg.data.msgId,
success: true,
result: undefined
};
try {
response.result = yield fetchManifest();
} catch (err) {
response.success = false;
response.result = cloneError(err);
}
sendAsyncMessage('DOM:ManifestObtainer:Obtain', response);
}));
const MessageHandler = {
registerListeners() {
addMessageListener(
"DOM:WebManifest:hasManifestLink",
this.hasManifestLink.bind(this)
);
addMessageListener(
"DOM:ManifestObtainer:Obtain",
this.obtainManifest.bind(this)
);
},
function cloneError(aError) {
/**
* Check if the content document includes a link to a web manifest.
* @param {Object} aMsg The IPC message, which is destructured to just
* get the id.
*/
hasManifestLink({data: {id}}) {
const response = makeMsgResponse(id);
response.result = ManifestFinder.contentHasManifestLink(content);
response.success = true;
sendAsyncMessage("DOM:WebManifest:hasManifestLink", response);
},
/**
* Asynchronously obtains a web manifest from content by using the
* ManifestObtainer and messages back the result.
* @param {Object} aMsg The IPC message, which is destructured to just
* get the id.
*/
obtainManifest: Task.async(function* ({data: {id}}) {
const response = makeMsgResponse(id);
try {
response.result = yield ManifestObtainer.contentObtainManifest(content);
response.success = true;
} catch (err) {
response.result = serializeError(err);
}
sendAsyncMessage("DOM:ManifestObtainer:Obtain", response);
}),
};
/**
* Utility function to Serializes an JS Error, so it can be transferred over
* the message channel.
* FIX ME: https://bugzilla.mozilla.org/show_bug.cgi?id=1172586
* @param {Error} aError The error to serialize.
* @return {Object} The serialized object.
*/
function serializeError(aError) {
const clone = {
'fileName': String(aError.fileName),
'lineNumber': String(aError.lineNumber),
'columnNumber': String(aError.columnNumber),
'stack': String(aError.stack),
'message': String(aError.message),
'name': String(aError.name)
"fileName": aError.fileName,
"lineNumber": aError.lineNumber,
"columnNumber": aError.columnNumber,
"stack": aError.stack,
"message": aError.message,
"name": aError.name
};
return clone;
}
function fetchManifest() {
return spawn(function* () {
if (!content || content.top !== content) {
let msg = 'Content window must be a top-level browsing context.';
throw new Error(msg);
}
const elem = content.document.querySelector('link[rel~="manifest"]');
if (!elem || !elem.getAttribute('href')) {
let msg = 'No manifest to fetch.';
throw new Error(msg);
}
// Throws on malformed URLs
const manifestURL = new content.URL(elem.href, elem.baseURI);
if (!canLoadManifest(elem)) {
let msg = `Content Security Policy: The page's settings blocked the `;
msg += `loading of a resource at ${elem.href}`;
throw new Error(msg);
}
const reqInit = {
mode: 'cors'
function makeMsgResponse(aId) {
return {
id: aId,
success: false,
result: undefined
};
if (elem.crossOrigin === 'use-credentials') {
reqInit.credentials = 'include';
}
const req = new content.Request(manifestURL, reqInit);
req.setContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
const response = yield content.fetch(req);
const manifest = yield processResponse(response, content);
return manifest;
});
}
}
function canLoadManifest(aElem) {
const contentPolicy = Cc['@mozilla.org/layout/content-policy;1']
.getService(Ci.nsIContentPolicy);
const mimeType = aElem.type || 'application/manifest+json';
const elemURI = BrowserUtils.makeURI(
aElem.href, aElem.ownerDocument.characterSet
);
const shouldLoad = contentPolicy.shouldLoad(
Ci.nsIContentPolicy.TYPE_WEB_MANIFEST, elemURI,
aElem.ownerDocument.documentURIObject,
aElem, mimeType, null
);
return shouldLoad === Ci.nsIContentPolicy.ACCEPT;
}
function processResponse(aResp, aContentWindow) {
return spawn(function* () {
const badStatus = aResp.status < 200 || aResp.status >= 300;
if (aResp.type === 'error' || badStatus) {
let msg =
`Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
throw new Error(msg);
}
const text = yield aResp.text();
const args = {
jsonText: text,
manifestURL: aResp.url,
docURL: aContentWindow.location.href
};
const processor = new ManifestProcessor();
const manifest = processor.process(args);
return Cu.cloneInto(manifest, content);
});
}
MessageHandler.registerListeners();

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

@ -0,0 +1,68 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/* globals Components, Task, PromiseMessage */
"use strict";
const {
utils: Cu
} = Components;
Cu.import("resource://gre/modules/PromiseMessage.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.ManifestFinder = {// jshint ignore:line
/**
* Check from content process if DOM Window has a conforming
* manifest link relationship.
* @param aContent DOM Window to check.
* @return {Promise<Boolean>}
*/
contentHasManifestLink(aContent) {
if (!aContent || isXULBrowser(aContent)) {
throw new TypeError("Invalid input.");
}
return checkForManifest(aContent);
},
/**
* Check from a XUL browser (parent process) if it's content document has a
* manifest link relationship.
* @param aBrowser The XUL browser to check.
* @return {Promise}
*/
browserHasManifestLink: Task.async(
function* (aBrowser) {
if (!isXULBrowser(aBrowser)) {
throw new TypeError("Invalid input.");
}
const msgKey = "DOM:WebManifest:hasManifestLink";
const mm = aBrowser.messageManager;
const reply = yield PromiseMessage.send(mm, msgKey);
return reply.data.result;
}
)
};
function isXULBrowser(aBrowser) {
if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
return false;
}
const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return (aBrowser.namespaceURI === XUL && aBrowser.localName === "browser");
}
function checkForManifest(aWindow) {
// Only top-level browsing contexts are valid.
if (!aWindow || aWindow.top !== aWindow) {
return false;
}
const elem = aWindow.document.querySelector("link[rel~='manifest']");
// Only if we have an element and a non-empty href attribute.
if (!elem || !elem.getAttribute("href")) {
return false;
}
return true;
}
this.EXPORTED_SYMBOLS = [// jshint ignore:line
"ManifestFinder"
];

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

@ -1,92 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ManifestObtainer is an implementation of:
* http://w3c.github.io/manifest/#obtaining
*
* Exposes public method `.obtainManifest(browserWindow)`, which returns
* a promise. If successful, you get back a manifest (string).
*
* For e10s compat, this JSM relies on the following to do
* the nessesary IPC:
* dom/ipc/manifestMessages.js
*
* whose internal URL is:
* 'chrome://global/content/manifestMessages.js'
*
* Which is injected into every browser instance via browser.js.
*
* BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410
* exported ManifestObtainer
*/
'use strict';
const MSG_KEY = 'DOM:ManifestObtainer:Obtain';
let messageCounter = 0;
// FIXME: Ideally, we would store a reference to the
// message manager in a weakmap instead of needing a
// browserMap. However, trying to store a messageManager
// results in a TypeError because of:
// https://bugzilla.mozilla.org/show_bug.cgi?id=888600
const browsersMap = new WeakMap();
function ManifestObtainer() {}
ManifestObtainer.prototype = {
obtainManifest(aBrowserWindow) {
if (!aBrowserWindow) {
const err = new TypeError('Invalid input. Expected xul browser.');
return Promise.reject(err);
}
const mm = aBrowserWindow.messageManager;
const onMessage = function(aMsg) {
const msgId = aMsg.data.msgId;
const {
resolve, reject
} = browsersMap.get(aBrowserWindow).get(msgId);
browsersMap.get(aBrowserWindow).delete(msgId);
// If we we've processed all messages,
// stop listening.
if (!browsersMap.get(aBrowserWindow).size) {
browsersMap.delete(aBrowserWindow);
mm.removeMessageListener(MSG_KEY, onMessage);
}
if (aMsg.data.success) {
return resolve(aMsg.data.result);
}
reject(toError(aMsg.data.result));
};
// If we are not already listening for messages
// start listening.
if (!browsersMap.has(aBrowserWindow)) {
browsersMap.set(aBrowserWindow, new Map());
mm.addMessageListener(MSG_KEY, onMessage);
}
return new Promise((resolve, reject) => {
const msgId = messageCounter++;
browsersMap.get(aBrowserWindow).set(msgId, {
resolve: resolve,
reject: reject
});
mm.sendAsyncMessage(MSG_KEY, {
msgId: msgId
});
});
function toError(aErrorClone) {
let error;
switch (aErrorClone.name) {
case 'TypeError':
error = new TypeError();
break;
default:
error = new Error();
}
Object.getOwnPropertyNames(aErrorClone)
.forEach(name => error[name] = aErrorClone[name]);
return error;
}
}
};
this.ManifestObtainer = ManifestObtainer; // jshint ignore:line
this.EXPORTED_SYMBOLS = ['ManifestObtainer']; // jshint ignore:line

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

@ -0,0 +1,175 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* ManifestObtainer is an implementation of:
* http://w3c.github.io/manifest/#obtaining
*
* Exposes 2 public method:
*
* .contentObtainManifest(aContent) - used in content process
* .browserObtainManifest(aBrowser) - used in browser/parent process
*
* both return a promise. If successful, you get back a manifest object.
*
* Import it with URL:
* 'chrome://global/content/manifestMessages.js'
*
* e10s IPC message from this components are handled by:
* dom/ipc/manifestMessages.js
*
* Which is injected into every browser instance via browser.js.
*
* exported ManifestObtainer
*/
/*globals Components, Task, PromiseMessage, XPCOMUtils, ManifestProcessor, BrowserUtils*/
"use strict";
const {
utils: Cu,
classes: Cc,
interfaces: Ci
} = Components;
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/PromiseMessage.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/ManifestProcessor.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", // jshint ignore:line
"resource://gre/modules/BrowserUtils.jsm");
this.ManifestObtainer = { // jshint ignore:line
/**
* Public interface for obtaining a web manifest from a XUL browser, to use
* on the parent process.
* @param {XULBrowser} The browser to check for the manifest.
* @return {Promise<Object>} The processed manifest.
*/
browserObtainManifest: Task.async(function* (aBrowser) {
const msgKey = "DOM:ManifestObtainer:Obtain";
if (!isXULBrowser(aBrowser)) {
throw new TypeError("Invalid input. Expected XUL browser.");
}
const mm = aBrowser.messageManager;
const {data: {success, result}} = yield PromiseMessage.send(mm, msgKey);
if (!success) {
const error = toError(result);
throw error;
}
return result;
}),
/**
* Public interface for obtaining a web manifest from a XUL browser.
* @param {Window} The content Window from which to extract the manifest.
* @return {Promise<Object>} The processed manifest.
*/
contentObtainManifest: Task.async(function* (aContent) {
if (!aContent || isXULBrowser(aContent)) {
throw new TypeError("Invalid input. Expected a DOM Window.");
}
const manifest = yield fetchManifest(aContent);
return manifest;
}
)};
function toError(aErrorClone) {
let error;
switch (aErrorClone.name) {
case "TypeError":
error = new TypeError();
break;
default:
error = new Error();
}
Object.getOwnPropertyNames(aErrorClone)
.forEach(name => error[name] = aErrorClone[name]);
return error;
}
function isXULBrowser(aBrowser) {
if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
return false;
}
const XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return (aBrowser.namespaceURI === XUL && aBrowser.localName === "browser");
}
/**
* Asynchronously processes the result of response after having fetched
* a manifest.
* @param {Response} aResp Response from fetch().
* @param {Window} aContentWindow The content window.
* @return {Promise<Object>} The processed manifest.
*/
const processResponse = Task.async(function* (aResp, aContentWindow) {
const badStatus = aResp.status < 200 || aResp.status >= 300;
if (aResp.type === "error" || badStatus) {
const msg =
`Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
throw new Error(msg);
}
const text = yield aResp.text();
const args = {
jsonText: text,
manifestURL: aResp.url,
docURL: aContentWindow.location.href
};
const manifest = ManifestProcessor.process(args);
return manifest;
});
/**
* Asynchronously fetches a web manifest.
* @param {Window} a The content Window from where to extract the manifest.
* @return {Promise<Object>}
*/
const fetchManifest = Task.async(function* (aWindow) {
if (!aWindow || aWindow.top !== aWindow) {
let msg = "Window must be a top-level browsing context.";
throw new Error(msg);
}
const elem = aWindow.document.querySelector("link[rel~='manifest']");
if (!elem || !elem.getAttribute("href")) {
let msg = `No manifest to fetch at ${aWindow.location}`;
throw new Error(msg);
}
// Throws on malformed URLs
const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
if (!canLoadManifest(elem)) {
let msg = `Content Security Policy: The page's settings blocked the `;
msg += `loading of a resource at ${elem.href}`;
throw new Error(msg);
}
const reqInit = {
mode: "cors"
};
if (elem.crossOrigin === "use-credentials") {
reqInit.credentials = "include";
}
const req = new aWindow.Request(manifestURL, reqInit);
req.setContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
const response = yield aWindow.fetch(req);
const manifest = yield processResponse(response, aWindow);
return manifest;
});
/**
* Checks against security manager if we can load the web manifest.
* @param {HTMLLinkElement} aElem The HTML element to security check.
* @return {Boolean} True if it can, false if it can't.
*/
function canLoadManifest(aElem) {
const contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
.getService(Ci.nsIContentPolicy);
const mimeType = aElem.type || "application/manifest+json";
const elemURI = BrowserUtils.makeURI(
aElem.href, aElem.ownerDocument.characterSet
);
const shouldLoad = contentPolicy.shouldLoad(
Ci.nsIContentPolicy.TYPE_WEB_MANIFEST, elemURI,
aElem.ownerDocument.documentURIObject,
aElem, mimeType, null
);
return shouldLoad === Ci.nsIContentPolicy.ACCEPT;
}
this.EXPORTED_SYMBOLS = ["ManifestObtainer"]; // jshint ignore:line

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

@ -19,12 +19,10 @@
* TODO: The constructor should accept the UA's supported display modes.
* TODO: hook up developer tools to console. (1086997).
*/
/*globals Components*/
/*globals Components, ValueExtractor, ImageObjectProcessor, ConsoleAPI*/
'use strict';
const {
utils: Cu,
interfaces: Ci,
classes: Cc
utils: Cu
} = Components;
Cu.importGlobalProperties(['URL']);
const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui',
@ -34,41 +32,23 @@ const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait',
'portrait-primary', 'portrait-secondary', 'landscape-primary',
'landscape-secondary'
]);
const {
ConsoleAPI
} = Cu.import('resource://gre/modules/devtools/Console.jsm', {});
Cu.import('resource://gre/modules/devtools/Console.jsm');
// ValueExtractor is used by the various processors to get values
// from the manifest and to report errors.
const {
ValueExtractor
} = Cu.import('resource://gre/modules/ValueExtractor.js', {});
Cu.import('resource://gre/modules/ValueExtractor.jsm');
// ImageObjectProcessor is used to process things like icons and images
const {
ImageObjectProcessor
} = Cu.import('resource://gre/modules/ImageObjectProcessor.js', {});
Cu.import('resource://gre/modules/ImageObjectProcessor.jsm');
function ManifestProcessor() {}
// Static getters
Object.defineProperties(ManifestProcessor, {
'defaultDisplayMode': {
get: function() {
return 'browser';
}
this.ManifestProcessor = { // jshint ignore:line
get defaultDisplayMode() {
return 'browser';
},
'displayModes': {
get: function() {
return displayModes;
}
get displayModes() {
return displayModes;
},
get orientationTypes() {
return orientationTypes;
},
'orientationTypes': {
get: function() {
return orientationTypes;
}
}
});
ManifestProcessor.prototype = {
// process() method processes JSON text into a clean manifest
// that conforms with the W3C specification. Takes an object
// expecting the following dictionary items:
@ -99,8 +79,8 @@ ManifestProcessor.prototype = {
const processedManifest = {
'lang': processLangMember(),
'start_url': processStartURLMember(),
'display': processDisplayMember(),
'orientation': processOrientationMember(),
'display': processDisplayMember.call(this),
'orientation': processOrientationMember.call(this),
'name': processNameMember(),
'icons': imgObjProcessor.process(
rawManifest, manifestURL, 'icons'
@ -145,7 +125,7 @@ ManifestProcessor.prototype = {
trim: true
};
const value = extractor.extractValue(spec);
if (ManifestProcessor.orientationTypes.has(value)) {
if (this.orientationTypes.has(value)) {
return value;
}
// The spec special-cases orientation to return the empty string.
@ -161,10 +141,10 @@ ManifestProcessor.prototype = {
trim: true
};
const value = extractor.extractValue(spec);
if (ManifestProcessor.displayModes.has(value)) {
if (displayModes.has(value)) {
return value;
}
return ManifestProcessor.defaultDisplayMode;
return this.defaultDisplayMode;
}
function processScopeMember() {
@ -249,8 +229,7 @@ ManifestProcessor.prototype = {
objectName: 'manifest',
object: rawManifest,
property: 'lang',
expectedType: 'string',
trim: true
expectedType: 'string', trim: true
};
let tag = extractor.extractValue(spec);
// TODO: Check if tag is structurally valid.
@ -265,5 +244,4 @@ ManifestProcessor.prototype = {
}
}
};
this.ManifestProcessor = ManifestProcessor; // jshint ignore:line
this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line

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

@ -25,9 +25,7 @@ ValueExtractor.prototype = {
// objectName: string used to construct the developer warning.
// property: the name of the property being extracted.
// trim: boolean, if the value should be trimmed (used by string type).
extractValue({
expectedType, object, objectName, property, trim
}) {
extractValue({expectedType, object, objectName, property, trim}) {
const value = object[property];
const isArray = Array.isArray(value);
// We need to special-case "array", as it's not a JS primitive.

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

@ -1,19 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*exported EXPORTED_SYMBOLS, ManifestProcessor, ManifestObtainer*/
/*globals Components */
'use strict';
const {
utils: Cu
} = Components;
this.EXPORTED_SYMBOLS = [
'ManifestObtainer',
'ManifestProcessor'
];
// Export public interfaces
for (let symbl of EXPORTED_SYMBOLS) {
Cu.import(`resource://gre/modules/${symbl}.js`);
}

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

@ -5,11 +5,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES += [
'ImageObjectProcessor.js',
'ManifestObtainer.js',
'ManifestProcessor.js',
'ValueExtractor.js',
'WebManifest.jsm'
'ImageObjectProcessor.jsm',
'ManifestFinder.jsm',
'ManifestObtainer.jsm',
'ManifestProcessor.jsm',
'ValueExtractor.jsm',
]
MOCHITEST_MANIFESTS += ['test/mochitest.ini']

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

@ -1,2 +1,3 @@
[DEFAULT]
[browser_ManifestObtainer_obtain.js]
[browser_ManifestFinder_browserHasManifestLink.js]
[browser_ManifestObtainer_obtain.js]

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

@ -0,0 +1,114 @@
//Used by JSHint:
/*global Cu, BrowserTestUtils, is, ok, add_task, gBrowser, ManifestFinder */
"use strict";
Cu.import("resource://gre/modules/ManifestFinder.jsm", this); // jshint ignore:line
const defaultURL =
"http://example.org/tests/dom/manifest/test/resource.sjs";
const tests = [{
expected: "Document has a web manifest.",
get tabURL() {
let query = [
`body=<h1>${this.expected}</h1>`,
"Content-Type=text/html; charset=utf-8",
];
const URL = `${defaultURL}?${query.join("&")}`;
return URL;
},
run(result) {
is(result, true, this.expected);
},
testData: `
<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"}'>`
}, {
expected: "Document does not have a web manifest.",
get tabURL() {
let query = [
`body=<h1>${this.expected}</h1>`,
"Content-Type=text/html; charset=utf-8",
];
const URL = `${defaultURL}?${query.join("&")}`;
return URL;
},
run(result) {
is(result, false, this.expected);
},
testData: `
<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"}'>`
}, {
expected: "Manifest link is has empty href.",
get tabURL() {
let query = [
`body=<h1>${this.expected}</h1>`,
"Content-Type=text/html; charset=utf-8",
];
const URL = `${defaultURL}?${query.join("&")}`;
return URL;
},
run(result) {
is(result, false, this.expected);
},
testData: `
<link rel="manifest" href="">
<link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
}, {
expected: "Manifest link is missing.",
get tabURL() {
let query = [
`body=<h1>${this.expected}</h1>`,
"Content-Type=text/html; charset=utf-8",
];
const URL = `${defaultURL}?${query.join("&")}`;
return URL;
},
run(result) {
is(result, false, this.expected);
},
testData: `
<link rel="manifest">
<link rel="manifest" href='${defaultURL}?body={"name":"fail"}'>`
}];
/**
* Test basic API error conditions
*/
add_task(function* () {
let expected = "Invalid types should throw a TypeError.";
for (let invalidValue of [undefined, null, 1, {}, "test"]) {
try {
yield ManifestFinder.contentManifestLink(invalidValue);
ok(false, expected);
} catch (e) {
is(e.name, "TypeError", expected);
}
try {
yield ManifestFinder.browserManifestLink(invalidValue);
ok(false, expected);
} catch (e) {
is(e.name, "TypeError", expected);
}
}
});
add_task(function* () {
for (let test of tests) {
let tabOptions = {
gBrowser: gBrowser,
url: test.tabURL,
};
yield BrowserTestUtils.withNewTab(
tabOptions,
browser => testHasManifest(browser, test)
);
}
function* testHasManifest(aBrowser, aTest) {
aBrowser.contentWindowAsCPOW.document.head.innerHTML = aTest.testData;
const result = yield ManifestFinder.browserHasManifestLink(aBrowser);
aTest.run(result);
}
});

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

@ -1,9 +1,8 @@
//Used by JSHint:
/*global Cu, BrowserTestUtils, add_task, SpecialPowers, gBrowser, Assert*/
'use strict';
/*global requestLongerTimeout, Cu, BrowserTestUtils, add_task, SpecialPowers, gBrowser, Assert*/ 'use strict';
const {
ManifestObtainer
} = Cu.import('resource://gre/modules/WebManifest.jsm', {});
} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
requestLongerTimeout(4); // e10s tests take time.
const defaultURL =
@ -177,10 +176,9 @@ add_task(function*() {
}
function* testObtainingManifest(aBrowser, aTest) {
const obtainer = new ManifestObtainer();
aBrowser.contentWindowAsCPOW.document.head.innerHTML = aTest.testData;
try {
const manifest = yield obtainer.obtainManifest(aBrowser);
const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
aTest.run(manifest);
} catch (e) {
aTest.run(e);
@ -194,7 +192,6 @@ add_task(function*() {
* in each tab. They should all return pass.
*/
add_task(function*() {
const obtainer = new ManifestObtainer();
const defaultPath = '/tests/dom/manifest/test/manifestLoader.html';
const tabURLs = [
`http://test:80${defaultPath}`,
@ -237,7 +234,7 @@ add_task(function*() {
// Flood random browsers with requests. Once promises settle, check that
// responses all pass.
const results = yield Promise.all((
for (browser of randBrowsers(browsers, 100)) obtainer.obtainManifest(browser)
for (browser of randBrowsers(browsers, 100)) ManifestObtainer.browserObtainManifest(browser)
));
const expected = 'Expect every manifest to have name equal to `pass`.';
const pass = results.every(manifest => manifest.name === 'pass');

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

@ -1,12 +1,12 @@
/**
* Common infrastructure for manifest tests.
**/
/*globals SpecialPowers, ManifestProcessor*/
'use strict';
const {
ManifestProcessor
} = SpecialPowers.Cu.import('resource://gre/modules/WebManifest.jsm');
const processor = new ManifestProcessor();
} = SpecialPowers.Cu.import('resource://gre/modules/ManifestProcessor.jsm');
const processor = ManifestProcessor;
const manifestURL = new URL(document.location.origin + '/manifest.json');
const docURL = document.location;
const seperators = '\u2028\u2029\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000';

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

@ -10,7 +10,7 @@
requestLongerTimeout(10); // e10s tests take time.
const {
ManifestObtainer
} = Components.utils.import('resource://gre/modules/WebManifest.jsm', {});
} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
const path = '/tests/dom/security/test/csp/';
const testFile = `file=${path}file_web_manifest.html`;
const remoteFile = `file=${path}file_web_manifest_remote.html`;
@ -220,12 +220,11 @@ add_task(function* () {
function* testObtainingManifest(aBrowser, aTest) {
const observer = (/blocks/.test(aTest.expected)) ? new NetworkObserver(aTest) : null;
const obtainer = new ManifestObtainer();
let manifest;
// Expect an exception (from promise rejection) if there a content policy
// that is violated.
try {
manifest = yield obtainer.obtainManifest(aBrowser);
manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
} catch (e) {
const msg = `Expected promise rejection obtaining.`;
ok(/blocked the loading of a resource/.test(e.message), msg);

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

@ -6,7 +6,7 @@
'use strict';
const {
ManifestObtainer
} = Components.utils.import('resource://gre/modules/WebManifest.jsm', {});
} = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {});
const path = '/tests/dom/security/test/csp/';
const mixedContent = `file=${path}file_web_manifest_mixed_content.html`;
const server = 'file_testserver.sjs';
@ -45,9 +45,8 @@ add_task(function*() {
}
function* testObtainingManifest(aBrowser, aTest) {
const obtainer = new ManifestObtainer();
try {
yield obtainer.obtainManifest(aBrowser);
yield ManifestObtainer.browserObtainManifest(aBrowser);
} catch (e) {
aTest.run(e)
}

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

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["PromiseMessage"];
let msgId = 0;
let PromiseMessage = {
send(messageManager, name, data = {}) {
let id = msgId++;
// Make a copy of data so that the caller doesn't see us setting 'id'.
let dataCopy = {};
for (let prop in data) {
dataCopy[prop] = data[prop];
}
dataCopy.id = id;
// Send the message.
messageManager.sendAsyncMessage(name, dataCopy);
// Return a promise that resolves when we get a reply (a message of the same name).
return new Promise(resolve => {
messageManager.addMessageListener(name, function listener(reply) {
if (reply.data.id !== id) {
return;
}
messageManager.removeMessageListener(name, listener);
resolve(reply);
});
});
}
};

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

@ -48,6 +48,7 @@ EXTRA_JS_MODULES += [
'ProfileAge.jsm',
'Promise-backend.js',
'Promise.jsm',
'PromiseMessage.jsm',
'PromiseUtils.jsm',
'PropertyListUtils.jsm',
'RemoteController.jsm',