Bug 1166405 - Consolidate classes into a general web manifest module. r=ehsan.
authorMarcos Caceres <marcos@marcosc.com>
Tue, 26 May 2015 17:04:59 -0400
changeset 245676 f3d11d05b40b3f4ab70bee22564060db7cd77582
parent 245675 c02250c4d3c905245864c6e5d587607838f89c84
child 245677 93943a21b457ade2ca468f73f0f0093cdd35b27f
push id60247
push usermcaceres@mozilla.com
push dateTue, 26 May 2015 21:05:22 +0000
treeherdermozilla-inbound@f3d11d05b40b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1166405, 100644
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1166405 - Consolidate classes into a general web manifest module. r=ehsan. Bound EXPORTED_SYMBOLS to `this` in WebManifest.jsm Reduced number of iterations on random tests --- dom/ipc/manifestMessages.js | 17 +--- ...ObjectProcessor.jsm => ImageObjectProcessor.js} | 35 ++++--- .../{ManifestObtainer.jsm => ManifestObtainer.js} | 4 +- ...{ManifestProcessor.jsm => ManifestProcessor.js} | 109 ++++++++++----------- ...anifestValueExtractor.jsm => ValueExtractor.js} | 25 +++-- dom/manifest/WebManifest.jsm | 19 ++++ dom/manifest/moz.build | 9 +- .../test/browser_ManifestObtainer_obtain.js | 9 +- dom/manifest/test/common.js | 32 +++--- 9 files changed, 135 insertions(+), 124 deletions(-) rename dom/manifest/{ManifestImageObjectProcessor.jsm => ImageObjectProcessor.js} (81%) rename dom/manifest/{ManifestObtainer.jsm => ManifestObtainer.js} (95%) rename dom/manifest/{ManifestProcessor.jsm => ManifestProcessor.js} (69%) rename dom/manifest/{ManifestValueExtractor.jsm => ValueExtractor.js} (77%) create mode 100644 dom/manifest/WebManifest.jsm
dom/ipc/manifestMessages.js
dom/manifest/ImageObjectProcessor.js
dom/manifest/ManifestImageObjectProcessor.jsm
dom/manifest/ManifestObtainer.js
dom/manifest/ManifestObtainer.jsm
dom/manifest/ManifestProcessor.js
dom/manifest/ManifestProcessor.jsm
dom/manifest/ManifestValueExtractor.jsm
dom/manifest/ValueExtractor.js
dom/manifest/WebManifest.jsm
dom/manifest/moz.build
dom/manifest/test/browser_ManifestObtainer_obtain.js
dom/manifest/test/common.js
--- a/dom/ipc/manifestMessages.js
+++ b/dom/ipc/manifestMessages.js
@@ -1,36 +1,29 @@
 /* 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/.*/
 /*
  * Manifest obtainer frame script implementation of:
- * http://w3c.github.io/manifest/#obtaining
+ * http://www.w3.org/TR/appmanifest/#obtaining
  *
  * It searches a top-level browsing context for
  * a <link rel=manifest> element. Then fetches
  * and processes the linked manifest.
  *
  * BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410
- * exported ManifestObtainer
  */
-/*globals content, ManifestProcessor, XPCOMUtils, sendAsyncMessage, addMessageListener, Components*/
+/*globals content, sendAsyncMessage, addMessageListener, Components*/
 'use strict';
 const {
   utils: Cu
 } = Components;
-
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-
-XPCOMUtils.defineLazyModuleGetter(this, 'ManifestProcessor',
-  'resource://gre/modules/ManifestProcessor.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'ManifestObtainer',
-  'resource://gre/modules/ManifestObtainer.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'BrowserUtils',
-  'resource://gre/modules/BrowserUtils.jsm');
+const {
+  ManifestProcessor
+} = Cu.import('resource://gre/modules/WebManifest.jsm', {});
 
 addMessageListener('DOM:ManifestObtainer:Obtain', (aMsg) => {
   fetchManifest()
     .then(
       manifest => sendAsyncMessage('DOM:ManifestObtainer:Obtain', {
         success: true,
         result: manifest,
         msgId: aMsg.data.msgId
rename from dom/manifest/ManifestImageObjectProcessor.jsm
rename to dom/manifest/ImageObjectProcessor.js
--- a/dom/manifest/ManifestImageObjectProcessor.jsm
+++ b/dom/manifest/ImageObjectProcessor.js
@@ -1,62 +1,60 @@
 /* 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/. */
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 /*
- * ManifestImageObjectProcessor
+ * ImageObjectProcessor
  * Implementation of Image Object processing algorithms from:
- * http://www.w3.org/2008/webapps/manifest/#image-object-and-its-members
+ * http://www.w3.org/TR/appmanifest/#image-object-and-its-members
  *
- * This is intended to be used in conjunction with ManifestProcessor.jsm
+ * This is intended to be used in conjunction with ManifestProcessor.js
  *
  * 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*/
+/*globals Components */
 'use strict';
-this.EXPORTED_SYMBOLS = ['ManifestImageObjectProcessor']; // jshint ignore:line
-const imports = {};
 const {
   utils: Cu,
-  classes: Cc,
-  interfaces: Ci
+  interfaces: Ci,
+  classes: Cc
 } = Components;
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
 Cu.importGlobalProperties(['URL']);
-imports.netutil = Cc['@mozilla.org/network/util;1']
+const netutil = Cc['@mozilla.org/network/util;1']
   .getService(Ci.nsINetUtil);
 
-function ManifestImageObjectProcessor(aConsole, aExtractor) {
+function ImageObjectProcessor(aConsole, aExtractor) {
   this.console = aConsole;
   this.extractor = aExtractor;
 }
 
 // Static getters
-Object.defineProperties(ManifestImageObjectProcessor, {
+Object.defineProperties(ImageObjectProcessor, {
   'decimals': {
     get: function() {
       return /^\d+$/;
     }
   },
   'anyRegEx': {
     get: function() {
       return new RegExp('any', 'i');
     }
   }
 });
 
-ManifestImageObjectProcessor.prototype.process = function(
+ImageObjectProcessor.prototype.process = function(
   aManifest, aBaseURL, aMemberName
 ) {
   const spec = {
     objectName: 'manifest',
     object: aManifest,
     property: aMemberName,
     expectedType: 'array',
     trim: false
@@ -89,17 +87,17 @@ ManifestImageObjectProcessor.prototype.p
       objectName: 'image',
       object: aImage,
       property: 'type',
       expectedType: 'string',
       trim: true
     };
     let value = extractor.extractValue(spec);
     if (value) {
-      value = imports.netutil.parseContentType(value, charset, hadCharset);
+      value = netutil.parseContentType(value, charset, hadCharset);
     }
     return value || undefined;
   }
 
   function processDensityMember(aImage) {
     const value = parseFloat(aImage.density);
     const validNum = Number.isNaN(value) || value === +Infinity || value <=
       0;
@@ -139,36 +137,37 @@ ManifestImageObjectProcessor.prototype.p
       value.split(/\s+/)
         .filter(isValidSizeValue)
         .forEach(size => sizes.add(size));
     }
     return sizes;
     // Implementation of HTML's link@size attribute checker.
     function isValidSizeValue(aSize) {
       const size = aSize.toLowerCase();
-      if (ManifestImageObjectProcessor.anyRegEx.test(aSize)) {
+      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 = ManifestImageObjectProcessor.decimals.test(w + h);
+      const validDecimals = ImageObjectProcessor.decimals.test(w + h);
       return (validStarts && validDecimals);
     }
   }
 
   function processBackgroundColorMember(aImage) {
     const spec = {
       objectName: 'image',
       object: aImage,
       property: 'background_color',
       expectedType: 'string',
       trim: true
     };
     return extractor.extractColorValue(spec);
   }
 };
-this.ManifestImageObjectProcessor = ManifestImageObjectProcessor; // jshint ignore:line
+this.ImageObjectProcessor = ImageObjectProcessor; // jshint ignore:line
+this.EXPORTED_SYMBOLS = ['ImageObjectProcessor']; // jshint ignore:line
rename from dom/manifest/ManifestObtainer.jsm
rename to dom/manifest/ManifestObtainer.js
--- a/dom/manifest/ManifestObtainer.jsm
+++ b/dom/manifest/ManifestObtainer.js
@@ -16,18 +16,16 @@
  *   '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';
-this.EXPORTED_SYMBOLS = ['ManifestObtainer'];
-
 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();
@@ -78,8 +76,10 @@ ManifestObtainer.prototype = {
     function toError(aErrorClone) {
       const 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
rename from dom/manifest/ManifestProcessor.jsm
rename to dom/manifest/ManifestProcessor.js
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.js
@@ -7,53 +7,50 @@
  * 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 ManifestImageObjectProcessor to process things like
+ * 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).
  */
-/*exported EXPORTED_SYMBOLS */
-/*JSLint options in comment below: */
-/*globals Components, XPCOMUtils, Intl*/
+/*globals Components*/
 'use strict';
-this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line
-const imports = {};
 const {
-  utils: Cu
+  utils: Cu,
+  interfaces: Ci,
+  classes: Cc
 } = Components;
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.importGlobalProperties(['URL']);
-XPCOMUtils.defineLazyModuleGetter(imports, 'Services',
-  'resource://gre/modules/Services.jsm');
 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 {
   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 {
-  ManifestImageObjectProcessor: ImgObjProcessor
-} = Cu.import('resource://gre/modules/ManifestImageObjectProcessor.jsm');
-
+  ValueExtractor
+} = Cu.import('resource://gre/modules/ValueExtractor.js', {});
+// ImageObjectProcessor is used to process things like icons and images
 const {
-  ManifestValueExtractor
-} = Cu.import('resource://gre/modules/ManifestValueExtractor.jsm');
+  ImageObjectProcessor
+} = Cu.import('resource://gre/modules/ImageObjectProcessor.js', {});
 
 function ManifestProcessor() {}
 
 // Static getters
 Object.defineProperties(ManifestProcessor, {
   'defaultDisplayMode': {
     get: function() {
       return 'browser';
@@ -70,193 +67,192 @@ Object.defineProperties(ManifestProcesso
     }
   }
 });
 
 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:
-  //  * aJsonText: the JSON string to be processed.
-  //  * aManifestURL: the URL of the manifest, to resolve URLs.
-  //  * aDocURL: the URL of the owner doc, for security checks
+  //  * 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: aJsonText,
+    jsonText,
     manifestURL: aManifestURL,
     docURL: aDocURL
   }) {
     const console = new ConsoleAPI({
       prefix: 'Web Manifest: '
     });
     const manifestURL = new URL(aManifestURL);
     const docURL = new URL(aDocURL);
     let rawManifest = {};
     try {
-      rawManifest = JSON.parse(aJsonText);
+      rawManifest = JSON.parse(jsonText);
     } catch (e) {}
     if (typeof rawManifest !== 'object' || rawManifest === null) {
       let msg = 'Manifest needs to be an object.';
       console.warn(msg);
       rawManifest = {};
     }
-    const extractor = new ManifestValueExtractor(console);
-    const imgObjProcessor = new ImgObjProcessor(console, extractor);
+    const extractor = new ValueExtractor(console);
+    const imgObjProcessor = new ImageObjectProcessor(console, extractor);
     const processedManifest = {
-      'lang': processLangMember(rawManifest),
-      'start_url': processStartURLMember(rawManifest, manifestURL, docURL),
-      'display': processDisplayMember(rawManifest),
-      'orientation': processOrientationMember(rawManifest),
-      'name': processNameMember(rawManifest),
+      'lang': processLangMember(),
+      'start_url': processStartURLMember(),
+      'display': processDisplayMember(),
+      'orientation': processOrientationMember(),
+      'name': processNameMember(),
       'icons': imgObjProcessor.process(
         rawManifest, manifestURL, 'icons'
       ),
       'splash_screens': imgObjProcessor.process(
         rawManifest, manifestURL, 'splash_screens'
       ),
-      'short_name': processShortNameMember(rawManifest),
-      'theme_color': processThemeColorMember(rawManifest),
+      'short_name': processShortNameMember(),
+      'theme_color': processThemeColorMember(),
     };
-    processedManifest.scope = processScopeMember(rawManifest, manifestURL,
-      docURL, new URL(processedManifest['start_url'])); // jshint ignore:line
-
+    processedManifest.scope = processScopeMember();
     return processedManifest;
 
-    function processNameMember(aManifest) {
+    function processNameMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'name',
         expectedType: 'string',
         trim: true
       };
       return extractor.extractValue(spec);
     }
 
-    function processShortNameMember(aManifest) {
+    function processShortNameMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'short_name',
         expectedType: 'string',
         trim: true
       };
       return extractor.extractValue(spec);
     }
 
-    function processOrientationMember(aManifest) {
+    function processOrientationMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'orientation',
         expectedType: 'string',
         trim: true
       };
       const value = extractor.extractValue(spec);
       if (ManifestProcessor.orientationTypes.has(value)) {
         return value;
       }
       // The spec special-cases orientation to return the empty string.
       return '';
     }
 
-    function processDisplayMember(aManifest) {
+    function processDisplayMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'display',
         expectedType: 'string',
         trim: true
       };
       const value = extractor.extractValue(spec);
       if (ManifestProcessor.displayModes.has(value)) {
         return value;
       }
       return ManifestProcessor.defaultDisplayMode;
     }
 
-    function processScopeMember(aManifest, aManifestURL, aDocURL, aStartURL) {
+    function processScopeMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        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, aManifestURL);
+        scopeURL = new URL(value, manifestURL);
       } catch (e) {
         let msg = 'The URL of scope is invalid.';
         console.warn(msg);
         return undefined;
       }
-      if (scopeURL.origin !== aDocURL.origin) {
+      if (scopeURL.origin !== docURL.origin) {
         let msg = 'Scope needs to be same-origin as Document.';
         console.warn(msg);
         return undefined;
       }
       // If start URL is not within scope of scope URL:
-      let isSameOrigin = aStartURL && aStartURL.origin !== scopeURL.origin;
-      if (isSameOrigin || !aStartURL.pathname.startsWith(scopeURL.pathname)) {
+      let isSameOrigin = startURL && startURL.origin !== scopeURL.origin;
+      if (isSameOrigin || !startURL.pathname.startsWith(scopeURL.pathname)) {
         let msg =
           'The start URL is outside the scope, so scope is invalid.';
         console.warn(msg);
         return undefined;
       }
       return scopeURL.href;
     }
 
-    function processStartURLMember(aManifest, aManifestURL, aDocURL) {
+    function processStartURLMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'start_url',
         expectedType: 'string',
         trim: false
       };
-      let result = new URL(aDocURL).href;
+      let result = new URL(docURL).href;
       const value = extractor.extractValue(spec);
       if (value === undefined || value === '') {
         return result;
       }
       let potentialResult;
       try {
-        potentialResult = new URL(value, aManifestURL);
+        potentialResult = new URL(value, manifestURL);
       } catch (e) {
         console.warn('Invalid URL.');
         return result;
       }
-      if (potentialResult.origin !== aDocURL.origin) {
+      if (potentialResult.origin !== docURL.origin) {
         let msg = 'start_url must be same origin as document.';
         console.warn(msg);
       } else {
         result = potentialResult.href;
       }
       return result;
     }
 
-    function processThemeColorMember(aManifest) {
+    function processThemeColorMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        object: rawManifest,
         property: 'theme_color',
         expectedType: 'string',
         trim: true
       };
       return extractor.extractColorValue(spec);
     }
 
-    function processLangMember(aManifest) {
+    function processLangMember() {
       const spec = {
         objectName: 'manifest',
-        object: aManifest,
+        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
@@ -265,8 +261,9 @@ ManifestProcessor.prototype = {
       //       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.ManifestProcessor = ManifestProcessor; // jshint ignore:line
+this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line
rename from dom/manifest/ManifestValueExtractor.jsm
rename to dom/manifest/ValueExtractor.js
--- a/dom/manifest/ManifestValueExtractor.jsm
+++ b/dom/manifest/ValueExtractor.js
@@ -1,43 +1,38 @@
 /* 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://www.mozilla.org/MPL/2.0/. */
+ * 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 imports = {};
 const {
   classes: Cc,
   interfaces: Ci
 } = Components;
-imports.DOMUtils = Cc['@mozilla.org/inspector/dom-utils;1']
-  .getService(Ci.inIDOMUtils);
 
-this.EXPORTED_SYMBOLS = ['ManifestValueExtractor']; // jshint ignore:line
-
-function ManifestValueExtractor(aConsole) {
+function ValueExtractor(aConsole) {
   this.console = aConsole;
 }
 
-ManifestValueExtractor.prototype = {
+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
-  }) {
+      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') {
         let msg = `Expected the ${objectName}'s ${property} `;
         msg += `member to be a ${expectedType}.`;
@@ -49,20 +44,22 @@ ManifestValueExtractor.prototype = {
     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 (imports.DOMUtils.isValidCSSColor(value)) {
+    if (DOMUtils.isValidCSSColor(value)) {
       color = value;
-    } else {
+    } else if (value) {
       const msg = `background_color: ${value} is not a valid CSS color.`;
       this.console.warn(msg);
     }
     return color;
   }
 };
-
-this.ManifestValueExtractor = ManifestValueExtractor; // jshint ignore:line
+this.ValueExtractor = ValueExtractor; // jshint ignore:line
+this.EXPORTED_SYMBOLS = ['ValueExtractor']; // jshint ignore:line
new file mode 100644
--- /dev/null
+++ b/dom/manifest/WebManifest.jsm
@@ -0,0 +1,19 @@
+/* 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`);
+}
--- a/dom/manifest/moz.build
+++ b/dom/manifest/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; 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 += [
-    'ManifestImageObjectProcessor.jsm',
-    'ManifestObtainer.jsm',
-    'ManifestProcessor.jsm',
-    'ManifestValueExtractor.jsm'
+    'ImageObjectProcessor.js',
+    'ManifestObtainer.js',
+    'ManifestProcessor.js',
+    'ValueExtractor.js',
+    'WebManifest.jsm'
 ]
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/dom/manifest/test/browser_ManifestObtainer_obtain.js
+++ b/dom/manifest/test/browser_ManifestObtainer_obtain.js
@@ -1,12 +1,15 @@
 //Used by JSHint:
-/*global Cu, ManifestObtainer, BrowserTestUtils, add_task, SpecialPowers, todo_is, gBrowser, Assert*/
+/*global Cu, BrowserTestUtils, add_task, SpecialPowers, gBrowser, Assert*/
 'use strict';
-Cu.import('resource://gre/modules/ManifestObtainer.jsm', this);
+const {
+  ManifestObtainer
+} = Cu.import('resource://gre/modules/WebManifest.jsm', {});
+
 requestLongerTimeout(4); // e10s tests take time.
 const defaultURL =
   'http://example.org/tests/dom/manifest/test/resource.sjs';
 const remoteURL =
   'http://mochi.test:8888/tests/dom/manifest/test/resource.sjs';
 const tests = [
   // Fetch tests.
   {
@@ -184,17 +187,17 @@ add_task(function*() {
   ];
   // 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, 1000)) obtainer.obtainManifest(browser)
+    for (browser of randBrowsers(browsers, 100)) obtainer.obtainManifest(browser)
   ));
   const expected = 'Expect every manifest to have name equal to `pass`.';
   const pass = results.every(manifest => manifest.name === 'pass');
   Assert.ok(pass, expected);
   //cleanup
   browsers
     .map(browser => gBrowser.getTabForBrowser(browser))
     .forEach(tab => gBrowser.removeTab(tab));
--- a/dom/manifest/test/common.js
+++ b/dom/manifest/test/common.js
@@ -1,20 +1,22 @@
 /**
  * Common infrastructure for manifest tests.
  **/
 
 'use strict';
-const bsp = SpecialPowers.Cu.import('resource://gre/modules/ManifestProcessor.jsm'),
-  processor = new bsp.ManifestProcessor(),
-  manifestURL = new URL(document.location.origin + '/manifest.json'),
-  docURL = document.location,
-  seperators = '\u2028\u2029\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000',
-  lineTerminators = '\u000D\u000A\u2028\u2029',
-  whiteSpace = `${seperators}${lineTerminators}`,
-  typeTests = [1, null, {},
-    [], false
-  ],
-  data = {
-    jsonText: '{}',
-    manifestURL: manifestURL,
-    docURL: docURL
-  };
+const {
+  ManifestProcessor
+} = SpecialPowers.Cu.import('resource://gre/modules/WebManifest.jsm');
+const processor = new 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
+};