Backed out changeset f30be176edfd (bug 1083410) on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 26 Mar 2015 11:53:51 -0400
changeset 264678 f51c5b9a3ba40e79a1050d21cb33af1db2fc1a45
parent 264677 b09aad0998513e4c3d9d18cbc52c5a8d238124a9
child 264679 d5a537c7c5582d7edcdf83a3705d888259c62a6e
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1083410
milestone39.0a1
backs outf30be176edfd2ab07663b0429db260471f84fca2
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
Backed out changeset f30be176edfd (bug 1083410) on a CLOSED TREE.
dom/manifest/ManifestProcessor.jsm
dom/manifest/test/common.js
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -1,360 +1,357 @@
 /* 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 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});
+ *   .process(jsonText, manifestURL, docURL);
  *
  * 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).
+ * TODO: hook up developer tools to issueDeveloperWarning (1086997).
  */
+/*globals Components*/
 /*exported EXPORTED_SYMBOLS */
-/*JSLint options in comment below: */
-/*globals Components, XPCOMUtils*/
 'use strict';
+
 this.EXPORTED_SYMBOLS = ['ManifestProcessor'];
-const imports = {};
 const {
   utils: Cu,
   classes: Cc,
   interfaces: Ci
 } = Components;
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const imports = {};
+Cu.import('resource://gre/modules/Services.jsm', imports);
 Cu.importGlobalProperties(['URL']);
-XPCOMUtils.defineLazyModuleGetter(imports, 'Services',
-  'resource://gre/modules/Services.jsm');
-imports.netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil);
-// 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 a 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;
-}
-const displayModes = new Set(['fullscreen', 'standalone', 'minimal-ui',
+const securityManager = imports.Services.scriptSecurityManager;
+const netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil);
+const defaultDisplayMode = 'browser';
+const displayModes = new Set([
+  'fullscreen',
+  'standalone',
+  'minimal-ui',
   'browser'
 ]);
-const orientationTypes = new Set(['any', 'natural', 'landscape', 'portrait',
-  'portrait-primary', 'portrait-secondary', 'landscape-primary',
+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');
 
-class ManifestProcessor {
+this.ManifestProcessor = function ManifestProcessor() {};
+/**
+ * process method: processes json text into a clean manifest
+ * that conforms with the W3C specification.
+ * @param jsonText - the JSON string to be processd.
+ * @param manifestURL - the URL of the manifest, to resolve URLs.
+ * @param docURL - the URL of the owner doc, for security checks
+ */
+this.ManifestProcessor.prototype.process = function({
+  jsonText: jsonText,
+  manifestURL: manifestURL,
+  docLocation: docURL
+}) {
+  /*
+   * This helper function is used to extract values from manifest members.
+   * It also reports conformance violations.
+   */
+  function extractValue(obj) {
+    let value = obj.object[obj.property];
+    //we need to special-case "array", as it's not a JS primitive
+    const type = (Array.isArray(value)) ? 'array' : typeof value;
 
-  constructor() {}
-
-  static get defaultDisplayMode() {
-    return 'browser';
+    if (type !== obj.expectedType) {
+      if (type !== 'undefined') {
+        let msg = `Expected the ${obj.objectName}'s ${obj.property}`;
+        msg += `member to a be a ${obj.expectedType}.`;
+        issueDeveloperWarning(msg);
+      }
+      value = undefined;
+    }
+    return value;
   }
 
-  static get displayModes() {
-    return displayModes;
+  function issueDeveloperWarning(msg) {
+    //https://bugzilla.mozilla.org/show_bug.cgi?id=1086997
   }
 
-  static get orientationTypes() {
-    return orientationTypes;
+  function processNameMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'name',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    return (value) ? value.trim() : value;
+  }
+
+  function processShortNameMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'short_name',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    return (value) ? value.trim() : value;
   }
 
-  // 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 processd.
-  //  * manifestURL: the URL of the manifest, to resolve URLs.
-  //  * docURL: the URL of the owner doc, for security checks.
-  process({
-    jsonText, manifestURL, docURL
-  }) {
-    const console = new ConsoleAPI({
-      prefix: 'Web Manifest: '
-    });
-    let rawManifest = {};
+  function processOrientationMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'orientation',
+      expectedType: 'string'
+    };
+    let value = extractValue(obj);
+    value = (value) ? value.trim() : undefined;
+    //The spec special-cases orientation to return the empty string
+    return (orientationTypes.has(value)) ? value : '';
+  }
+
+  function processDisplayMember(manifest) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'display',
+      expectedType: 'string'
+    };
+
+    let value = extractValue(obj);
+    value = (value) ? value.trim() : value;
+    return (displayModes.has(value)) ? value : defaultDisplayMode;
+  }
+
+  function processScopeMember(manifest, manifestURL, docURL, startURL) {
+    const spec = {
+        objectName: 'manifest',
+        object: manifest,
+        property: 'scope',
+        expectedType: 'string',
+        dontTrim: true
+      },
+      value = extractValue(spec);
+    let scopeURL;
     try {
-      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 processedManifest = {
-      start_url: processStartURLMember(rawManifest, manifestURL, docURL),
-      display: processDisplayMember(rawManifest),
-      orientation: processOrientationMember(rawManifest),
-      name: processNameMember(rawManifest),
-      icons: IconsProcessor.process(rawManifest, manifestURL, console),
-      short_name: processShortNameMember(rawManifest),
-    };
-    processedManifest.scope = processScopeMember(rawManifest, manifestURL,
-      docURL, processedManifest.start_url);
-    return processedManifest;
-
-    function processNameMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'name',
-        expectedType: 'string',
-        trim: true
-      };
-      return extractValue(spec, console);
-    }
-
-    function processShortNameMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'short_name',
-        expectedType: 'string',
-        trim: true
-      };
-      return extractValue(spec, console);
+      scopeURL = new URL(value, manifestURL);
+    } catch (e) {
+      let msg = 'The URL of scope is invalid.';
+      issueDeveloperWarning(msg);
+      return undefined;
     }
 
-    function processOrientationMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'orientation',
-        expectedType: 'string',
-        trim: true
-      };
-      const value = extractValue(spec, console);
-      if (ManifestProcessor.orientationTypes.has(value)) {
-        return value;
-      }
-      // The spec special-cases orientation to return the empty string.
-      return '';
+    if (scopeURL.origin !== docURL.origin) {
+      let msg = 'Scope needs to be same-origin as Document.';
+      issueDeveloperWarning(msg);
+      return undefined;
     }
 
-    function processDisplayMember(aManifest) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'display',
-        expectedType: 'string',
-        trim: true
-      };
-      const value = extractValue(spec, console);
-      if (ManifestProcessor.displayModes.has(value)) {
-        return value;
-      }
-      return ManifestProcessor.defaultDisplayMode;
+    //If start URL is not within scope of scope URL:
+    if (startURL && startURL.origin !== scopeURL.origin || !startURL.pathname.startsWith(scopeURL.pathname)) {
+      let msg = 'The start URL is outside the scope, so scope is invalid.';
+      issueDeveloperWarning(msg);
+      return undefined;
+    }
+    return scopeURL;
+  }
+
+  function processStartURLMember(manifest, manifestURL, docURL) {
+    const obj = {
+      objectName: 'manifest',
+      object: manifest,
+      property: 'start_url',
+      expectedType: 'string'
+    };
+
+    let value = extractValue(obj),
+      result = new URL(docURL),
+      targetURI = makeURI(result),
+      sameOrigin = false,
+      potentialResult,
+      referrerURI;
+
+    if (value === undefined || value === '') {
+      return result;
     }
 
-    function processScopeMember(aManifest, aManifestURL, aDocURL, aStartURL) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'scope',
-        expectedType: 'string',
-        trim: false
-      };
-      const value = extractValue(spec, console);
-      let scopeURL;
-      try {
-        scopeURL = new URL(value, aManifestURL);
-      } catch (e) {
-        let msg = 'The URL of scope is invalid.';
-        console.warn(msg);
-        return undefined;
-      }
-      if (scopeURL.origin !== aDocURL.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 msg =
-          'The start URL is outside the scope, so scope is invalid.';
-        console.warn(msg);
-        return undefined;
-      }
-      return scopeURL;
-    }
-
-    function processStartURLMember(aManifest, aManifestURL, aDocURL) {
-      const spec = {
-        objectName: 'manifest',
-        object: aManifest,
-        property: 'start_url',
-        expectedType: 'string',
-        trim: false
-      };
-      let result = new URL(aDocURL);
-      const value = extractValue(spec, console);
-      if (value === undefined || value === '') {
-        return result;
-      }
-      let potentialResult;
-      try {
-        potentialResult = new URL(value, aManifestURL);
-      } catch (e) {
-        console.warn('Invalid URL.');
-        return result;
-      }
-      if (potentialResult.origin !== aDocURL.origin) {
-        let msg = 'start_url must be same origin as document.';
-        console.warn(msg);
-      } else {
-        result = potentialResult;
-      }
+    try {
+      potentialResult = new URL(value, manifestURL);
+    } catch (e) {
+      issueDeveloperWarning('Invalid URL.');
       return result;
     }
-  }
-}
-this.ManifestProcessor = ManifestProcessor;
-
-class IconsProcessor {
+    referrerURI = makeURI(potentialResult);
+    try {
+      securityManager.checkSameOriginURI(referrerURI, targetURI, false);
+      sameOrigin = true;
+    } catch (e) {}
+    if (!sameOrigin) {
+      let msg = 'start_url must be same origin as document.';
+      issueDeveloperWarning(msg);
+    } else {
+      result = potentialResult;
+    }
+    return result;
 
-  constructor() {
-    throw new Error('Static use only.');
-  }
-
-  static get onlyDecimals() {
-    return /^\d+$/;
+    //Converts a URL to a Gecko URI
+    function makeURI(webURL) {
+      return imports.Services.io.newURI(webURL.toString(), null, null);
+    }
   }
 
-  static get anyRegEx() {
-    return new RegExp('any', 'i');
-  }
+  //Constants used by IconsProcessor
+  const onlyDecimals = /^\d+$/,
+    anyRegEx = new RegExp('any', 'i');
+
+  function IconsProcessor() {}
+  IconsProcessor.prototype.processIcons = function(manifest, baseURL) {
+    const obj = {
+        objectName: 'manifest',
+        object: manifest,
+        property: 'icons',
+        expectedType: 'array'
+      },
+      icons = [];
+    let value = extractValue(obj);
 
-  static process(aManifest, aBaseURL, console) {
-    const spec = {
-      objectName: 'manifest',
-      object: aManifest,
-      property: 'icons',
-      expectedType: 'array',
-      trim: false
-    };
-    const icons = [];
-    const value = extractValue(spec, console);
     if (Array.isArray(value)) {
-      // Filter out icons whose "src" is not useful.
-      value.filter(item => !!processSrcMember(item, aBaseURL))
-        .map(toIconObject)
-        .forEach(icon => icons.push(icon));
+      //filter out icons with no "src" or src is empty string
+      let processableIcons = value.filter(
+        icon => icon && Object.prototype.hasOwnProperty.call(icon, 'src') && icon.src !== ''
+      );
+      for (let potentialIcon of processableIcons) {
+        let src = processSrcMember(potentialIcon, baseURL)
+        if(src !== undefined){
+          let icon = {
+            src: src,
+            type: processTypeMember(potentialIcon),
+            sizes: processSizesMember(potentialIcon),
+            density: processDensityMember(potentialIcon)
+          };
+          icons.push(icon);
+        }
+      }
     }
     return icons;
 
-    function toIconObject(aIconData) {
-      return {
-        src: processSrcMember(aIconData, aBaseURL),
-        type: processTypeMember(aIconData),
-        sizes: processSizesMember(aIconData),
-        density: processDensityMember(aIconData)
-      };
+    function processTypeMember(icon) {
+      const charset = {},
+        hadCharset = {},
+        obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'type',
+          expectedType: 'string'
+        };
+      let value = extractValue(obj),
+        isParsable = (typeof value === 'string' && value.length > 0);
+      value = (isParsable) ? netutil.parseContentType(value.trim(), charset, hadCharset) : undefined;
+      return (value === '') ? undefined : value;
     }
 
-    function processTypeMember(aIcon) {
-      const charset = {};
-      const hadCharset = {};
-      const spec = {
-        objectName: 'icon',
-        object: aIcon,
-        property: 'type',
-        expectedType: 'string',
-        trim: true
-      };
-      let value = extractValue(spec, console);
-      if (value) {
-        value = imports.netutil.parseContentType(value, charset, hadCharset);
-      }
-      return value || undefined;
+    function processDensityMember(icon) {
+      const hasDensity = Object.prototype.hasOwnProperty.call(icon, 'density'),
+        rawValue = (hasDensity) ? icon.density : undefined,
+        value = parseFloat(rawValue),
+        result = (Number.isNaN(value) || value === +Infinity || value <= 0) ? 1.0 : value;
+      return result;
     }
 
-    function processDensityMember(aIcon) {
-      const value = parseFloat(aIcon.density);
-      const validNum = Number.isNaN(value) || value === +Infinity || value <=
-        0;
-      return (validNum) ? 1.0 : value;
-    }
-
-    function processSrcMember(aIcon, aBaseURL) {
-      const spec = {
-        objectName: 'icon',
-        object: aIcon,
-        property: 'src',
-        expectedType: 'string',
-        trim: false
-      };
-      const value = extractValue(spec, console);
+    function processSrcMember(icon, baseURL) {
+      const obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'src',
+          expectedType: 'string'
+        },
+        value = extractValue(obj);
       let url;
-      if (value && value.length) {
+      if (typeof value === 'string' && value.trim() !== '') {
         try {
-          url = new URL(value, aBaseURL);
+          url = new URL(value, baseURL);
         } catch (e) {}
       }
       return url;
     }
 
-    function processSizesMember(aIcon) {
+    function processSizesMember(icon) {
       const sizes = new Set(),
-        spec = {
+        obj = {
           objectName: 'icon',
-          object: aIcon,
+          object: icon,
           property: 'sizes',
-          expectedType: 'string',
-          trim: true
-        },
-        value = extractValue(spec, console);
+          expectedType: 'string'
+        };
+      let value = extractValue(obj);
+      value = (value) ? value.trim() : value;
       if (value) {
-        // Split on whitespace and filter out invalid values.
-        value.split(/\s+/)
-          .filter(isValidSizeValue)
-          .forEach(size => sizes.add(size));
+        //split on whitespace and filter out invalid values
+        let validSizes = value.split(/\s+/).filter(isValidSizeValue);
+        validSizes.forEach((size) => sizes.add(size));
       }
       return sizes;
-      // Implementation of HTML's link@size attribute checker.
-      function isValidSizeValue(aSize) {
-        const size = aSize.toLowerCase();
-        if (IconsProcessor.anyRegEx.test(aSize)) {
+
+      /*
+       * Implementation of HTML's link@size attribute checker
+       */
+      function isValidSizeValue(size) {
+        if (anyRegEx.test(size)) {
           return true;
         }
+        size = size.toLowerCase();
         if (!size.contains('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 = IconsProcessor.onlyDecimals.test(w + h);
-        return (validStarts && validDecimals);
+
+        //split left of x for width, after x for height
+        const width = size.substring(0, size.indexOf('x'));
+        const height = size.substring(size.indexOf('x') + 1, size.length);
+        const isValid = !(height.startsWith('0') || width.startsWith('0') || !onlyDecimals.test(width + height));
+        return isValid;
       }
     }
+  };
+
+  function processIconsMember(manifest, manifestURL) {
+    const iconsProcessor = new IconsProcessor();
+    return iconsProcessor.processIcons(manifest, manifestURL);
   }
-}
+
+  //Processing starts here!
+  let manifest = {};
+
+  try {
+    manifest = JSON.parse(jsonText);
+    if (typeof manifest !== 'object' || manifest === null) {
+      let msg = 'Manifest needs to be an object.';
+      issueDeveloperWarning(msg);
+      manifest = {};
+    }
+  } catch (e) {
+    issueDeveloperWarning(e);
+  }
+
+  const processedManifest = {
+    start_url: processStartURLMember(manifest, manifestURL, docURL),
+    display: processDisplayMember(manifest),
+    orientation: processOrientationMember(manifest),
+    name: processNameMember(manifest),
+    icons: processIconsMember(manifest, manifestURL),
+    short_name: processShortNameMember(manifest)
+  };
+  processedManifest.scope = processScopeMember(manifest, manifestURL, docURL, processedManifest.start_url);
+  return processedManifest;
+};
\ No newline at end of file
--- a/dom/manifest/test/common.js
+++ b/dom/manifest/test/common.js
@@ -11,10 +11,10 @@ const bsp = SpecialPowers.Cu.import('res
   lineTerminators = '\u000D\u000A\u2028\u2029',
   whiteSpace = `${seperators}${lineTerminators}`,
   typeTests = [1, null, {},
     [], false
   ],
   data = {
     jsonText: '{}',
     manifestURL: manifestURL,
-    docURL: docLocation
+    docLocation: docLocation
   };