From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/manifest/ImageObjectProcessor.jsm | 153 ++++++++++++ dom/manifest/ManifestFinder.jsm | 68 +++++ dom/manifest/ManifestObtainer.jsm | 160 ++++++++++++ dom/manifest/ManifestProcessor.jsm | 273 +++++++++++++++++++++ dom/manifest/ValueExtractor.jsm | 65 +++++ dom/manifest/moz.build | 16 ++ dom/manifest/test/browser.ini | 9 + ...rowser_ManifestFinder_browserHasManifestLink.js | 84 +++++++ .../test/browser_ManifestObtainer_obtain.js | 181 ++++++++++++++ .../test/browser_fire_appinstalled_event.js | 49 ++++ dom/manifest/test/common.js | 22 ++ dom/manifest/test/file_reg_appinstalled_event.html | 15 ++ dom/manifest/test/file_testserver.sjs | 54 ++++ dom/manifest/test/manifestLoader.html | 13 + dom/manifest/test/mochitest.ini | 23 ++ dom/manifest/test/resource.sjs | 85 +++++++ .../test/test_ImageObjectProcessor_sizes.html | 95 +++++++ .../test/test_ImageObjectProcessor_src.html | 106 ++++++++ .../test/test_ImageObjectProcessor_type.html | 57 +++++ dom/manifest/test/test_ManifestProcessor_JSON.html | 34 +++ .../test_ManifestProcessor_background_color.html | 118 +++++++++ dom/manifest/test/test_ManifestProcessor_dir.html | 57 +++++ .../test/test_ManifestProcessor_display.html | 78 ++++++ .../test/test_ManifestProcessor_icons.html | 30 +++ dom/manifest/test/test_ManifestProcessor_lang.html | 112 +++++++++ ...test_ManifestProcessor_name_and_short_name.html | 79 ++++++ .../test/test_ManifestProcessor_orientation.html | 86 +++++++ .../test/test_ManifestProcessor_scope.html | 89 +++++++ .../test/test_ManifestProcessor_start_url.html | 59 +++++ .../test/test_ManifestProcessor_theme_color.html | 118 +++++++++ .../test/test_ManifestProcessor_warnings.html | 90 +++++++ .../test/test_window_onappinstalled_event.html | 98 ++++++++ 32 files changed, 2576 insertions(+) create mode 100644 dom/manifest/ImageObjectProcessor.jsm create mode 100644 dom/manifest/ManifestFinder.jsm create mode 100644 dom/manifest/ManifestObtainer.jsm create mode 100644 dom/manifest/ManifestProcessor.jsm create mode 100644 dom/manifest/ValueExtractor.jsm create mode 100644 dom/manifest/moz.build create mode 100644 dom/manifest/test/browser.ini create mode 100644 dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js create mode 100644 dom/manifest/test/browser_ManifestObtainer_obtain.js create mode 100644 dom/manifest/test/browser_fire_appinstalled_event.js create mode 100644 dom/manifest/test/common.js create mode 100644 dom/manifest/test/file_reg_appinstalled_event.html create mode 100644 dom/manifest/test/file_testserver.sjs create mode 100644 dom/manifest/test/manifestLoader.html create mode 100644 dom/manifest/test/mochitest.ini create mode 100644 dom/manifest/test/resource.sjs create mode 100644 dom/manifest/test/test_ImageObjectProcessor_sizes.html create mode 100644 dom/manifest/test/test_ImageObjectProcessor_src.html create mode 100644 dom/manifest/test/test_ImageObjectProcessor_type.html create mode 100644 dom/manifest/test/test_ManifestProcessor_JSON.html create mode 100644 dom/manifest/test/test_ManifestProcessor_background_color.html create mode 100644 dom/manifest/test/test_ManifestProcessor_dir.html create mode 100644 dom/manifest/test/test_ManifestProcessor_display.html create mode 100644 dom/manifest/test/test_ManifestProcessor_icons.html create mode 100644 dom/manifest/test/test_ManifestProcessor_lang.html create mode 100644 dom/manifest/test/test_ManifestProcessor_name_and_short_name.html create mode 100644 dom/manifest/test/test_ManifestProcessor_orientation.html create mode 100644 dom/manifest/test/test_ManifestProcessor_scope.html create mode 100644 dom/manifest/test/test_ManifestProcessor_start_url.html create mode 100644 dom/manifest/test/test_ManifestProcessor_theme_color.html create mode 100644 dom/manifest/test/test_ManifestProcessor_warnings.html create mode 100644 dom/manifest/test/test_window_onappinstalled_event.html (limited to 'dom/manifest') diff --git a/dom/manifest/ImageObjectProcessor.jsm b/dom/manifest/ImageObjectProcessor.jsm new file mode 100644 index 000000000..7ef5fe811 --- /dev/null +++ b/dom/manifest/ImageObjectProcessor.jsm @@ -0,0 +1,153 @@ +/* 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/. */ +/* + * ImageObjectProcessor + * Implementation of Image Object processing algorithms from: + * http://www.w3.org/TR/appmanifest/#image-object-and-its-members + * + * This is intended to be used in conjunction with ManifestProcessor.jsm + * + * Creates an object to process Image Objects as defined by the + * W3C specification. This is used to process things like the + * icon member and the splash_screen member. + * + * Usage: + * + * .process(aManifest, aBaseURL, aMemberName); + * + */ +/*exported EXPORTED_SYMBOLS*/ +/*globals Components */ +'use strict'; +const { + utils: Cu, + interfaces: Ci, + classes: Cc +} = Components; + +Cu.importGlobalProperties(['URL']); +const netutil = Cc['@mozilla.org/network/util;1'] + .getService(Ci.nsINetUtil); + +function ImageObjectProcessor(aConsole, aExtractor) { + this.console = aConsole; + this.extractor = aExtractor; +} + +// Static getters +Object.defineProperties(ImageObjectProcessor, { + 'decimals': { + get: function() { + return /^\d+$/; + } + }, + 'anyRegEx': { + get: function() { + return new RegExp('any', 'i'); + } + } +}); + +ImageObjectProcessor.prototype.process = function( + aManifest, aBaseURL, aMemberName +) { + const spec = { + objectName: 'manifest', + object: aManifest, + property: aMemberName, + expectedType: 'array', + trim: false + }; + const extractor = this.extractor; + const images = []; + const value = extractor.extractValue(spec); + if (Array.isArray(value)) { + // Filter out images whose "src" is not useful. + value.filter(item => !!processSrcMember(item, aBaseURL)) + .map(toImageObject) + .forEach(image => images.push(image)); + } + return images; + + function toImageObject(aImageSpec) { + return { + 'src': processSrcMember(aImageSpec, aBaseURL), + 'type': processTypeMember(aImageSpec), + 'sizes': processSizesMember(aImageSpec), + }; + } + + function processTypeMember(aImage) { + const charset = {}; + const hadCharset = {}; + const spec = { + objectName: 'image', + object: aImage, + property: 'type', + expectedType: 'string', + trim: true + }; + let value = extractor.extractValue(spec); + if (value) { + value = netutil.parseRequestContentType(value, charset, hadCharset); + } + return value || undefined; + } + + function processSrcMember(aImage, aBaseURL) { + const spec = { + objectName: 'image', + object: aImage, + property: 'src', + expectedType: 'string', + trim: false + }; + const value = extractor.extractValue(spec); + let url; + if (value && value.length) { + try { + url = new URL(value, aBaseURL).href; + } catch (e) {} + } + return url; + } + + function processSizesMember(aImage) { + const sizes = new Set(); + const spec = { + objectName: 'image', + object: aImage, + property: 'sizes', + expectedType: 'string', + trim: true + }; + const value = extractor.extractValue(spec); + if (value) { + // Split on whitespace and filter out invalid values. + value.split(/\s+/) + .filter(isValidSizeValue) + .reduce((collector, size) => collector.add(size), sizes); + } + return (sizes.size) ? Array.from(sizes).join(" ") : undefined; + // Implementation of HTML's link@size attribute checker. + function isValidSizeValue(aSize) { + const size = aSize.toLowerCase(); + if (ImageObjectProcessor.anyRegEx.test(aSize)) { + return true; + } + if (!size.includes('x') || size.indexOf('x') !== size.lastIndexOf('x')) { + return false; + } + // Split left of x for width, after x for height. + const widthAndHeight = size.split('x'); + const w = widthAndHeight.shift(); + const h = widthAndHeight.join('x'); + const validStarts = !w.startsWith('0') && !h.startsWith('0'); + const validDecimals = ImageObjectProcessor.decimals.test(w + h); + return (validStarts && validDecimals); + } + } +}; +this.ImageObjectProcessor = ImageObjectProcessor; // jshint ignore:line +this.EXPORTED_SYMBOLS = ['ImageObjectProcessor']; // jshint ignore:line diff --git a/dom/manifest/ManifestFinder.jsm b/dom/manifest/ManifestFinder.jsm new file mode 100644 index 000000000..1e9a9fb15 --- /dev/null +++ b/dom/manifest/ManifestFinder.jsm @@ -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} + */ + 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" +]; diff --git a/dom/manifest/ManifestObtainer.jsm b/dom/manifest/ManifestObtainer.jsm new file mode 100644 index 000000000..88cba0d91 --- /dev/null +++ b/dom/manifest/ManifestObtainer.jsm @@ -0,0 +1,160 @@ +/* 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} 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} The processed manifest. + */ + contentObtainManifest: Task.async(function* (aContent) { + if (!aContent || isXULBrowser(aContent)) { + throw new TypeError("Invalid input. Expected a DOM Window."); + } + let manifest; + try { + manifest = yield fetchManifest(aContent); + } catch (err) { + throw err; + } + 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} 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} + */ +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); + const reqInit = { + mode: "cors" + }; + if (elem.crossOrigin === "use-credentials") { + reqInit.credentials = "include"; + } + const request = new aWindow.Request(manifestURL, reqInit); + request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST); + let response; + try { + response = yield aWindow.fetch(request); + } catch (err) { + throw err; + } + const manifest = yield processResponse(response, aWindow); + return manifest; +}); + +this.EXPORTED_SYMBOLS = ["ManifestObtainer"]; // jshint ignore:line diff --git a/dom/manifest/ManifestProcessor.jsm b/dom/manifest/ManifestProcessor.jsm new file mode 100644 index 000000000..c4f837009 --- /dev/null +++ b/dom/manifest/ManifestProcessor.jsm @@ -0,0 +1,273 @@ +/* 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/. */ +/* + * ManifestProcessor + * Implementation of processing algorithms from: + * http://www.w3.org/2008/webapps/manifest/ + * + * Creates manifest processor that lets you process a JSON file + * or individual parts of a manifest object. A manifest is just a + * standard JS object that has been cleaned up. + * + * .process({jsonText,manifestURL,docURL}); + * + * Depends on ImageObjectProcessor to process things like + * icons and splash_screens. + * + * TODO: The constructor should accept the UA's supported orientations. + * TODO: The constructor should accept the UA's supported display modes. + * TODO: hook up developer tools to console. (1086997). + */ +/*globals Components, ValueExtractor, ImageObjectProcessor, ConsoleAPI*/ +'use strict'; +const { + utils: Cu +} = Components; +Cu.importGlobalProperties(['URL']); +const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui', + 'browser' +]); +const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait', + 'portrait-primary', 'portrait-secondary', 'landscape-primary', + 'landscape-secondary' +]); +const textDirections = new Set(['ltr', 'rtl', 'auto']); + +Cu.import('resource://gre/modules/Console.jsm'); +Cu.import("resource://gre/modules/Services.jsm"); +// ValueExtractor is used by the various processors to get values +// from the manifest and to report errors. +Cu.import('resource://gre/modules/ValueExtractor.jsm'); +// ImageObjectProcessor is used to process things like icons and images +Cu.import('resource://gre/modules/ImageObjectProcessor.jsm'); + +this.ManifestProcessor = { // jshint ignore:line + get defaultDisplayMode() { + return 'browser'; + }, + get displayModes() { + return displayModes; + }, + get orientationTypes() { + return orientationTypes; + }, + get textDirections() { + return textDirections; + }, + // process() method processes JSON text into a clean manifest + // that conforms with the W3C specification. Takes an object + // expecting the following dictionary items: + // * jsonText: the JSON string to be processed. + // * manifestURL: the URL of the manifest, to resolve URLs. + // * docURL: the URL of the owner doc, for security checks + process({ + jsonText, + manifestURL: aManifestURL, + docURL: aDocURL + }) { + const domBundle = Services.strings.createBundle("chrome://global/locale/dom/dom.properties"); + + const console = new ConsoleAPI({ + prefix: 'Web Manifest' + }); + const manifestURL = new URL(aManifestURL); + const docURL = new URL(aDocURL); + let rawManifest = {}; + try { + rawManifest = JSON.parse(jsonText); + } catch (e) {} + if (typeof rawManifest !== 'object' || rawManifest === null) { + console.warn(domBundle.GetStringFromName('ManifestShouldBeObject')); + rawManifest = {}; + } + const extractor = new ValueExtractor(console, domBundle); + const imgObjProcessor = new ImageObjectProcessor(console, extractor); + const processedManifest = { + 'dir': processDirMember.call(this), + 'lang': processLangMember(), + 'start_url': processStartURLMember(), + 'display': processDisplayMember.call(this), + 'orientation': processOrientationMember.call(this), + 'name': processNameMember(), + 'icons': imgObjProcessor.process( + rawManifest, manifestURL, 'icons' + ), + 'short_name': processShortNameMember(), + 'theme_color': processThemeColorMember(), + 'background_color': processBackgroundColorMember(), + }; + processedManifest.scope = processScopeMember(); + return processedManifest; + + function processDirMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'dir', + expectedType: 'string', + trim: true, + }; + const value = extractor.extractValue(spec); + if (this.textDirections.has(value)) { + return value; + } + return 'auto'; + } + + function processNameMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'name', + expectedType: 'string', + trim: true + }; + return extractor.extractValue(spec); + } + + function processShortNameMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'short_name', + expectedType: 'string', + trim: true + }; + return extractor.extractValue(spec); + } + + function processOrientationMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'orientation', + expectedType: 'string', + trim: true + }; + const value = extractor.extractValue(spec); + if (value && typeof value === "string" && this.orientationTypes.has(value.toLowerCase())) { + return value.toLowerCase(); + } + return undefined; + } + + function processDisplayMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'display', + expectedType: 'string', + trim: true + }; + const value = extractor.extractValue(spec); + if (value && typeof value === "string" && displayModes.has(value.toLowerCase())) { + return value.toLowerCase(); + } + return this.defaultDisplayMode; + } + + function processScopeMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'scope', + expectedType: 'string', + trim: false + }; + let scopeURL; + const startURL = new URL(processedManifest.start_url); + const value = extractor.extractValue(spec); + if (value === undefined || value === '') { + return undefined; + } + try { + scopeURL = new URL(value, manifestURL); + } catch (e) { + console.warn(domBundle.GetStringFromName('ManifestScopeURLInvalid')); + return undefined; + } + if (scopeURL.origin !== docURL.origin) { + console.warn(domBundle.GetStringFromName('ManifestScopeNotSameOrigin')); + return undefined; + } + // If start URL is not within scope of scope URL: + let isSameOrigin = startURL && startURL.origin !== scopeURL.origin; + if (isSameOrigin || !startURL.pathname.startsWith(scopeURL.pathname)) { + console.warn(domBundle.GetStringFromName('ManifestStartURLOutsideScope')); + return undefined; + } + return scopeURL.href; + } + + function processStartURLMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'start_url', + expectedType: 'string', + trim: false + }; + let result = new URL(docURL).href; + const value = extractor.extractValue(spec); + if (value === undefined || value === '') { + return result; + } + let potentialResult; + try { + potentialResult = new URL(value, manifestURL); + } catch (e) { + console.warn(domBundle.GetStringFromName('ManifestStartURLInvalid')) + return result; + } + if (potentialResult.origin !== docURL.origin) { + console.warn(domBundle.GetStringFromName('ManifestStartURLShouldBeSameOrigin')); + } else { + result = potentialResult.href; + } + return result; + } + + function processThemeColorMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'theme_color', + expectedType: 'string', + trim: true + }; + return extractor.extractColorValue(spec); + } + + function processBackgroundColorMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'background_color', + expectedType: 'string', + trim: true + }; + return extractor.extractColorValue(spec); + } + + function processLangMember() { + const spec = { + objectName: 'manifest', + object: rawManifest, + property: 'lang', + expectedType: 'string', trim: true + }; + let tag = extractor.extractValue(spec); + // TODO: Check if tag is structurally valid. + // Cannot do this because we don't support Intl API on Android. + // https://bugzilla.mozilla.org/show_bug.cgi?id=864843 + // https://github.com/tc39/ecma402/issues/5 + // TODO: perform canonicalization on the tag. + // Can't do this today because there is no direct means to + // access canonicalization algorithms through Intl API. + // https://github.com/tc39/ecma402/issues/5 + return tag; + } + } +}; +this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line diff --git a/dom/manifest/ValueExtractor.jsm b/dom/manifest/ValueExtractor.jsm new file mode 100644 index 000000000..f3dd25905 --- /dev/null +++ b/dom/manifest/ValueExtractor.jsm @@ -0,0 +1,65 @@ +/* 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/. */ +/* + * Helper functions extract values from manifest members + * and reports conformance violations. + */ +/*globals Components*/ +'use strict'; +const { + classes: Cc, + interfaces: Ci +} = Components; + +function ValueExtractor(aConsole, aBundle) { + this.console = aConsole; + this.domBundle = aBundle; +} + +ValueExtractor.prototype = { + // This function takes a 'spec' object and destructures + // it to extract a value. If the value is of th wrong type, it + // warns the developer and returns undefined. + // expectType: is the type of a JS primitive (string, number, etc.) + // object: is the object from which to extract the value. + // 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}) { + const value = object[property]; + const isArray = Array.isArray(value); + // We need to special-case "array", as it's not a JS primitive. + const type = (isArray) ? 'array' : typeof value; + if (type !== expectedType) { + if (type !== 'undefined') { + this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidType", + [objectName, property, expectedType], + 3)); + } + return undefined; + } + // Trim string and returned undefined if the empty string. + const shouldTrim = expectedType === 'string' && value && trim; + if (shouldTrim) { + return value.trim() || undefined; + } + return value; + }, + extractColorValue(spec) { + const value = this.extractValue(spec); + const DOMUtils = Cc['@mozilla.org/inspector/dom-utils;1'] + .getService(Ci.inIDOMUtils); + let color; + if (DOMUtils.isValidCSSColor(value)) { + color = value; + } else if (value) { + this.console.warn(this.domBundle.formatStringFromName("ManifestInvalidCSSColor", + [spec.property, value], + 2)); + } + return color; + } +}; +this.ValueExtractor = ValueExtractor; // jshint ignore:line +this.EXPORTED_SYMBOLS = ['ValueExtractor']; // jshint ignore:line diff --git a/dom/manifest/moz.build b/dom/manifest/moz.build new file mode 100644 index 000000000..338794a19 --- /dev/null +++ b/dom/manifest/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXTRA_JS_MODULES += [ + 'ImageObjectProcessor.jsm', + 'ManifestFinder.jsm', + 'ManifestObtainer.jsm', + 'ManifestProcessor.jsm', + 'ValueExtractor.jsm', +] + +MOCHITEST_MANIFESTS += ['test/mochitest.ini'] +BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] diff --git a/dom/manifest/test/browser.ini b/dom/manifest/test/browser.ini new file mode 100644 index 000000000..ad98fe26c --- /dev/null +++ b/dom/manifest/test/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + file_reg_appinstalled_event.html + file_testserver.sjs + manifestLoader.html + resource.sjs +[browser_ManifestFinder_browserHasManifestLink.js] +[browser_ManifestObtainer_obtain.js] +[browser_fire_appinstalled_event.js] \ No newline at end of file diff --git a/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js new file mode 100644 index 000000000..5ec663962 --- /dev/null +++ b/dom/manifest/test/browser_ManifestFinder_browserHasManifestLink.js @@ -0,0 +1,84 @@ +//Used by JSHint: +/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */ +"use strict"; +const { ManifestFinder } = Cu.import("resource://gre/modules/ManifestFinder.jsm", {}); +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: ` + + + + `, + run(result) { + ok(result, "Document has a web manifest."); + }, +}, { + body: ` + + + `, + run(result) { + ok(!result, "Document does not have a web manifest."); + }, +}, { + body: ` + + `, + run(result) { + ok(!result, "Manifest link is has empty href."); + }, +}, { + body: ` + + `, + 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(function*() { + const 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*() { + const runningTests = tests + .map( + test => ({ + gBrowser, + test, + url: makeTestURL(test), + }) + ) + .map( + tabOptions => BrowserTestUtils.withNewTab(tabOptions, function*(browser) { + const result = yield ManifestFinder.browserHasManifestLink(browser); + tabOptions.test.run(result); + }) + ); + yield Promise.all(runningTests); +}); diff --git a/dom/manifest/test/browser_ManifestObtainer_obtain.js b/dom/manifest/test/browser_ManifestObtainer_obtain.js new file mode 100644 index 000000000..a2e468905 --- /dev/null +++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js @@ -0,0 +1,181 @@ +//Used by JSHint: +/*global ok, is, Cu, BrowserTestUtils, add_task, gBrowser, makeTestURL, requestLongerTimeout*/ +'use strict'; +const { ManifestObtainer } = Cu.import('resource://gre/modules/ManifestObtainer.jsm', {}); +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: ` + + + `, + run(manifest) { + is(manifest.name, 'pass-1', 'Manifest is first `link` where @rel contains token manifest.'); + } + }, { + body: ` + + + `, + run(manifest) { + is(manifest.name, 'pass-2', 'Manifest is first `link` where @rel contains token manifest.'); + }, + }, { + body: ``, + 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 = + ``; + 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 = + ``; + return link; + }, + run(err) { + is(err.name, 'TypeError', 'Fetch blocked by CORS - origin does not match.'); + }, + }, { + body: ``, + run(err) { + is(err.name, 'TypeError', 'Trying to load from about:whatever is TypeError.'); + }, + }, { + body: ``, + run(err) { + is(err.name, 'TypeError', 'Trying to load from file://whatever is a TypeError.'); + }, + }, + //URL parsing tests + { + body: ``, + 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(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; + }, []); + + const results = yield Promise.all(promises); + + function testObtainingManifest(aTest) { + return function*(aBrowser) { + try { + const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser); + aTest.run(manifest); + } catch (e) { + aTest.run(e); + } + }; + } +}); + +/* + * e10s race condition tests + * Open a bunch of tabs and load manifests + * in each tab. They should all return pass. + */ +add_task(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 = [ + for (url of tabURLs) gBrowser.addTab(url).linkedBrowser + ]; + // Once all the pages have loaded, run a bunch of tests in "parallel". + yield Promise.all(( + for (browser of browsers) BrowserTestUtils.browserLoaded(browser) + )); + // Flood random browsers with requests. Once promises settle, check that + // responses all pass. + const results = yield Promise.all(( + for (browser of randBrowsers(browsers, 50)) 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_fire_appinstalled_event.js b/dom/manifest/test/browser_fire_appinstalled_event.js new file mode 100644 index 000000000..517b120d3 --- /dev/null +++ b/dom/manifest/test/browser_fire_appinstalled_event.js @@ -0,0 +1,49 @@ +//Used by JSHint: +/*global Cu, BrowserTestUtils, ok, add_task, gBrowser */ +"use strict"; +const { PromiseMessage } = Cu.import("resource://gre/modules/PromiseMessage.jsm", {}); +const testPath = "/browser/dom/manifest/test/file_reg_appinstalled_event.html"; +const defaultURL = new URL("http://example.org/browser/dom/manifest/test/file_testserver.sjs"); +const testURL = new URL(defaultURL); +testURL.searchParams.append("file", testPath); + +// Enable window.onappinstalled, so we can fire events at it. +function enableOnAppInstalledPref() { + const ops = { + "set": [ + ["dom.manifest.onappinstalled", true], + ], + }; + return SpecialPowers.pushPrefEnv(ops); +} + +// Send a message for the even to be fired. +// This cause file_reg_install_event.html to be dynamically change. +function* theTest(aBrowser) { + aBrowser.allowEvents = true; + let waitForInstall = ContentTask.spawn(aBrowser, null, function*() { + yield ContentTaskUtils.waitForEvent(content.window, "appinstalled"); + }); + const { data: { success } } = yield PromiseMessage + .send(aBrowser.messageManager, "DOM:Manifest:FireAppInstalledEvent"); + ok(success, "message sent and received successfully."); + try { + yield waitForInstall; + ok(true, "AppInstalled event fired"); + } catch (err) { + ok(false, "AppInstalled event didn't fire: " + err.message); + } +} + +// Open a tab and run the test +add_task(function*() { + yield enableOnAppInstalledPref(); + let tabOptions = { + gBrowser: gBrowser, + url: testURL.href, + }; + yield BrowserTestUtils.withNewTab( + tabOptions, + theTest + ); +}); diff --git a/dom/manifest/test/common.js b/dom/manifest/test/common.js new file mode 100644 index 000000000..4f618be80 --- /dev/null +++ b/dom/manifest/test/common.js @@ -0,0 +1,22 @@ +/** + * Common infrastructure for manifest tests. + **/ +/*globals SpecialPowers, ManifestProcessor*/ +'use strict'; +const { + 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'; +const lineTerminators = '\u000D\u000A\u2028\u2029'; +const whiteSpace = `${seperators}${lineTerminators}`; +const typeTests = [1, null, {}, + [], false +]; +const data = { + jsonText: '{}', + manifestURL: manifestURL, + docURL: docURL +}; diff --git a/dom/manifest/test/file_reg_appinstalled_event.html b/dom/manifest/test/file_reg_appinstalled_event.html new file mode 100644 index 000000000..80ff15e11 --- /dev/null +++ b/dom/manifest/test/file_reg_appinstalled_event.html @@ -0,0 +1,15 @@ + + +

waiting for event

diff --git a/dom/manifest/test/file_testserver.sjs b/dom/manifest/test/file_testserver.sjs new file mode 100644 index 000000000..5229de9f9 --- /dev/null +++ b/dom/manifest/test/file_testserver.sjs @@ -0,0 +1,54 @@ +"use strict"; +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.importGlobalProperties(["URLSearchParams"]); + +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 = + Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + + const testHTMLFileStream = + Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + + path + .split("/") + .filter(path => path) + .reduce((file, path) => { + testHTMLFile.append(path) + 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/manifestLoader.html b/dom/manifest/test/manifestLoader.html new file mode 100644 index 000000000..e24426090 --- /dev/null +++ b/dom/manifest/test/manifestLoader.html @@ -0,0 +1,13 @@ + + + + +

Manifest loader

+

Uses resource.sjs to load a Web Manifest that can be loaded cross-origin. The manifest looks like this:

+
+{
+	"name":"pass"
+}
+
diff --git a/dom/manifest/test/mochitest.ini b/dom/manifest/test/mochitest.ini new file mode 100644 index 000000000..24e3b120d --- /dev/null +++ b/dom/manifest/test/mochitest.ini @@ -0,0 +1,23 @@ +[DEFAULT] +support-files = + common.js + resource.sjs + manifestLoader.html + file_reg_appinstalled_event.html + file_testserver.sjs +[test_ImageObjectProcessor_sizes.html] +[test_ImageObjectProcessor_src.html] +[test_ImageObjectProcessor_type.html] +[test_ManifestProcessor_background_color.html] +[test_ManifestProcessor_dir.html] +[test_ManifestProcessor_display.html] +[test_ManifestProcessor_icons.html] +[test_ManifestProcessor_JSON.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_window_onappinstalled_event.html] \ No newline at end of file diff --git a/dom/manifest/test/resource.sjs b/dom/manifest/test/resource.sjs new file mode 100644 index 000000000..ec7804d3f --- /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=

hello

&Hello=hi + * + * Outputs: + * HTTP/1.1 200 OK + * Content-Type: text/html + * Hello: hi + *

hello

+ */ +//global handleRequest +'use strict'; +Components.utils.importGlobalProperties(["URLSearchParams"]); +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_sizes.html b/dom/manifest/test/test_ImageObjectProcessor_sizes.html new file mode 100644 index 000000000..82a8ef991 --- /dev/null +++ b/dom/manifest/test/test_ImageObjectProcessor_sizes.html @@ -0,0 +1,95 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ImageObjectProcessor_src.html b/dom/manifest/test/test_ImageObjectProcessor_src.html new file mode 100644 index 000000000..cb77af0bd --- /dev/null +++ b/dom/manifest/test/test_ImageObjectProcessor_src.html @@ -0,0 +1,106 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ImageObjectProcessor_type.html b/dom/manifest/test/test_ImageObjectProcessor_type.html new file mode 100644 index 000000000..d1b95044d --- /dev/null +++ b/dom/manifest/test/test_ImageObjectProcessor_type.html @@ -0,0 +1,57 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_JSON.html b/dom/manifest/test/test_ManifestProcessor_JSON.html new file mode 100644 index 000000000..0319445eb --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_JSON.html @@ -0,0 +1,34 @@ + + + + + + Test for Bug 1079453 + + + + + 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 000000000..e7249df4c --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_background_color.html @@ -0,0 +1,118 @@ + + + + + + Test for Bug 1195018 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_dir.html b/dom/manifest/test/test_ManifestProcessor_dir.html new file mode 100644 index 000000000..1978eeca3 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_dir.html @@ -0,0 +1,57 @@ + + + + + + Test for Bug 1258899 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_display.html b/dom/manifest/test/test_ManifestProcessor_display.html new file mode 100644 index 000000000..10106465a --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_display.html @@ -0,0 +1,78 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_icons.html b/dom/manifest/test/test_ManifestProcessor_icons.html new file mode 100644 index 000000000..9bd3d90ec --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_icons.html @@ -0,0 +1,30 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_lang.html b/dom/manifest/test/test_ManifestProcessor_lang.html new file mode 100644 index 000000000..f5e994175 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_lang.html @@ -0,0 +1,112 @@ + + + +Test for Bug 1143879 - Implement lang member of Web manifest + + + + 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 000000000..682c8d225 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html @@ -0,0 +1,79 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_orientation.html b/dom/manifest/test/test_ManifestProcessor_orientation.html new file mode 100644 index 000000000..67f19a9ff --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_orientation.html @@ -0,0 +1,86 @@ + + + + + + Test for Bug 1079453 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_scope.html b/dom/manifest/test/test_ManifestProcessor_scope.html new file mode 100644 index 000000000..b1cc9dbd1 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_scope.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1079453 + + + + + 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 000000000..d0b381fa2 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_start_url.html @@ -0,0 +1,59 @@ + + + + + + Test for Bug 1079453 + + + + + 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 000000000..c08830025 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_theme_color.html @@ -0,0 +1,118 @@ + + + + + + Test for Bug 1195018 + + + + + diff --git a/dom/manifest/test/test_ManifestProcessor_warnings.html b/dom/manifest/test/test_ManifestProcessor_warnings.html new file mode 100644 index 000000000..865ef8054 --- /dev/null +++ b/dom/manifest/test/test_ManifestProcessor_warnings.html @@ -0,0 +1,90 @@ + + + + + + Test for Bug 1086997 + + + + + diff --git a/dom/manifest/test/test_window_onappinstalled_event.html b/dom/manifest/test/test_window_onappinstalled_event.html new file mode 100644 index 000000000..af57fbf77 --- /dev/null +++ b/dom/manifest/test/test_window_onappinstalled_event.html @@ -0,0 +1,98 @@ + + + + + + + Test for Bug 1309099 - Web Manifest: Implement window.onappinstalled + + + + -- cgit v1.2.3