зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
c1a0be69de
Коммит
2465cf3a99
|
@ -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',
|
||||
|
|
Загрузка…
Ссылка в новой задаче