Bug 1079453 - Implement the manifest processor part of W3C Manifest. r=ehsan
authorMarcos Caceres <mcaceres@mozilla.com>
Fri, 31 Oct 2014 11:24:47 -0700
changeset 213445 c3624a3708406844f3dbdc46a1a61937d162f197
parent 213444 a07e226542b536fbf7d9c779c5f0316bf169b2b9
child 213446 9e70fe4a4a98a0b37df18516759d302cd48c623c
push id27753
push userphilringnalda@gmail.com
push dateSun, 02 Nov 2014 16:27:30 +0000
treeherdermozilla-central@443853a35898 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs1079453
milestone36.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 1079453 - Implement the manifest processor part of W3C Manifest. r=ehsan * spec conforming implementation * test suite
dom/manifest/ManifestProcessor.jsm
dom/manifest/moz.build
dom/manifest/test/common.js
dom/manifest/test/mochitest.ini
dom/manifest/test/test_IconsProcessor_density.html
dom/manifest/test/test_IconsProcessor_sizes.html
dom/manifest/test/test_IconsProcessor_src.html
dom/manifest/test/test_IconsProcessor_type.html
dom/manifest/test/test_ManifestProcessor_JSON.html
dom/manifest/test/test_ManifestProcessor_display.html
dom/manifest/test/test_ManifestProcessor_icons.html
dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
dom/manifest/test/test_ManifestProcessor_orientation.html
dom/manifest/test/test_ManifestProcessor_start_url.html
dom/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -0,0 +1,323 @@
+/* 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);
+ *
+ * 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 issueDeveloperWarning (1086997).
+ */
+/*globals Components*/
+/*exported EXPORTED_SYMBOLS */
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['ManifestProcessor'];
+const {
+  utils: Cu,
+  classes: Cc,
+  interfaces: Ci
+} = Components;
+const imports = {};
+Cu.import('resource://gre/modules/Services.jsm', imports);
+Cu.importGlobalProperties(['URL']);
+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',
+  'landscape-secondary'
+]);
+
+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;
+
+    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;
+  }
+
+  function issueDeveloperWarning(msg) {
+    //https://bugzilla.mozilla.org/show_bug.cgi?id=1086997
+  }
+
+  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;
+  }
+
+  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 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;
+    }
+
+    try {
+      potentialResult = new URL(value, manifestURL);
+    } catch (e) {
+      issueDeveloperWarning('Invalid URL.');
+      return result;
+    }
+    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;
+
+    //Converts a URL to a Gecko URI
+    function makeURI(webURL) {
+      return imports.Services.io.newURI(webURL.toString(), null, null);
+    }
+  }
+
+  //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);
+
+    if (Array.isArray(value)) {
+      //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 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 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 processSrcMember(icon, baseURL) {
+      const obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'src',
+          expectedType: 'string'
+        },
+        value = extractValue(obj);
+      let url;
+      if (typeof value === 'string' && value.trim() !== '') {
+        try {
+          url = new URL(value, baseURL);
+        } catch (e) {}
+      }
+      return url;
+    }
+
+    function processSizesMember(icon) {
+      const sizes = new Set(),
+        obj = {
+          objectName: 'icon',
+          object: icon,
+          property: 'sizes',
+          expectedType: 'string'
+        };
+      let value = extractValue(obj);
+      value = (value) ? value.trim() : value;
+      if (value) {
+        //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(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 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)
+  };
+  return processedManifest;
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/manifest/moz.build
@@ -0,0 +1,12 @@
+# -*- 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 += [
+    'ManifestProcessor.jsm',
+]
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+# BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/common.js
@@ -0,0 +1,20 @@
+/**
+ * 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'),
+  docLocation = new URL('', document.location.origin),
+  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,
+    docLocation: docLocation
+  };
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/mochitest.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+skip-if = e10s
+
+support-files =
+	common.js
+
+[test_IconsProcessor_density.html]
+[test_IconsProcessor_sizes.html]
+[test_IconsProcessor_src.html]
+[test_IconsProcessor_type.html]
+[test_ManifestProcessor_display.html]
+[test_ManifestProcessor_icons.html]
+[test_ManifestProcessor_JSON.html]
+[test_ManifestProcessor_name_and_short_name.html]
+[test_ManifestProcessor_orientation.html]
+[test_ManifestProcessor_start_url.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_IconsProcessor_density.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * icon member's density member
+ * https://w3c.github.io/manifest/#density-member
+ **/
+'use strict';
+
+var testIcon = {
+  icons: [{
+    src: 'test',
+    density: undefined
+  }]
+};
+
+var iconDensityValueTests = [null, {},
+  [], false, '', -0, '-0', -1.0000, -123131132, -1.2e+200,
+  'Infinity', '-Infinity',
+  '-1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+  '1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+];
+
+iconDensityValueTests.forEach((density) => {
+  var expected = `Expect density to default to 1.0.`;
+  testIcon.icons[0].density = density;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  ise(result.icons[0].density, 1.0, expected);
+});
+
+testIcon = {
+  icons: [{
+    src: 'test',
+    density: undefined
+  }]
+};
+
+var parseFloatTests = [3.14, '3.14', `${whiteSpace}3.14${whiteSpace}`, 12e300];
+parseFloatTests.forEach((testNumber) => {
+  var expected = `Expect density to be ${parseFloat(testNumber)}.`;
+  testIcon.icons[0].density = testNumber;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  ise(result.icons[0].density, parseFloat(testNumber), expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_IconsProcessor_sizes.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * icon member's sizes property
+ * https://w3c.github.io/manifest/#sizes-member
+ **/
+'use strict';
+var validSizes = [{
+  test: '16x16',
+  expect: ['16x16']
+}, {
+  test: 'hello 16x16 16x16',
+  expect: ['16x16']
+}, {
+  test: '32x32 16 48x48 12',
+  expect: ['32x32', '48x48']
+}, {
+  test: `${whiteSpace}128x128${whiteSpace}512x512 8192x8192 32768x32768${whiteSpace}`,
+  expect: ['128x128', '512x512', '8192x8192', '32768x32768']
+}, {
+  test: 'any',
+  expect: ['any']
+}, {
+  test: 'Any',
+  expect: ['Any']
+}, {
+  test: '16x32',
+  expect: ['16x32']
+}, {
+  test: '17x33',
+  expect: ['17x33']
+}, {
+  test: '32x32 32x32',
+  expect: ['32x32']
+}, {
+  test: '32X32',
+  expect: ['32X32']
+}, {
+  test: 'any 32x32',
+  expect: ['any', '32x32']
+}];
+
+var testIcon = {
+  icons: [{
+    src: 'test',
+    sizes: undefined
+  }]
+};
+
+validSizes.forEach(({
+  test, expect
+}) => {
+  testIcon.icons[0].sizes = test;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  var sizes = result.icons[0].sizes;
+  for (var expectedSize of expect) {
+    var expected = `Expect sizes to contain ${expectedSize}`;
+    ise(sizes.has(expectedSize), true, expected);
+  }
+  expected = `Expect the size of the set to be ${expect.length}.`;
+  ise(sizes.size, expect.length, expected);
+});
+
+var testIcon = {
+  icons: [{
+    src: 'test',
+    sizes: undefined
+  }]
+};
+
+var invalidSizes = ['invalid', '', ' ', '16 x 16', '32', '21', '16xx16', '16 x x 6'];
+invalidSizes.forEach((invalidSize) => {
+  var expected = 'Expect invalid sizes to return an empty set.';
+  testIcon.icons[0].sizes = invalidSize;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  var sizes = result.icons[0].sizes;
+  ise(sizes.size, 0, expected);
+});
+
+typeTests.forEach((type) => {
+  var expected = `Expect non-string sizes to be empty set: ${typeof type}.`;
+  testIcon.icons[0].sizes = type;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  var sizes = result.icons[0].sizes;
+  ise(sizes.size, 0, expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_IconsProcessor_src.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * icon member's src member
+ * https://w3c.github.io/manifest/#src-member
+ **/
+'use strict';
+var noSrc = {
+  icons: [{}, {
+    src: []
+  }, {
+    src: {}
+  }, {
+    src: null
+  }, {
+    density: '1'
+  }, {
+    type: 'image/jpg'
+  }, {
+    sizes: '1x1,2x2'
+  }, {
+    density: '1',
+    sizes: 'any',
+    type: 'image/jpg'
+  }]
+};
+
+var expected = `Expect icons without a src prop to be filtered out.`;
+data.jsonText = JSON.stringify(noSrc);
+var result = processor.process(data);
+ise(result.icons.length, 0, expected);
+
+var invalidSrc = {
+  icons: [{
+    src: null
+  }, {
+    src: 1
+  }, {
+    src: []
+  }, {
+    src: {}
+  }, {
+    src: true
+  }, {
+    src: ''
+  }]
+};
+
+var expected = `Expect icons with invalid src prop to be filtered out.`;
+data.jsonText = JSON.stringify(noSrc);
+var result = processor.process(data);
+ise(result.icons.length, 0, expected);
+
+var expected = `Expect icon's src to be an instance of URL.`;
+var withSrc = {
+  icons: [{
+    src: 'pass'
+  }]
+};
+data.jsonText = JSON.stringify(withSrc);
+var result = processor.process(data);
+ise(SpecialPowers.unwrap(result.icons[0].src) instanceof URL, true, expected);
+
+var expected = `Expect only icons with a src prop to be kept.`;
+var withSrc = {
+  icons: [{
+    src: 'pass'
+  }, {
+    src: 'pass',
+    density: 1
+  }, {}, {
+    foo: 'foo'
+  }]
+};
+data.jsonText = JSON.stringify(withSrc);
+var result = processor.process(data);
+ise(result.icons.length, 2, expected);
+
+var expectedURL = new URL('pass', manifestURL);
+for (var icon of result.icons) {
+  var expected = `Expect src prop to be ${expectedURL.toString()}`;
+  ise(icon.src.toString(), expectedURL.toString(), expected);
+}
+
+//Resolve URLs relative to manfiest
+var URLs = ['path', '/path', '../../path'];
+
+URLs.forEach((url) => {
+  var expected = `Resolve icon src URLs relative to manifest.`;
+  data.jsonText = JSON.stringify({
+    icons: [{
+      src: url
+    }]
+  });
+  var absURL = new URL(url, manifestURL).toString();
+  var result = processor.process(data);
+  ise(result.icons[0].src.toString(), absURL, expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_IconsProcessor_type.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * icon member's type property
+ * https://w3c.github.io/manifest/#type-member
+ **/
+
+'use strict';
+var testIcon = {
+  icons: [{
+    src: 'test',
+    type: undefined
+  }]
+};
+
+var invalidMimeTypes = ['application / text', 'test;test', ';test?test', 'application\\text'];
+invalidMimeTypes.forEach((invalidMime) => {
+  var expected = `Expect invalid mime to be treated like undefined.`;
+  testIcon.icons[0].type = invalidMime;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  ise(result.icons[0].type, undefined, expected);
+});
+
+var validTypes = [
+  'image/jpeg',
+  'IMAGE/jPeG',
+  `${whiteSpace}image/jpeg${whiteSpace}`,
+  'image/JPEG; whatever=something',
+  'image/JPEG;whatever'
+];
+
+validTypes.forEach((validMime) => {
+  var expected = `Expect valid mime to be parsed to : image/jpeg.`;
+  testIcon.icons[0].type = validMime;
+  data.jsonText = JSON.stringify(testIcon);
+  var result = processor.process(data);
+  ise(result.icons[0].type, 'image/jpeg', expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_JSON.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * JSON parsing/processing tests
+ * https://w3c.github.io/manifest/#processing
+ **/
+'use strict';
+var invalidJson = ['', ` \t \n ${whiteSpace} `, '{', '{[[}'];
+invalidJson.forEach((testString) => {
+  var expected = `Expect to recover from invalid JSON: ${testString}`;
+  data.jsonText = testString;
+  var result = processor.process(data);
+  SimpleTest.ok(result.start_url.href === docLocation.href, true, expected);
+});
+
+var validButUnhelpful = ["1", 1, "", "[{}]", "null"];
+validButUnhelpful.forEach((testString) => {
+  var expected = `Expect to recover from invalid JSON: ${testString}`;
+  data.jsonText = testString;
+  var result = processor.process(data);
+  SimpleTest.ok(result.start_url.href === docLocation.href, true, expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_display.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * display member
+ * https://w3c.github.io/manifest/#display-member
+ **/
+'use strict';
+//Type checks
+typeTests.forEach((type) => {
+  var expected = `Expect non - string display to default to "browser".`;
+  data.jsonText = JSON.stringify({
+    display: type
+  });
+  var result = processor.process(data);
+  ise(result.display, 'browser', expected);
+});
+
+/*Test valid modes*/
+var validModes = ['fullscreen', 'standalone', 'minimal-ui', 'browser']
+validModes.forEach((mode) => {
+  var expected = `Expect display mode to be ${mode}.`;
+  data.jsonText = JSON.stringify({
+    display: mode
+  });
+  var result = processor.process(data);
+  ise(result.display, mode, expected);
+});
+
+//trim tests
+validModes.forEach((display) => {
+  var expected = `Expect trimmed display mode to be returned.`;
+  var expandedDisplay =  seperators + lineTerminators + display + lineTerminators + seperators;
+  data.jsonText = JSON.stringify({
+    display: expandedDisplay
+  });
+  var result = processor.process(data);
+  ise(result.display, display, expected);
+});
+
+//Unknown modes
+var invalidModes = ['foo', `fooo${whiteSpace}`, '', 'fullscreen,standalone', 'standalone fullscreen', 'FULLSCreEN'];
+invalidModes.forEach((invalidMode) => {
+  var expected = `Expect default display mode "browser" to be returned: '${invalidMode}'`;
+  data.jsonText = JSON.stringify({
+    display: invalidMode
+  });
+  var result = processor.process(data);
+  ise(result.display, 'browser', expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_icons.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * Manifest icons member
+ * https://w3c.github.io/manifest/#icons-member
+ **/
+
+'use strict';
+
+typeTests.forEach((type) => {
+  var expected = `Expect non-array icons to be empty array: ${typeof type}.`;
+  data.jsonText = JSON.stringify({
+    icons: type
+  });
+  var result = processor.process(data);
+  var y = SpecialPowers.unwrap(result.icons);
+  ise(result.icons.length, 0, expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_name_and_short_name.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * name and short_name members
+ * https://w3c.github.io/manifest/#name-member
+ * https://w3c.github.io/manifest/#short_name-member
+ **/
+
+'use strict';
+
+var trimNamesTests = [
+  `${seperators}pass${seperators}`,
+  `${lineTerminators}pass${lineTerminators}`,
+  `${whiteSpace}pass${whiteSpace}`,
+  //BOM
+  `\uFEFFpass\uFEFF`
+];
+var props = ['name', 'short_name'];
+
+props.forEach((prop) => {
+  trimNamesTests.forEach((trimmableString) => {
+    var assetion = `Expecting ${prop} to be trimmed.`;
+    var obj = {};
+    obj[prop] = trimmableString;
+    data.jsonText = JSON.stringify(obj);
+    var result = processor.process(data);
+    ise(result[prop], 'pass', assetion);
+  });
+});
+
+/*
+ * If the object is not a string, it becomes undefined
+ */
+props.forEach((prop) => {
+  typeTests.forEach((type) => {
+    var expected = `Expect non - string ${prop} to be undefined: ${typeof type}`;
+    var obj = {};
+    obj[prop] = type;
+    data.jsonText = JSON.stringify(obj);
+    var result = processor.process(data);
+    SimpleTest.ok(result[prop] === undefined, true, expected);
+  });
+});
+
+/**
+ * acceptable names - including long names
+ */
+var acceptableNames = [
+  'pass',
+  `pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+   pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+   pass pass pass pass pass pass pass pass pass pass pass pass pass pass
+   pass pass pass pass pass pass pass pass pass pass pass pass`,
+  'これは許容できる名前です',
+  'ນີ້ແມ່ນຊື່ທີ່ຍອມຮັບໄດ້'
+];
+
+props.forEach((prop) => {
+  acceptableNames.forEach((name) => {
+    var expected = `Expecting name to be acceptable : ${name}`;
+    var obj = {};
+    obj[prop] = name;
+    data.jsonText = JSON.stringify(obj);
+    var result = processor.process(data);
+    ise(result[prop], name, expected);
+  });
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_orientation.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * orientation member
+ * https://w3c.github.io/manifest/#orientation-member
+ **/
+'use strict';
+
+typeTests.forEach((type) => {
+  var expected = `Expect non-string orientation to be empty string : ${typeof type}.`;
+  data.jsonText = JSON.stringify({
+    orientation: type
+  });
+  var result = processor.process(data);
+  ise(result.orientation, '', expected);
+});
+
+
+var validOrientations = [
+  'any',
+  'natural',
+  'landscape',
+  'portrait',
+  'portrait-primary',
+  'portrait-secondary',
+  'landscape-primary',
+  'landscape-secondary'
+];
+
+validOrientations.forEach((orientation) => {
+  var expected = `Expect orientation to be returned: ${orientation}.`;
+  data.jsonText = JSON.stringify({
+    orientation: orientation
+  });
+  var result = processor.process(data);
+  ise(result.orientation, orientation, expected);
+});
+
+var invalidOrientations = [
+  'all',
+  'ANY',
+  'NaTuRal',
+  'portrait-primary portrait-secondary',
+  'portrait-primary,portrait-secondary',
+  'any-natural',
+  'portrait-landscape',
+  'primary-portrait',
+  'secondary-portrait',
+  'landscape-landscape',
+  'secondary-primary'
+]
+
+invalidOrientations.forEach((orientation) => {
+  var expected = `Expect orientation to be empty string: ${orientation}.`;
+  data.jsonText = JSON.stringify({
+    orientation: orientation
+  });
+  var result = processor.process(data);
+  ise(result.orientation, "", expected);
+});
+
+//Trim tests
+validOrientations.forEach((orientation) => {
+  var expected = `Expect trimmed orientation to be returned.`;
+  var expandedOrientation = `${seperators}${lineTerminators}${orientation}${lineTerminators}${seperators}`;
+  data.jsonText = JSON.stringify({
+    orientation: expandedOrientation
+  });
+  var result = processor.process(data);
+  ise(result.orientation, orientation, expected);
+});
+  </script>
+</head>
new file mode 100644
--- /dev/null
+++ b/dom/manifest/test/test_ManifestProcessor_start_url.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1079453
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1079453</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script src="common.js"></script>
+  <script>
+/**
+ * Manifest start_url
+ * https://w3c.github.io/manifest/#start_url-member
+ **/
+'use strict';
+typeTests.forEach((type) => {
+  var expected = `Expect non - string start_url to be doc's url: ${typeof type}.`;
+  data.jsonText = JSON.stringify({
+    start_url: type
+  });
+  var result = processor.process(data);
+  ise(result.start_url.toString(), docLocation.toString(), expected);
+});
+
+//Not same origin
+var expected = `Expect different origin URLs to become document's URL.`;
+data.jsonText = JSON.stringify({
+  start_url: 'http://not-same-origin'
+});
+var result = processor.process(data);
+ise(result.start_url.toString(), docLocation.toString(), expected);
+
+//Empty string test
+var expected = `Expect empty string for start_url to become document's URL.`;
+data.jsonText = JSON.stringify({
+  start_url: ''
+});
+var result = processor.process(data);
+ise(result.start_url.toString(), docLocation.toString(), expected);
+
+//Resolve URLs relative to manfiest
+var URLs = ['path', '/path', '../../path',
+  `${whiteSpace}path${whiteSpace}`,
+  `${whiteSpace}/path`,
+  `${whiteSpace}../../path`
+];
+URLs.forEach((url) => {
+  var expected = `Resolve URLs relative to manifest.`;
+  data.jsonText = JSON.stringify({
+    start_url: url
+  });
+  var absURL = new URL(url, manifestURL).toString();
+  var result = processor.process(data);
+  ise(result.start_url.toString(), absURL, expected);
+});
+  </script>
+</head>
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -97,16 +97,17 @@ DIRS += [
     'voicemail',
     'inputmethod',
     'webidl',
     'xbl',
     'xml',
     'xslt',
     'xul',
     'resourcestats',
+    'manifest',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['plugins/ipc/hangui']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += [
         'speakermanager',