Bug 1164235 - Add theme_color support to web manifest processor. r=ehsan
authorMarcos Caceres <marcos@marcosc.com>
Wed, 20 May 2015 10:58:00 -0400
changeset 245080 a90ab68fa4123b4baf7e74446f7573c081dce7d4
parent 245079 374eac1b621bc9c4706340be4e3513509924a6a5
child 245081 6472e42af0c7648cb51f4c045c3053b6e0a3b35f
push id60108
push userryanvm@gmail.com
push dateFri, 22 May 2015 14:28:54 +0000
treeherdermozilla-inbound@00f2351e7744 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1164235
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 1164235 - Add theme_color support to web manifest processor. r=ehsan
dom/manifest/ManifestImageObjectProcessor.jsm
dom/manifest/ManifestProcessor.jsm
dom/manifest/ManifestValueExtractor.jsm
dom/manifest/manifestValueExtractor.js
dom/manifest/moz.build
--- a/dom/manifest/ManifestImageObjectProcessor.jsm
+++ b/dom/manifest/ManifestImageObjectProcessor.jsm
@@ -9,69 +9,66 @@
  * 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, console);
+ *   .process(aManifest, aBaseURL, aMemberName);
  *
  */
-/*exported EXPORTED_SYMBOLS */
-/*globals extractValue, Components*/
+/*exported EXPORTED_SYMBOLS*/
+/*globals Components*/
 'use strict';
 this.EXPORTED_SYMBOLS = ['ManifestImageObjectProcessor']; // jshint ignore:line
 const imports = {};
 const {
   utils: Cu,
   classes: Cc,
   interfaces: Ci
 } = Components;
-const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']
-  .getService(Ci.mozIJSSubScriptLoader);
-scriptLoader.loadSubScript(
-  'resource://gre/modules/manifestValueExtractor.js',
-  this); // jshint ignore:line
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.importGlobalProperties(['URL']);
 imports.netutil = Cc['@mozilla.org/network/util;1']
   .getService(Ci.nsINetUtil);
-imports.DOMUtils = Cc['@mozilla.org/inspector/dom-utils;1']
-  .getService(Ci.inIDOMUtils);
 
-function ManifestImageObjectProcessor() {}
+function ManifestImageObjectProcessor(aConsole, aExtractor) {
+  this.console = aConsole;
+  this.extractor = aExtractor;
+}
 
 // Static getters
 Object.defineProperties(ManifestImageObjectProcessor, {
   'decimals': {
     get: function() {
       return /^\d+$/;
     }
   },
   'anyRegEx': {
     get: function() {
       return new RegExp('any', 'i');
     }
   }
 });
 
-ManifestImageObjectProcessor.process = function(
-  aManifest, aBaseURL, aMemberName, console
+ManifestImageObjectProcessor.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 = extractValue(spec, console);
+  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;
 
@@ -90,17 +87,17 @@ ManifestImageObjectProcessor.process = f
     const hadCharset = {};
     const spec = {
       objectName: 'image',
       object: aImage,
       property: 'type',
       expectedType: 'string',
       trim: true
     };
-    let value = extractValue(spec, console);
+    let value = extractor.extractValue(spec);
     if (value) {
       value = imports.netutil.parseContentType(value, charset, hadCharset);
     }
     return value || undefined;
   }
 
   function processDensityMember(aImage) {
     const value = parseFloat(aImage.density);
@@ -112,17 +109,17 @@ ManifestImageObjectProcessor.process = f
   function processSrcMember(aImage, aBaseURL) {
     const spec = {
       objectName: 'image',
       object: aImage,
       property: 'src',
       expectedType: 'string',
       trim: false
     };
-    const value = extractValue(spec, console);
+    const value = extractor.extractValue(spec);
     let url;
     if (value && value.length) {
       try {
         url = new URL(value, aBaseURL).href;
       } catch (e) {}
     }
     return url;
   }
@@ -131,17 +128,17 @@ ManifestImageObjectProcessor.process = f
     const sizes = new Set();
     const spec = {
       objectName: 'image',
       object: aImage,
       property: 'sizes',
       expectedType: 'string',
       trim: true
     };
-    const value = extractValue(spec, console);
+    const value = extractor.extractValue(spec);
     if (value) {
       // Split on whitespace and filter out invalid values.
       value.split(/\s+/)
         .filter(isValidSizeValue)
         .forEach(size => sizes.add(size));
     }
     return sizes;
     // Implementation of HTML's link@size attribute checker.
@@ -166,20 +163,12 @@ ManifestImageObjectProcessor.process = f
   function processBackgroundColorMember(aImage) {
     const spec = {
       objectName: 'image',
       object: aImage,
       property: 'background_color',
       expectedType: 'string',
       trim: true
     };
-    const value = extractValue(spec, console);
-    let color;
-    if (imports.DOMUtils.isValidCSSColor(value)) {
-      color = value;
-    } else {
-      const msg = `background_color: ${value} is not a valid CSS color.`;
-      console.warn(msg);
-    }
-    return color;
+    return extractor.extractColorValue(spec);
   }
 };
 this.ManifestImageObjectProcessor = ManifestImageObjectProcessor; // jshint ignore:line
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -16,49 +16,44 @@
  * 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, extractValue*/
+/*globals Components, XPCOMUtils*/
 'use strict';
 this.EXPORTED_SYMBOLS = ['ManifestProcessor']; // jshint ignore:line
 const imports = {};
 const {
-  utils: Cu,
-  classes: Cc,
-  interfaces: Ci
+  utils: Cu
 } = Components;
-const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']
-  .getService(Ci.mozIJSSubScriptLoader);
-//Add extractValue() helper to this context.
-scriptLoader.loadSubScript(
-  'resource://gre/modules/manifestValueExtractor.js',
-  this); // jshint ignore:line
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.importGlobalProperties(['URL']);
 XPCOMUtils.defineLazyModuleGetter(imports, 'Services',
   'resource://gre/modules/Services.jsm');
-XPCOMUtils.defineLazyModuleGetter(imports, 'ManifestImageObjectProcessor',
-  'resource://gre/modules/ManifestImageObjectProcessor.jsm');
-imports.netutil = Cc['@mozilla.org/network/util;1']
-  .getService(Ci.nsINetUtil);
 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');
+const {
+  ManifestImageObjectProcessor: ImgObjProcessor
+} = Cu.import('resource://gre/modules/ManifestImageObjectProcessor.jsm');
+
+const {
+  ManifestValueExtractor
+} = Cu.import('resource://gre/modules/ManifestValueExtractor.jsm');
 
 function ManifestProcessor() {}
 
 // Static getters
 Object.defineProperties(ManifestProcessor, {
   'defaultDisplayMode': {
     get: function() {
       return 'browser';
@@ -72,122 +67,125 @@ Object.defineProperties(ManifestProcesso
   'orientationTypes': {
     get: function() {
       return orientationTypes;
     }
   }
 });
 
 ManifestProcessor.prototype = {
-  // process method: processes JSON text into a clean manifest
+  // 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.
+  //  * aDocURL: the URL of the owner doc, for security checks
   process({
     jsonText: aJsonText,
     manifestURL: aManifestURL,
     docURL: aDocURL
   }) {
-    const manifestURL = new URL(aManifestURL);
-    const docURL = new URL(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);
     } 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 processedManifest = {
       'start_url': processStartURLMember(rawManifest, manifestURL, docURL),
       'display': processDisplayMember(rawManifest),
       'orientation': processOrientationMember(rawManifest),
       'name': processNameMember(rawManifest),
-      'icons': imports.ManifestImageObjectProcessor.process(
-        rawManifest, manifestURL, 'icons', console
+      'icons': imgObjProcessor.process(
+        rawManifest, manifestURL, 'icons'
       ),
-      'splash_screens': imports.ManifestImageObjectProcessor.process(
-        rawManifest, manifestURL, 'splash_screens', console
+      'splash_screens': imgObjProcessor.process(
+        rawManifest, manifestURL, 'splash_screens'
       ),
       'short_name': processShortNameMember(rawManifest),
+      'theme_color': processThemeColorMember(rawManifest),
     };
     processedManifest.scope = processScopeMember(rawManifest, manifestURL,
       docURL, new URL(processedManifest['start_url'])); // jshint ignore:line
 
     return processedManifest;
 
     function processNameMember(aManifest) {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'name',
         expectedType: 'string',
         trim: true
       };
-      return extractValue(spec, console);
+      return extractor.extractValue(spec);
     }
 
     function processShortNameMember(aManifest) {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'short_name',
         expectedType: 'string',
         trim: true
       };
-      return extractValue(spec, console);
+      return extractor.extractValue(spec);
     }
 
     function processOrientationMember(aManifest) {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'orientation',
         expectedType: 'string',
         trim: true
       };
-      const value = extractValue(spec, console);
+      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) {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'display',
         expectedType: 'string',
         trim: true
       };
-      const value = extractValue(spec, console);
+      const value = extractor.extractValue(spec);
       if (ManifestProcessor.displayModes.has(value)) {
         return value;
       }
       return ManifestProcessor.defaultDisplayMode;
     }
 
     function processScopeMember(aManifest, aManifestURL, aDocURL, aStartURL) {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'scope',
         expectedType: 'string',
         trim: false
       };
       let scopeURL;
-      const value = extractValue(spec, console);
+      const value = extractor.extractValue(spec);
       if (value === undefined || value === '') {
         return undefined;
       }
       try {
         scopeURL = new URL(value, aManifestURL);
       } catch (e) {
         let msg = 'The URL of scope is invalid.';
         console.warn(msg);
@@ -213,17 +211,17 @@ ManifestProcessor.prototype = {
       const spec = {
         objectName: 'manifest',
         object: aManifest,
         property: 'start_url',
         expectedType: 'string',
         trim: false
       };
       let result = new URL(aDocURL).href;
-      const value = extractValue(spec, console);
+      const value = extractor.extractValue(spec);
       if (value === undefined || value === '') {
         return result;
       }
       let potentialResult;
       try {
         potentialResult = new URL(value, aManifestURL);
       } catch (e) {
         console.warn('Invalid URL.');
@@ -232,11 +230,23 @@ ManifestProcessor.prototype = {
       if (potentialResult.origin !== aDocURL.origin) {
         let msg = 'start_url must be same origin as document.';
         console.warn(msg);
       } else {
         result = potentialResult.href;
       }
       return result;
     }
+
+    function processThemeColorMember(aManifest) {
+      const spec = {
+        objectName: 'manifest',
+        object: aManifest,
+        property: 'theme_color',
+        expectedType: 'string',
+        trim: true
+      };
+      return extractor.extractColorValue(spec);
+    }
   }
 };
+
 this.ManifestProcessor = ManifestProcessor; // jshint ignore:line
new file mode 100644
--- /dev/null
+++ b/dom/manifest/ManifestValueExtractor.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://www.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) {
+  this.console = aConsole;
+}
+
+ManifestValueExtractor.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') {
+        let msg = `Expected the ${objectName}'s ${property} `;
+        msg += `member to be a ${expectedType}.`;
+        this.console.log(msg);
+      }
+      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);
+    let color;
+    if (imports.DOMUtils.isValidCSSColor(value)) {
+      color = value;
+    } else {
+      const msg = `background_color: ${value} is not a valid CSS color.`;
+      this.console.warn(msg);
+    }
+    return color;
+  }
+};
+
+this.ManifestValueExtractor = ManifestValueExtractor; // jshint ignore:line
deleted file mode 100644
--- a/dom/manifest/manifestValueExtractor.js
+++ /dev/null
@@ -1,34 +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/. */
-/*
- * Helper function extracts values from manifest members
- * and reports conformance violations.
- */
-
-function extractValue({
-  objectName,
-  object,
-  property,
-  expectedType,
-  trim
-}, console) {
-  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}.`;
-      console.log(msg);
-    }
-    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;
-}
--- a/dom/manifest/moz.build
+++ b/dom/manifest/moz.build
@@ -3,13 +3,13 @@
 # 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.js'
+    'ManifestValueExtractor.jsm'
 ]
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']