Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 11 Mar 2014 13:23:55 -0700
changeset 191342 1fe2de4278663300966d3297951bc3a48a21fe87
parent 191341 ac800841c8532e6e1016965c759b4fc4404d4cb3 (current diff)
parent 191269 23005b395ae8e5589b0a37778747fc35be58a2ff (diff)
child 191343 f25fb5db8893f3dec09a66c00ded2d546356335b
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.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
Merge m-c to fx-team.
content/media/apple/Makefile.in
content/media/omx/Makefile.in
content/media/omx/MediaOmxStateMachine.h
dom/events/nsContentEventHandler.cpp
dom/events/nsContentEventHandler.h
layout/svg/Makefile.in
media/omx-plugin/lib/gb/libstagefright_color_conversion/Makefile.in
netwerk/cache2/CacheEntriesEnumerator.cpp
netwerk/cache2/CacheEntriesEnumerator.h
widget/gonk/libdisplay/Makefile.in
--- a/accessible/tests/mochitest/jsat/a11y.ini
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -1,15 +1,18 @@
 [DEFAULT]
 support-files =
+  dom_helper.js
+  gestures.json
   jsatcommon.js
   output.js
   doc_traversal.html
   doc_content_integration.html
 
 [test_alive.html]
 [test_content_integration.html]
 [test_explicit_names.html]
 [test_landmarks.html]
 [test_live_regions.html]
 [test_output.html]
 [test_tables.html]
+[test_touch_adapter.html]
 [test_traversal.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/dom_helper.js
@@ -0,0 +1,157 @@
+'use strict';
+
+/*global getMainChromeWindow, getBoundsForDOMElm, AccessFuTest, Point*/
+/* exported loadJSON*/
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+Cu.import('resource://gre/modules/Geometry.jsm');
+
+var win = getMainChromeWindow(window);
+
+var winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
+  Ci.nsIDOMWindowUtils);
+
+/**
+ * For a given list of points calculate their coordinates in relation to the
+ * document body.
+ * @param  {Array} aTouchPoints An array of objects of the following format: {
+ *   base: {String}, // Id of an element to server as a base for the touch.
+ *   x: {Number},    // An optional x offset from the base element's geometric
+ *                   // centre.
+ *   y: {Number}     // An optional y offset from the base element's geometric
+ *                   // centre.
+ * }
+ * @return {JSON} An array of {x, y} coordinations.
+ */
+function calculateTouchListCoordinates(aTouchPoints) {
+  var coords = [];
+  var dpi = winUtils.displayDPI;
+  for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) {
+    var bounds = getBoundsForDOMElm(target.base);
+    var parentBounds = getBoundsForDOMElm('root');
+    var point = new Point(target.x || 0, target.y || 0);
+    point.scale(dpi);
+    point.add(bounds[0], bounds[1]);
+    point.add(bounds[2] / 2, bounds[3] / 2);
+    point.subtract(parentBounds[0], parentBounds[0]);
+    coords.push({
+      x: point.x,
+      y: point.y
+    });
+  }
+  return coords;
+}
+
+/**
+ * Send a touch event with specified touchPoints.
+ * @param  {Array} aTouchPoints An array of points to be associated with
+ * touches.
+ * @param  {String} aName A name of the touch event.
+ */
+function sendTouchEvent(aTouchPoints, aName) {
+  var touchList = sendTouchEvent.touchList;
+  if (aName === 'touchend') {
+    sendTouchEvent.touchList = null;
+  } else {
+    var coords = calculateTouchListCoordinates(aTouchPoints);
+    var touches = [];
+    for (var i = 0; i < coords.length; ++i) {
+      var {x, y} = coords[i];
+      var node = document.elementFromPoint(x, y);
+      var touch = document.createTouch(window, node, aName === 'touchstart' ?
+        1 : touchList.item(i).identifier, x, y, x, y);
+      touches.push(touch);
+    }
+    touchList = document.createTouchList(touches);
+    sendTouchEvent.touchList = touchList;
+  }
+  var evt = document.createEvent('TouchEvent');
+  evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false,
+    touchList, touchList, touchList);
+  document.dispatchEvent(evt);
+}
+
+sendTouchEvent.touchList = null;
+
+/**
+ * A map of event names to the functions that actually send them.
+ * @type {Object}
+ */
+var eventMap = {
+  touchstart: sendTouchEvent,
+  touchend: sendTouchEvent,
+  touchmove: sendTouchEvent
+};
+
+/**
+ * Attach a listener for the mozAccessFuGesture event that tests its
+ * type.
+ * @param  {Array} aExpectedGestures A stack of expected event types.
+ * Note: the listener is removed once the stack reaches 0.
+ */
+function testMozAccessFuGesture(aExpectedGestures) {
+  var types = typeof aExpectedGestures === "string" ?
+    [aExpectedGestures] : aExpectedGestures;
+  function handleGesture(aEvent) {
+    if (aEvent.detail.type !== types[0]) {
+      // The is not the event of interest.
+      return;
+    }
+    ok(true, 'Received correct mozAccessFuGesture: ' + types.shift() + '.');
+    if (types.length === 0) {
+      win.removeEventListener('mozAccessFuGesture', handleGesture);
+      if (AccessFuTest.sequenceCleanup) {
+        AccessFuTest.sequenceCleanup();
+      }
+      AccessFuTest.nextTest();
+    }
+  }
+  win.addEventListener('mozAccessFuGesture', handleGesture);
+}
+
+/**
+ * An extention to AccessFuTest that adds an ability to test a sequence of
+ * touch/mouse/etc events and their expected mozAccessFuGesture events.
+ * @param {Object} aSequence An object that has a list of touch/mouse/etc events
+ * to be generated and the expected mozAccessFuGesture events.
+ */
+AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
+  AccessFuTest.addFunc(function testSequence() {
+    testMozAccessFuGesture(aSequence.expectedGestures);
+    var events = aSequence.events;
+    function fireEvent(aEvent) {
+      eventMap[aEvent.type](aEvent.target, aEvent.type);
+      processEvents();
+    }
+    function processEvents() {
+      if (events.length === 0) {
+        return;
+      }
+      var event = events.shift();
+      if (event.delay) {
+        window.setTimeout(fireEvent, event.delay, event);
+      } else {
+        fireEvent(event);
+      }
+    }
+    processEvents();
+  });
+};
+
+/**
+ * A helper function that loads JSON files.
+ * @param {String} aPath A path to a JSON file.
+ * @param {Function} aCallback A callback to be called on success.
+ */
+function loadJSON(aPath, aCallback) {
+  var request = new XMLHttpRequest();
+  request.open('GET', aPath, true);
+  request.responseType = 'json';
+  request.onload = function onload() {
+    aCallback(request.response);
+  };
+  request.send();
+}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/gestures.json
@@ -0,0 +1,194 @@
+[
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 50}
+    ],
+    "expectedGestures": "tap"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "tap"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["dwell", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": 0.03, "y": 0.02}], "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": -0.03, "y": 0.01}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["tap", "doubletap"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["tap", "doubletap", "tripletap"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["tap", "doubletap", "doubletaphold", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchend", "delay": 25},
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 525}
+    ],
+    "expectedGestures": ["tap", "taphold", "dwellend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0.5, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeleft"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0.5}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipedown"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeup"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0.1}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.5, "y": 0.1}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": -0.05}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeleft"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0}]},
+      {"type": "touchmove",
+        "target": [{"base": "button", "x": -0.1, "y": 0.5}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipedown"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.1, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swipeup"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
+        {"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
+        {"base": "button", "x": 0.5, "y": 0.5}], "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart", "target": [{"base": "button", "x": 0, "y": 0},
+        {"base": "button", "x": 0, "y": 0.5},
+        {"base": "button", "x": 0, "y": 1}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0},
+        {"base": "button", "x": 0.5, "y": 0.5},
+        {"base": "button", "x": 0.5, "y": 1}],
+        "delay": 25},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": "swiperight"
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0.6, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 525},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["dwell", "explore", "exploreend"]
+  },
+  {
+    "events": [
+      {"type": "touchstart",
+        "target": [{"base": "button", "x": 0, "y": 0.5}]},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0, "y": 0}],
+        "delay": 525},
+      {"type": "touchmove", "target": [{"base": "button", "x": 0.5, "y": 0}],
+        "delay": 125},
+      {"type": "touchend", "delay": 25}
+    ],
+    "expectedGestures": ["dwell", "explore", "explore", "exploreend"]
+  }
+]
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -1,10 +1,15 @@
 // A common module to run tests on the AccessFu module
 
+'use strict';
+
+/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger,
+  AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/
+
 /**
   * A global variable holding an array of test functions.
   */
 var gTestFuncs = [];
 /**
   * A global Iterator for the array of test functions.
   */
 var gIterator;
@@ -112,22 +117,22 @@ var AccessFuTest = {
       return;
     }
     testFunc();
   },
 
   runTests: function AccessFuTest_runTests() {
     if (gTestFuncs.length === 0) {
       ok(false, "No tests specified!");
-      simpleTest.finish();
+      SimpleTest.finish();
       return;
     }
 
     // Create an Iterator for gTestFuncs array.
-    gIterator = Iterator(gTestFuncs);
+    gIterator = Iterator(gTestFuncs); // jshint ignore:line
 
     // Start AccessFu and put it in stand-by.
     Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm");
 
     AccessFu.attach(getMainChromeWindow(window));
 
     AccessFu.readyCallback = function readyCallback() {
       // Enable logging to the console service.
@@ -139,17 +144,17 @@ var AccessFuTest = {
       'set': [['accessibility.accessfu.notify_output', 1],
               ['dom.mozSettings.enabled', true]]
     }, function () {
       if (AccessFuTest._waitForExplicitFinish) {
         // Run all test functions asynchronously.
         AccessFuTest.nextTest();
       } else {
         // Run all test functions synchronously.
-        [testFunc() for (testFunc of gTestFuncs)];
+        [testFunc() for (testFunc of gTestFuncs)]; // jshint ignore:line
         AccessFuTest.finish();
       }
     });
   }
 };
 
 function AccessFuContentTest(aFuncResultPairs) {
   this.queue = aFuncResultPairs;
@@ -194,17 +199,17 @@ AccessFuContentTest.prototype = {
           } else {
             elem.focus();
           }
         }
       });
     }
 
     aMessageManager.addMessageListener('AccessFu:Present', this);
-    aMessageManager.addMessageListener('AccessFu:Ready', function (aMessage) {
+    aMessageManager.addMessageListener('AccessFu:Ready', function () {
       aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
       aMessageManager.sendAsyncMessage('AccessFu:Start', { buildApp: 'browser' });
     });
 
     aMessageManager.loadFrameScript(
       'chrome://global/content/accessibility/content-script.js', false);
     aMessageManager.loadFrameScript(
       'data:,(' + contentScript.toString() + ')();', false);
@@ -221,17 +226,17 @@ AccessFuContentTest.prototype = {
          this.currentPair[0].json);
       }
 
       if (!this.currentPair[1]) {
        this.pump();
      }
     } else if (this.finishedCallback) {
       for (var mm of this.mms) {
-       mm.sendAsyncMessage('AccessFu:Stop');
+        mm.sendAsyncMessage('AccessFu:Stop');
       }
       this.finishedCallback();
     }
   },
 
   receiveMessage: function(aMessage) {
     if (!this.currentPair) {
       return;
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_touch_adapter.html
@@ -0,0 +1,66 @@
+<html>
+
+<head>
+  <title>AccessFu tests for touch adapters.</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="../common.js"></script>
+  <script type="application/javascript" src="../layout.js"></script>
+  <script type="application/javascript" src="./jsatcommon.js"></script>
+  <script type="application/javascript" src="./dom_helper.js"></script>
+  <script type="application/javascript">
+
+    Components.utils.import(
+      "resource://gre/modules/accessibility/TouchAdapter.jsm");
+
+    function startComponents() {
+      TouchAdapter.start();
+      AccessFuTest.nextTest();
+    }
+
+    /**
+     * Clean up all state data related to previous events/gestures.
+     */
+    function cleanupTouchAdapter() {
+      TouchAdapter.cleanupTouches();
+      TouchAdapter._touchPoints = {};
+      TouchAdapter._dwellTimeout = 0;
+      TouchAdapter._prevGestures = {};
+      TouchAdapter._lastExploreTime = 0;
+    }
+
+    function stopComponents() {
+      TouchAdapter.stop();
+      AccessFuTest.finish();
+    }
+
+    function doTest() {
+      loadJSON("./gestures.json", function onSuccess(gestures) {
+        AccessFuTest.addFunc(startComponents);
+        AccessFuTest.sequenceCleanup = cleanupTouchAdapter;
+        gestures.forEach(AccessFuTest.addSequence);
+        AccessFuTest.addFunc(stopComponents);
+        AccessFuTest.waitForExplicitFinish();
+        AccessFuTest.runTests();
+      });
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+<body id="root">
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082"
+     title="[AccessFu] Provide tests for touch adapter.">
+    Mozilla Bug 976082
+  </a>
+  <div>
+    <button id="button">I am a button</button>
+  </div>
+</body>
+</html>
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -53,17 +53,17 @@ function Buffer(subject, encoding /*, bu
         else
           throw new Error('Could not instantiate buffer');
       }
       break;
     case 'string':
       // If string encode it and use buffer for the returned Uint8Array
       // to create a local patched version that acts like node buffer.
       encoding = encoding || 'utf8';
-      return Uint8Array(TextEncoder(encoding).encode(subject).buffer);
+      return Uint8Array(new TextEncoder(encoding).encode(subject).buffer);
     case 'object':
       // This form of the constructor uses the form of
       // Uint8Array(buffer, offset, length);
       // So we can instantiate a typed array within the constructor
       // to inherit the appropriate properties, where both the
       // `subject` and newly instantiated buffer share the same underlying
       // data structure.
       if (arguments.length === 3)
@@ -79,28 +79,28 @@ exports.Buffer = Buffer;
 
 // Tests if `value` is a Buffer.
 Buffer.isBuffer = value => value instanceof Buffer
 
 // Returns true if the encoding is a valid encoding argument & false otherwise
 Buffer.isEncoding = function (encoding) {
   if (!encoding) return false;
   try {
-    TextDecoder(encoding);
+    new TextDecoder(encoding);
   } catch(e) {
     return false;
   }
   return true;
 }
 
 // Gives the actual byte length of a string. encoding defaults to 'utf8'.
 // This is not the same as String.prototype.length since that returns the
 // number of characters in a string.
 Buffer.byteLength = (value, encoding = 'utf8') =>
-  TextEncoder(encoding).encode(value).byteLength
+  new TextEncoder(encoding).encode(value).byteLength
 
 // Direct copy of the nodejs's buffer implementation:
 // https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
 Buffer.concat = function(list, length) {
   if (!Array.isArray(list))
     throw new TypeError('Usage: Buffer.concat(list[, length])');
 
   if (typeof length === 'undefined') {
@@ -151,17 +151,17 @@ Object.defineProperties(Buffer.prototype
       return view;
     }
   },
   toString: {
     value: function(encoding, start, end) {
       encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';
       start = Math.max(0, ~~start);
       end = Math.min(this.length, end === void(0) ? this.length : ~~end);
-      return TextDecoder(encoding).decode(this.subarray(start, end));
+      return new TextDecoder(encoding).decode(this.subarray(start, end));
     }
   },
   toJSON: {
     value: function() {
       return { type: 'Buffer', data: Array.slice(this, 0) };
     }
   },
   get: {
@@ -257,17 +257,17 @@ Object.defineProperties(Buffer.prototype
 
       offset = ~~offset;
 
       // Clamp length if it would overflow buffer, or if its
       // undefined
       if (length == null || length + offset > this.length)
         length = this.length - offset;
 
-      let buffer = TextEncoder(encoding).encode(string);
+      let buffer = new TextEncoder(encoding).encode(string);
       let result = Math.min(buffer.length, length);
       if (buffer.length !== length)
         buffer = buffer.subarray(0, length);
 
       Uint8Array.set(this, buffer, offset);
       return result;
     }
   },
--- a/addon-sdk/source/test/fixtures/chrome-worker/xhr.js
+++ b/addon-sdk/source/test/fixtures/chrome-worker/xhr.js
@@ -1,11 +1,11 @@
 /* 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/. */
 'use strict';
 
-let xhr = XMLHttpRequest();
+let xhr = new XMLHttpRequest();
 xhr.open("GET", "data:text/plain,ok", true);
 xhr.onload = function () {
   postMessage(xhr.responseText);
 };
 xhr.send(null);
--- a/b2g/app/ua-update.json.in
+++ b/b2g/app/ua-update.json.in
@@ -16,18 +16,16 @@
   // bug 826353, itau.com.br
   "itau.com.br": "\\(Mobile#(Android; Mobile",
   // bug 826504, orkut.com.br
   "orkut.com.br": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 826510, r7.com
   "r7.com": "\\(Mobile#(Android; Mobile",
   // bug 826514, estadao.com.br
   "estadao.com.br": "\\(Mobile#(Android; Mobile",
-  // bug 826517, letras.mus.br
-  "letras.mus.br": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 826711, bb.com.br
   "bb.com.br": "\\(Mobile#(Android; Mobile",
   // bug 826712, orkut.com
   "orkut.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 826715, noticias.uol.com.br
   "noticias.uol.com.br": "\\(Mobile#(Android; Mobile",
   // bug 826845, techtudo.com.br
   "techtudo.com.br": "\\(Mobile#(Android; Mobile",
@@ -36,24 +34,20 @@
   // bug 827622, bing.com
   "bing.com": "\\(Mobile#(Android; Mobile",
   // bug 827626, magazineluiza.com.br
   "magazineluiza.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827628, groupon.com.br
   "groupon.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827630, vagalume.com.br
   "vagalume.com.br": "\\(Mobile#(Android; Mobile",
-  // bug 827631, climatempo.com.br
-  "climatempo.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827632, tecmundo.com.br
   "tecmundo.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827633, hao123.com
   "hao123.com": "\\(Mobile#(Android; Mobile",
-  // bug 827576, lancenet.com.br
-  "lancenet.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827573, webmotors.com.br
   "webmotors.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827661, mercadolibre.com.co
   "mercadolibre.com.co": "\\(Mobile#(Android; Mobile",
   // bug 827670, elpais.com.co
   "elpais.com.co": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 827674, avianca.com
   "avianca.com": "\\(Mobile#(Android; Mobile",
@@ -74,86 +68,64 @@
   // bug 828380, deser.pl
   "deser.pl": "\\(Mobile#(Android; Mobile",
   // bug 828386, ebay.es
   "ebay.es": "\\(Mobile#(Android; Mobile",
   // bug 828392, infojobs.net
   "infojobs.net": "\\(Mobile#(Android; Mobile",
   // bug 828399, antena3.com
   "antena3.com": "\\(Mobile#(Android; Mobile",
-  // bug 828403, fotocasa.es
-  "fotocasa.es": "\\(Mobile#(Android; Mobile",
   // bug 828406, orange.es
   "orange.es": "\\(Mobile#(Android; Mobile",
   // bug 828416, loteriasyapuestas.es
   "loteriasyapuestas.es": "\\(Mobile#(Android; Mobile",
   // bug 828418, bbva.es
   "bbva.es": "\\(Mobile#(Android; Mobile",
   // bug 828422, publico.es
   "publico.es": "\\(Mobile#(Android; Mobile",
   // bug 828425, mercadolibre.com.ve
   "mercadolibre.com.ve": "\\(Mobile#(Android; Mobile",
   // bug 828439, movistar.com.ve
   "movistar.com.ve": "\\(Mobile#(Android; Mobile",
-  // bug 828445, bumeran.com.ve
-  "bumeran.com.ve": "\\(Mobile#(Android; Mobile",
-  // bug 843114, einforma.com
-  "einforma.com": "\\(Mobile#(Android; Mobile",
   // bug 843126, es.playstation.com
   "es.playstation.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 843129, 11870.com
-  "11870.com": "\\(Mobile#(Android; Mobile",
-  // bug 843200, iphonejuegosgratis.com
   "iphonejuegosgratis.com": "\\(Mobile#(Android; Mobile",
   // bug 843132, comunio.es
   "comunio.es": "\\(Mobile#(Android; Mobile",
   // bug 843151, citibank.com
   "citibank.com": "\\(Mobile#(Android; Mobile",
   // bug 843153, games.com
   "games.com": "\\(Mobile#(Android; Mobile",
   // bug 843160, ehow.com
   "ehow.com": "\\(Mobile#(Android; Mobile",
-  // bug 843165, virginatlantic.com
-  "virginatlantic.com": "\\(Mobile#(Android; Mobile",
-  // bug 843172, zimbio.com
-  "zimbio.com": "\\(Mobile#(Android; Mobile",
-  // bug 843181, slashgear.com
-  "slashgear.com": "\\(Mobile#(Android; Mobile",
   // bug 843186, chevrolet.com
   "chevrolet.com": "\\(Mobile#(Android; Mobile",
   // bug 866577, 3g.qq.com
   "3g.qq.com": "\\(Mobile#(Android; Mobile",
-  // bug 878222, arukereso.hu
-  "arukereso.hu": "\\(Mobile#(Android; Mobile",
   // bug 878228, blikk.hu
   "blikk.hu": "\\(Mobile#(Android; Mobile",
   // bug 878230, citromail.hu
   "citromail.hu": "\\(Mobile#(Android; Mobile",
   // bug 878232, hazipatika.com
   "hazipatika.com": "\\(Mobile#(Android; Mobile",
-  // bug 878234, hvg.hu
-  "hvg.hu": "\\(Mobile#(Android; Mobile",
   // bug 878238, koponyeg.hu
   "koponyeg.hu": "\\(Mobile#(Android; Mobile",
   // bug 878240, kuruc.info
   "kuruc.info": "\\(Mobile#(Android; Mobile",
   // bug 878242, nemzetisport.hu
   "nemzetisport.hu": "\\(Mobile#(Android; Mobile",
-  // bug 878244, nlcafe.hu
-  "nlcafe.hu": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 878246, port.hu
   "port.hu": "\\(Mobile#(Android; Mobile",
   // bug 878249, portfolio.hu
   "portfolio.hu": "\\(Mobile#(Android; Mobile",
   // bug 878253, vatera.hu
   "vatera.hu": "\\(Mobile#(Android; Mobile",
   // bug 878255, 24sata.hr
   "24sata.hr": "\\(Mobile#(Android; Mobile",
-  // bug 878258, blackhatteam.com
-  "blackhatteam.com": "\\(Mobile#(Android; Mobile",
   // bug 878260, cdm.me
   "cdm.me": "\\(Mobile#(Android; Mobile",
   // bug 878262, download.com
   "download.com": "\\(Mobile#(Android; Mobile",
   // bug 878264, haber.ba
   "haber.ba": "\\(Mobile#(Android; Mobile",
   // bug 878271, kurir-info.rs
   "kurir-info.rs": "\\(Mobile#(Android; Mobile",
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -660,17 +660,18 @@ let settingsToObserve = {
   'wap.UAProf.url': '',
   'wap.UAProf.tagname': 'x-wap-profile',
   'devtools.eventlooplag.threshold': 100,
   'privacy.donottrackheader.enabled': false,
   'apz.force-enable': {
     prefName: 'dom.browser_frames.useAsyncPanZoom',
     defaultValue: false
   },
-  'layers.enable-tiles': false,
+  'layers.enable-tiles': true,
+  'layers.simple-tiles': false,
   'layers.progressive-paint': false,
   'layers.draw-tile-borders': false,
   'layers.dump': false,
   'debug.fps.enabled': {
     prefName: 'layers.acceleration.draw-fps',
     defaultValue: false
   },
   'debug.paint-flashing.enabled': {
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "c5bd933fe99317a7e99822f2d9345ae67a3043df", 
+    "revision": "93de9c5bcb90ae32bd3599b185f4bbf67e4529ff", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a351fe62c11737c722ad33aaff438f6ccd00bd4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3005269d4dcabcc7d27eaf72bda44a969873af8c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7092,40 +7092,36 @@ XPCOMUtils.defineLazyGetter(window, "gSh
   // Only show resizers on Windows 2000 and XP
   return parseFloat(Services.sysinfo.getProperty("version")) < 6;
 #else
   return false;
 #endif
 });
 
 var MousePosTracker = {
-  _listeners: [],
+  _listeners: new Set(),
   _x: 0,
   _y: 0,
   get _windowUtils() {
     delete this._windowUtils;
     return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
   },
 
   addListener: function (listener) {
-    if (this._listeners.indexOf(listener) >= 0)
+    if (this._listeners.has(listener))
       return;
 
     listener._hover = false;
-    this._listeners.push(listener);
+    this._listeners.add(listener);
 
     this._callListener(listener);
   },
 
   removeListener: function (listener) {
-    var index = this._listeners.indexOf(listener);
-    if (index < 0)
-      return;
-
-    this._listeners.splice(index, 1);
+    this._listeners.delete(listener);
   },
 
   handleEvent: function (event) {
     var fullZoom = this._windowUtils.fullZoom;
     this._x = event.screenX / fullZoom - window.mozInnerScreenX;
     this._y = event.screenY / fullZoom - window.mozInnerScreenY;
 
     this._listeners.forEach(function (listener) {
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -487,17 +487,17 @@ function sendDragEvent(aEventType, aTarg
 
 /**
  * Creates a custom drag event.
  * @param aEventType The drag event's type.
  * @param aData The event's drag data (optional).
  * @return The drag event.
  */
 function createDragEvent(aEventType, aData) {
-  let dataTransfer = new getContentWindow().DataTransfer("dragstart", false);
+  let dataTransfer = new (getContentWindow()).DataTransfer("dragstart", false);
   dataTransfer.mozSetDataAt("text/x-moz-url", aData, 0);
   let event = getContentDocument().createEvent("DragEvents");
   event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
                       false, false, false, false, 0, null, dataTransfer);
 
   return event;
 }
 
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -88,16 +88,17 @@ skip-if = true # Bug 921984, hopefully f
 [browser_tabview_bug626791.js]
 [browser_tabview_bug627736.js]
 [browser_tabview_bug628061.js]
 [browser_tabview_bug628165.js]
 [browser_tabview_bug628270.js]
 [browser_tabview_bug628887.js]
 [browser_tabview_bug629189.js]
 [browser_tabview_bug629195.js]
+skip-if = os == 'linux'&&debug # bug 981703
 [browser_tabview_bug630102.js]
 [browser_tabview_bug630157.js]
 skip-if = true # Bug 922422
 [browser_tabview_bug631662.js]
 skip-if = true # Bug 922422
 [browser_tabview_bug631752.js]
 [browser_tabview_bug633788.js]
 [browser_tabview_bug634077.js]
--- a/browser/devtools/sourceeditor/test/browser.ini
+++ b/browser/devtools/sourceeditor/test/browser.ini
@@ -20,8 +20,10 @@ support-files =
 [browser_editor_history.js]
 [browser_editor_markers.js]
 [browser_editor_movelines.js]
 [browser_editor_addons.js]
 [browser_codemirror.js]
 [browser_css_autocompletion.js]
 [browser_css_statemachine.js]
 [browser_vimemacs.js]
+skip-if = os == 'linux'&&debug # bug 981707
+
--- a/build/valgrind/output_handler.py
+++ b/build/valgrind/output_handler.py
@@ -53,17 +53,17 @@ class OutputHandler(object):
             r'(Mismatched free\(\) / delete / delete \[\])|' + \
             r'(Invalid (read|write) of size \d+)|' + \
             r'(Jump to the invalid address stated on the next line)|' + \
             r'(Source and destination overlap in .*)|' + \
             r'(.* bytes in .* blocks are .* lost)' + \
             r')'
         # Match identifer chars, plus ':' for namespaces, and '\?' in order to
         # match "???" which Valgrind sometimes produces.
-        self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0_9_:\?]+)'
+        self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)'
         self.re_suppression = r' *<insert_a_suppression_name_here>'
         self.error_count = 0
         self.suppression_count = 0
         self.number_of_stack_entries_to_get = 0
         self.curr_failure_msg = None
         self.buffered_lines = None
 
     def __call__(self, line):
--- a/build/valgrind/x86_64-redhat-linux-gnu.sup
+++ b/build/valgrind/x86_64-redhat-linux-gnu.sup
@@ -55,8 +55,19 @@
    fun:malloc
    obj:/lib64/libresolv-2.12.so
    ...
    fun:gaih_inet
    fun:getaddrinfo
    fun:PR_GetAddrInfoByName
    ...
 }
+{
+   Bug 979242
+   Memcheck:Leak
+   fun:calloc
+   fun:xcb_connect_to_fd
+   fun:xcb_connect_to_display_with_auth_info
+   fun:_XConnectXCB
+   fun:XOpenDisplay
+   fun:gdk_display_open
+   ...
+}
--- a/content/base/test/chrome/test_bug780199.xul
+++ b/content/base/test/chrome/test_bug780199.xul
@@ -34,17 +34,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function continueTest() {
     // Check that a new page wasn't loaded.
     is(b.contentDocument.documentElement.textContent, "testvalue");
     SimpleTest.finish();
   }
 
   function test() {
     b = document.getElementById("b");
-    var m = MutationObserver(callback);
+    var m = new MutationObserver(callback);
     m.observe(b, { attributes: true, attributeOldValue: true });
     b.contentDocument.documentElement.textContent = "testvalue";
     b.setAttribute("src", b.getAttribute("src"));
   }
 
   ]]>
   </script>
   <browser id="b" src="data:text/plain,initial"/>
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -117,25 +117,26 @@ struct WebGLContextOptions {
 class WebGLContext :
     public nsIDOMWebGLRenderingContext,
     public nsICanvasRenderingContextInternal,
     public nsSupportsWeakReference,
     public WebGLRectangleObject,
     public nsWrapperCache
 {
     friend class WebGLContextUserData;
+    friend class WebGLExtensionCompressedTextureATC;
+    friend class WebGLExtensionCompressedTextureETC1;
+    friend class WebGLExtensionCompressedTexturePVRTC;
+    friend class WebGLExtensionCompressedTextureS3TC;
+    friend class WebGLExtensionDepthTexture;
+    friend class WebGLExtensionDrawBuffers;
+    friend class WebGLExtensionLoseContext;
+    friend class WebGLExtensionVertexArray;
     friend class WebGLMemoryPressureObserver;
     friend class WebGLMemoryTracker;
-    friend class WebGLExtensionLoseContext;
-    friend class WebGLExtensionCompressedTextureS3TC;
-    friend class WebGLExtensionCompressedTextureATC;
-    friend class WebGLExtensionCompressedTexturePVRTC;
-    friend class WebGLExtensionDepthTexture;
-    friend class WebGLExtensionDrawBuffers;
-    friend class WebGLExtensionVertexArray;
 
     enum {
         UNPACK_FLIP_Y_WEBGL = 0x9240,
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
@@ -904,16 +905,17 @@ protected:
         OES_standard_derivatives,
         OES_texture_float,
         OES_texture_float_linear,
         OES_texture_half_float,
         OES_texture_half_float_linear,
         OES_vertex_array_object,
         WEBGL_color_buffer_float,
         WEBGL_compressed_texture_atc,
+        WEBGL_compressed_texture_etc1,
         WEBGL_compressed_texture_pvrtc,
         WEBGL_compressed_texture_s3tc,
         WEBGL_debug_renderer_info,
         WEBGL_debug_shaders,
         WEBGL_depth_texture,
         WEBGL_lose_context,
         WEBGL_draw_buffers,
         ANGLE_instanced_arrays,
--- a/content/canvas/src/WebGLContextExtensions.cpp
+++ b/content/canvas/src/WebGLContextExtensions.cpp
@@ -25,16 +25,17 @@ static const char *sExtensionNames[] = {
     "OES_standard_derivatives",
     "OES_texture_float",
     "OES_texture_float_linear",
     "OES_texture_half_float",
     "OES_texture_half_float_linear",
     "OES_vertex_array_object",
     "WEBGL_color_buffer_float",
     "WEBGL_compressed_texture_atc",
+    "WEBGL_compressed_texture_etc1",
     "WEBGL_compressed_texture_pvrtc",
     "WEBGL_compressed_texture_s3tc",
     "WEBGL_debug_renderer_info",
     "WEBGL_debug_shaders",
     "WEBGL_depth_texture",
     "WEBGL_lose_context",
     "WEBGL_draw_buffers",
     "ANGLE_instanced_arrays"
@@ -126,16 +127,18 @@ bool WebGLContext::IsExtensionSupported(
                      gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt3) &&
                      gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt5))
             {
                 return true;
             }
             return false;
         case WEBGL_compressed_texture_atc:
             return gl->IsExtensionSupported(GLContext::AMD_compressed_ATC_texture);
+        case WEBGL_compressed_texture_etc1:
+            return gl->IsExtensionSupported(GLContext::OES_compressed_ETC1_RGB8_texture);
         case WEBGL_compressed_texture_pvrtc:
             return gl->IsExtensionSupported(GLContext::IMG_texture_compression_pvrtc);
         case WEBGL_depth_texture:
             // WEBGL_depth_texture supports DEPTH_STENCIL textures
             if (!gl->IsSupported(GLFeature::packed_depth_stencil)) {
                 return false;
             }
             return gl->IsSupported(GLFeature::depth_texture) ||
@@ -263,16 +266,19 @@ WebGLContext::EnableExtension(WebGLExten
             obj = new WebGLExtensionLoseContext(this);
             break;
         case WEBGL_compressed_texture_s3tc:
             obj = new WebGLExtensionCompressedTextureS3TC(this);
             break;
         case WEBGL_compressed_texture_atc:
             obj = new WebGLExtensionCompressedTextureATC(this);
             break;
+        case WEBGL_compressed_texture_etc1:
+            obj = new WebGLExtensionCompressedTextureETC1(this);
+            break;
         case WEBGL_compressed_texture_pvrtc:
             obj = new WebGLExtensionCompressedTexturePVRTC(this);
             break;
         case WEBGL_debug_renderer_info:
             obj = new WebGLExtensionDebugRendererInfo(this);
             break;
         case WEBGL_debug_shaders:
             obj = new WebGLExtensionDebugShaders(this);
--- a/content/canvas/src/WebGLContextUtils.cpp
+++ b/content/canvas/src/WebGLContextUtils.cpp
@@ -220,16 +220,17 @@ WebGLContext::IsTextureFormatCompressed(
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
         case LOCAL_GL_ATC_RGB:
         case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
         case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
+        case LOCAL_GL_ETC1_RGB8_OES:
             return true;
         default:
             return false;
     }
 }
 
 GLenum
 WebGLContext::GetAndFlushUnderlyingGLErrors()
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -47,16 +47,19 @@ BlockSizeFor(GLenum format, GLint* block
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
         if (blockWidth)
             *blockWidth = 4;
         if (blockHeight)
             *blockHeight = 4;
         break;
+
+    case LOCAL_GL_ETC1_RGB8_OES:
+        // 4x4 blocks, but no 4-multiple requirement.
     default:
         break;
     }
 }
 
 /**
  * Return the displayable name for the texture function that is the
  * source for validation.
@@ -100,16 +103,17 @@ NameFrom(GLenum glenum)
         XX(COMPRESSED_RGB_PVRTC_2BPPV1);
         XX(COMPRESSED_RGB_PVRTC_4BPPV1);
         XX(COMPRESSED_RGB_S3TC_DXT1_EXT);
         XX(DEPTH_COMPONENT);
         XX(DEPTH_COMPONENT16);
         XX(DEPTH_COMPONENT32);
         XX(DEPTH_STENCIL);
         XX(DEPTH24_STENCIL8);
+        XX(ETC1_RGB8_OES);
         XX(FLOAT);
         XX(HALF_FLOAT);
         XX(LUMINANCE);
         XX(LUMINANCE_ALPHA);
         XX(RGB);
         XX(RGB16F);
         XX(RGB32F);
         XX(RGBA);
@@ -169,16 +173,17 @@ IsAllowedFromSource(GLenum format, WebGL
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
         return (func == WebGLTexImageFunc::CompTexImage ||
                 func == WebGLTexImageFunc::CompTexSubImage);
 
     case LOCAL_GL_ATC_RGB:
     case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
     case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
+    case LOCAL_GL_ETC1_RGB8_OES:
         return func == WebGLTexImageFunc::CompTexImage;
     }
 
     return true;
 }
 
 /**
  * Returns true if func is a CopyTexImage variant.
@@ -321,16 +326,21 @@ WebGLContext::BaseTexFormat(GLenum inter
 
         if (internalFormat == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA ||
             internalFormat == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA)
         {
             return LOCAL_GL_RGBA;
         }
     }
 
+    if (IsExtensionEnabled(WEBGL_compressed_texture_etc1)) {
+        if (internalFormat == LOCAL_GL_ETC1_RGB8_OES)
+            return LOCAL_GL_RGB;
+    }
+
     if (IsExtensionEnabled(WEBGL_compressed_texture_pvrtc)) {
         if (internalFormat == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 ||
             internalFormat == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1)
         {
             return LOCAL_GL_RGB;
         }
 
         if (internalFormat == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 ||
@@ -616,16 +626,25 @@ WebGLContext::ValidateTexImageFormat(GLe
     {
         bool validFormat = IsExtensionEnabled(WEBGL_compressed_texture_atc);
         if (!validFormat)
             ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_atc enabled",
                              InfoFrom(func), NameFrom(format));
         return validFormat;
     }
 
+    // WEBGL_compressed_texture_etc1
+    if (format == LOCAL_GL_ETC1_RGB8_OES) {
+        bool validFormat = IsExtensionEnabled(WEBGL_compressed_texture_etc1);
+        if (!validFormat)
+            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_etc1 enabled",
+                             InfoFrom(func), NameFrom(format));
+        return validFormat;
+    }
+
 
     if (format == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 ||
         format == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1 ||
         format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 ||
         format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1)
     {
         bool validFormat = IsExtensionEnabled(WEBGL_compressed_texture_pvrtc);
         if (!validFormat)
@@ -857,16 +876,17 @@ WebGLContext::ValidateCompTexImageDataSi
     MOZ_ASSERT(width >= 0 && height >= 0);
 
     CheckedUint32 required_byteLength = 0;
 
     switch (format) {
         case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
         case LOCAL_GL_ATC_RGB:
+        case LOCAL_GL_ETC1_RGB8_OES:
         {
             required_byteLength = ((CheckedUint32(width) + 3) / 4) * ((CheckedUint32(height) + 3) / 4) * 8;
             break;
         }
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
         case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
         case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
@@ -1110,16 +1130,17 @@ WebGLContext::GetBitsPerTexel(GLenum for
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
         return 2;
 
     case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
     case LOCAL_GL_ATC_RGB:
     case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
+    case LOCAL_GL_ETC1_RGB8_OES:
         return 4;
 
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
     case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
     case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
         return 8;
 
@@ -1179,16 +1200,17 @@ WebGLContext::ValidateTexImageFormatAndT
 
     case LOCAL_GL_DEPTH_STENCIL:
         validCombo = (type == LOCAL_GL_UNSIGNED_INT_24_8);
         break;
 
     case LOCAL_GL_ATC_RGB:
     case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
     case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
+    case LOCAL_GL_ETC1_RGB8_OES:
     case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
     case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
     case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
     case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
     case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLExtensionCompressedTextureETC1.cpp
@@ -0,0 +1,22 @@
+/* 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/. */
+
+#include "WebGLExtensions.h"
+
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "WebGLContext.h"
+
+using namespace mozilla;
+
+WebGLExtensionCompressedTextureETC1::WebGLExtensionCompressedTextureETC1(WebGLContext* context)
+    : WebGLExtensionBase(context)
+{
+    context->mCompressedTextureFormats.AppendElement(LOCAL_GL_ETC1_RGB8_OES);
+}
+
+WebGLExtensionCompressedTextureETC1::~WebGLExtensionCompressedTextureETC1()
+{
+}
+
+IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionCompressedTextureETC1)
--- a/content/canvas/src/WebGLExtensions.h
+++ b/content/canvas/src/WebGLExtensions.h
@@ -49,16 +49,26 @@ class WebGLExtensionCompressedTextureATC
 {
 public:
     WebGLExtensionCompressedTextureATC(WebGLContext*);
     virtual ~WebGLExtensionCompressedTextureATC();
 
     DECL_WEBGL_EXTENSION_GOOP
 };
 
+class WebGLExtensionCompressedTextureETC1
+    : public WebGLExtensionBase
+{
+public:
+    WebGLExtensionCompressedTextureETC1(WebGLContext*);
+    virtual ~WebGLExtensionCompressedTextureETC1();
+
+    DECL_WEBGL_EXTENSION_GOOP
+};
+
 class WebGLExtensionCompressedTexturePVRTC
     : public WebGLExtensionBase
 {
 public:
     WebGLExtensionCompressedTexturePVRTC(WebGLContext*);
     virtual ~WebGLExtensionCompressedTexturePVRTC();
 
     DECL_WEBGL_EXTENSION_GOOP
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -42,16 +42,17 @@ if CONFIG['MOZ_WEBGL']:
         'WebGLContextValidate.cpp',
         'WebGLContextVertexArray.cpp',
         'WebGLContextVertices.cpp',
         'WebGLElementArrayCache.cpp',
         'WebGLExtensionBase.cpp',
         'WebGLExtensionColorBufferFloat.cpp',
         'WebGLExtensionColorBufferHalfFloat.cpp',
         'WebGLExtensionCompressedTextureATC.cpp',
+        'WebGLExtensionCompressedTextureETC1.cpp',
         'WebGLExtensionCompressedTexturePVRTC.cpp',
         'WebGLExtensionCompressedTextureS3TC.cpp',
         'WebGLExtensionDebugRendererInfo.cpp',
         'WebGLExtensionDebugShaders.cpp',
         'WebGLExtensionDepthTexture.cpp',
         'WebGLExtensionDrawBuffers.cpp',
         'WebGLExtensionElementIndexUint.cpp',
         'WebGLExtensionFragDepth.cpp',
--- a/content/canvas/test/webgl/conformance/extensions/00_test_list.txt
+++ b/content/canvas/test/webgl/conformance/extensions/00_test_list.txt
@@ -1,9 +1,10 @@
 oes-standard-derivatives.html
 ext-texture-filter-anisotropic.html
 oes-texture-float.html
 oes-vertex-array-object.html
 webgl-debug-renderer-info.html
 webgl-debug-shaders.html
+webgl-compressed-texture-etc1.html
 webgl-compressed-texture-s3tc.html
 --min-version 1.0.2 webgl-depth-texture.html
 ext-sRGB.html
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/conformance/extensions/webgl-compressed-texture-etc1.html
@@ -0,0 +1,506 @@
+<!--
+
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="../resources/webgl-test.js"></script>
+<script src="../resources/webgl-test-utils.js"></script>
+<title>WebGL WEBGL_compressed_texture_etc1 Conformance Tests</title>
+<style>
+img {
+ border: 1px solid black;
+ margin-right: 1em;
+}
+.testimages {
+}
+
+.testimages br {
+  clear: both;
+}
+
+.testimages > div {
+  float: left;
+  margin: 1em;
+}
+</style>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="8" height="8" style="width: 8px; height: 8px;"></canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_compressed_texture_etc1 extension, if it is available.");
+
+debug("");
+
+var img_4x4_rgb_etc1 = new Uint8Array([
+    0x00, 0xc0, 0x00, 0xff, 0x07, 0x45, 0x07, 0x45
+]);
+var img_8x8_rgb_etc1 = new Uint8Array([
+    0x00, 0xff, 0x55, 0xfc, 0xff, 0xff, 0x07, 0x45,
+    0x11, 0x11, 0xff, 0xfc, 0xf8, 0xba, 0x07, 0x45,
+    0xee, 0x00, 0xee, 0xfc, 0x07, 0x45, 0x07, 0x45,
+    0x00, 0x90, 0xf8, 0x92, 0x07, 0x45, 0xff, 0xff,
+]);
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false});
+var program = wtu.setupTexturedQuad(gl);
+var ext = null;
+var vao = null;
+var validFormats = {
+    COMPRESSED_RGB_ETC1_WEBGL : 0x8D64
+};
+var name;
+var supportedFormats;
+
+if (!gl) {
+    testFailed("WebGL context does not exist");
+} else {
+    testPassed("WebGL context exists");
+
+    // Run tests with extension disabled
+    runTestDisabled();
+
+    // Query the extension and store globally so shouldBe can access it
+    ext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_etc1");
+    if (!ext) {
+        testPassed("No WEBGL_compressed_texture_etc1 support -- this is legal");
+        runSupportedTest(false);
+    } else {
+        testPassed("Successfully enabled WEBGL_compressed_texture_etc1 extension");
+
+        runSupportedTest(true);
+        runTestExtension();
+    }
+}
+
+function runSupportedTest(extensionEnabled) {
+    var name = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_compressed_texture_etc1");
+    if (name !== undefined) {
+        if (extensionEnabled) {
+            testPassed("WEBGL_compressed_texture_etc1 listed as supported and getExtension succeeded");
+        } else {
+            testFailed("WEBGL_compressed_texture_etc1 listed as supported but getExtension failed");
+        }
+    } else {
+        if (extensionEnabled) {
+            testFailed("WEBGL_compressed_texture_etc1 not listed as supported but getExtension succeeded");
+        } else {
+            testPassed("WEBGL_compressed_texture_etc1 not listed as supported and getExtension failed -- this is legal");
+        }
+    }
+}
+
+
+function runTestDisabled() {
+    debug("Testing binding enum with extension disabled");
+
+    shouldBe('gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS)', '[]');
+}
+
+function formatExists(format, supportedFormats) {
+    for (var ii = 0; ii < supportedFormats.length; ++ii) {
+        if (format == supportedFormats[ii]) {
+            testPassed("supported format " + formatToString(format) + " is exists");
+            return;
+        }
+    }
+    testFailed("supported format " + formatToString(format) + " does not exist");
+}
+
+function formatToString(format) {
+    for (var p in ext) {
+        if (ext[p] == format) {
+            return p;
+        }
+    }
+    return "0x" + format.toString(16);
+}
+
+function runTestExtension() {
+    debug("Testing WEBGL_compressed_texture_etc1");
+
+    // check that all format enums exist.
+    for (name in validFormats) {
+        var expected = "0x" + validFormats[name].toString(16);
+        var actual = "ext['" + name + "']";
+        shouldBe(actual, expected);
+    }
+
+    supportedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
+    // There should be exactly 4 formats
+    shouldBe("supportedFormats.length", "1");
+
+    // check that all 4 formats exist
+    for (var name in validFormats.length) {
+        formatExists(validFormats[name], supportedFormats);
+    }
+
+    // Test each format
+    testETC1_RGB();
+}
+
+function testETC1_RGB() {
+    var tests = [
+        {   width: 4,
+            height: 4,
+            channels: 3,
+            data: img_4x4_rgb_etc1,
+            format: ext.COMPRESSED_RGB_ETC1_WEBGL
+        },
+        {   width: 8,
+            height: 8,
+            channels: 3,
+            data: img_8x8_rgb_etc1,
+            format: ext.COMPRESSED_RGB_ETC1_WEBGL
+        }
+    ];
+    testETCTextures(tests);
+}
+
+function testETCTextures(tests) {
+    for (var ii = 0; ii < tests.length; ++ii) {
+        debug("<hr/>");
+        testETCTexture(tests[ii]);
+    }
+}
+
+function offset_color(c, o) {
+    return [
+        Math.min(Math.max(0, c[0] + o), 255),
+        Math.min(Math.max(0, c[1] + o), 255),
+        Math.min(Math.max(0, c[2] + o), 255),
+        c[3]
+    ];
+}
+
+function uncompressETC1Block(destBuffer, destX, destY, destWidth, src) {
+    'use strict';
+    var xx, yy, basecols;
+    var _deltatable = [ 0, 1, 2, 3, -4, -3, -2, -1 ];
+    var _modtable = [
+        [ 2,    8,  -2,   -8 ],
+        [ 5,   17,  -5,  -17 ],
+        [ 9,   29,  -9,  -29 ],
+        [ 13,  42, -13,  -42 ],
+        [ 18,  60, -18,  -60 ],
+        [ 24,  80, -24,  -80 ],
+        [ 33, 106, -33, -106 ],
+        [ 47, 183, -47, -183 ]
+    ];
+    var _sl = [
+        0x00, 0x01, 0x04, 0x05,
+        0x10, 0x11, 0x14, 0x15,
+        0x40, 0x41, 0x44, 0x45,
+        0x50, 0x51, 0x54, 0x55
+    ];
+    var _sh = [
+        0x00, 0x02, 0x08, 0x0a,
+        0x20, 0x22, 0x28, 0x2a,
+        0x80, 0x82, 0x88, 0x8a,
+        0xa0, 0xa2, 0xa8, 0xaa
+    ];
+
+    function extend_4to8bits(r, g, b) {
+        return [
+            (r & 0xf0) | ((r >> 4) & 0x0f),
+            (g & 0xf0) | ((g >> 4) & 0x0f),
+            (b & 0xf0) | ((b >> 4) & 0x0f),
+            255
+        ];
+    }
+
+    function extend_5to8bits(r, g, b) {
+        return [
+            (r & 0xf8) | ((r >> 5) & 0x07),
+            (g & 0xf8) | ((g >> 5) & 0x07),
+            (b & 0xf8) | ((b >> 5) & 0x07),
+            255
+        ];
+    }
+
+    function base_colors(src, mode) {
+        var col_1, col_2, didx, d;
+        if (mode === 'I') {
+            col_1 = extend_4to8bits(src[0], src[1], src[2]);
+            col_2 = extend_4to8bits(src[0] << 4, src[1] << 4, src[2] << 4);
+            return [ col_1, col_2 ];
+        }
+
+        if (mode === 'D') {
+            col_1 = extend_5to8bits(src[0], src[1], src[2]);
+            col_2 = extend_5to8bits(src[0] + 8 * _deltatable[(src[0] & 0x7)],
+                                    src[1] + 8 * _deltatable[(src[1] & 0x7)],
+                                    src[2] + 8 * _deltatable[(src[2] & 0x7)]);
+            return [ col_1, col_2 ];
+        }
+
+        return [];
+    }
+
+    function mode(src) {
+        return (src[3] & 0x2) ? 'D' : 'I';
+    }
+
+    function flip(src) {
+        return (src[3] & 0x1) === 0x1;
+    }
+
+    function subblock_modtable(src, sb) {
+        var shift = (sb ? 2 : 5);
+        var idx = (src[3] >> shift) & 0x7;
+        return _modtable[idx];
+    }
+
+    function interleave_table_indices(src) {
+        var result =
+                (_sl[src[7]         & 0xf] | _sh[src[5]        & 0xf]) |
+                ((_sl[(src[7] >> 4) & 0xf] | _sh[(src[5] >> 4) & 0xf]) << 8) |
+                ((_sl[src[6]        & 0xf] | _sh[src[4]        & 0xf]) << 16) |
+                ((_sl[(src[6] >> 4) & 0xf] | _sh[(src[4] >> 4) & 0xf]) << 24);
+        return result;
+    }
+
+    function subblock(n, flip) {
+        var mask = flip ? 0x2 : 0x8;
+        return (n & mask) ? 1 : 0;
+    }
+
+    var m = mode(src);
+    basecols = base_colors(src, m);
+
+    var alpha = 255;
+    var flipbit = flip(src);
+    var table_indices = interleave_table_indices(src);
+
+    var n = 0;
+    for (xx = 0; xx < 4; ++xx) {
+        for (yy = 0; yy < 4; ++yy) {
+            var dstOff = ((destY + yy) * destWidth + destX + xx) * 4;
+
+            var sb = subblock(n, flipbit);
+            var mod = subblock_modtable(src, sb);
+            var offset = mod[(table_indices & 0x3)];
+            var col = offset_color(basecols[sb], offset);
+
+            destBuffer[dstOff]     = col[0];
+            destBuffer[dstOff + 1] = col[1];
+            destBuffer[dstOff + 2] = col[2];
+            destBuffer[dstOff + 3] = alpha;
+            table_indices >>= 2;
+            n++;
+        }
+    }
+}
+
+function uncompressETC1(width, height, data, format) {
+    if (width % 4 || height % 4) throw "bad width or height";
+
+    var dest = new Uint8Array(width * height * 4);
+    var blocksAcross = width / 4;
+    var blocksDown = height / 4;
+    var blockSize = 8;
+    for (var yy = 0; yy < blocksDown; ++yy) {
+        for (var xx = 0; xx < blocksAcross; ++xx) {
+            var srcOffset = (yy * blocksAcross + xx) * blockSize;
+            var srcblk = data.subarray(srcOffset, srcOffset + blockSize);
+            uncompressETC1Block(dest, xx * 4, yy * 4, width, srcblk);
+        }
+    }
+    return dest;
+}
+
+function testETCTexture(test) {
+    var data = new Uint8Array(test.data);
+    var width = test.width;
+    var height = test.height;
+    var format = test.format;
+
+    var uncompressedData = uncompressETC1(width, height, data, format);
+
+    canvas.width = width;
+    canvas.height = height;
+    gl.viewport(0, 0, width, height);
+    debug("testing " + formatToString(format) + " " + width + "x" + height);
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+    gl.generateMipmap(gl.TEXTURE_2D);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "trying to generate mipmaps from compressed texture");
+    wtu.clearAndDrawUnitQuad(gl);
+    compareRect(width, height, test.channels, width, height, uncompressedData, data, format);
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 1, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "non 0 border");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width + 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height + 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 4, height, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 4, 0, data);
+    glErrorShouldBe(gl, gl.INVALID_VALUE, "data size does not match dimensions");
+
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 1, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "non multiple-of-4 supported");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width - 2, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "non multiple-of-4 supported");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 1, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "non multiple-of-4 supported");
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height - 2, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "non multiple-of-4 supported");
+
+    if (width == 4) {
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 1, height, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, 2, height, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+    }
+    if (height == 4) {
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 1, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+      gl.compressedTexImage2D(gl.TEXTURE_2D, 1, format, width, 2, 0, data);
+      glErrorShouldBe(gl, gl.NO_ERROR, "valid dimensions for level > 0");
+    }
+
+    // Reupload the complete texture before SubImage tests.
+    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, data);
+    glErrorShouldBe(gl, gl.NO_ERROR, "uploading compressed texture");
+
+    /* OES_compressed_ETC1_RGB8_texture:
+     *   INVALID_OPERATION is generated by CompressedTexSubImage2D,
+     *   TexSubImage2D, or CopyTexSubImage2D if the texture image
+     *   <level> bound to <target> has internal format ETC1_RGB8_OES.
+     */
+    gl.compressedTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, data);
+    glErrorShouldBe(gl, gl.INVALID_OPERATION, "ETC1 should not support compressedTexSubImage2D.");
+}
+
+function insertImg(element, caption, img) {
+    var div = document.createElement("div");
+    div.appendChild(img);
+    var label = document.createElement("div");
+    label.appendChild(document.createTextNode(caption));
+    div.appendChild(label);
+    element.appendChild(div);
+}
+
+function makeImage(imageWidth, imageHeight, dataWidth, data, alpha) {
+    var scale = 8;
+    var c = document.createElement("canvas");
+    c.width = imageWidth * scale;
+    c.height = imageHeight * scale;
+    var ctx = c.getContext("2d");
+    for (var yy = 0; yy < imageHeight; ++yy) {
+        for (var xx = 0; xx < imageWidth; ++xx) {
+            var offset = (yy * dataWidth + xx) * 4;
+            ctx.fillStyle = "rgba(" +
+                    data[offset + 0] + "," +
+                    data[offset + 1] + "," +
+                    data[offset + 2] + "," +
+                    (alpha ? data[offset + 3] / 255 : 1) + ")";
+            ctx.fillRect(xx * scale, yy * scale, scale, scale);
+        }
+    }
+    var img = document.createElement("img");
+    img.src = c.toDataURL();
+    return img;
+}
+
+function compareRect(actualWidth, actualHeight, actualChannels,
+                     dataWidth, dataHeight, expectedData,
+                     testData, testFormat)
+{
+    var actual = new Uint8Array(actualWidth * actualHeight * 4);
+    gl.readPixels(0, 0, actualWidth, actualHeight,
+                  gl.RGBA, gl.UNSIGNED_BYTE, actual);
+
+    var div = document.createElement("div");
+    div.className = "testimages";
+    insertImg(div, "expected", makeImage(
+            actualWidth, actualHeight, dataWidth, expectedData,
+            actualChannels == 4));
+    insertImg(div, "actual", makeImage(
+            actualWidth, actualHeight, actualWidth, actual,
+            actualChannels == 4));
+    div.appendChild(document.createElement('br'));
+    document.getElementById("console").appendChild(div);
+
+    var failed = false;
+    for (var yy = 0; yy < actualHeight; ++yy) {
+        for (var xx = 0; xx < actualWidth; ++xx) {
+            var actualOffset = (yy * actualWidth + xx) * 4;
+            var expectedOffset = (yy * dataWidth + xx) * 4;
+            var expected = [
+                    expectedData[expectedOffset + 0],
+                    expectedData[expectedOffset + 1],
+                    expectedData[expectedOffset + 2],
+                    (actualChannels == 3 ? 255
+                                         : expectedData[expectedOffset + 3])
+            ];
+
+            if (actual[actualOffset + 0] != expected[0] ||
+                actual[actualOffset + 1] != expected[1] ||
+                actual[actualOffset + 2] != expected[2] ||
+                actual[actualOffset + 3] != expected[3])
+            {
+                failed = true;
+                var was = actual[actualOffset + 0].toString();
+                for (var j = 1; j < 4; ++j) {
+                    was += "," + actual[actualOffset + j];
+                }
+                testFailed('at (' + xx + ', ' + yy +
+                           ') expected: ' + expected + ' was ' + was);
+            }
+        }
+    }
+    if (!failed) {
+        testPassed("texture rendered correctly");
+    }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script>finishTest();</script>
+
+</body>
+</html>
--- a/content/canvas/test/webgl/conformance/resources/webgl-test-utils.js
+++ b/content/canvas/test/webgl/conformance/resources/webgl-test-utils.js
@@ -213,16 +213,50 @@ var setupSimpleTextureProgram = function
  *      created.
  */
 var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) {
   return setupUnitQuadWithTexCoords(gl, [ 0.0, 0.0 ], [ 1.0, 1.0 ],
                                     opt_positionLocation, opt_texcoordLocation);
 };
 
 /**
+ * Draws a previously setupUnitQuad.
+ * @param {!WebGLContext} gl The WebGLContext to use.
+ */
+var drawUnitQuad = function(gl) {
+  gl.drawArrays(gl.TRIANGLES, 0, 6);
+};
+
+/**
+ * Clears then Draws a previously setupUnitQuad.
+ * @param {!WebGLContext} gl The WebGLContext to use.
+ * @param {!Array.<number>} opt_color The color to fill clear with before
+ * drawing. A 4 element array where each element is in the range 0 to
+ * 255. Default [255, 255, 255, 255]
+ */
+var clearAndDrawUnitQuad = function(gl, opt_color) {
+  opt_color = opt_color || [255, 255, 255, 255];
+
+  // Save and restore.
+  var prevClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE);
+
+  gl.clearColor(opt_color[0] / 255,
+                opt_color[1] / 255,
+                opt_color[2] / 255,
+                opt_color[3] / 255);
+  gl.clear(gl.COLOR_BUFFER_BIT);
+  drawUnitQuad(gl);
+
+  gl.clearColor(prevClearColor[0],
+                prevClearColor[1],
+                prevClearColor[2],
+                prevClearColor[3]);
+};
+
+/**
  * Creates buffers for a textured unit quad with specified lower left
  * and upper right texture coordinates, and attaches them to vertex
  * attribs.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {!Array.<number>} lowerLeftTexCoords The texture coordinates for the lower left corner.
  * @param {!Array.<number>} upperRightTexCoords The texture coordinates for the upper right corner.
  * @param {number} opt_positionLocation The attrib location for position.
  * @param {number} opt_texcoordLocation The attrib location for texture coords.
@@ -642,17 +676,17 @@ var glErrorShouldBe = function(gl, glErr
                 getGLErrorAsString(gl, glError) + " : " + opt_msg);
   }
 };
 
 /**
  * Links a WebGL program, throws if there are errors.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {!WebGLProgram} program The WebGLProgram to link.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  */
 var linkProgram = function(gl, program, opt_errorCallback) {
   errFn = opt_errorCallback || testFailed;
   // Link the program
   gl.linkProgram(program);
 
   // Check the link status
   var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
@@ -958,18 +992,18 @@ var readFileList = function(url) {
   }
   return files;
 };
 
 /**
  * Loads a shader.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {string} shaderSource The shader source.
- * @param {number} shaderType The type of shader. 
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {number} shaderType The type of shader.
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLShader} The created shader.
  */
 var loadShader = function(gl, shaderSource, shaderType, opt_errorCallback) {
   var errFn = opt_errorCallback || error;
   // Create the shader object
   var shader = gl.createShader(shaderType);
   if (shader == null) {
     errFn("*** Error: unable to create shader '"+shaderSource+"'");
@@ -1000,17 +1034,17 @@ var loadShader = function(gl, shaderSour
   return shader;
 }
 
 /**
  * Loads a shader from a URL.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {file} file The URL of the shader source.
  * @param {number} type The type of shader.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLShader} The created shader.
  */
 var loadShaderFromFile = function(gl, file, type, opt_errorCallback) {
   var shaderSource = readFile(file);
   return loadShader(gl, shaderSource, type, opt_errorCallback);
 };
 
 /**
@@ -1025,17 +1059,17 @@ var getScript = function(scriptId) {
 };
 
 /**
  * Loads a shader from a script tag.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {string} scriptId The id of the script tag.
  * @param {number} opt_shaderType The type of shader. If not passed in it will
  *     be derived from the type of the script tag.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLShader} The created shader.
  */
 var loadShaderFromScript = function(
     gl, scriptId, opt_shaderType, opt_errorCallback) {
   var shaderSource = "";
   var shaderType;
   var shaderScript = document.getElementById(scriptId);
   if (!shaderScript) {
@@ -1067,17 +1101,17 @@ var loadStandardProgram = function(gl) {
   return program;
 };
 
 /**
  * Loads shaders from files, creates a program, attaches the shaders and links.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {string} vertexShaderPath The URL of the vertex shader.
  * @param {string} fragmentShaderPath The URL of the fragment shader.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLProgram} The created program.
  */
 var loadProgramFromFile = function(
     gl, vertexShaderPath, fragmentShaderPath, opt_errorCallback) {
   var program = gl.createProgram();
   gl.attachShader(
       program,
       loadShaderFromFile(
@@ -1093,17 +1127,17 @@ var loadProgramFromFile = function(
 /**
  * Loads shaders from script tags, creates a program, attaches the shaders and
  * links.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {string} vertexScriptId The id of the script tag that contains the
  *        vertex shader.
  * @param {string} fragmentScriptId The id of the script tag that contains the
  *        fragment shader.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLProgram} The created program.
  */
 var loadProgramFromScript = function loadProgramFromScript(
     gl, vertexScriptId, fragmentScriptId, opt_errorCallback) {
   var program = gl.createProgram();
   gl.attachShader(
       program,
       loadShaderFromScript(
@@ -1117,17 +1151,17 @@ var loadProgramFromScript = function loa
 };
 
 /**
  * Loads shaders from source, creates a program, attaches the shaders and
  * links.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {string} vertexShader The vertex shader.
  * @param {string} fragmentShader The fragment shader.
- * @param {function(string): void) opt_errorCallback callback for errors. 
+ * @param {function(string): void) opt_errorCallback callback for errors.
  * @return {!WebGLProgram} The created program.
  */
 var loadProgram = function(
     gl, vertexShader, fragmentShader, opt_errorCallback) {
   var program = gl.createProgram();
   gl.attachShader(
       program,
       loadShader(
@@ -1308,23 +1342,25 @@ var addShaderSource = function(element, 
     }, false);
   div.appendChild(l);
   div.appendChild(s);
   element.appendChild(div);
 }
 
 return {
   addShaderSource: addShaderSource,
+  clearAndDrawUnitQuad : clearAndDrawUnitQuad,
   create3DContext: create3DContext,
   create3DContextWithWrapperThatThrowsOnGLError:
     create3DContextWithWrapperThatThrowsOnGLError,
   checkCanvas: checkCanvas,
   checkCanvasRect: checkCanvasRect,
   createColoredTexture: createColoredTexture,
   drawQuad: drawQuad,
+  drawUnitQuad: drawUnitQuad,
   endsWith: endsWith,
   getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes,
   getFileListAsync: getFileListAsync,
   getLastError: getLastError,
   getScript: getScript,
   getSupportedExtensionWithKnownPrefixes: getSupportedExtensionWithKnownPrefixes,
   getUrlArguments: getUrlArguments,
   glEnumToString: glEnumToString,
--- a/content/canvas/test/webgl/mochitest-conformance-files.ini
+++ b/content/canvas/test/webgl/mochitest-conformance-files.ini
@@ -37,16 +37,17 @@ support-files =
   conformance/context/premultiplyalpha-test.html
   conformance/context/resource-sharing-test.html
   conformance/extensions/00_test_list.txt
   conformance/extensions/ext-sRGB.html
   conformance/extensions/ext-texture-filter-anisotropic.html
   conformance/extensions/oes-standard-derivatives.html
   conformance/extensions/oes-texture-float.html
   conformance/extensions/oes-vertex-array-object.html
+  conformance/extensions/webgl-compressed-texture-etc1.html
   conformance/extensions/webgl-compressed-texture-s3tc.html
   conformance/extensions/webgl-debug-renderer-info.html
   conformance/extensions/webgl-debug-shaders.html
   conformance/extensions/webgl-depth-texture.html
   conformance/glsl/00_test_list.txt
   conformance/glsl/functions/00_test_list.txt
   conformance/glsl/functions/glsl-function-abs.html
   conformance/glsl/functions/glsl-function-acos.html
--- a/content/html/content/src/HTMLAnchorElement.cpp
+++ b/content/html/content/src/HTMLAnchorElement.cpp
@@ -50,21 +50,23 @@ NS_INTERFACE_TABLE_TAIL_INHERITING(nsGen
 NS_IMPL_ADDREF_INHERITED(HTMLAnchorElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLAnchorElement, Element)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAnchorElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLAnchorElement,
                                                   nsGenericHTMLElement)
   tmp->Link::Traverse(cb);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLAnchorElement,
                                                 nsGenericHTMLElement)
   tmp->Link::Unlink();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ELEMENT_CLONE(HTMLAnchorElement)
 
 JSObject*
 HTMLAnchorElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope)
 {
   return HTMLAnchorElementBinding::Wrap(aCx, aScope, this);
@@ -278,16 +280,25 @@ HTMLAnchorElement::GetTarget(nsAString& 
 }
 
 NS_IMETHODIMP
 HTMLAnchorElement::SetTarget(const nsAString& aValue)
 {
   return SetAttr(kNameSpaceID_None, nsGkAtoms::target, aValue, true);
 }
 
+nsDOMTokenList*
+HTMLAnchorElement::RelList()
+{
+  if (!mRelList) {
+    mRelList = new nsDOMTokenList(this, nsGkAtoms::rel);
+  }
+  return mRelList;
+}
+
 #define IMPL_URI_PART(_part)                                 \
   NS_IMETHODIMP                                              \
   HTMLAnchorElement::Get##_part(nsAString& a##_part)         \
   {                                                          \
     Link::Get##_part(a##_part);                              \
     return NS_OK;                                            \
   }                                                          \
   NS_IMETHODIMP                                              \
--- a/content/html/content/src/HTMLAnchorElement.h
+++ b/content/html/content/src/HTMLAnchorElement.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_HTMLAnchorElement_h
 #define mozilla_dom_HTMLAnchorElement_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Link.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLAnchorElement.h"
+#include "nsDOMTokenList.h"
 
 namespace mozilla {
 namespace dom {
 
 class HTMLAnchorElement MOZ_FINAL : public nsGenericHTMLElement,
                                     public nsIDOMHTMLAnchorElement,
                                     public Link
 {
@@ -112,16 +113,17 @@ public:
   void GetRel(nsString& aValue)
   {
     GetHTMLAttr(nsGkAtoms::rel, aValue);
   }
   void SetRel(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::rel, aValue, rv);
   }
+  nsDOMTokenList* RelList();
   void GetHreflang(nsString& aValue)
   {
     GetHTMLAttr(nsGkAtoms::hreflang, aValue);
   }
   void SetHreflang(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::hreflang, aValue, rv);
   }
@@ -193,14 +195,15 @@ public:
     GetHref(aResult);
   }
 
 protected:
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
   virtual JSObject* WrapNode(JSContext *aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+  nsRefPtr<nsDOMTokenList > mRelList;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLAnchorElement_h
--- a/content/html/content/src/HTMLAreaElement.cpp
+++ b/content/html/content/src/HTMLAreaElement.cpp
@@ -34,21 +34,23 @@ NS_INTERFACE_TABLE_TAIL_INHERITING(nsGen
 NS_IMPL_ADDREF_INHERITED(HTMLAreaElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLAreaElement, Element)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAreaElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLAreaElement,
                                                   nsGenericHTMLElement)
   tmp->Link::Traverse(cb);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLAreaElement,
                                                 nsGenericHTMLElement)
   tmp->Link::Unlink();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ELEMENT_CLONE(HTMLAreaElement)
 
 
 NS_IMPL_STRING_ATTR(HTMLAreaElement, Alt, alt)
 NS_IMPL_STRING_ATTR(HTMLAreaElement, Coords, coords)
 NS_IMPL_URI_ATTR(HTMLAreaElement, Href, href)
@@ -111,16 +113,25 @@ void
 HTMLAreaElement::GetLinkTarget(nsAString& aTarget)
 {
   GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
   if (aTarget.IsEmpty()) {
     GetBaseTarget(aTarget);
   }
 }
 
+nsDOMTokenList* 
+HTMLAreaElement::RelList()
+{
+  if (!mRelList) {
+    mRelList = new nsDOMTokenList(this, nsGkAtoms::rel);
+  }
+  return mRelList;
+}
+
 nsresult
 HTMLAreaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                             nsIContent* aBindingParent,
                             bool aCompileEventHandlers)
 {
   Link::ResetLinkState(false, Link::ElementHasHref());
   if (aDocument) {
     aDocument->RegisterPendingLinkUpdate(this);
--- a/content/html/content/src/HTMLAreaElement.h
+++ b/content/html/content/src/HTMLAreaElement.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_HTMLAreaElement_h
 #define mozilla_dom_HTMLAreaElement_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Link.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
+#include "nsDOMTokenList.h"
 #include "nsIDOMHTMLAreaElement.h"
 #include "nsIURL.h"
 
 class nsIDocument;
 
 namespace mozilla {
 namespace dom {
 
@@ -106,17 +107,27 @@ public:
     SetHTMLAttr(nsGkAtoms::download, aDownload, aError);
   }
 
   // The XPCOM GetPing is OK for us
   void SetPing(const nsAString& aPing, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::ping, aPing, aError);
   }
+  
+  void GetRel(nsString& aValue)
+  {
+    GetHTMLAttr(nsGkAtoms::rel, aValue);
+  }
 
+  void SetRel(const nsAString& aRel, ErrorResult& aError)
+  {
+    SetHTMLAttr(nsGkAtoms::rel, aRel, aError);
+  } 
+  nsDOMTokenList* RelList();
   // The Link::GetOrigin is OK for us
 
   // The XPCOM GetProtocol is OK for us
   // The XPCOM SetProtocol is OK for us
 
   // The Link::GetUsername is OK for us
   // The Link::SetUsername is OK for us
 
@@ -160,14 +171,15 @@ public:
   }
 
 protected:
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
+  nsRefPtr<nsDOMTokenList > mRelList;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLAreaElement_h */
--- a/content/html/content/src/HTMLAudioElement.cpp
+++ b/content/html/content/src/HTMLAudioElement.cpp
@@ -241,16 +241,22 @@ HTMLAudioElement::CanPlayChanged(int32_t
     SetMutedInternal(mMuted | MUTED_BY_AUDIO_CHANNEL);
   }
 
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HTMLAudioElement::WindowVolumeChanged()
+{
+  return HTMLMediaElement::WindowVolumeChanged();
+}
+
+NS_IMETHODIMP
 HTMLAudioElement::Notify(nsITimer* aTimer)
 {
 #ifdef MOZ_B2G
   mTimerActivated = false;
   UpdateAudioChannelPlayingState();
 #endif
   return NS_OK;
 }
@@ -269,17 +275,18 @@ HTMLAudioElement::UpdateAudioChannelPlay
 
     if (!mAudioChannelAgent) {
       nsresult rv;
       mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
       if (!mAudioChannelAgent) {
         return;
       }
       // Use a weak ref so the audio channel agent can't leak |this|.
-      mAudioChannelAgent->InitWithWeakCallback(mAudioChannelType, this);
+      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetWindow(),
+                                               mAudioChannelType, this);
 
       mAudioChannelAgent->SetVisibilityState(!OwnerDoc()->Hidden());
     }
 
     if (mPlayingThroughTheAudioChannel) {
       int32_t canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
       CanPlayChanged(canPlay);
--- a/content/html/content/src/HTMLImageElement.cpp
+++ b/content/html/content/src/HTMLImageElement.cpp
@@ -317,53 +317,29 @@ HTMLImageElement::AfterSetAttr(int32_t a
   }
 
   if (aNameSpaceID == kNameSpaceID_None &&
       aName == nsGkAtoms::src &&
       !aValue) {
     CancelImageRequests(aNotify);
   }
 
-  // If we plan to call LoadImage, we want to do it first so that the image load
-  // kicks off. But if aNotify is false, we are coming from the parser or some
-  // such place; we'll get bound after all the attributes have been set, so
-  // we'll do the image load from BindToTree. Skip the LoadImage call in that case.
+  // If aNotify is false, we are coming from the parser or some such place;
+  // we'll get bound after all the attributes have been set, so we'll do the
+  // image load from BindToTree. Skip the LoadImage call in that case.
   if (aNotify &&
       aNameSpaceID == kNameSpaceID_None &&
       aName == nsGkAtoms::crossorigin) {
     // We want aForce == true in this LoadImage call, because we want to force
     // a new load of the image with the new cross origin policy.
     nsAutoString uri;
     GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri);
     LoadImage(uri, true, aNotify);
   }
 
-  if (aNotify &&
-      aNameSpaceID == kNameSpaceID_None &&
-      aName == nsGkAtoms::src &&
-      aValue) {
-
-    // Prevent setting image.src by exiting early
-    if (nsContentUtils::IsImageSrcSetDisabled()) {
-      return NS_OK;
-    }
-
-    // A hack to get animations to reset. See bug 594771.
-    mNewRequestsWillNeedAnimationReset = true;
-
-    // Force image loading here, so that we'll try to load the image from
-    // network if it's set to be not cacheable...  If we change things so that
-    // the state gets in Element's attr-setting happen around this
-    // LoadImage call, we could start passing false instead of aNotify
-    // here.
-    LoadImage(aValue->GetStringValue(), true, aNotify);
-
-    mNewRequestsWillNeedAnimationReset = false;
-  }
-
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
                                             aValue, aNotify);
 }
 
 
 nsresult
 HTMLImageElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
@@ -421,28 +397,51 @@ HTMLImageElement::IsHTMLFocusable(bool a
   return false;
 }
 
 nsresult
 HTMLImageElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                           nsIAtom* aPrefix, const nsAString& aValue,
                           bool aNotify)
 {
+  // We need to force our image to reload.  This must be done here, not in
+  // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
+  // being set to its existing value, which is normally optimized away as a
+  // no-op.
+  //
+  // If aNotify is false, we are coming from the parser or some such place;
+  // we'll get bound after all the attributes have been set, so we'll do the
+  // image load from BindToTree. Skip the LoadImage call in that case.
+  if (aNotify &&
+      aNameSpaceID == kNameSpaceID_None &&
+      aName == nsGkAtoms::src) {
+
+    // Prevent setting image.src by exiting early
+    if (nsContentUtils::IsImageSrcSetDisabled()) {
+      return NS_OK;
+    }
+
+    // A hack to get animations to reset. See bug 594771.
+    mNewRequestsWillNeedAnimationReset = true;
+
+    // Force image loading here, so that we'll try to load the image from
+    // network if it's set to be not cacheable...  If we change things so that
+    // the state gets in Element's attr-setting happen around this
+    // LoadImage call, we could start passing false instead of aNotify
+    // here.
+    LoadImage(aValue, true, aNotify);
+
+    mNewRequestsWillNeedAnimationReset = false;
+  }
+
   return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                        aNotify);
 }
 
 nsresult
-HTMLImageElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                            bool aNotify)
-{
-  return nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
-}
-
-nsresult
 HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                              nsIContent* aBindingParent,
                              bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/content/html/content/src/HTMLImageElement.h
+++ b/content/html/content/src/HTMLImageElement.h
@@ -60,18 +60,16 @@ public:
   nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) MOZ_OVERRIDE;
-  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify) MOZ_OVERRIDE;
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
   virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
 
   virtual nsEventStates IntrinsicState() const MOZ_OVERRIDE;
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
--- a/content/html/content/src/HTMLLinkElement.cpp
+++ b/content/html/content/src/HTMLLinkElement.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/HTMLLinkElementBinding.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsAsyncDOMEvent.h"
 #include "nsContentUtils.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
+#include "nsDOMTokenList.h"
 #include "nsIDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsIStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
@@ -41,22 +42,24 @@ HTMLLinkElement::~HTMLLinkElement()
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
                                                   nsGenericHTMLElement)
   tmp->nsStyleLinkElement::Traverse(cb);
   tmp->Link::Traverse(cb);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
                                                 nsGenericHTMLElement)
   tmp->nsStyleLinkElement::Unlink();
   tmp->Link::Unlink();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(HTMLLinkElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLLinkElement, Element)
 
 
 // QueryInterface implementation for HTMLLinkElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLLinkElement)
@@ -320,16 +323,25 @@ void
 HTMLLinkElement::GetLinkTarget(nsAString& aTarget)
 {
   GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
   if (aTarget.IsEmpty()) {
     GetBaseTarget(aTarget);
   }
 }
 
+nsDOMTokenList* 
+HTMLLinkElement::RelList()
+{
+  if (!mRelList) {
+    mRelList = new nsDOMTokenList(this, nsGkAtoms::rel);
+  }
+  return mRelList;
+}
+
 already_AddRefed<nsIURI>
 HTMLLinkElement::GetHrefURI() const
 {
   return GetHrefURIForAnchors();
 }
 
 already_AddRefed<nsIURI>
 HTMLLinkElement::GetStyleSheetURL(bool* aIsInline)
--- a/content/html/content/src/HTMLLinkElement.h
+++ b/content/html/content/src/HTMLLinkElement.h
@@ -91,16 +91,17 @@ public:
   {
     SetHTMLAttr(nsGkAtoms::crossorigin, aCrossOrigin, aRv);
   }
   // XPCOM GetRel is fine.
   void SetRel(const nsAString& aRel, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::rel, aRel, aRv);
   }
+  nsDOMTokenList* RelList();
   // XPCOM GetMedia is fine.
   void SetMedia(const nsAString& aMedia, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::media, aMedia, aRv);
   }
   // XPCOM GetHreflang is fine.
   void SetHreflang(const nsAString& aHreflang, ErrorResult& aRv)
   {
@@ -135,14 +136,15 @@ protected:
                                  nsAString& aMedia,
                                  bool* aIsScoped,
                                  bool* aIsAlternate) MOZ_OVERRIDE;
   virtual CORSMode GetCORSMode() const;
 protected:
   // nsGenericHTMLElement
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
+  nsRefPtr<nsDOMTokenList > mRelList;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLLinkElement_h
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -1724,16 +1724,24 @@ void HTMLMediaElement::SetMutedInternal(
   SetVolumeInternal();
 }
 
 void HTMLMediaElement::SetVolumeInternal()
 {
   float effectiveVolume = mMuted ? 0.0f :
     mAudioChannelFaded ? float(mVolume) * FADED_VOLUME_RATIO : float(mVolume);
 
+  if (mAudioChannelAgent) {
+    float volume;
+    nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+    if (NS_SUCCEEDED(rv)) {
+      effectiveVolume *= volume;
+    }
+  }
+
   if (mDecoder) {
     mDecoder->SetVolume(effectiveVolume);
   } else if (mAudioStream) {
     mAudioStream->SetVolume(effectiveVolume);
   } else if (mSrcStream) {
     GetSrcMediaStream()->SetAudioOutputVolume(this, effectiveVolume);
   }
 }
@@ -3874,19 +3882,21 @@ void HTMLMediaElement::UpdateAudioChanne
       nsresult rv;
       mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
       if (!mAudioChannelAgent) {
         return;
       }
       nsCOMPtr<nsIDOMHTMLVideoElement> video = do_QueryObject(this);
       // Use a weak ref so the audio channel agent can't leak |this|.
       if (AUDIO_CHANNEL_NORMAL == mAudioChannelType && video) {
-        mAudioChannelAgent->InitWithVideo(mAudioChannelType, this, true);
+        mAudioChannelAgent->InitWithVideo(OwnerDoc()->GetWindow(),
+                                          mAudioChannelType, this, true);
       } else {
-        mAudioChannelAgent->InitWithWeakCallback(mAudioChannelType, this);
+        mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetWindow(),
+                                                 mAudioChannelType, this);
       }
       mAudioChannelAgent->SetVisibilityState(!OwnerDoc()->Hidden());
     }
 
     if (mPlayingThroughTheAudioChannel) {
       int32_t canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
       CanPlayChanged(canPlay);
@@ -3914,16 +3924,22 @@ NS_IMETHODIMP HTMLMediaElement::CanPlayC
 
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   UpdateChannelMuteState(static_cast<AudioChannelState>(canPlay));
   mPaused.SetCanPlay(canPlay != AUDIO_CHANNEL_STATE_MUTED);
   return NS_OK;
 }
 
+NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
+{
+  SetVolumeInternal();
+  return NS_OK;
+}
+
 /* readonly attribute TextTrackList textTracks; */
 TextTrackList*
 HTMLMediaElement::TextTracks() const
 {
   return mTextTrackManager ? mTextTrackManager->TextTracks() : nullptr;
 }
 
 already_AddRefed<TextTrack>
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -1071,17 +1071,18 @@ nsGenericHTMLElement::ParseAttribute(int
 
     if (aAttribute == nsGkAtoms::contenteditable) {
       aResult.ParseAtom(aValue);
       return true;
     }
 
     if (aAttribute == nsGkAtoms::itemref ||
         aAttribute == nsGkAtoms::itemprop ||
-        aAttribute == nsGkAtoms::itemtype) {
+        aAttribute == nsGkAtoms::itemtype ||
+        aAttribute == nsGkAtoms::rel) {
       aResult.ParseAtomArray(aValue);
       return true;
     }
   }
 
   return nsGenericHTMLElementBase::ParseAttribute(aNamespaceID, aAttribute,
                                                   aValue, aResult);
 }
--- a/content/html/content/test/mochitest.ini
+++ b/content/html/content/test/mochitest.ini
@@ -430,16 +430,17 @@ skip-if = buildapp == 'b2g' # b2g(Crash,
 skip-if = buildapp == 'b2g' || toolkit == 'android' # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported)
 [test_iframe_sandbox_popups.html]
 skip-if = buildapp == 'b2g' # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_iframe_sandbox_popups_inheritance.html]
 skip-if = buildapp == 'b2g' # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_iframe_sandbox_same_origin.html]
 [test_iframe_sandbox_workers.html]
 [test_img_attributes_reflection.html]
+[test_imageSrcSet.html]
 [test_li_attributes_reflection.html]
 [test_link_attributes_reflection.html]
 [test_map_attributes_reflection.html]
 [test_meta_attributes_reflection.html]
 [test_mod_attributes_reflection.html]
 [test_mozaudiochannel.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(Perma-orange on debug emulator) b2g-desktop(Bug 931116, b2g desktop specific, initial triage)
 [test_named_options.html]
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_imageSrcSet.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=980243
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 980243</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 980243 **/
+  SimpleTest.waitForExplicitFinish();
+
+  addLoadEvent(function() {
+    var img = document.querySelector("img");
+    img.onload = function() {
+      ok(true, "Reached here");
+      SimpleTest.finish();
+    }
+    // If ths spec ever changes to treat .src sets differently from
+    // setAttribute("src"), we'll need some sort of canonicalization step
+    // earlier to make the attr value an absolute URI.
+    img.setAttribute("src", img.getAttribute("src"));
+  });
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=980243">Mozilla Bug 980243</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  <img src="file_formSubmission_img.jpg">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/content/media/AbstractMediaDecoder.h
+++ b/content/media/AbstractMediaDecoder.h
@@ -107,16 +107,20 @@ public:
   // May be called by the reader to notify this decoder that the metadata from
   // the media file has been read. Call on the decode thread only.
   virtual void OnReadMetadataCompleted() = 0;
 
   // Returns the owner of this media decoder. The owner should only be used
   // on the main thread.
   virtual MediaDecoderOwner* GetOwner() = 0;
 
+  // May be called by the reader to notify the decoder that the resources
+  // required to begin playback have been acquired. Can be called on any thread.
+  virtual void NotifyWaitingForResourcesStatusChanged() = 0;
+
   // Stack based class to assist in notifying the frame statistics of
   // parsed and decoded frames. Use inside video demux & decode functions
   // to ensure all parsed and decoded frames are reported on all return paths.
   class AutoNotifyDecoded {
   public:
     AutoNotifyDecoded(AbstractMediaDecoder* aDecoder, uint32_t& aParsed, uint32_t& aDecoded)
       : mDecoder(aDecoder), mParsed(aParsed), mDecoded(aDecoded) {}
     ~AutoNotifyDecoded() {
--- a/content/media/BufferDecoder.cpp
+++ b/content/media/BufferDecoder.cpp
@@ -177,16 +177,22 @@ BufferDecoder::UpdatePlaybackPosition(in
 }
 
 void
 BufferDecoder::OnReadMetadataCompleted()
 {
   // ignore
 }
 
+void
+BufferDecoder::NotifyWaitingForResourcesStatusChanged()
+{
+  // ignore
+}
+
 MediaDecoderOwner*
 BufferDecoder::GetOwner()
 {
   // unknown
   return nullptr;
 }
 
 } // namespace mozilla
--- a/content/media/BufferDecoder.h
+++ b/content/media/BufferDecoder.h
@@ -69,16 +69,18 @@ public:
   virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
 
   virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual MediaDecoderOwner* GetOwner() MOZ_FINAL MOZ_OVERRIDE;
 
+  virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE;
+
 protected:
   // This monitor object is not really used to synchronize access to anything.
   // It's just there in order for us to be able to override
   // GetReentrantMonitor correctly.
   ReentrantMonitor mReentrantMonitor;
   nsCOMPtr<nsIThread> mDecodeThread;
   nsRefPtr<MediaResource> mResource;
 };
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1587,16 +1587,25 @@ void MediaDecoder::UpdatePlaybackPositio
   mDecoderStateMachine->UpdatePlaybackPosition(aTime);
 }
 
 // Provide access to the state machine object
 MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
   return mDecoderStateMachine;
 }
 
+void
+MediaDecoder::NotifyWaitingForResourcesStatusChanged()
+{
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  if (mDecoderStateMachine) {
+    mDecoderStateMachine->NotifyWaitingForResourcesStatusChanged();
+  }
+}
+
 bool MediaDecoder::IsShutdown() const {
   NS_ENSURE_TRUE(GetStateMachine(), true);
   return GetStateMachine()->IsShutdown();
 }
 
 int64_t MediaDecoder::GetEndMediaTime() const {
   NS_ENSURE_TRUE(GetStateMachine(), -1);
   return GetStateMachine()->GetEndMediaTime();
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -336,16 +336,18 @@ public:
   // Pause video playback.
   virtual void Pause();
   // Adjust the speed of the playback, optionally with pitch correction,
   virtual void SetVolume(double aVolume);
   // Sets whether audio is being captured. If it is, we won't play any
   // of our audio.
   virtual void SetAudioCaptured(bool aCaptured);
 
+  virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE;
+
   void SetPlaybackRate(double aPlaybackRate);
   void SetPreservesPitch(bool aPreservesPitch);
 
   // All MediaStream-related data is protected by mReentrantMonitor.
   // We have at most one DecodedStreamData per MediaDecoder. Its stream
   // is used as the input for each ProcessedMediaStream created by calls to
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -73,26 +73,26 @@ public:
   // Moves the decode head to aTime microseconds. aStartTime and aEndTime
   // denote the start and end times of the media in usecs, and aCurrentTime
   // is the current playback position in microseconds.
   virtual nsresult Seek(int64_t aTime,
                         int64_t aStartTime,
                         int64_t aEndTime,
                         int64_t aCurrentTime) = 0;
 
-  // Called when the decode thread is started, before calling any other
-  // decode, read metadata, or seek functions. Do any thread local setup
-  // in this function.
-  virtual void OnDecodeThreadStart() {}
-
-  // Called when the decode thread is about to finish, after all calls to
-  // any other decode, read metadata, or seek functions. Any backend specific
-  // thread local tear down must be done in this function. Note that another
-  // decode thread could start up and run in future.
-  virtual void OnDecodeThreadFinish() {}
+  // Called to move the reader into idle/active state. When the reader is
+  // created it is assumed to be active (i.e. not idle). When the media
+  // element is paused and we don't need to decode any more data, the state
+  // machine calls SetIdle() to inform the reader that its decoder won't be
+  // needed for a while. When we need to decode data again, the state machine
+  // calls SetActive() to activate the decoder. The reader can use these
+  // notifications to enter/exit a low power state when the decoder isn't
+  // needed, if desired. This is most useful on mobile.
+  virtual void SetIdle() { }
+  virtual void SetActive() { }
 
   // Tell the reader that the data decoded are not for direct playback, so it
   // can accept more files, in particular those which have more channels than
   // available in the audio output.
   void SetIgnoreAudioOutputFormat()
   {
     mIgnoreAudioOutputFormat = true;
   }
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -77,17 +77,17 @@ const int64_t AMPLE_AUDIO_USECS = 100000
 // Maximum number of bytes we'll allocate and write at once to the audio
 // hardware when the audio stream contains missing frames and we're
 // writing silence in order to fill the gap. We limit our silence-writes
 // to 32KB in order to avoid allocating an impossibly large chunk of
 // memory if we encounter a large chunk of silence.
 const uint32_t SILENCE_BYTES_CHUNK = 32 * 1024;
 
 // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
-// we're not "pumping video", we'll skip the video up to the next keyframe
+// we're not "prerolling video", we'll skip the video up to the next keyframe
 // which is at or after the current playback position.
 static const uint32_t LOW_VIDEO_FRAMES = 1;
 
 // Arbitrary "frame duration" when playing only audio.
 static const int AUDIO_DURATION_USECS = 40000;
 
 // If we increase our "low audio threshold" (see LOW_AUDIO_USECS above), we
 // use this as a factor in all our calculations. Increasing this will cause
@@ -165,62 +165,55 @@ MediaDecoderStateMachine::MediaDecoderSt
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mVolume(1.0),
   mPlaybackRate(1.0),
   mPreservesPitch(true),
   mBasePosition(0),
+  mAmpleVideoFrames(2),
+  mLowAudioThresholdUsecs(LOW_AUDIO_USECS),
+  mAmpleAudioThresholdUsecs(AMPLE_AUDIO_USECS),
+  mDispatchedAudioDecodeTask(false),
+  mDispatchedVideoDecodeTask(false),
+  mIsReaderIdle(false),
   mAudioCaptured(false),
   mTransportSeekable(true),
   mMediaSeekable(true),
   mPositionChangeQueued(false),
   mAudioCompleted(false),
   mGotDurationFromMetaData(false),
-  mStopDecodeThread(true),
   mDispatchedEventToDecode(false),
   mStopAudioThread(true),
   mQuickBuffering(false),
   mIsRunning(false),
   mRunAgain(false),
   mDispatchedRunEvent(false),
   mDecodeThreadWaiting(false),
   mRealTime(aRealTime),
-  mDidThrottleAudioDecoding(false),
-  mDidThrottleVideoDecoding(false),
   mEventManager(aDecoder),
   mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  // only enable realtime mode when "media.realtime_decoder.enabled" is true.
+  // Only enable realtime mode when "media.realtime_decoder.enabled" is true.
   if (Preferences::GetBool("media.realtime_decoder.enabled", false) == false)
     mRealTime = false;
 
+  mAmpleVideoFrames =
+    std::max<uint32_t>(Preferences::GetUint("media.video-queue.default-size", 10), 3);
+
   mBufferingWait = mRealTime ? 0 : BUFFERING_WAIT_S;
   mLowDataThresholdUsecs = mRealTime ? 0 : LOW_DATA_THRESHOLD_USECS;
 
-  // If we've got more than mAmpleVideoFrames decoded video frames waiting in
-  // the video queue, we will not decode any more video frames until some have
-  // been consumed by the play state machine thread.
-#if defined(MOZ_OMX_DECODER) || defined(MOZ_MEDIA_PLUGINS)
-  // On B2G and Android this is decided by a similar value which varies for
-  // each OMX decoder |OMX_PARAM_PORTDEFINITIONTYPE::nBufferCountMin|. This
-  // number must be less than the OMX equivalent or gecko will think it is
-  // chronically starved of video frames. All decoders seen so far have a value
-  // of at least 4.
-  mAmpleVideoFrames = Preferences::GetUint("media.video-queue.default-size", 3);
-#else
-  mAmpleVideoFrames = Preferences::GetUint("media.video-queue.default-size", 10);
-#endif
-  if (mAmpleVideoFrames < 2) {
-    mAmpleVideoFrames = 2;
-  }
+  mVideoPrerollFrames = mRealTime ? 0 : mAmpleVideoFrames / 2;
+  mAudioPrerollUsecs = mRealTime ? 0 : LOW_AUDIO_USECS * 2;
+
 #ifdef XP_WIN
   // Ensure high precision timers are enabled on Windows, otherwise the state
   // machine thread isn't woken up at reliable intervals to set the next frame,
   // and we drop frames while painting. Note that multiple calls to this
   // function per-process is OK, provided each call is matched by a corresponding
   // timeEndPeriod() call.
   timeBeginPeriod(1);
 #endif
@@ -263,76 +256,26 @@ bool MediaDecoderStateMachine::HasFuture
 
 bool MediaDecoderStateMachine::HaveNextFrameData() const {
   AssertCurrentThreadInMonitor();
   return (!HasAudio() || HasFutureAudio()) &&
          (!HasVideo() || mReader->VideoQueue().GetSize() > 0);
 }
 
 int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() {
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
+               "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
   int64_t audioDecoded = mReader->AudioQueue().Duration();
   if (mAudioEndTime != -1) {
     audioDecoded += mAudioEndTime - GetMediaTime();
   }
   return audioDecoded;
 }
 
-void MediaDecoderStateMachine::DecodeThreadRun()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  if (mReader) {
-    mReader->OnDecodeThreadStart();
-  }
-
-  if (mState == DECODER_STATE_DECODING_METADATA &&
-      NS_FAILED(DecodeMetadata())) {
-    NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                  "Should be in shutdown state if metadata loading fails.");
-    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-  }
-
-  while (mState != DECODER_STATE_SHUTDOWN &&
-         mState != DECODER_STATE_COMPLETED &&
-         mState != DECODER_STATE_DORMANT &&
-         !mStopDecodeThread)
-  {
-    if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
-      DecodeLoop();
-    } else if (mState == DECODER_STATE_SEEKING) {
-      DecodeSeek();
-    } else if (mState == DECODER_STATE_DECODING_METADATA) {
-      if (NS_FAILED(DecodeMetadata())) {
-        NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                      "Should be in shutdown state if metadata loading fails.");
-        DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decode thread"));
-      }
-    } else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
-      mDecoder->GetReentrantMonitor().Wait();
-
-      if (!mReader->IsWaitingMediaResources()) {
-        // change state to DECODER_STATE_WAIT_FOR_RESOURCES
-        StartDecodeMetadata();
-      }
-    } else if (mState == DECODER_STATE_DORMANT) {
-      mDecoder->GetReentrantMonitor().Wait();
-    }
-  }
-
-  if (mReader) {
-    mReader->OnDecodeThreadFinish();
-  }
-
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
-  mDispatchedEventToDecode = false;
-  mon.NotifyAll();
-}
-
 void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
                                                DecodedStreamData* aStream,
                                                AudioSegment* aOutput)
 {
   NS_ASSERTION(OnDecodeThread() ||
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
 
@@ -518,30 +461,27 @@ void MediaDecoderStateMachine::SendStrea
       stream->mHaveSentFinish = true;
       stream->mStream->Finish();
     }
   }
 
   if (mAudioCaptured) {
     // Discard audio packets that are no longer needed.
     while (true) {
-      nsAutoPtr<AudioData> a(mReader->AudioQueue().PopFront());
-      if (!a)
-        break;
+      const AudioData* a = mReader->AudioQueue().PeekFront();
       // Packet times are not 100% reliable so this may discard packets that
       // actually contain data for mCurrentFrameTime. This means if someone might
       // create a new output stream and we actually don't have the audio for the
       // very start. That's OK, we'll play silence instead for a brief moment.
       // That's OK. Seeking to this time would have a similar issue for such
       // badly muxed resources.
-      if (a->GetEndTime() >= minLastAudioPacketTime) {
-        mReader->AudioQueue().PushFront(a.forget());
+      if (!a || a->GetEndTime() >= minLastAudioPacketTime)
         break;
-      }
       mAudioEndTime = std::max(mAudioEndTime, a->GetEndTime());
+      delete mReader->AudioQueue().PopFront();
     }
 
     if (finished) {
       mAudioCompleted = true;
       UpdateReadyState();
     }
   }
 }
@@ -580,211 +520,199 @@ bool MediaDecoderStateMachine::HaveEnoug
 
   return true;
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
 {
   AssertCurrentThreadInMonitor();
 
-  if (static_cast<uint32_t>(mReader->VideoQueue().GetSize()) < GetAmpleVideoFrames() * mPlaybackRate) {
+  if (static_cast<uint32_t>(mReader->VideoQueue().GetSize()) < mAmpleVideoFrames * mPlaybackRate) {
     return false;
   }
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
     if (!stream->mStream->HaveEnoughBuffered(TRACK_VIDEO)) {
       return false;
     }
     stream->mStream->DispatchWhenNotEnoughBuffered(TRACK_VIDEO,
         GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
-void MediaDecoderStateMachine::DecodeLoop()
+bool
+MediaDecoderStateMachine::NeedToDecodeVideo()
 {
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get()));
-
   AssertCurrentThreadInMonitor();
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  return mIsVideoDecoding && !HaveEnoughDecodedVideo();
+}
+
+void
+MediaDecoderStateMachine::DecodeVideo()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
 
-  // We want to "pump" the decode until we've got a few frames decoded
-  // before we consider whether decode is falling behind.
-  bool audioPump = true;
-  bool videoPump = true;
-
-  // If the video decode is falling behind the audio, we'll start dropping the
-  // inter-frames up until the next keyframe which is at or before the current
-  // playback position. skipToNextKeyframe is true if we're currently
-  // skipping up to the next keyframe.
-  bool skipToNextKeyframe = false;
-
-  // Once we've decoded more than videoPumpThreshold video frames, we'll
-  // no longer be considered to be "pumping video".
-  const unsigned videoPumpThreshold = mRealTime ? 0 : GetAmpleVideoFrames() / 2;
-
-  // After the audio decode fills with more than audioPumpThreshold usecs
-  // of decoded audio, we'll start to check whether the audio or video decode
-  // is falling behind.
-  const unsigned audioPumpThreshold = mRealTime ? 0 : LOW_AUDIO_USECS * 2;
-
-  // Our local low audio threshold. We may increase this if we're slow to
-  // decode video frames, in order to reduce the chance of audio underruns.
-  int64_t lowAudioThreshold = LOW_AUDIO_USECS;
-
-  // Our local ample audio threshold. If we increase lowAudioThreshold, we'll
-  // also increase this too appropriately (we don't want lowAudioThreshold to
-  // be greater than ampleAudioThreshold, else we'd stop decoding!).
-  int64_t ampleAudioThreshold = AMPLE_AUDIO_USECS;
-
-  // Main decode loop.
-  bool videoPlaying = HasVideo();
-  bool audioPlaying = HasAudio();
-  while ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
-         !mStopDecodeThread &&
-         (videoPlaying || audioPlaying))
+  if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) {
+    mDispatchedVideoDecodeTask = false;
+    return;
+  }
+  EnsureActive();
+
+  // We don't want to consider skipping to the next keyframe if we've
+  // only just started up the decode loop, so wait until we've decoded
+  // some frames before enabling the keyframe skip logic on video.
+  if (mIsVideoPrerolling &&
+      (static_cast<uint32_t>(mReader->VideoQueue().GetSize())
+        >= mVideoPrerollFrames * mPlaybackRate))
   {
-    // We don't want to consider skipping to the next keyframe if we've
-    // only just started up the decode loop, so wait until we've decoded
-    // some frames before enabling the keyframe skip logic on video.
-    if (videoPump &&
+    mIsVideoPrerolling = false;
+  }
+
+  // We'll skip the video decode to the nearest keyframe if we're low on
+  // audio, or if we're low on video, provided we're not running low on
+  // data to decode. If we're running low on downloaded data to decode,
+  // we won't start keyframe skipping, as we'll be pausing playback to buffer
+  // soon anyway and we'll want to be able to display frames immediately
+  // after buffering finishes.
+  if (mState == DECODER_STATE_DECODING &&
+      !mSkipToNextKeyFrame &&
+      mIsVideoDecoding &&
+      ((!mIsAudioPrerolling && mIsAudioDecoding &&
+        GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) ||
+        (!mIsVideoPrerolling && mIsVideoDecoding &&
         (static_cast<uint32_t>(mReader->VideoQueue().GetSize())
-         >= videoPumpThreshold * mPlaybackRate))
-    {
-      videoPump = false;
-    }
-
-    // We don't want to consider skipping to the next keyframe if we've
-    // only just started up the decode loop, so wait until we've decoded
-    // some audio data before enabling the keyframe skip logic on audio.
-    if (audioPump && GetDecodedAudioDuration() >= audioPumpThreshold * mPlaybackRate) {
-      audioPump = false;
-    }
-
-    // We'll skip the video decode to the nearest keyframe if we're low on
-    // audio, or if we're low on video, provided we're not running low on
-    // data to decode. If we're running low on downloaded data to decode,
-    // we won't start keyframe skipping, as we'll be pausing playback to buffer
-    // soon anyway and we'll want to be able to display frames immediately
-    // after buffering finishes.
-    if (mState == DECODER_STATE_DECODING &&
-        !skipToNextKeyframe &&
-        videoPlaying &&
-        ((!audioPump && audioPlaying && !mDidThrottleAudioDecoding &&
-          GetDecodedAudioDuration() < lowAudioThreshold * mPlaybackRate) ||
-         (!videoPump && videoPlaying && !mDidThrottleVideoDecoding &&
-          (static_cast<uint32_t>(mReader->VideoQueue().GetSize())
-           < LOW_VIDEO_FRAMES * mPlaybackRate))) &&
-        !HasLowUndecodedData())
-    {
-      skipToNextKeyframe = true;
-      DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
-    }
-
-    // Video decode.
-    bool throttleVideoDecoding = !videoPlaying || HaveEnoughDecodedVideo();
-    if (mDidThrottleVideoDecoding && !throttleVideoDecoding) {
-      videoPump = true;
-    }
-    mDidThrottleVideoDecoding = throttleVideoDecoding;
-    if (!throttleVideoDecoding)
-    {
-      // Time the video decode, so that if it's slow, we can increase our low
-      // audio threshold to reduce the chance of an audio underrun while we're
-      // waiting for a video decode to complete.
-      TimeDuration decodeTime;
-      {
-        int64_t currentTime = GetMediaTime();
-        ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-        TimeStamp start = TimeStamp::Now();
-        videoPlaying = mReader->DecodeVideoFrame(skipToNextKeyframe, currentTime);
-        decodeTime = TimeStamp::Now() - start;
-        if (!videoPlaying) {
-          // Playback ended for this stream, close the sample queue.
-          mReader->VideoQueue().Finish();
-        }
-      }
-      if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > lowAudioThreshold &&
-          !HasLowUndecodedData())
-      {
-        lowAudioThreshold =
-          std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
-        ampleAudioThreshold = std::max(THRESHOLD_FACTOR * lowAudioThreshold,
-                                     ampleAudioThreshold);
-        DECODER_LOG(PR_LOG_DEBUG,
-                    ("Slow video decode, set lowAudioThreshold=%lld ampleAudioThreshold=%lld",
-                    lowAudioThreshold, ampleAudioThreshold));
-      }
-    }
-
-    // Audio decode.
-    bool throttleAudioDecoding = !audioPlaying || HaveEnoughDecodedAudio(ampleAudioThreshold * mPlaybackRate);
-    if (mDidThrottleAudioDecoding && !throttleAudioDecoding) {
-      audioPump = true;
-    }
-    mDidThrottleAudioDecoding = throttleAudioDecoding;
-    if (!mDidThrottleAudioDecoding) {
-      ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-      audioPlaying = mReader->DecodeAudioData();
-      if (!audioPlaying) {
-        // Playback ended for this stream, close the sample queue.
-        mReader->AudioQueue().Finish();
-      }
-    }
-
-    SendStreamData();
-
-    // Notify to ensure that the AudioLoop() is not waiting, in case it was
-    // waiting for more audio to be decoded.
-    mDecoder->GetReentrantMonitor().NotifyAll();
-
-    // The ready state can change when we've decoded data, so update the
-    // ready state, so that DOM events can fire.
-    UpdateReadyState();
-
-    if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
-        !mStopDecodeThread &&
-        (videoPlaying || audioPlaying) &&
-        throttleAudioDecoding && throttleVideoDecoding)
-    {
-      // All active bitstreams' decode is well ahead of the playback
-      // position, we may as well wait for the playback to catch up. Note the
-      // audio push thread acquires and notifies the decoder monitor every time
-      // it pops AudioData off the audio queue. So if the audio push thread pops
-      // the last AudioData off the audio queue right after that queue reported
-      // it was non-empty here, we'll receive a notification on the decoder
-      // monitor which will wake us up shortly after we sleep, thus preventing
-      // both the decode and audio push threads waiting at the same time.
-      // See bug 620326.
-      mDecodeThreadWaiting = true;
-      if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING) {
-        // We're not playing, and the decode is about to wait. This means
-        // the decode thread may not be needed in future. Signal the state
-        // machine thread to run, so it can decide whether to shutdown the
-        // decode thread.
-        ScheduleStateMachine();
-      }
-      mDecoder->GetReentrantMonitor().Wait();
-      mDecodeThreadWaiting = false;
-    }
-
-  } // End decode loop.
-
-  if (!mStopDecodeThread &&
-      mState != DECODER_STATE_SHUTDOWN &&
-      mState != DECODER_STATE_DORMANT &&
-      mState != DECODER_STATE_SEEKING)
+          < LOW_VIDEO_FRAMES * mPlaybackRate))) &&
+      !HasLowUndecodedData())
+  {
+    mSkipToNextKeyFrame = true;
+    DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
+  }
+
+  // Time the video decode, so that if it's slow, we can increase our low
+  // audio threshold to reduce the chance of an audio underrun while we're
+  // waiting for a video decode to complete.
+  TimeDuration decodeTime;
+  {
+    int64_t currentTime = GetMediaTime();
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    TimeStamp start = TimeStamp::Now();
+    mIsVideoDecoding = mReader->DecodeVideoFrame(mSkipToNextKeyFrame, currentTime);
+    decodeTime = TimeStamp::Now() - start;
+  }
+  if (!mIsVideoDecoding) {
+    // Playback ended for this stream, close the sample queue.
+    mReader->VideoQueue().Finish();
+    CheckIfDecodeComplete();
+  }
+
+  if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
+      !HasLowUndecodedData())
   {
+    mLowAudioThresholdUsecs =
+      std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
+    mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
+                                          mAmpleAudioThresholdUsecs);
+    DECODER_LOG(PR_LOG_DEBUG,
+                ("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
+                mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs));
+  }
+
+  SendStreamData();
+
+  // The ready state can change when we've decoded data, so update the
+  // ready state, so that DOM events can fire.
+  UpdateReadyState();
+
+  mDispatchedVideoDecodeTask = false;
+  DispatchDecodeTasksIfNeeded();
+}
+
+bool
+MediaDecoderStateMachine::NeedToDecodeAudio()
+{
+  AssertCurrentThreadInMonitor();
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  return mIsAudioDecoding &&
+         !HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate);
+}
+
+void
+MediaDecoderStateMachine::DecodeAudio()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) {
+    mDispatchedAudioDecodeTask = false;
+    return;
+  }
+  EnsureActive();
+
+  // We don't want to consider skipping to the next keyframe if we've
+  // only just started up the decode loop, so wait until we've decoded
+  // some audio data before enabling the keyframe skip logic on audio.
+  if (mIsAudioPrerolling &&
+      GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) {
+    mIsAudioPrerolling = false;
+  }
+
+  {
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    mIsAudioDecoding = mReader->DecodeAudioData();
+  }
+  if (!mIsAudioDecoding) {
+    // Playback ended for this stream, close the sample queue.
+    mReader->AudioQueue().Finish();
+    CheckIfDecodeComplete();
+  }
+
+  SendStreamData();
+
+  // Notify to ensure that the AudioLoop() is not waiting, in case it was
+  // waiting for more audio to be decoded.
+  mDecoder->GetReentrantMonitor().NotifyAll();
+
+  // The ready state can change when we've decoded data, so update the
+  // ready state, so that DOM events can fire.
+  UpdateReadyState();
+
+  mDispatchedAudioDecodeTask = false;
+  DispatchDecodeTasksIfNeeded();
+}
+
+void
+MediaDecoderStateMachine::CheckIfDecodeComplete()
+{
+  AssertCurrentThreadInMonitor();
+  if (mState == DECODER_STATE_SHUTDOWN ||
+      mState == DECODER_STATE_SEEKING ||
+      mState == DECODER_STATE_COMPLETED) {
+    // Don't change our state if we've already been shutdown, or we're seeking,
+    // since we don't want to abort the shutdown or seek processes.
+    return;
+  }
+  MOZ_ASSERT(!mReader->AudioQueue().IsFinished() || !mIsAudioDecoding);
+  MOZ_ASSERT(!mReader->VideoQueue().IsFinished() || !mIsVideoDecoding);
+  if (!mIsVideoDecoding && !mIsAudioDecoding) {
+    // We've finished decoding all active streams,
+    // so move to COMPLETED state.
     mState = DECODER_STATE_COMPLETED;
+    DispatchDecodeTasksIfNeeded();
     ScheduleStateMachine();
   }
-
-  DECODER_LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get()));
+  DECODER_LOG(PR_LOG_DEBUG,
+    ("%p CheckIfDecodeComplete %scompleted", mDecoder.get(),
+    ((mState == DECODER_STATE_COMPLETED) ? "" : "NOT ")));
 }
 
 bool MediaDecoderStateMachine::IsPlaying()
 {
   AssertCurrentThreadInMonitor();
 
   return !mPlayStartTime.IsNull();
 }
@@ -1121,16 +1049,18 @@ void MediaDecoderStateMachine::StopPlayb
     mPlayDuration = GetClock();
     mPlayStartTime = TimeStamp();
   }
   // Notify the audio thread, so that it notices that we've stopped playing,
   // so it can pause audio playback.
   mDecoder->GetReentrantMonitor().NotifyAll();
   NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
   mDecoder->UpdateStreamBlockingForStateMachinePlaying();
+
+  DispatchDecodeTasksIfNeeded();
 }
 
 void MediaDecoderStateMachine::SetSyncPointForMediaStream()
 {
   AssertCurrentThreadInMonitor();
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
   if (!stream) {
@@ -1373,37 +1303,63 @@ void MediaDecoderStateMachine::Shutdown(
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (mState != DECODER_STATE_DECODING) {
-    mDecodeStartTime = TimeStamp::Now();
+  if (mState == DECODER_STATE_DECODING) {
+    return;
   }
   mState = DECODER_STATE_DECODING;
+
+  mDecodeStartTime = TimeStamp::Now();
+
+  // Reset our "stream finished decoding" flags, so we try to decode all
+  // streams that we have when we start decoding.
+  mIsVideoDecoding = HasVideo() && !mReader->VideoQueue().IsFinished();
+  mIsAudioDecoding = HasAudio() && !mReader->AudioQueue().IsFinished();
+
+  CheckIfDecodeComplete();
+  if (mState == DECODER_STATE_COMPLETED) {
+    return;
+  }
+
+  // Reset other state to pristine values before starting decode.
+  mSkipToNextKeyFrame = false;
+  mIsAudioPrerolling = true;
+  mIsVideoPrerolling = true;
+
+  // Ensure that we've got tasks enqueued to decode data if we need to.
+  DispatchDecodeTasksIfNeeded();
+
   ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::StartWaitForResources()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  AssertCurrentThreadInMonitor();
   mState = DECODER_STATE_WAIT_FOR_RESOURCES;
 }
 
-void MediaDecoderStateMachine::StartDecodeMetadata()
+void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
 {
-  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
-               "Should be on state machine or decode thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  AssertCurrentThreadInMonitor();
+  if (mState != DECODER_STATE_WAIT_FOR_RESOURCES ||
+      mReader->IsWaitingMediaResources()) {
+    return;
+  }
+  // The reader is no longer waiting for resources (say a hardware decoder),
+  // we can now proceed to decode metadata.
   mState = DECODER_STATE_DECODING_METADATA;
+  EnqueueDecodeMetadataTask();
 }
 
 void MediaDecoderStateMachine::Play()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   // When asked to play, switch to decoding state only if
   // we are currently buffering. In other cases, we'll start playing anyway
   // when the state machine notices the decoder's state change to PLAYING.
@@ -1487,24 +1443,16 @@ void MediaDecoderStateMachine::Seek(doub
   DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime));
   mState = DECODER_STATE_SEEKING;
   if (mDecoder->GetDecodedStream()) {
     mDecoder->RecreateDecodedStream(mSeekTime - mStartTime);
   }
   ScheduleStateMachine();
 }
 
-void MediaDecoderStateMachine::StopDecodeThread()
-{
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-  AssertCurrentThreadInMonitor();
-  mStopDecodeThread = true;
-  mDecoder->GetReentrantMonitor().NotifyAll();
-}
-
 void MediaDecoderStateMachine::StopAudioThread()
 {
   NS_ASSERTION(OnDecodeThread() ||
                OnStateMachineThread(), "Should be on decode thread or state machine thread");
   AssertCurrentThreadInMonitor();
 
   if (mStopAudioThread) {
     // Nothing to do, since the thread is already stopping
@@ -1522,31 +1470,205 @@ void MediaDecoderStateMachine::StopAudio
     mAudioThread = nullptr;
     // Now that the audio thread is dead, try sending data to our MediaStream(s).
     // That may have been waiting for the audio thread to stop.
     SendStreamData();
   }
 }
 
 nsresult
-MediaDecoderStateMachine::ScheduleDecodeThread()
+MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
+{
+  AssertCurrentThreadInMonitor();
+
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return NS_OK;
+  }
+  nsresult rv = mDecodeTaskQueue->Dispatch(
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+void
+MediaDecoderStateMachine::EnsureActive()
+{
+  AssertCurrentThreadInMonitor();
+  MOZ_ASSERT(OnDecodeThread());
+  if (!mIsReaderIdle) {
+    return;
+  }
+  mIsReaderIdle = false;
+  SetReaderActive();
+}
+
+void
+MediaDecoderStateMachine::SetReaderIdle()
+{
+  DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderIdle()", mDecoder.get()));
+  MOZ_ASSERT(OnDecodeThread());
+  mReader->SetIdle();
+}
+
+void
+MediaDecoderStateMachine::SetReaderActive()
+{
+  DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderActive()", mDecoder.get()));
+  MOZ_ASSERT(OnDecodeThread());
+  mReader->SetActive();
+}
+
+void
+MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
 {
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   AssertCurrentThreadInMonitor();
-
-  mStopDecodeThread = false;
+  
+  // NeedToDecodeAudio() can go from false to true while we hold the
+  // monitor, but it can't go from true to false. This can happen because
+  // NeedToDecodeAudio() takes into account the amount of decoded audio
+  // that's been written to the AudioStream but not played yet. So if we
+  // were calling NeedToDecodeAudio() twice and we thread-context switch
+  // between the calls, audio can play, which can affect the return value
+  // of NeedToDecodeAudio() giving inconsistent results. So we cache the
+  // value returned by NeedToDecodeAudio(), and make decisions
+  // based on the cached value. If NeedToDecodeAudio() has
+  // returned false, and then subsequently returns true and we're not
+  // playing, it will probably be OK since we don't need to consume data
+  // anyway.
+
+  const bool needToDecodeAudio = NeedToDecodeAudio();
+  const bool needToDecodeVideo = NeedToDecodeVideo();
+
+  // If we're in completed state, we should not need to decode anything else.
+  MOZ_ASSERT(mState != DECODER_STATE_COMPLETED ||
+             (!needToDecodeAudio && !needToDecodeVideo));
+
+  bool needIdle = mDecoder->GetState() == MediaDecoder::PLAY_STATE_PAUSED &&
+                  !needToDecodeAudio &&
+                  !needToDecodeVideo &&
+                  !IsPlaying();
+
+  if (needToDecodeAudio) {
+    EnsureAudioDecodeTaskQueued();
+  }
+  if (needToDecodeVideo) {
+    EnsureVideoDecodeTaskQueued();
+  }
+
+  if (mIsReaderIdle == needIdle) {
+    return;
+  }
+  mIsReaderIdle = needIdle;
+  nsRefPtr<nsIRunnable> event;
+  if (mIsReaderIdle) {
+    event = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SetReaderIdle);
+  } else {
+    event = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SetReaderActive);
+  }
+  if (NS_FAILED(mDecodeTaskQueue->Dispatch(event)) &&
+      mState != DECODER_STATE_SHUTDOWN) {
+    NS_WARNING("Failed to dispatch event to set decoder idle state");
+  }
+}
+
+nsresult
+MediaDecoderStateMachine::EnqueueDecodeSeekTask()
+{
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  AssertCurrentThreadInMonitor();
+
+  if (mState != DECODER_STATE_SEEKING) {
+    return NS_OK;
+  }
+  nsresult rv = mDecodeTaskQueue->Dispatch(
+    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+
+  if (NeedToDecodeAudio()) {
+    return EnsureAudioDecodeTaskQueued();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+
   if (mState >= DECODER_STATE_COMPLETED) {
     return NS_OK;
   }
-  if (!mDispatchedEventToDecode) {
+
+  MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
+
+  if (mIsAudioDecoding && !mDispatchedAudioDecodeTask) {
     nsresult rv = mDecodeTaskQueue->Dispatch(
-      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun));
-    NS_ENSURE_SUCCESS(rv, rv);
-    mDispatchedEventToDecode = true;
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio));
+    if (NS_SUCCEEDED(rv)) {
+      mDispatchedAudioDecodeTask = true;
+    } else {
+      NS_WARNING("Failed to dispatch task to decode audio");
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+
+  if (NeedToDecodeVideo()) {
+    return EnsureVideoDecodeTaskQueued();
   }
+
+  return NS_OK;
+}
+
+nsresult
+MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+
+  if (mState >= DECODER_STATE_COMPLETED) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA);
+
+  if (mIsVideoDecoding && !mDispatchedVideoDecodeTask) {
+    nsresult rv = mDecodeTaskQueue->Dispatch(
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo));
+    if (NS_SUCCEEDED(rv)) {
+      mDispatchedVideoDecodeTask = true;
+    } else {
+      NS_WARNING("Failed to dispatch task to decode video");
+    }
+  }
+
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::StartAudioThread()
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
@@ -1634,53 +1756,85 @@ bool MediaDecoderStateMachine::HasLowUnd
 void MediaDecoderStateMachine::SetFrameBufferLength(uint32_t aLength)
 {
   NS_ASSERTION(aLength >= 512 && aLength <= 16384,
                "The length must be between 512 and 16384");
   AssertCurrentThreadInMonitor();
   mEventManager.SetSignalBufferLength(aLength);
 }
 
+void
+MediaDecoderStateMachine::DecodeError()
+{
+  AssertCurrentThreadInMonitor();
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+
+  // Change state to shutdown before sending error report to MediaDecoder
+  // and the HTMLMediaElement, so that our pipeline can start exiting
+  // cleanly during the sync dispatch below.
+  DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
+  ScheduleStateMachine();
+  mState = DECODER_STATE_SHUTDOWN;
+  mDecoder->GetReentrantMonitor().NotifyAll();
+
+  // Dispatch the event to call DecodeError synchronously. This ensures
+  // we're in shutdown state by the time we exit the decode thread.
+  // If we just moved to shutdown state here on the decode thread, we may
+  // cause the state machine to shutdown/free memory without closing its
+  // media stream properly, and we'll get callbacks from the media stream
+  // causing a crash.
+ {
+    nsCOMPtr<nsIRunnable> event =
+      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
+    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
+    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
+  }
+}
+
+void
+MediaDecoderStateMachine::CallDecodeMetadata()
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return;
+  }
+  if (NS_FAILED(DecodeMetadata())) {
+    DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decoder"));
+    DecodeError();
+  }
+}
+
 nsresult MediaDecoderStateMachine::DecodeMetadata()
 {
+  AssertCurrentThreadInMonitor();
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mState == DECODER_STATE_DECODING_METADATA,
-               "Only call when in metadata decoding state");
-
   DECODER_LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
+  if (mState != DECODER_STATE_DECODING_METADATA) {
+    return NS_ERROR_FAILURE;
+  }
+  EnsureActive();
+
   nsresult res;
   MediaInfo info;
   MetadataTags* tags;
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     res = mReader->ReadMetadata(&info, &tags);
   }
-  if (NS_SUCCEEDED(res) && (mState == DECODER_STATE_DECODING_METADATA) && (mReader->IsWaitingMediaResources())) {
+  if (NS_SUCCEEDED(res) &&
+      mState == DECODER_STATE_DECODING_METADATA &&
+      mReader->IsWaitingMediaResources()) {
     // change state to DECODER_STATE_WAIT_FOR_RESOURCES
     StartWaitForResources();
     return NS_OK;
   }
 
   mInfo = info;
 
   if (NS_FAILED(res) || (!info.HasValidMedia())) {
-    // Dispatch the event to call DecodeError synchronously. This ensures
-    // we're in shutdown state by the time we exit the decode thread.
-    // If we just moved to shutdown state here on the decode thread, we may
-    // cause the state machine to shutdown/free memory without closing its
-    // media stream properly, and we'll get callbacks from the media stream
-    // causing a crash. Note the state machine shutdown joins this decode
-    // thread during shutdown (and other state machines can run on the state
-    // machine thread while the join is waiting), so it's safe to do this
-    // synchronously.
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
     return NS_ERROR_FAILURE;
   }
   mDecoder->StartProgressUpdates();
   mGotDurationFromMetaData = (GetDuration() != -1);
 
   VideoData* videoData = FindStartTime();
   if (videoData) {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -1719,40 +1873,55 @@ nsresult MediaDecoderStateMachine::Decod
     new AudioMetadataEventRunner(mDecoder,
                                  mInfo.mAudio.mChannels,
                                  mInfo.mAudio.mRate,
                                  HasAudio(),
                                  HasVideo(),
                                  tags);
   NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
 
+  if (HasAudio()) {
+    RefPtr<nsIRunnable> decodeTask(
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
+    mReader->AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
+  }
+  if (HasVideo()) {
+    RefPtr<nsIRunnable> decodeTask(
+      NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
+    mReader->VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue);
+  }
+
   if (mState == DECODER_STATE_DECODING_METADATA) {
     DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get()));
     StartDecoding();
   }
 
+  // For very short media FindStartTime() can decode the entire media.
+  // So we need to check if this has occurred, else our decode pipeline won't
+  // run (since it doesn't need to) and we won't detect end of stream.
+  CheckIfDecodeComplete();
+
   if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED) &&
       mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
       !IsPlaying())
   {
     StartPlayback();
   }
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::DecodeSeek()
 {
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(mState == DECODER_STATE_SEEKING,
-               "Only call when in seeking state");
-
-  mDidThrottleAudioDecoding = false;
-  mDidThrottleVideoDecoding = false;
+  if (mState != DECODER_STATE_SEEKING) {
+    return;
+  }
+  EnsureActive();
 
   // During the seek, don't have a lock on the decoder state,
   // otherwise long seek operations can block the main thread.
   // The events dispatched to the main thread are SYNC calls.
   // These calls are made outside of the decode monitor lock so
   // it is safe for the main thread to makes calls that acquire
   // the lock since it won't deadlock. We check the state when
   // acquiring the lock again in case shutdown has occurred
@@ -1817,16 +1986,18 @@ void MediaDecoderStateMachine::DecodeSee
             ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
             RenderVideoFrame(video, TimeStamp::Now());
           }
           nsCOMPtr<nsIRunnable> event =
             NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate);
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
       }
+    } else {
+      DecodeError();
     }
   }
   mDecoder->StartProgressUpdates();
   if (mState == DECODER_STATE_DECODING_METADATA ||
       mState == DECODER_STATE_DORMANT ||
       mState == DECODER_STATE_SHUTDOWN) {
     return;
   }
@@ -1843,17 +2014,22 @@ void MediaDecoderStateMachine::DecodeSee
   bool isLiveStream = mDecoder->GetResource()->GetLength() == -1;
   if (GetMediaTime() == mEndTime && !isLiveStream) {
     // Seeked to end of media, move to COMPLETED state. Note we don't do
     // this if we're playing a live stream, since the end of media will advance
     // once we download more data!
     DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
                                mDecoder.get(), seekTime));
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd);
+    // Explicitly set our state so we don't decode further, and so
+    // we report playback ended to the media element.
     mState = DECODER_STATE_COMPLETED;
+    mIsAudioDecoding = false;
+    mIsVideoDecoding = false;
+    DispatchDecodeTasksIfNeeded();
   } else {
     DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
                                mDecoder.get(), seekTime));
     stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStopped);
     StartDecoding();
   }
   {
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@@ -1922,30 +2098,35 @@ nsresult MediaDecoderStateMachine::RunSt
       // If mAudioThread is non-null after StopAudioThread completes, we are
       // running in a nested event loop waiting for Shutdown() on
       // mAudioThread to complete.  Return to the event loop and let it
       // finish processing before continuing with shutdown.
       if (mAudioThread) {
         MOZ_ASSERT(mStopAudioThread);
         return NS_OK;
       }
-      StopDecodeThread();
+
+      // The reader's listeners hold references to the state machine,
+      // creating a cycle which keeps the state machine and its shared
+      // thread pools alive. So break it here.
+      mReader->AudioQueue().ClearListeners();
+      mReader->VideoQueue().ClearListeners();
 
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         // Wait for the thread decoding to exit.
         mDecodeTaskQueue->Shutdown();
         mReader->ReleaseMediaResources();
       }
       // Now that those threads are stopped, there's no possibility of
       // mPendingWakeDecoder being needed again. Revoke it.
       mPendingWakeDecoder = nullptr;
 
-      NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
-                   "How did we escape from the shutdown state?");
+      MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
+                 "How did we escape from the shutdown state?");
       // We must daisy-chain these events to destroy the decoder. We must
       // destroy the decoder on the main thread, but we can't destroy the
       // decoder while this thread holds the decoder monitor. We can't
       // dispatch an event to the main thread to destroy the decoder from
       // here, as the event may run before the dispatch returns, and we
       // hold the decoder monitor here. We also want to guarantee that the
       // state machine is destroyed on the main thread, and so the
       // event runner running this function (which holds a reference to the
@@ -1959,17 +2140,16 @@ nsresult MediaDecoderStateMachine::RunSt
       return NS_OK;
     }
 
     case DECODER_STATE_DORMANT: {
       if (IsPlaying()) {
         StopPlayback();
       }
       StopAudioThread();
-      StopDecodeThread();
       // Now that those threads are stopped, there's no possibility of
       // mPendingWakeDecoder being needed again. Revoke it.
       mPendingWakeDecoder = nullptr;
       {
         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
         // Wait for the thread decoding, if any, to exit.
         mDecodeTaskQueue->AwaitIdle();
         mReader->ReleaseMediaResources();
@@ -1978,67 +2158,43 @@ nsresult MediaDecoderStateMachine::RunSt
     }
 
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       // Ensure we have a decode thread to decode metadata.
-      return ScheduleDecodeThread();
+      return EnqueueDecodeMetadataTask();
     }
 
     case DECODER_STATE_DECODING: {
       if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
           IsPlaying())
       {
         // We're playing, but the element/decoder is in paused state. Stop
-        // playing! Note we do this before StopDecodeThread() below because
-        // that blocks this state machine's execution, and can cause a
-        // perceptible delay between the pause command, and playback actually
-        // pausing.
+        // playing!
         StopPlayback();
       }
 
       if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING &&
           !IsPlaying()) {
         // We are playing, but the state machine does not know it yet. Tell it
         // that it is, so that the clock can be properly queried.
         StartPlayback();
       }
 
-      if (IsPausedAndDecoderWaiting()) {
-        // The decode buffers are full, and playback is paused. Shutdown the
-        // decode thread.
-        StopDecodeThread();
-        return NS_OK;
-      }
-
-      // We're playing and/or our decode buffers aren't full. Ensure we have
-      // an active decode thread.
-      if (NS_FAILED(ScheduleDecodeThread())) {
-        NS_WARNING("Failed to start media decode thread!");
-        return NS_ERROR_FAILURE;
-      }
-
       AdvanceFrame();
       NS_ASSERTION(mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING ||
                    IsStateMachineScheduled() ||
                    mPlaybackRate == 0.0, "Must have timer scheduled");
       return NS_OK;
     }
 
     case DECODER_STATE_BUFFERING: {
-      if (IsPausedAndDecoderWaiting()) {
-        // The decode buffers are full, and playback is paused. Shutdown the
-        // decode thread.
-        StopDecodeThread();
-        return NS_OK;
-      }
-
       TimeStamp now = TimeStamp::Now();
       NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
 
       // We will remain in the buffering state if we've not decoded enough
       // data to begin playback, or if we've not downloaded a reasonable
       // amount of data inside our buffering time.
       TimeDuration elapsed = now - mBufferingStart;
       bool isLiveStream = resource->GetLength() == -1;
@@ -2074,38 +2230,26 @@ nsresult MediaDecoderStateMachine::RunSt
         StartPlayback();
       }
       NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
       return NS_OK;
     }
 
     case DECODER_STATE_SEEKING: {
       // Ensure we have a decode thread to perform the seek.
-     return ScheduleDecodeThread();
+     return EnqueueDecodeSeekTask();
     }
 
     case DECODER_STATE_COMPLETED: {
-      StopDecodeThread();
-
-      if (mState != DECODER_STATE_COMPLETED) {
-        // While we're waiting for the decode thread to shutdown, we can
-        // change state, for example to seeking or shutdown state.
-        // Whatever changed our state should have scheduled another state
-        // machine run.
-        NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
-        return NS_OK;
-      }
-
       // Play the remaining media. We want to run AdvanceFrame() at least
       // once to ensure the current playback position is advanced to the
       // end of the media, and so that we update the readyState.
-      if (mState == DECODER_STATE_COMPLETED &&
-          (mReader->VideoQueue().GetSize() > 0 ||
-           (HasAudio() && !mAudioCompleted) ||
-           (mDecoder->GetDecodedStream() && !mDecoder->GetDecodedStream()->IsFinished())))
+      if (mReader->VideoQueue().GetSize() > 0 ||
+          (HasAudio() && !mAudioCompleted) ||
+          (mDecoder->GetDecodedStream() && !mDecoder->GetDecodedStream()->IsFinished()))
       {
         AdvanceFrame();
         NS_ASSERTION(mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING ||
                      mPlaybackRate == 0 ||
                      IsStateMachineScheduled(),
                      "Must have timer scheduled");
         return NS_OK;
       }
@@ -2137,17 +2281,17 @@ nsresult MediaDecoderStateMachine::RunSt
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
 void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
-                                                    TimeStamp aTarget)
+                                                TimeStamp aTarget)
 {
   NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
                "Should be on state machine or decode thread.");
   mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
 
   if (aData->mDuplicate) {
     return;
   }
@@ -2478,42 +2622,32 @@ void MediaDecoderStateMachine::StartBuff
   // will check the current state and decide whether to tell
   // the element we're buffering or not.
   UpdateReadyState();
   mState = DECODER_STATE_BUFFERING;
   DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs",
                              mDecoder.get(), decodeDuration.ToSeconds()));
 #ifdef PR_LOGGING
   MediaDecoder::Statistics stats = mDecoder->GetStatistics();
-#endif
   DECODER_LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
               mDecoder.get(),
               stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
               stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)"));
+#endif
 }
 
 nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) {
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource, NS_ERROR_FAILURE);
   resource->Pin();
   nsresult res = mReader->GetBuffered(aBuffered, mStartTime);
   resource->Unpin();
   return res;
 }
 
-bool MediaDecoderStateMachine::IsPausedAndDecoderWaiting() {
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
-
-  return
-    mDecodeThreadWaiting &&
-    mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
-    (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING);
-}
-
 nsresult MediaDecoderStateMachine::Run()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
 
   return CallRunStateMachine();
 }
 
@@ -2568,18 +2702,18 @@ void MediaDecoderStateMachine::TimeoutEx
   }
   // Otherwise, an event has already been dispatched to run the state machine
   // as soon as possible. Nothing else needed to do, the state machine is
   // going to run anyway.
 }
 
 void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mon.NotifyAll();
-  ScheduleStateMachine();
+  DispatchAudioDecodeTaskIfNeeded();
+  DispatchVideoDecodeTaskIfNeeded();
 }
 
 nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
   AssertCurrentThreadInMonitor();
   NS_ABORT_IF_FALSE(GetStateMachineThread(),
     "Must have a state machine thread to schedule");
 
   if (mState == DECODER_STATE_SHUTDOWN) {
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -352,18 +352,22 @@ public:
   bool IsShutdown();
 
   void QueueMetadata(int64_t aPublishTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags);
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying();
 
+  // Called when the reader may have acquired the hardware resources required
+  // to begin decoding. The state machine may move into DECODING_METADATA if
+  // appropriate. The decoder monitor must be held while calling this.
+  void NotifyWaitingForResourcesStatusChanged();
+
 protected:
-  virtual uint32_t GetAmpleVideoFrames() { return mAmpleVideoFrames; }
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
 
 private:
   class WakeDecoderRunnable : public nsRunnable {
   public:
     WakeDecoderRunnable(MediaDecoderStateMachine* aSM)
       : mMutex("WakeDecoderRunnable"), mStateMachine(aSM) {}
@@ -393,16 +397,30 @@ private:
     // would mean in some cases we'd have to destroy mStateMachine from this
     // object, which would be problematic since MediaDecoderStateMachine can
     // only be destroyed on the main thread whereas this object can be destroyed
     // on the media stream graph thread.
     MediaDecoderStateMachine* mStateMachine;
   };
   WakeDecoderRunnable* GetWakeDecoderRunnable();
 
+  // True if our buffers of decoded audio are not full, and we should
+  // decode more.
+  bool NeedToDecodeAudio();
+
+  // Decodes some audio. This should be run on the decode task queue.
+  void DecodeAudio();
+
+  // True if our buffers of decoded video are not full, and we should
+  // decode more.
+  bool NeedToDecodeVideo();
+
+  // Decodes some video. This should be run on the decode task queue.
+  void DecodeVideo();
+
   // Returns true if we've got less than aAudioUsecs microseconds of decoded
   // and playable data. The decoder monitor must be held.
   bool HasLowDecodedData(int64_t aAudioUsecs) const;
 
   // Returns true if we're running low on data which is not yet decoded.
   // The decoder monitor must be held.
   bool HasLowUndecodedData() const;
 
@@ -486,31 +504,20 @@ private:
                        uint32_t aChannels,
                        uint64_t aFrameOffset);
 
   // Pops an audio chunk from the front of the audio queue, and pushes its
   // audio data to the audio hardware. MozAudioAvailable data is also queued
   // here. Called on the audio thread.
   uint32_t PlayFromAudioQueue(uint64_t aFrameOffset, uint32_t aChannels);
 
-  // Stops the decode thread, and if we have a pending request for a new
-  // decode thread it is canceled. The decoder monitor must be held with exactly
-  // one lock count. Called on the state machine thread.
-  void StopDecodeThread();
-
   // Stops the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopAudioThread();
 
-  // Ensures the decode thread is running if it already exists, or requests
-  // a new decode thread be started if there currently is no decode thread.
-  // The decoder monitor must be held with exactly one lock count. Called on
-  // the state machine thread.
-  nsresult ScheduleDecodeThread();
-
   // Starts the audio thread. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   nsresult StartAudioThread();
 
   // The main loop for the audio thread. Sent to the thread as
   // an nsRunnableMethod. This continually does blocking writes to
   // to audio stream to play audio data.
   void AudioLoop();
@@ -522,19 +529,72 @@ private:
   // Sets internal state which causes playback of media to begin or resume.
   // Must be called with the decode monitor held.
   void StartPlayback();
 
   // Moves the decoder into decoding state. Called on the state machine
   // thread. The decoder monitor must be held.
   void StartDecoding();
 
+  // Moves the decoder into the shutdown state, and dispatches an error
+  // event to the media element. This begins shutting down the decoder.
+  // The decoder monitor must be held. This is only called on the
+  // decode thread.
+  void DecodeError();
+
   void StartWaitForResources();
 
-  void StartDecodeMetadata();
+  // Dispatches a task to the decode task queue to begin decoding metadata.
+  // This is threadsafe and can be called on any thread.
+  // The decoder monitor must be held.
+  nsresult EnqueueDecodeMetadataTask();
+
+  nsresult DispatchAudioDecodeTaskIfNeeded();
+
+  // Ensures a to decode audio has been dispatched to the decode task queue.
+  // If a task to decode has already been dispatched, this does nothing,
+  // otherwise this dispatches a task to do the decode.
+  // This is called on the state machine or decode threads.
+  // The decoder monitor must be held.
+  nsresult EnsureAudioDecodeTaskQueued();
+
+  nsresult DispatchVideoDecodeTaskIfNeeded();
+
+  // Ensures a to decode video has been dispatched to the decode task queue.
+  // If a task to decode has already been dispatched, this does nothing,
+  // otherwise this dispatches a task to do the decode.
+  // The decoder monitor must be held.
+  nsresult EnsureVideoDecodeTaskQueued();
+
+  // Dispatches a task to the decode task queue to seek the decoder.
+  // The decoder monitor must be held.
+  nsresult EnqueueDecodeSeekTask();
+
+  // Calls the reader's SetIdle(), with aIsIdle as parameter. This is only
+  // called in a task dispatched to the decode task queue, don't call it
+  // directly.
+  void SetReaderIdle();
+  void SetReaderActive();
+
+  // Re-evaluates the state and determines whether we need to dispatch
+  // events to run the decode, or if not whether we should set the reader
+  // to idle mode. This is threadsafe, and can be called from any thread.
+  // The decoder monitor must be held.
+  void DispatchDecodeTasksIfNeeded();
+
+  // Called before we do anything on the decode task queue to set the reader
+  // as not idle if it was idle. This is called before we decode, seek, or
+  // decode metadata (in case we were dormant or awaiting resources).
+  void EnsureActive();
+
+  // Queries our state to see whether the decode has finished for all streams.
+  // If so, we move into DECODER_STATE_COMPLETED and schedule the state machine
+  // to run.
+  // The decoder monitor must be held.
+  void CheckIfDecodeComplete();
 
   // Returns the "media time". This is the absolute time which the media
   // playback has reached. i.e. this returns values in the range
   // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does
   // not start at 0. Note this is different to the value returned
   // by GetCurrentTime(), which is in the range [0,duration].
   int64_t GetMediaTime() const {
     AssertCurrentThreadInMonitor();
@@ -557,19 +617,17 @@ private:
   // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor
   // must be held with exactly one lock count.
   void DecodeSeek();
 
   // Decode loop, decodes data until EOF or shutdown.
   // Called on the decode thread.
   void DecodeLoop();
 
-  // Decode thread run function. Determines which of the Decode*() functions
-  // to call.
-  void DecodeThreadRun();
+  void CallDecodeMetadata();
 
   // Copy audio from an AudioData packet to aOutput. This may require
   // inserting silence depending on the timing of the audio packet.
   void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream,
                        AudioSegment* aOutput);
 
   // State machine thread run function. Defers to RunStateMachine().
   nsresult CallRunStateMachine();
@@ -735,16 +793,82 @@ private:
   // unbuffered data.
   uint32_t mBufferingWait;
   int64_t  mLowDataThresholdUsecs;
 
   // If we've got more than mAmpleVideoFrames decoded video frames waiting in
   // the video queue, we will not decode any more video frames until some have
   // been consumed by the play state machine thread.
   uint32_t mAmpleVideoFrames;
+
+  // Low audio threshold. If we've decoded less than this much audio we
+  // consider our audio decode "behind", and we may skip video decoding
+  // in order to allow our audio decoding to catch up. We favour audio
+  // decoding over video. We increase this threshold if we're slow to
+  // decode video frames, in order to reduce the chance of audio underruns.
+  // Note that we don't ever reset this threshold, it only ever grows as
+  // we detect that the decode can't keep up with rendering.
+  int64_t mLowAudioThresholdUsecs;
+
+  // Our "ample" audio threshold. Once we've this much audio decoded, we
+  // pause decoding. If we increase mLowAudioThresholdUsecs, we'll also
+  // increase this too appropriately (we don't want mLowAudioThresholdUsecs
+  // to be greater than ampleAudioThreshold, else we'd stop decoding!).
+  // Note that we don't ever reset this threshold, it only ever grows as
+  // we detect that the decode can't keep up with rendering.
+  int64_t mAmpleAudioThresholdUsecs;
+
+  // At the start of decoding we want to "preroll" the decode until we've
+  // got a few frames decoded before we consider whether decode is falling
+  // behind. Otherwise our "we're falling behind" logic will trigger
+  // unneccessarily if we start playing as soon as the first sample is
+  // decoded. These two fields store how many video frames and audio
+  // samples we must consume before are considered to be finished prerolling.
+  uint32_t mAudioPrerollUsecs;
+  uint32_t mVideoPrerollFrames;
+
+  // When we start decoding (either for the first time, or after a pause)
+  // we may be low on decoded data. We don't want our "low data" logic to
+  // kick in and decide that we're low on decoded data because the download
+  // can't keep up with the decode, and cause us to pause playback. So we
+  // have a "preroll" stage, where we ignore the results of our "low data"
+  // logic during the first few frames of our decode. This occurs during
+  // playback. The flags below are true when the corresponding stream is
+  // being "prerolled".
+  bool mIsAudioPrerolling;
+  bool mIsVideoPrerolling;
+
+  // True when we have an audio stream that we're decoding, and we have not
+  // yet decoded to end of stream.
+  bool mIsAudioDecoding;
+
+  // True when we have a video stream that we're decoding, and we have not
+  // yet decoded to end of stream.
+  bool mIsVideoDecoding;
+
+  // True when we have dispatched a task to the decode task queue to run
+  // the audio decode.
+  bool mDispatchedAudioDecodeTask;
+
+  // True when we have dispatched a task to the decode task queue to run
+  // the video decode.
+  bool mDispatchedVideoDecodeTask;
+
+  // True when the reader is initialized, but has been ordered "idle" by the
+  // state machine. This happens when the MediaQueue's of decoded data are
+  // "full" and playback is paused. The reader may choose to use the idle
+  // notification to enter a low power state.
+  bool mIsReaderIdle;
+
+  // If the video decode is falling behind the audio, we'll start dropping the
+  // inter-frames up until the next keyframe which is at or before the current
+  // playback position. skipToNextKeyframe is true if we're currently
+  // skipping up to the next keyframe.
+  bool mSkipToNextKeyFrame;
+
   // True if we shouldn't play our audio (but still write it to any capturing
   // streams). When this is true, mStopAudioThread is always true and
   // the audio thread will never start again after it has stopped.
   bool mAudioCaptured;
 
   // True if the media resource can be seeked on a transport level. Accessed
   // from the state machine and main threads. Synchronised via decoder monitor.
   bool mTransportSeekable;
@@ -768,20 +892,16 @@ private:
   // When data is being sent to a MediaStream, this is true when all data has
   // been written to the MediaStream.
   bool mAudioCompleted;
 
   // True if mDuration has a value obtained from an HTTP header, or from
   // the media index/metadata. Accessed on the state machine thread.
   bool mGotDurationFromMetaData;
 
-  // False while decode thread should be running. Accessed state machine
-  // and decode threads. Syncrhonised by decoder monitor.
-  bool mStopDecodeThread;
-
   // True if we've dispatched an event to the decode task queue to call
   // DecodeThreadRun(). We use this flag to prevent us from dispatching
   // unneccessary runnables, since the decode thread runs in a loop.
   bool mDispatchedEventToDecode;
 
   // False while audio thread should be running. Accessed state machine
   // and audio threads. Syncrhonised by decoder monitor.
   bool mStopAudioThread;
@@ -811,22 +931,16 @@ private:
   // True if the decode thread has gone filled its buffers and is now
   // waiting to be awakened before it continues decoding. Synchronized
   // by the decoder monitor.
   bool mDecodeThreadWaiting;
 
   // True is we are decoding a realtime stream, like a camera stream
   bool mRealTime;
 
-  // Record whether audio and video decoding were throttled during the
-  // previous iteration of DecodeLooop. When we transition from
-  // throttled to not-throttled we need to pump decoding.
-  bool mDidThrottleAudioDecoding;
-  bool mDidThrottleVideoDecoding;
-
   // Manager for queuing and dispatching MozAudioAvailable events.  The
   // event manager is accessed from the state machine and audio threads,
   // and takes care of synchronizing access to its internal queue.
   AudioAvailableEventManager mEventManager;
 
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
--- a/content/media/MediaQueue.h
+++ b/content/media/MediaQueue.h
@@ -4,16 +4,18 @@
  * 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/. */
 #if !defined(MediaQueue_h_)
 #define MediaQueue_h_
 
 #include "nsDeque.h"
 #include "nsTArray.h"
 #include "mozilla/ReentrantMonitor.h"
+#include "mozilla/RefPtr.h"
+#include "MediaTaskQueue.h"
 
 namespace mozilla {
 
 // Thread and type safe wrapper around nsDeque.
 template <class T>
 class MediaQueueDeallocator : public nsDequeFunctor {
   virtual void* operator() (void* anObject) {
     delete static_cast<T*>(anObject);
@@ -44,24 +46,23 @@ template <class T> class MediaQueue : pr
     nsDeque::Push(aItem);
   }
 
   inline void PushFront(T* aItem) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     nsDeque::PushFront(aItem);
   }
 
-  inline T* Pop() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::Pop());
-  }
-
   inline T* PopFront() {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::PopFront());
+    T* rv = static_cast<T*>(nsDeque::PopFront());
+    if (rv) {
+      NotifyPopListeners();
+    }
+    return rv;
   }
 
   inline T* Peek() {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     return static_cast<T*>(nsDeque::Peek());
   }
 
   inline T* PeekFront() {
@@ -147,19 +148,53 @@ template <class T> class MediaQueue : pr
     uint32_t frames = 0;
     for (int32_t i = 0; i < GetSize(); ++i) {
       T* v = static_cast<T*>(ObjectAt(i));
       frames += v->mFrames;
     }
     return frames;
   }
 
+  void ClearListeners() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mPopListeners.Clear();
+  }
+
+  void AddPopListener(nsIRunnable* aRunnable, MediaTaskQueue* aTaskQueue) {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mPopListeners.AppendElement(Listener(aRunnable, aTaskQueue));
+  }
+
 private:
   mutable ReentrantMonitor mReentrantMonitor;
 
+  struct Listener {
+    Listener(nsIRunnable* aRunnable, MediaTaskQueue* aTaskQueue)
+      : mRunnable(aRunnable)
+      , mTarget(aTaskQueue)
+    {
+    }
+    Listener(const Listener& aOther)
+      : mRunnable(aOther.mRunnable)
+      , mTarget(aOther.mTarget)
+    {
+    }
+    RefPtr<nsIRunnable> mRunnable;
+    RefPtr<MediaTaskQueue> mTarget;
+  };
+
+  nsTArray<Listener> mPopListeners;
+
+  void NotifyPopListeners() {
+    for (uint32_t i = 0; i < mPopListeners.Length(); i++) {
+      Listener& l = mPopListeners[i];
+      l.mTarget->Dispatch(l.mRunnable);
+    }
+  }
+
   // True when we've decoded the last frame of data in the
   // bitstream for which we're queueing frame data.
   bool mEndOfStream;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -199,17 +199,17 @@ public:
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSISTREAMINGPROTOCOLLISTENER
 
     void Revoke() { mResource = nullptr; }
 
   private:
-    RtspMediaResource* mResource;
+    nsRefPtr<RtspMediaResource> mResource;
   };
   friend class Listener;
 
 protected:
   // Main thread access only.
   // These are called on the main thread by Listener.
   NS_DECL_NSISTREAMINGPROTOCOLLISTENER
 
deleted file mode 100644
--- a/content/media/apple/Makefile.in
+++ /dev/null
@@ -1,7 +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/.
-
-LDFLAGS += \
-    -framework AudioToolbox \
-    $(NULL)
--- a/content/media/apple/moz.build
+++ b/content/media/apple/moz.build
@@ -12,8 +12,10 @@ EXPORTS += [
 UNIFIED_SOURCES += [
     'AppleDecoder.cpp',
     'AppleMP3Reader.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'gklayout'
+
+LDFLAGS += ['-framework AudioToolbox']
--- a/content/media/directshow/DirectShowReader.cpp
+++ b/content/media/directshow/DirectShowReader.cpp
@@ -389,31 +389,16 @@ DirectShowReader::Seek(int64_t aTargetUs
 
   hr = mControl->Run();
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   return DecodeToTarget(aTargetUs);
 }
 
 void
-DirectShowReader::OnDecodeThreadStart()
-{
-  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
-  NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
-}
-
-void
-DirectShowReader::OnDecodeThreadFinish()
-{
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  CoUninitialize();
-}
-
-void
 DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mMP3FrameParser.IsMP3()) {
     return;
   }
   mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
   int64_t duration = mMP3FrameParser.GetDuration();
--- a/content/media/directshow/DirectShowReader.h
+++ b/content/media/directshow/DirectShowReader.h
@@ -60,19 +60,16 @@ public:
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
   nsresult Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime) MOZ_OVERRIDE;
 
-  void OnDecodeThreadStart() MOZ_OVERRIDE;
-  void OnDecodeThreadFinish() MOZ_OVERRIDE;
-
   void NotifyDataArrived(const char* aBuffer,
                          uint32_t aLength,
                          int64_t aOffset) MOZ_OVERRIDE;
 
 private:
 
   // Notifies the filter graph that playback is complete. aStatus is
   // the code to send to the filter graph. Always returns false, so
deleted file mode 100644
--- a/content/media/omx/Makefile.in
+++ /dev/null
@@ -1,22 +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/.
-
-include $(topsrcdir)/config/rules.mk
-
-CXXFLAGS += \
-		-I$(ANDROID_SOURCE)/dalvik/libnativehelper/include/nativehelper \
-		-I$(ANDROID_SOURCE)/frameworks/base/include/ \
-		-I$(ANDROID_SOURCE)/frameworks/base/include/binder/ \
-		-I$(ANDROID_SOURCE)/frameworks/base/include/utils/ \
-		-I$(ANDROID_SOURCE)/frameworks/base/include/media/ \
-		-I$(ANDROID_SOURCE)/frameworks/base/include/media/stagefright/openmax \
-		-I$(ANDROID_SOURCE)/frameworks/base/media/libstagefright/include/ \
-		$(NULL)
-
-# These includes are for Android JB, using MediaCodec on OMXCodec.
-INCLUDES	+= \
-		-I$(ANDROID_SOURCE)/frameworks/native/opengl/include/ \
-		-I$(ANDROID_SOURCE)/frameworks/native/include/ \
-		-I$(ANDROID_SOURCE)/frameworks/av/include/media/ \
-		$(NULL)
--- a/content/media/omx/MediaOmxDecoder.cpp
+++ b/content/media/omx/MediaOmxDecoder.cpp
@@ -1,33 +1,33 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #include "MediaOmxDecoder.h"
 #include "MediaOmxReader.h"
-#include "MediaOmxStateMachine.h"
+#include "MediaDecoderStateMachine.h"
 
 namespace mozilla {
 
 MediaOmxDecoder::MediaOmxDecoder() :
   MediaDecoder()
 {
 }
 
 MediaDecoder* MediaOmxDecoder::Clone()
 {
   return new MediaOmxDecoder();
 }
 
 MediaDecoderStateMachine* MediaOmxDecoder::CreateStateMachine()
 {
-  return new MediaOmxStateMachine(this, new MediaOmxReader(this));
+  return new MediaDecoderStateMachine(this, new MediaOmxReader(this));
 }
 
 MediaOmxDecoder::~MediaOmxDecoder()
 {
 }
 
 } // namespace mozilla
 
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -356,23 +356,25 @@ nsresult MediaOmxReader::Seek(int64_t aT
 
 static uint64_t BytesToTime(int64_t offset, uint64_t length, uint64_t durationUs) {
   double perc = double(offset) / double(length);
   if (perc > 1.0)
     perc = 1.0;
   return uint64_t(double(durationUs) * perc);
 }
 
-void MediaOmxReader::OnDecodeThreadFinish() {
-  if (mOmxDecoder.get()) {
-    mOmxDecoder->Pause();
+void MediaOmxReader::SetIdle() {
+  if (!mOmxDecoder.get()) {
+    return;
   }
+  mOmxDecoder->Pause();
 }
 
-void MediaOmxReader::OnDecodeThreadStart() {
-  if (mOmxDecoder.get()) {
-    DebugOnly<nsresult> result = mOmxDecoder->Play();
-    NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding");
+void MediaOmxReader::SetActive() {
+  if (!mOmxDecoder.get()) {
+    return;
   }
+  DebugOnly<nsresult> result = mOmxDecoder->Play();
+  NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding");
 }
 
 } // namespace mozilla
 
--- a/content/media/omx/MediaOmxReader.h
+++ b/content/media/omx/MediaOmxReader.h
@@ -74,16 +74,16 @@ public:
   virtual void ReleaseMediaResources();
 
   virtual void ReleaseDecoder() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
   virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
 
-  virtual void OnDecodeThreadStart() MOZ_OVERRIDE;
+  virtual void SetIdle() MOZ_OVERRIDE;
+  virtual void SetActive() MOZ_OVERRIDE;
 
-  virtual void OnDecodeThreadFinish() MOZ_OVERRIDE;
 };
 
 } // namespace mozilla
 
 #endif
deleted file mode 100644
--- a/content/media/omx/MediaOmxStateMachine.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "MediaDecoder.h"
-#include "MediaDecoderReader.h"
-#include "MediaDecoderStateMachine.h"
-
-namespace mozilla {
-
-class MediaOmxStateMachine : public MediaDecoderStateMachine
-{
-public:
-  MediaOmxStateMachine(MediaDecoder *aDecoder,
-                       MediaDecoderReader *aReader,
-                       bool aRealTime = false)
-    : MediaDecoderStateMachine(aDecoder, aReader, aRealTime) { }
-
-protected:
-  // Due to a bug in the OMX.qcom.video.decoder.mpeg4 decoder, we can't own too
-  // many video buffers before shutting down the decoder. When we release these
-  // buffers, they asynchronously signal to OMXCodec that we have returned
-  // ownership of the buffer.
-  // If this signal happens while the OMXCodec is shutting down, OMXCodec will
-  // crash. If the OMXCodec shuts down before all buffers are returned,
-  // OMXCodec will crash.
-  // So we need few enough buffers in the queue that all buffers will be
-  // returned before OMXCodec begins shutdown.
-  uint32_t GetAmpleVideoFrames() { return 3; }
-};
-
-} // namespace mozilla
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -1012,18 +1012,19 @@ void OmxDecoder::onMessageReceived(const
       if (!mIsVideoSeeking) {
         ReleaseAllPendingVideoBuffersLocked();
       }
       break;
     }
 
     case kNotifyStatusChanged:
     {
-      mozilla::ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-      mDecoder->GetReentrantMonitor().NotifyAll();
+      // Our decode may have acquired the hardware resource that it needs
+      // to start. Notify the state machine to resume loading metadata.
+      mDecoder->NotifyWaitingForResourcesStatusChanged();
       break;
     }
 
     default:
       TRESPASS();
       break;
   }
 }
--- a/content/media/omx/RtspOmxDecoder.cpp
+++ b/content/media/omx/RtspOmxDecoder.cpp
@@ -2,25 +2,26 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #include "RtspMediaResource.h"
 #include "RtspOmxDecoder.h"
 #include "RtspOmxReader.h"
-#include "MediaOmxStateMachine.h"
+#include "MediaDecoderStateMachine.h"
 
 namespace mozilla {
 
 MediaDecoder* RtspOmxDecoder::Clone()
 {
   return new RtspOmxDecoder();
 }
 
 MediaDecoderStateMachine* RtspOmxDecoder::CreateStateMachine()
 {
-  return new MediaOmxStateMachine(this, new RtspOmxReader(this),
-                                  mResource->IsRealTime());
+  return new MediaDecoderStateMachine(this,
+                                      new RtspOmxReader(this),
+                                      mResource->IsRealTime());
 }
 
 } // namespace mozilla
 
--- a/content/media/omx/RtspOmxReader.cpp
+++ b/content/media/omx/RtspOmxReader.cpp
@@ -294,49 +294,37 @@ nsresult RtspOmxReader::Seek(int64_t aTi
 
   // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
   // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
   // that store the decoded data and also call the |DecodeToTarget| to pass
   // the seek time to OMX a/v decoders.
   return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
 }
 
-void RtspOmxReader::OnDecodeThreadStart() {
-  // Start RTSP streaming right after starting the decoding thread in
-  // MediaDecoderStateMachine and before starting OMXCodec decoding.
-  if (mRtspResource) {
-    nsIStreamingProtocolController* controller =
-        mRtspResource->GetMediaStreamController();
-    if (controller) {
-      controller->Play();
-    }
-  }
-
-  // Call parent class to start OMXCodec decoding.
-  MediaOmxReader::OnDecodeThreadStart();
-}
-
-void RtspOmxReader::OnDecodeThreadFinish() {
-  // Call parent class to pause OMXCodec decoding.
-  MediaOmxReader::OnDecodeThreadFinish();
-
-  // Stop RTSP streaming right before destroying the decoding thread in
-  // MediaDecoderStateMachine and after pausing OMXCodec decoding.
-  // RTSP streaming should not be paused until OMXCodec has been paused and
-  // until the decoding thread in MediaDecoderStateMachine is about to be
-  // destroyed. Otherwise, RtspMediaSource::read() would block the binder
-  // thread of OMXCodecObserver::onMessage() --> OMXCodec::on_message() -->
-  // OMXCodec::drainInputBuffer() due to network data starvation. Because
-  // OMXCodec::mLock is held by the binder thread in this case, all other
-  // threads would be blocked when they try to lock this mutex. As a result, the
-  // decoding thread in MediaDecoderStateMachine would be blocked forever in
-  // OMXCodec::read() if there is no enough data for RtspMediaSource::read() to
-  // return.
+void RtspOmxReader::SetIdle() {
+  // Need to pause RTSP streaming OMXCodec decoding.
   if (mRtspResource) {
     nsIStreamingProtocolController* controller =
         mRtspResource->GetMediaStreamController();
     if (controller) {
       controller->Pause();
     }
   }
+
+  // Call parent class to set OMXCodec idle.
+  MediaOmxReader::SetIdle();
+}
+
+void RtspOmxReader::SetActive() {
+  // Need to start RTSP streaming OMXCodec decoding.
+  if (mRtspResource) {
+    nsIStreamingProtocolController* controller =
+        mRtspResource->GetMediaStreamController();
+    if (controller) {
+      controller->Play();  
+    }
+  }
+
+  // Call parent class to set OMXCodec active.
+  MediaOmxReader::SetActive();
 }
 
 } // namespace mozilla
--- a/content/media/omx/RtspOmxReader.h
+++ b/content/media/omx/RtspOmxReader.h
@@ -66,19 +66,18 @@ public:
   // For Rtsp, we don't have the first video frame in DECODING_METADATA state.
   // It will be available until player request Play() and media decoder enters
   // DECODING state.
   virtual VideoData* FindStartTime(int64_t& aOutStartTime)
     MOZ_FINAL MOZ_OVERRIDE {
     return nullptr;
   }
 
-  virtual void OnDecodeThreadStart() MOZ_OVERRIDE;
-
-  virtual void OnDecodeThreadFinish() MOZ_OVERRIDE;
+  virtual void SetIdle() MOZ_OVERRIDE;
+  virtual void SetActive() MOZ_OVERRIDE;
 
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspOmxReader.
   RtspMediaResource* mRtspResource;
 };
--- a/content/media/omx/moz.build
+++ b/content/media/omx/moz.build
@@ -40,8 +40,23 @@ include('/ipc/chromium/chromium-config.m
 FINAL_LIBRARY = 'gklayout'
 LOCAL_INCLUDES += [
     '/content/base/src',
     '/content/html/content/src',
     '/ipc/chromium/src',
     'mediaresourcemanager',
 ]
 
+CXXFLAGS += [
+    '-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+        'dalvik/libnativehelper/include/nativehelper',
+        'frameworks/av/include/media',
+        'frameworks/base/include',
+        'frameworks/base/include/binder',
+        'frameworks/base/include/utils',
+        'frameworks/base/include/media',
+        'frameworks/base/include/media/stagefright/openmax',
+        'frameworks/base/media/libstagefright/include',
+        'frameworks/native/opengl/include',
+        'frameworks/native/include',
+    ]
+]
+
--- a/content/media/test/crashtests/868504.html
+++ b/content/media/test/crashtests/868504.html
@@ -1,14 +1,14 @@
 <!DOCTYPE html>
 <html>
 <head>
 <script>
 
 function boom()
 {
-    AudioContext().createBufferSource().playbackRate.linearRampToValueAtTime(0, -1);
+    new AudioContext().createBufferSource().playbackRate.linearRampToValueAtTime(0, -1);
 }
 
 </script>
 </head>
 <body onload="boom();"></body>
 </html>
--- a/content/media/test/crashtests/876024-2.html
+++ b/content/media/test/crashtests/876024-2.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta charset="UTF-8">
 <script>
 
 function boom()
 {
-  var bufferSource = AudioContext().createScriptProcessor().context.createBufferSource();
+  var bufferSource = new AudioContext().createScriptProcessor().context.createBufferSource();
   bufferSource.start(0, 0, 0);
   bufferSource.stop(562949953421313);
 }
 
 </script></head>
 
 <body onload="boom();"></body>
 </html>
--- a/content/media/test/crashtests/876834.html
+++ b/content/media/test/crashtests/876834.html
@@ -1,4 +1,4 @@
 <!DOCTYPE html>
 <script>
-OfflineAudioContext(0, 0, 3229622);
+new OfflineAudioContext(0, 0, 3229622);
 </script>
--- a/content/media/test/crashtests/894104.html
+++ b/content/media/test/crashtests/894104.html
@@ -5,16 +5,16 @@
 <script>
 
 function boom()
 {
   var frame = document.getElementById("f");
   var frameWin = frame.contentWindow;
   frameWin.VTTCue;
   document.body.removeChild(frame);
-  frameWin.VTTCue(0, 1, "Bug 894104").getCueAsHTML();
+  new frameWin.VTTCue(0, 1, "Bug 894104").getCueAsHTML();
 }
 
 </script>
 </head>
 
 <body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
 </html>
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -59,18 +59,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(AudioContext, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(AudioContext, nsDOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioContext)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
-static uint8_t gWebAudioOutputKey;
-
 static float GetSampleRateForAudioContext(bool aIsOffline, float aSampleRate)
 {
   if (aIsOffline) {
     return aSampleRate;
   } else {
     AudioStream::InitPreferredSampleRate();
     return static_cast<float>(AudioStream::PreferredSampleRate());
   }
@@ -90,17 +88,16 @@ AudioContext::AudioContext(nsPIDOMWindow
   , mIsShutDown(false)
 {
   aWindow->AddAudioContext(this);
 
   // Note: AudioDestinationNode needs an AudioContext that must already be
   // bound to the window.
   mDestination = new AudioDestinationNode(this, aIsOffline, aNumberOfChannels,
                                           aLength, aSampleRate);
-  mDestination->Stream()->AddAudioOutput(&gWebAudioOutputKey);
   // We skip calling SetIsOnlyNodeForContext during mDestination's constructor,
   // because we can only call SetIsOnlyNodeForContext after mDestination has
   // been set up.
   mDestination->SetIsOnlyNodeForContext(true);
 }
 
 AudioContext::~AudioContext()
 {
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -18,16 +18,18 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIAppShell.h"
 #include "nsWidgetsCID.h"
 
 namespace mozilla {
 namespace dom {
 
+static uint8_t gWebAudioOutputKey;
+
 class OfflineDestinationNodeEngine : public AudioNodeEngine
 {
 public:
   typedef AutoFallibleTArray<nsAutoArrayPtr<float>, 2> InputChannels;
 
   OfflineDestinationNodeEngine(AudioDestinationNode* aNode,
                                uint32_t aNumberOfChannels,
                                uint32_t aLength,
@@ -228,16 +230,17 @@ AudioDestinationNode::AudioDestinationNo
                             MediaStreamGraph::GetInstance();
   AudioNodeEngine* engine = aIsOffline ?
                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
                                                              aLength, aSampleRate) :
                             static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
 
   mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
   mStream->AddMainThreadListener(this);
+  mStream->AddAudioOutput(&gWebAudioOutputKey);
 
   if (!aIsOffline && UseAudioChannelService()) {
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
     if (target) {
       target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
                                      /* useCapture = */ true,
                                      /* wantsUntrusted = */ false);
     }
@@ -375,16 +378,33 @@ AudioDestinationNode::HandleEvent(nsIDOM
 
 NS_IMETHODIMP
 AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
 {
   SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+AudioDestinationNode::WindowVolumeChanged()
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  if (!mStream) {
+    return NS_OK;
+  }
+
+  float volume;
+  nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
+  return NS_OK;
+}
+
 AudioChannel
 AudioDestinationNode::MozAudioChannelType() const
 {
   return mAudioChannel;
 }
 
 void
 AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
@@ -473,17 +493,17 @@ AudioDestinationNode::CreateAudioChannel
 
     case AudioChannel::Publicnotification:
       type = AUDIO_CHANNEL_PUBLICNOTIFICATION;
       break;
 
   }
 
   mAudioChannelAgent = new AudioChannelAgent();
-  mAudioChannelAgent->InitWithWeakCallback(type, this);
+  mAudioChannelAgent->InitWithWeakCallback(GetOwner(), type, this);
 
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
   if (docshell) {
     bool isActive = false;
     docshell->GetIsActive(&isActive);
     mAudioChannelAgent->SetVisibilityState(isActive);
   }
 
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -33,16 +33,17 @@ public:
                        uint32_t aNumberOfChannels = 0,
                        uint32_t aLength = 0,
                        float aSampleRate = 0.0f);
 
   virtual void DestroyMediaStream() MOZ_OVERRIDE;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationNode, AudioNode)
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE
   {
     return 0;
   }
@@ -56,19 +57,16 @@ public:
 
   void StartRendering();
 
   void OfflineShutdown();
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
-  // nsIAudioChannelAgentCallback
-  NS_IMETHOD CanPlayChanged(int32_t aCanPlay);
-
   AudioChannel MozAudioChannelType() const;
   void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
   void FireOfflineCompletionEvent();
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
--- a/content/media/webaudio/MediaBufferDecoder.cpp
+++ b/content/media/webaudio/MediaBufferDecoder.cpp
@@ -264,18 +264,16 @@ MediaDecodeTask::Decode()
 
   mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
 
   // Tell the decoder reader that we are not going to play the data directly,
   // and that we should not reject files with more channels than the audio
   // bakend support.
   mDecoderReader->SetIgnoreAudioOutputFormat();
 
-  mDecoderReader->OnDecodeThreadStart();
-
   MediaInfo mediaInfo;
   nsAutoPtr<MetadataTags> tags;
   nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags));
   if (NS_FAILED(rv)) {
     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
     return;
   }
 
@@ -284,18 +282,16 @@ MediaDecodeTask::Decode()
     return;
   }
 
   while (mDecoderReader->DecodeAudioData()) {
     // consume all of the buffer
     continue;
   }
 
-  mDecoderReader->OnDecodeThreadFinish();
-
   MediaQueue<AudioData>& audioQueue = mDecoderReader->AudioQueue();
   uint32_t frameCount = audioQueue.FrameCount();
   uint32_t channelCount = mediaInfo.mAudio.mChannels;
   uint32_t sampleRate = mediaInfo.mAudio.mRate;
 
   if (!frameCount || !channelCount || !sampleRate) {
     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
     return;
--- a/content/media/webaudio/blink/PeriodicWave.cpp
+++ b/content/media/webaudio/blink/PeriodicWave.cpp
@@ -159,18 +159,18 @@ void PeriodicWave::createBandLimitedTabl
 
     numberOfComponents = std::min(numberOfComponents, halfSize);
 
     m_bandLimitedTables.SetCapacity(m_numberOfRanges);
 
     for (unsigned rangeIndex = 0; rangeIndex < m_numberOfRanges; ++rangeIndex) {
         // This FFTBlock is used to cull partials (represented by frequency bins).
         FFTBlock frame(fftSize);
-        float* realP = new float[halfSize];
-        float* imagP = new float[halfSize];
+        nsAutoArrayPtr<float> realP(new float[halfSize]);
+        nsAutoArrayPtr<float> imagP(new float[halfSize]);
 
         // Copy from loaded frequency data and scale.
         float scale = fftSize;
         AudioBufferCopyWithScale(realData, scale, realP, numberOfComponents);
         AudioBufferCopyWithScale(imagData, scale, imagP, numberOfComponents);
 
         // If fewer components were provided than 1/2 FFT size,
         // then clear the remaining bins.
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -75,38 +75,16 @@ WMFReader::~WMFReader()
     DebugOnly<nsresult> rv = mByteStream->Shutdown();
     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to shutdown WMFByteStream");
   }
   DebugOnly<HRESULT> hr = wmf::MFShutdown();
   NS_ASSERTION(SUCCEEDED(hr), "MFShutdown failed");
   MOZ_COUNT_DTOR(WMFReader);
 }
 
-void
-WMFReader::OnDecodeThreadStart()
-{
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-
-  // XXX WebAudio will call this on the main thread so CoInit will definitely
-  // fail. You cannot change the concurrency model once already set.
-  // The main thread will continue to be STA, which seems to work, but MSDN
-  // recommends that MTA be used.
-  mCOMInitialized = SUCCEEDED(CoInitializeEx(0, COINIT_MULTITHREADED));
-  NS_ENSURE_TRUE_VOID(mCOMInitialized);
-}
-
-void
-WMFReader::OnDecodeThreadFinish()
-{
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  if (mCOMInitialized) {
-    CoUninitialize();
-  }
-}
-
 bool
 WMFReader::InitializeDXVA()
 {
   if (!Preferences::GetBool("media.windows-media-foundation.use-dxva", false)) {
     return false;
   }
   MOZ_ASSERT(mDecoder->GetImageContainer());
 
--- a/content/media/wmf/WMFReader.h
+++ b/content/media/wmf/WMFReader.h
@@ -42,20 +42,16 @@ public:
 
   nsresult ReadMetadata(MediaInfo* aInfo,
                         MetadataTags** aTags) MOZ_OVERRIDE;
 
   nsresult Seek(int64_t aTime,
                 int64_t aStartTime,
                 int64_t aEndTime,
                 int64_t aCurrentTime) MOZ_OVERRIDE;
-
-  void OnDecodeThreadStart() MOZ_OVERRIDE;
-  void OnDecodeThreadFinish() MOZ_OVERRIDE;
-
 private:
 
   HRESULT ConfigureAudioDecoder();
   HRESULT ConfigureVideoDecoder();
   HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
   void GetSupportedAudioCodecs(const GUID** aCodecs, uint32_t* aNumCodecs);
 
   HRESULT CreateBasicVideoFrame(IMFSample* aSample,
--- a/dom/activities/src/ActivitiesService.jsm
+++ b/dom/activities/src/ActivitiesService.jsm
@@ -216,17 +216,17 @@ let Activities = {
 
       function getActivityChoice(aChoice) {
         debug("Activity choice: " + aChoice);
 
         // The user has cancelled the choice, fire an error.
         if (aChoice === -1) {
           Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireError", {
             "id": aMsg.id,
-            "error": "USER_ABORT"
+            "error": "ActivityCanceled"
           });
           delete Activities.callers[aMsg.id];
           return;
         }
 
         let sysmm = Cc["@mozilla.org/system-message-internal;1"]
                       .getService(Ci.nsISystemMessagesInternal);
         if (!sysmm) {
@@ -352,17 +352,17 @@ let Activities = {
       case "Activities:GetContentTypes":
         this.sendContentTypes(mm);
         break;
       case "child-process-shutdown":
         for (let id in this.callers) {
           if (this.callers[id].childMM == mm) {
             this.callers[id].mm.sendAsyncMessage("Activity:FireError", {
               "id": id,
-              "error": "USER_ABORT"
+              "error": "ActivityCanceled"
             });
             delete this.callers[id];
             break;
           }
         }
         break;
     }
   },
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -1,19 +1,22 @@
 /* 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/. */
 
 #include "AudioChannelAgent.h"
 #include "AudioChannelCommon.h"
 #include "AudioChannelService.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULAppAPI.h"
 
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_1(AudioChannelAgent, mCallback)
+NS_IMPL_CYCLE_COLLECTION_2(AudioChannelAgent, mWindow, mCallback)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioChannelAgent)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgent)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
@@ -35,44 +38,56 @@ AudioChannelAgent::~AudioChannelAgent()
 
 /* readonly attribute long audioChannelType; */
 NS_IMETHODIMP AudioChannelAgent::GetAudioChannelType(int32_t *aAudioChannelType)
 {
   *aAudioChannelType = mAudioChannelType;
   return NS_OK;
 }
 
-/* boolean init (in long channelType, in nsIAudioChannelAgentCallback callback); */
-NS_IMETHODIMP AudioChannelAgent::Init(int32_t channelType, nsIAudioChannelAgentCallback *callback)
+/* boolean init (in nsIDOMWindow window, in long channelType,
+ *               in nsIAudioChannelAgentCallback callback); */
+NS_IMETHODIMP
+AudioChannelAgent::Init(nsIDOMWindow* aWindow, int32_t aChannelType,
+                        nsIAudioChannelAgentCallback *aCallback)
 {
-  return InitInternal(channelType, callback, /* useWeakRef = */ false);
+  return InitInternal(aWindow, aChannelType, aCallback,
+                      /* useWeakRef = */ false);
 }
 
-/* boolean initWithWeakCallback (in long channelType,
+/* boolean initWithWeakCallback (in nsIDOMWindow window, in long channelType,
  *                               in nsIAudioChannelAgentCallback callback); */
 NS_IMETHODIMP
-AudioChannelAgent::InitWithWeakCallback(int32_t channelType,
-                                        nsIAudioChannelAgentCallback *callback)
+AudioChannelAgent::InitWithWeakCallback(nsIDOMWindow* aWindow,
+                                        int32_t aChannelType,
+                                        nsIAudioChannelAgentCallback *aCallback)
 {
-  return InitInternal(channelType, callback, /* useWeakRef = */ true);
+  return InitInternal(aWindow, aChannelType, aCallback,
+                      /* useWeakRef = */ true);
 }
 
+/* void initWithVideo(in nsIDOMWindow window, in long channelType,
+ *                    in nsIAudioChannelAgentCallback callback, in boolean weak); */
 NS_IMETHODIMP
-AudioChannelAgent::InitWithVideo(int32_t channelType,
-                                 nsIAudioChannelAgentCallback *callback,
+AudioChannelAgent::InitWithVideo(nsIDOMWindow* aWindow, int32_t aChannelType,
+                                 nsIAudioChannelAgentCallback *aCallback,
                                  bool aUseWeakRef)
 {
-  return InitInternal(channelType, callback, aUseWeakRef, true);
+  return InitInternal(aWindow, aChannelType, aCallback, aUseWeakRef,
+                      /* withVideo = */ true);
 }
 
 nsresult
-AudioChannelAgent::InitInternal(int32_t aChannelType,
+AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType,
                                 nsIAudioChannelAgentCallback *aCallback,
                                 bool aUseWeakRef, bool aWithVideo)
 {
+  // We need the window only for IPC.
+  MOZ_ASSERT(aWindow || XRE_GetProcessType() == GeckoProcessType_Default);
+
   // We syncd the enum of channel type between nsIAudioChannelAgent.idl and
   // AudioChannelCommon.h the same.
   static_assert(static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NORMAL) ==
                 AUDIO_CHANNEL_NORMAL &&
                 static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_CONTENT) ==
                 AUDIO_CHANNEL_CONTENT &&
                 static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NOTIFICATION) ==
                 AUDIO_CHANNEL_NOTIFICATION &&
@@ -87,16 +102,17 @@ AudioChannelAgent::InitInternal(int32_t 
                 "Enum of channel on nsIAudioChannelAgent.idl should be the same with AudioChannelCommon.h");
 
   if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
       aChannelType > AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION ||
       aChannelType < AUDIO_AGENT_CHANNEL_NORMAL) {
     return NS_ERROR_FAILURE;
   }
 
+  mWindow = aWindow;
   mAudioChannelType = aChannelType;
 
   if (aUseWeakRef) {
     mWeakCallback = do_GetWeakReference(aCallback);
   } else {
     mCallback = aCallback;
   }
 
@@ -163,8 +179,34 @@ already_AddRefed<nsIAudioChannelAgentCal
 AudioChannelAgent::GetCallback()
 {
   nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
   if (!callback) {
     callback = do_QueryReferent(mWeakCallback);
   }
   return callback.forget();
 }
+
+void
+AudioChannelAgent::WindowVolumeChanged()
+{
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+  if (!callback) {
+    return;
+  }
+
+  callback->WindowVolumeChanged();
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::GetWindowVolume(float* aVolume)
+{
+  NS_ENSURE_ARG_POINTER(aVolume);
+
+  nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mWindow);
+  if (!win) {
+    *aVolume = 1.0f;
+    return NS_OK;
+  }
+
+  *aVolume = win->GetAudioGlobalVolume();
+  return NS_OK;
+}
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -12,42 +12,52 @@
 #include "nsCOMPtr.h"
 #include "nsWeakPtr.h"
 
 #define NS_AUDIOCHANNELAGENT_CONTRACTID "@mozilla.org/audiochannelagent;1"
 // f27688e2-3dd7-11e2-904e-10bf48d64bd4
 #define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \
       {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
 
+class nsIDOMWindow;
+
 namespace mozilla {
 namespace dom {
 
 /* Header file */
 class AudioChannelAgent : public nsIAudioChannelAgent
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIAUDIOCHANNELAGENT
 
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
 
   AudioChannelAgent();
   virtual void NotifyAudioChannelStateChanged();
 
+  void WindowVolumeChanged();
+
+  nsIDOMWindow* Window() const
+  {
+    return mWindow;
+  }
+
 private:
   virtual ~AudioChannelAgent();
 
   // Returns mCallback if that's non-null, or otherwise tries to get an
   // nsIAudioChannelAgentCallback out of mWeakCallback.
   already_AddRefed<nsIAudioChannelAgentCallback> GetCallback();
 
-  nsresult InitInternal(int32_t aAudioAgentType,
+  nsresult InitInternal(nsIDOMWindow* aWindow, int32_t aAudioAgentType,
                         nsIAudioChannelAgentCallback* aCallback,
                         bool aUseWeakRef, bool aWithVideo=false);
 
+  nsCOMPtr<nsIDOMWindow> mWindow;
   nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
   nsWeakPtr mWeakCallback;
   int32_t mAudioChannelType;
   bool mIsRegToService;
   bool mVisible;
   bool mWithVideo;
 };
 
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/ContentParent.h"
 
 #include "nsThreadUtils.h"
 #include "nsHashPropertyBag.h"
 #include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
 #include "nsServiceManagerUtils.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIAudioManager.h"
 #include "SpeakerManagerService.h"
 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
@@ -107,16 +108,28 @@ AudioChannelService::RegisterAudioChanne
   MOZ_ASSERT(aType != AUDIO_CHANNEL_DEFAULT);
 
   AudioChannelAgentData* data = new AudioChannelAgentData(aType,
                                 true /* aElementHidden */,
                                 AUDIO_CHANNEL_STATE_MUTED /* aState */,
                                 aWithVideo);
   mAgents.Put(aAgent, data);
   RegisterType(aType, CONTENT_PROCESS_ID_MAIN, aWithVideo);
+
+  // If this is the first agent for this window, we must notify the observers.
+  uint32_t count = CountWindow(aAgent->Window());
+  if (count == 1) {
+    nsCOMPtr<nsIObserverService> observerService =
+      services::GetObserverService();
+    if (observerService) {
+      observerService->NotifyObservers(ToSupports(aAgent->Window()),
+                                       "media-playback",
+                                       NS_LITERAL_STRING("active").get());
+    }
+  }
 }
 
 void
 AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID, bool aWithVideo)
 {
   if (mDisabled) {
     return;
   }
@@ -175,16 +188,28 @@ AudioChannelService::UnregisterAudioChan
                    CONTENT_PROCESS_ID_MAIN, data->mWithVideo);
   }
 #ifdef MOZ_WIDGET_GONK
   bool active = AnyAudioChannelIsActive();
   for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
     mSpeakerManager[i]->SetAudioChannelActive(active);
   }
 #endif
+
+  // If this is the last agent for this window, we must notify the observers.
+  uint32_t count = CountWindow(aAgent->Window());
+  if (count == 0) {
+    nsCOMPtr<nsIObserverService> observerService =
+      services::GetObserverService();
+    if (observerService) {
+      observerService->NotifyObservers(ToSupports(aAgent->Window()),
+                                       "media-playback",
+                                       NS_LITERAL_STRING("inactive").get());
+    }
+  }
 }
 
 void
 AudioChannelService::UnregisterType(AudioChannelType aType,
                                     bool aElementHidden,
                                     uint64_t aChildID,
                                     bool aWithVideo)
 {
@@ -775,8 +800,83 @@ AudioChannelService::GetInternalType(Aud
 
     case AUDIO_CHANNEL_LAST:
     default:
       break;
   }
 
   MOZ_CRASH("unexpected audio channel type");
 }
+
+struct RefreshAgentsVolumeData
+{
+  RefreshAgentsVolumeData(nsPIDOMWindow* aWindow)
+    : mWindow(aWindow)
+  {}
+
+  nsPIDOMWindow* mWindow;
+  nsTArray<nsRefPtr<AudioChannelAgent>> mAgents;
+};
+
+PLDHashOperator
+AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
+                                                   AudioChannelAgentData* aUnused,
+                                                   void* aPtr)
+{
+  MOZ_ASSERT(aAgent);
+  RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr);
+  MOZ_ASSERT(data);
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
+  if (window && !window->IsInnerWindow()) {
+    window = window->GetCurrentInnerWindow();
+  }
+
+  if (window == data->mWindow) {
+    data->mAgents.AppendElement(aAgent);
+  }
+
+  return PL_DHASH_NEXT;
+}
+void
+AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
+{
+  RefreshAgentsVolumeData data(aWindow);
+  mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data);
+
+  for (uint32_t i = 0; i < data.mAgents.Length(); ++i) {
+    data.mAgents[i]->WindowVolumeChanged();
+  }
+}
+
+struct CountWindowData
+{
+  CountWindowData(nsIDOMWindow* aWindow)
+    : mWindow(aWindow)
+    , mCount(0)
+  {}
+
+  nsIDOMWindow* mWindow;
+  uint32_t mCount;
+};
+
+PLDHashOperator
+AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent,
+                                           AudioChannelAgentData* aUnused,
+                                           void* aPtr)
+{
+  CountWindowData* data = static_cast<CountWindowData*>(aPtr);
+  MOZ_ASSERT(aAgent);
+
+  if (aAgent->Window() == data->mWindow) {
+    ++data->mCount;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+uint32_t
+AudioChannelService::CountWindow(nsIDOMWindow* aWindow)
+{
+  CountWindowData data(aWindow);
+  mAgents.EnumerateRead(CountWindowEnumerator, &data);
+  return data.mCount;
+}
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -11,16 +11,18 @@
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "nsITimer.h"
 
 #include "AudioChannelCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsClassHashtable.h"
 
+class nsPIDOMWindow;
+
 namespace mozilla {
 namespace dom {
 #ifdef MOZ_WIDGET_GONK
 class SpeakerManagerService;
 #endif
 class AudioChannelService
 : public nsIObserver
 , public nsITimerCallback
@@ -80,16 +82,18 @@ public:
    * AudioChannelManager calls this function to notify the default channel used
    * to adjust volume when there is no any active channel.
    */
   virtual void SetDefaultVolumeControlChannel(AudioChannelType aType,
                                               bool aHidden);
 
   bool AnyAudioChannelIsActive();
 
+  void RefreshAgentsVolume(nsPIDOMWindow* aWindow);
+
 #ifdef MOZ_WIDGET_GONK
   void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
   {
     if (!mSpeakerManager.Contains(aSpeakerManager)) {
       mSpeakerManager.AppendElement(aSpeakerManager);
     }
   }
 
@@ -174,16 +178,29 @@ protected:
     AudioChannelState mState;
     const bool mWithVideo;
   };
 
   static PLDHashOperator
   NotifyEnumerator(AudioChannelAgent* aAgent,
                    AudioChannelAgentData* aData, void *aUnused);
 
+  static PLDHashOperator
+  RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
+                                AudioChannelAgentData* aUnused,
+                                void *aPtr);
+
+  static PLDHashOperator
+  CountWindowEnumerator(AudioChannelAgent* aAgent,
+                        AudioChannelAgentData* aUnused,
+                        void *aPtr);
+
+  // This returns the number of agents from this aWindow.
+  uint32_t CountWindow(nsIDOMWindow* aWindow);
+
   nsClassHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
 #ifdef MOZ_WIDGET_GONK
   nsTArray<SpeakerManagerService*>  mSpeakerManager;
 #endif
   nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   AudioChannelType mCurrentHigherChannel;
   AudioChannelType mCurrentVisibleHigherChannel;
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -1,43 +1,51 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
-[function, scriptable, uuid(c7227506-5f8e-11e2-8bb3-10bf48d64bd4)]
+interface nsIDOMWindow;
+
+[scriptable, uuid(194b55d9-39c0-45c6-b8ef-b8049f978ea5)]
 interface nsIAudioChannelAgentCallback : nsISupports
 {
   /**
    * Notified when the playable status of channel is changed.
    *
    * @param canPlay
    *        Callback from agent to notify component of the playable status
    *        of the channel. If canPlay is muted state, component SHOULD stop
    *        playing media associated with this channel as soon as possible. if
    *        it is faded state then the volume of media should be reduced.
    */
   void canPlayChanged(in long canPlay);
+
+  /**
+   * Notified when the window volume/mute is changed
+   */
+  void windowVolumeChanged();
 };
 
 /**
  * This interface provides an agent for gecko components to participate
  * in the audio channel service. Gecko components are responsible for
- *   1. Indicating what channel type they are using (via the init() member function).
+ *   1. Indicating what channel type they are using (via the init() member
+ *      function).
  *   2. Before playing, checking the playable status of the channel.
  *   3. Notifying the agent when they start/stop using this channel.
  *   4. Notifying the agent of changes to the visibility of the component using
- *       this channel.
+ *      this channel.
  *
  * The agent will invoke a callback to notify Gecko components of
  *   1. Changes to the playable status of this channel.
  */
 
-[scriptable, uuid(86ef883d-9cec-4c04-994f-5de198286e7c)]
+[scriptable, uuid(2b0222a5-8f7b-49d2-9ab8-cd01b744b23e)]
 interface nsIAudioChannelAgent : nsISupports
 {
   const long AUDIO_AGENT_CHANNEL_NORMAL             = 0;
   const long AUDIO_AGENT_CHANNEL_CONTENT            = 1;
   const long AUDIO_AGENT_CHANNEL_NOTIFICATION       = 2;
   const long AUDIO_AGENT_CHANNEL_ALARM              = 3;
   const long AUDIO_AGENT_CHANNEL_TELEPHONY          = 4;
   const long AUDIO_AGENT_CHANNEL_RINGER             = 5;
@@ -53,43 +61,50 @@ interface nsIAudioChannelAgent : nsISupp
    * Before init() is called, this returns AUDIO_AGENT_CHANNEL_ERROR.
    */
   readonly attribute long audioChannelType;
 
   /**
    * Initialize the agent with a channel type.
    * Note: This function should only be called once.
    *
+   * @param window
+   *    The window
    * @param channelType
    *    Audio Channel Type listed as above
    * @param callback
-   *    1. Once the playable status changes, agent uses this callback function to notify
-   *       Gecko component.
-   *    2. The callback is allowed to be null. Ex: telephony doesn't need to listen change
-   *       of the playable status.
-   *    3. The AudioChannelAgent keeps a strong reference to the callback object.
+   *    1. Once the playable status changes, agent uses this callback function
+   *       to notify Gecko component.
+   *    2. The callback is allowed to be null. Ex: telephony doesn't need to
+   *       listen change of the playable status.
+   *    3. The AudioChannelAgent keeps a strong reference to the callback
+   *       object.
    */
-  void init(in long channelType, in nsIAudioChannelAgentCallback callback);
+  void init(in nsIDOMWindow window, in long channelType,
+            in nsIAudioChannelAgentCallback callback);
 
   /**
    * This method is just like init(), except the audio channel agent keeps a
    * weak reference to the callback object.
    *
    * In order for this to work, |callback| must implement
    * nsISupportsWeakReference.
    */
-  void initWithWeakCallback(in long channelType, in nsIAudioChannelAgentCallback callback);
+  void initWithWeakCallback(in nsIDOMWindow window, in long channelType,
+                            in nsIAudioChannelAgentCallback callback);
 
   /**
-   * This method is just like init(), and specify the channel is associated with video.
+   * This method is just like init(), and specify the channel is associated
+   * with video.
    *
    * @param weak
    *    true if weak reference should be hold.
    */
-  void initWithVideo(in long channelType, in nsIAudioChannelAgentCallback callback, in boolean weak);
+  void initWithVideo(in nsIDOMWindow window, in long channelType,
+                     in nsIAudioChannelAgentCallback callback, in boolean weak);
 
   /**
    * Notify the agent that we want to start playing.
    * Note: Gecko component SHOULD call this function first then start to
    *          play audio stream only when return value is true.
    *
    *
    * @return
@@ -113,10 +128,15 @@ interface nsIAudioChannelAgent : nsISupp
   void stopPlaying();
 
   /**
    * Notify the agent of the visibility state of the window using this agent.
    * @param visible
    *    True if the window associated with the agent is visible.
    */
   void setVisibilityState(in boolean visible);
+
+  /**
+   * Retrieve the volume from the window.
+   */
+  readonly attribute float windowVolume;
 };
 
--- a/dom/audiochannel/tests/TestAudioChannelService.cpp
+++ b/dom/audiochannel/tests/TestAudioChannelService.cpp
@@ -46,20 +46,20 @@ public:
       StopPlaying();
     }
   }
 
   nsresult Init(bool video=false)
   {
     nsresult rv = NS_OK;
     if (video) {
-      rv = mAgent->InitWithVideo(mType, this, true);
+      rv = mAgent->InitWithVideo(nullptr, mType, this, true);
     }
     else {
-      rv = mAgent->InitWithWeakCallback(mType, this);
+      rv = mAgent->InitWithWeakCallback(nullptr, mType, this);
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     return mAgent->SetVisibilityState(false);
   }
 
   nsresult StartPlaying(AudioChannelState *_ret)
   {
@@ -99,16 +99,21 @@ public:
 
   NS_IMETHODIMP CanPlayChanged(int32_t canPlay)
   {
     mCanPlay = static_cast<AudioChannelState>(canPlay);
     mWaitCallback = false;
     return NS_OK;
   }
 
+  NS_IMETHODIMP WindowVolumeChanged()
+  {
+    return NS_OK;
+  }
+
   nsresult GetCanPlay(AudioChannelState *_ret)
   {
     int loop = 0;
     while (mWaitCallback) {
       #ifdef XP_WIN
       Sleep(1000);
       #else
       sleep(1);
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3727,8 +3727,58 @@ nsDOMWindowUtils::GetOMTAOrComputedStyle
   nsCOMPtr<nsIDOMCSSStyleDeclaration> style;
   rv = element->GetCurrentDoc()->GetWindow()->
     GetComputedStyle(aElement, aProperty, getter_AddRefs(style));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return style->GetPropertyValue(aProperty, aResult);
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioMuted(bool* aMuted)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  *aMuted = window->GetAudioMuted();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioMuted(bool aMuted)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  window->SetAudioMuted(aMuted);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioVolume(float* aVolume)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  *aVolume = window->GetAudioVolume();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioVolume(float aVolume)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  return window->SetAudioVolume(aVolume);
+}
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -55,16 +55,17 @@
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozilla/dom/workers/Workers.h"
 #include "nsJSPrincipals.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Debug.h"
 #include "mozilla/MouseEvents.h"
+#include "AudioChannelService.h"
 
 // Interfaces Needed
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsIWidget.h"
 #include "nsIWidgetListener.h"
 #include "nsIBaseWindow.h"
 #include "nsIDeviceSensors.h"
@@ -559,16 +560,17 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWind
 : mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
   mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
   mIsModalContentWindow(false),
   mIsActive(false), mIsBackground(false),
+  mAudioMuted(false), mAudioVolume(1.0),
   mInnerWindow(nullptr), mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(++gNextWindowID), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0)
  {}
 
 nsPIDOMWindow::~nsPIDOMWindow() {}
 
@@ -3577,16 +3579,110 @@ nsPIDOMWindow::CreatePerformanceObjectIf
       !timingEnabled) {
     timedChannel = nullptr;
   }
   if (timing) {
     mPerformance = new nsPerformance(this, timing, timedChannel);
   }
 }
 
+bool
+nsPIDOMWindow::GetAudioMuted() const
+{
+  if (!IsInnerWindow()) {
+    return mInnerWindow->GetAudioMuted();
+  }
+
+  return mAudioMuted;
+}
+
+void
+nsPIDOMWindow::SetAudioMuted(bool aMuted)
+{
+  if (!IsInnerWindow()) {
+    mInnerWindow->SetAudioMuted(aMuted);
+    return;
+  }
+
+  if (mAudioMuted == aMuted) {
+    return;
+  }
+
+  mAudioMuted = aMuted;
+  RefreshMediaElements();
+}
+
+float
+nsPIDOMWindow::GetAudioVolume() const
+{
+  if (!IsInnerWindow()) {
+    return mInnerWindow->GetAudioVolume();
+  }
+
+  return mAudioVolume;
+}
+
+nsresult
+nsPIDOMWindow::SetAudioVolume(float aVolume)
+{
+  if (!IsInnerWindow()) {
+    return mInnerWindow->SetAudioVolume(aVolume);
+  }
+
+  if (aVolume < 0.0) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  if (mAudioVolume == aVolume) {
+    return NS_OK;
+  }
+
+  mAudioVolume = aVolume;
+  RefreshMediaElements();
+  return NS_OK;
+}
+
+float
+nsPIDOMWindow::GetAudioGlobalVolume()
+{
+  float globalVolume = 1.0;
+  nsCOMPtr<nsPIDOMWindow> window = this;
+
+  do {
+    if (window->GetAudioMuted()) {
+      return 0;
+    }
+
+    globalVolume *= window->GetAudioVolume();
+
+    nsCOMPtr<nsIDOMWindow> win;
+    window->GetParent(getter_AddRefs(win));
+    if (window == win) {
+      break;
+    }
+
+    window = do_QueryInterface(win);
+
+    // If there is not parent, or we are the toplevel or the volume is
+    // already 0.0, we don't continue.
+  } while (window && window != this && globalVolume);
+
+  return globalVolume;
+}
+
+void
+nsPIDOMWindow::RefreshMediaElements()
+{
+  nsRefPtr<AudioChannelService> service =
+    AudioChannelService::GetAudioChannelService();
+  if (service) {
+    service->RefreshAgentsVolume(this);
+  }
+}
+
 // nsISpeechSynthesisGetter
 
 #ifdef MOZ_WEBSPEECH
 SpeechSynthesis*
 nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
 {
   FORWARD_TO_INNER_OR_THROW(GetSpeechSynthesis, (aError), aError, nullptr);
 
@@ -10647,24 +10743,24 @@ nsGlobalWindow::ShowSlowScriptDialog()
                           (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1));
 
   // Add a third button if necessary.
   if (debugPossible)
     buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2;
 
   // Null out the operation callback while we're re-entering JS here.
   JSRuntime* rt = JS_GetRuntime(cx);
-  JSOperationCallback old = JS_SetOperationCallback(rt, nullptr);
+  JSInterruptCallback old = JS_SetInterruptCallback(rt, nullptr);
 
   // Open the dialog.
   rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton,
                          debugButton, neverShowDlg, &neverShowDlgChk,
                          &buttonPressed);
 
-  JS_SetOperationCallback(rt, old);
+  JS_SetInterruptCallback(rt, old);
 
   if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) {
     return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript;
   }
   if ((buttonPressed == 2) && debugPossible) {
     return js_CallContextDebugHandler(cx) ? ContinueSlowScript : KillSlowScript;
   }
   JS_ClearPendingException(cx);
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -185,21 +185,33 @@ public:
     if (!mDoc) {
       MaybeCreateDoc();
     }
     return mDoc;
   }
 
   virtual NS_HIDDEN_(bool) IsRunningTimeout() = 0;
 
+  // Audio API
+  bool GetAudioMuted() const;
+  void SetAudioMuted(bool aMuted);
+
+  float GetAudioVolume() const;
+  nsresult SetAudioVolume(float aVolume);
+
+  float GetAudioGlobalVolume();
+
 protected:
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
+  float GetAudioGlobalVolumeInternal(float aVolume);
+  void RefreshMediaElements();
+
 public:
   // Internal getter/setter for the frame element, this version of the
   // getter crosses chrome boundaries whereas the public scriptable
   // one doesn't for security reasons.
   mozilla::dom::Element* GetFrameElementInternal() const;
   void SetFrameElementInternal(mozilla::dom::Element* aFrameElement);
 
   bool IsLoadingOrRunningTimeout() const
@@ -763,16 +775,19 @@ protected:
   // Only used on outer windows.
   bool                   mIsActive;
 
   // Tracks whether our docshell is active.  If it is, mIsBackground
   // is false.  Too bad we have so many different concepts of
   // "active".  Only used on outer windows.
   bool                   mIsBackground;
 
+  bool                   mAudioMuted;
+  float                  mAudioVolume;
+
   // And these are the references between inner and outer windows.
   nsPIDOMWindow         *mInnerWindow;
   nsCOMPtr<nsPIDOMWindow> mOuterWindow;
 
   // the element within the document that is currently focused when this
   // window is active
   nsCOMPtr<nsIContent> mFocusedNode;
 
copy from content/html/content/test/wakelock.ogg
copy to dom/base/test/audio.ogg
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -1,18 +1,21 @@
 [DEFAULT]
 support-files =
+  audio.ogg
   iframe_messageChannel_cloning.html
   iframe_messageChannel_pingpong.html
   iframe_messageChannel_post.html
   file_empty.html
   iframe_postMessage_solidus.html
 
 [test_Image_constructor.html]
 [test_appname_override.html]
+[test_audioWindowUtils.html]
+[test_audioNotification.html]
 [test_bug913761.html]
 [test_bug978522.html]
 [test_bug979109.html]
 [test_clearTimeoutIntervalNoArg.html]
 [test_consoleEmptyStack.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_document.all_unqualified.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_audioNotification.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for audio controller in windows</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var expectedNotification = null;
+
+var observer = {
+  observe: function(subject, topic, data) {
+    is(topic, "media-playback", "media-playback received");
+    is(data, expectedNotification, "This is the right notification");
+    runTest();
+  }
+};
+
+var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+                                   .getService(SpecialPowers.Ci.nsIObserverService);
+
+var audio = new Audio();
+audio.src = "audio.ogg";
+
+var tests = [
+  function() {
+    SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
+  },
+
+  function() {
+    observerService.addObserver(observer, "media-playback", false);
+    ok(true, "Observer set");
+    runTest();
+  },
+
+  function() {
+    expectedNotification = 'active';
+    audio.play();
+  },
+
+  function() {
+    expectedNotification = 'inactive';
+    audio.pause();
+  },
+
+  function() {
+    observerService.removeObserver(observer, "media-playback");
+    ok(true, "Observer removed");
+    runTest();
+  }
+];
+
+function runTest() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_audioWindowUtils.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for audio controller in windows</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<iframe src="about:blank" id="iframe"></iframe>
+<script type="application/javascript">
+
+function runTest() {
+  var utils = SpecialPowers.wrap(window).
+        QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+        getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+  ok(utils, "nsIDOMWindowUtils");
+
+  is(utils.audioMuted, false, "By default utils.audioMuted is false");
+  utils.audioMuted = true;
+  is(utils.audioMuted, true, "utils.audioMuted is true");
+  utils.audioMuted = false;
+  is(utils.audioMuted, false, "utils.audioMuted is true");
+
+  is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
+  utils.audioVolume = 0.4;
+  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  utils.audioMuted = true;
+  is(utils.audioMuted, true, "utils.audioMuted is true");
+  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  utils.audioMuted = false;
+
+  utils.audioVolume = 2.0;
+  is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
+
+  try {
+    utils.audioVolume = -42;
+    ok(false, "This should throw");
+  } catch(e) {
+    ok(true, "This should throw");
+  }
+
+  utils.audioVolume = 0;
+  is(utils.audioVolume, 0.0, "utils.audioVolume is ok");
+  utils.audioVolume = 1.0;
+  is(utils.audioVolume, 1.0, "utils.audioVolume is ok");
+
+  var iframe = document.getElementById("iframe");
+  ok(iframe, "IFrame exists");
+
+  utils = SpecialPowers.wrap(iframe.contentWindow).
+        QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+        getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+  ok(utils, "nsIDOMWindowUtils");
+
+  is(utils.audioMuted, false, "By default utils.audioMuted is false");
+  utils.audioMuted = true;
+  is(utils.audioMuted, true, "utils.audioMuted is true");
+  utils.audioMuted = false;
+  is(utils.audioMuted, false, "utils.audioMuted is true");
+
+  is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
+  utils.audioVolume = 0.4;
+  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  utils.audioMuted = true;
+  is(utils.audioMuted, true, "utils.audioMuted is true");
+  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  utils.audioMuted = false;
+
+  utils.audioVolume = 2.0;
+  is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
+
+  try {
+    utils.audioVolume = -42;
+    ok(false, "This should throw");
+  } catch(e) {
+    ok(true, "This should throw");
+  }
+
+  utils.audioVolume = 0;
+  is(utils.audioVolume, 0.0, "utils.audioVolume is ok");
+  utils.audioVolume = 1.0;
+  is(utils.audioVolume, 1.0, "utils.audioVolume is ok");
+
+  SimpleTest.finish();
+}
+
+SpecialPowers.pushPrefEnv({ "set": [["media.useAudioChannelService", true]]}, runTest);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -862,16 +862,22 @@ QueryInterface(JSContext* cx, unsigned a
 }
 
 bool
 ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp)
 {
   return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR);
 }
 
+bool
+ThrowConstructorWithoutNew(JSContext* cx, const char* name)
+{
+  return ThrowErrorMessage(cx, MSG_CONSTRUCTOR_WITHOUT_NEW, name);
+}
+
 inline const NativePropertyHooks*
 GetNativePropertyHooks(JSContext *cx, JS::Handle<JSObject*> obj,
                        DOMObjectType& type)
 {
   const DOMClass* domClass = GetDOMClass(obj);
   if (domClass) {
     type = eInstance;
     return domClass->mNativeHooks;
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -112,16 +112,17 @@ ThrowMethodFailedWithDetails(JSContext* 
       rv.ReportJSExceptionFromJSImplementation(cx);
     } else {
       rv.ReportJSException(cx);
     }
     return false;
   }
   if (rv.IsNotEnoughArgsError()) {
     rv.ReportNotEnoughArgsError(cx, ifaceName, memberName);
+    return false;
   }
   return Throw(cx, rv.ErrorCode());
 }
 
 // Returns true if the JSClass is used for DOM objects.
 inline bool
 IsDOMClass(const JSClass* clasp)
 {
@@ -1461,16 +1462,19 @@ WantsQueryInterface
   {
     return NS_IsMainThread() && IsChromeOrXBL(aCx, aGlobal);
   }
 };
 
 bool
 ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
 
+bool
+ThrowConstructorWithoutNew(JSContext* cx, const char* name);
+
 // vp is allowed to be null; in that case no get will be attempted,
 // and *found will simply indicate whether the property exists.
 bool
 GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<jsid> id, bool* found,
                        JS::Value* vp);
 
 bool
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1262,16 +1262,21 @@ DOMInterfaces = {
     'headerFile': 'WebGLBuffer.h'
 },
 
 'WebGLExtensionCompressedTextureATC': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTextureATC',
     'headerFile': 'WebGLExtensions.h'
 },
 
+'WebGLExtensionCompressedTextureETC1': {
+    'nativeType': 'mozilla::WebGLExtensionCompressedTextureETC1',
+    'headerFile': 'WebGLExtensions.h'
+},
+
 'WebGLExtensionCompressedTexturePVRTC': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTexturePVRTC',
     'headerFile': 'WebGLExtensions.h'
 },
 
 'WebGLExtensionCompressedTextureS3TC': {
     'nativeType': 'mozilla::WebGLExtensionCompressedTextureS3TC',
     'headerFile': 'WebGLExtensions.h'
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1178,36 +1178,58 @@ class CGClassConstructor(CGAbstractStati
             return ""
         return CGAbstractStaticMethod.define(self)
 
     def definition_body(self):
         return self.generate_code()
 
     def generate_code(self):
         preamble = """
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> obj(cx, &args.callee());
+JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+JS::Rooted<JSObject*> obj(cx, &args.callee());
 """
         # [ChromeOnly] interfaces may only be constructed by chrome.
-        # Additionally, we want to throw if a non-chrome caller does a bareword invocation of a
-        # constructor without |new|. We don't enforce this for chrome to avoid the addon compat
-        # fallout of making that change. See bug 916644.
         if isChromeOnly(self._ctor):
-            mayInvokeCtor = "nsContentUtils::ThreadsafeIsCallerChrome()"
-        else:
-            mayInvokeCtor = "(args.isConstructing() || nsContentUtils::ThreadsafeIsCallerChrome())"
-        preamble += """  if (!%s) {
-    return ThrowingConstructor(cx, argc, vp);
-  }
-""" % mayInvokeCtor
+            preamble += (
+                "if (!nsContentUtils::ThreadsafeIsCallerChrome()) {\n"
+                "  return ThrowingConstructor(cx, argc, vp);\n"
+                "}\n\n")
+
+        # Additionally, we want to throw if a caller does a bareword invocation
+        # of a constructor without |new|. We don't enforce this for chrome in
+        # realease builds to avoid the addon compat fallout of making that
+        # change. See bug 916644.
+        #
+        # Figure out the name of our constructor for error reporting purposes.
+        # For unnamed webidl constructors, identifier.name is "constructor" but
+        # the name JS sees is the interface name; for named constructors
+        # identifier.name is the actual name.
+        name = self._ctor.identifier.name
+        if name != "constructor":
+            ctorName = name
+        else:
+            ctorName = self.descriptor.interface.identifier.name
+        preamble += (
+            "bool mayInvoke = args.isConstructing();\n"
+            "#ifdef RELEASE_BUILD\n"
+            "mayInvoke = mayInvoke || nsContentUtils::ThreadsafeIsCallerChrome();\n"
+            "#endif // RELEASE_BUILD\n"
+            "if (!mayInvoke) {\n"
+            "  // XXXbz wish I could get the name from the callee instead of\n"
+            "  // Adding more relocations\n"
+            '  return ThrowConstructorWithoutNew(cx, "%s");\n'
+            "}" % ctorName)
+
         name = self._ctor.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
         callGenerator = CGMethodCall(nativeName, True, self.descriptor,
-                                     self._ctor, isConstructor=True)
-        return preamble + callGenerator.define();
+                                     self._ctor, isConstructor=True,
+                                     constructorName=ctorName)
+        return CGList([CGIndenter(CGGeneric(preamble)), callGenerator],
+                      "\n").define()
 
 # Encapsulate the constructor in a helper method to share genConstructorBody with CGJSImplMethod.
 class CGConstructNavigatorObjectHelper(CGAbstractStaticMethod):
     """
     Construct a new JS-implemented WebIDL DOM object, for use on navigator.
     """
     def __init__(self, descriptor):
         name = "ConstructNavigatorObjectHelper"
@@ -5415,20 +5437,24 @@ class CGCase(CGList):
         self.append(CGGeneric("}"))
 
 class CGMethodCall(CGThing):
     """
     A class to generate selection of a method signature from a set of
     signatures and generation of a call to that signature.
     """
     def __init__(self, nativeMethodName, static, descriptor, method,
-                 isConstructor=False):
+                 isConstructor=False, constructorName=None):
         CGThing.__init__(self)
 
-        methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name)
+        if isConstructor:
+            assert constructorName is not None
+            methodName = constructorName
+        else:
+            methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name)
         argDesc = "argument %d of " + methodName
 
         def requiredArgCount(signature):
             arguments = signature[1]
             if len(arguments) == 0:
                 return 0
             requiredArgs = len(arguments)
             while requiredArgs and arguments[requiredArgs-1].optional:
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -28,16 +28,17 @@ MSG_DEF(MSG_METHOD_THIS_DOES_NOT_IMPLEME
 MSG_DEF(MSG_METHOD_THIS_UNWRAPPING_DENIED, 1, "Permission to call '{0}' denied.")
 MSG_DEF(MSG_GETTER_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 2, "'{0}' getter called on an object that does not implement interface {1}.")
 MSG_DEF(MSG_GETTER_THIS_UNWRAPPING_DENIED, 1, "Permission to call '{0}' getter denied.")
 MSG_DEF(MSG_SETTER_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 2, "'{0}' setter called on an object that does not implement interface {1}.")
 MSG_DEF(MSG_SETTER_THIS_UNWRAPPING_DENIED, 1, "Permission to call '{0}' setter denied.")
 MSG_DEF(MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 1, "\"this\" object does not implement interface {0}.")
 MSG_DEF(MSG_NOT_IN_UNION, 2, "{0} could not be converted to any of: {1}.")
 MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.")
+MSG_DEF(MSG_CONSTRUCTOR_WITHOUT_NEW, 1, "Constructor {0} requires 'new'")
 MSG_DEF(MSG_NO_INDEXED_SETTER, 1, "{0} doesn't have an indexed property setter.")
 MSG_DEF(MSG_NO_NAMED_SETTER, 1, "{0} doesn't have a named property setter.")
 MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.")
 MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.")
 MSG_DEF(MSG_NOT_SEQUENCE, 1, "{0} can't be converted to a sequence.")
 MSG_DEF(MSG_NOT_DICTIONARY, 1, "{0} can't be converted to a dictionary.")
 MSG_DEF(MSG_OVERLOAD_RESOLUTION_FAILED, 3, "Argument {0} is not valid for any of the {1}-argument overloads of {2}.")
 MSG_DEF(MSG_GLOBAL_NOT_NATIVE, 0, "Global is not a native object.")
--- a/dom/bluetooth/tests/marionette/head.js
+++ b/dom/bluetooth/tests/marionette/head.js
@@ -4,22 +4,23 @@
  * 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/. */
 
 let Promise =
   SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
 
 let bluetoothManager;
 
-/* Get mozSettings value specified by @aKey.
+/**
+ * Get mozSettings value specified by @aKey.
  *
  * Resolve if that mozSettings value is retrieved successfully, reject
  * otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   The corresponding mozSettings value of the key.
  * Reject params: (none)
  *
  * @param aKey
  *        A string.
  *
  * @return A deferred promise.
  */
@@ -34,21 +35,22 @@ function getSettings(aKey) {
   request.addEventListener("error", function() {
     ok(false, "getSettings(" + aKey + ")");
     deferred.reject();
   });
 
   return deferred.promise;
 }
 
-/* Set mozSettings values.
+/**
+ * Set mozSettings values.
  *
  * Resolve if that mozSettings value is set successfully, reject otherwise.
  *
- * Forfill params: (none)
+ * Fulfill params: (none)
  * Reject params: (none)
  *
  * @param aSettings
  *        An object of format |{key1: value1, key2: value2, ...}|.
  *
  * @return A deferred promise.
  */
 function setSettings(aSettings) {
@@ -62,53 +64,56 @@ function setSettings(aSettings) {
   request.addEventListener("error", function() {
     ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
     deferred.reject();
   });
 
   return deferred.promise;
 }
 
-/* Get mozSettings value of 'bluetooth.enabled'.
+/**
+ * Get mozSettings value of 'bluetooth.enabled'.
  *
  * Resolve if that mozSettings value is retrieved successfully, reject
  * otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   A boolean value.
  * Reject params: (none)
  *
  * @return A deferred promise.
  */
 function getBluetoothEnabled() {
   return getSettings("bluetooth.enabled");
 }
 
-/* Set mozSettings value of 'bluetooth.enabled'.
+/**
+ * Set mozSettings value of 'bluetooth.enabled'.
  *
  * Resolve if that mozSettings value is set successfully, reject otherwise.
  *
- * Forfill params: (none)
+ * Fulfill params: (none)
  * Reject params: (none)
  *
  * @param aEnabled
  *        A boolean value.
  *
  * @return A deferred promise.
  */
 function setBluetoothEnabled(aEnabled) {
   let obj = {};
   obj["bluetooth.enabled"] = aEnabled;
   return setSettings(obj);
 }
 
-/* Push required permissions and test if |navigator.mozBluetooth| exists.
+/**
+ * Push required permissions and test if |navigator.mozBluetooth| exists.
  * Resolve if it does, reject otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   bluetoothManager -- an reference to navigator.mozBluetooth.
  * Reject params: (none)
  *
  * @param aPermissions
  *        Additional permissions to push before any test cases.  Could be either
  *        a string or an array of strings.
  *
  * @return A deferred promise.
@@ -146,43 +151,45 @@ function ensureBluetoothManager(aPermiss
     } else {
       deferred.reject();
     }
   });
 
   return deferred.promise;
 }
 
-/* Wait for one named BluetoothManager event.
+/**
+ * Wait for one named BluetoothManager event.
  *
  * Resolve if that named event occurs.  Never reject.
  *
- * Forfill params: the DOMEvent passed.
+ * Fulfill params: the DOMEvent passed.
  *
  * @return A deferred promise.
  */
 function waitForManagerEvent(aEventName) {
   let deferred = Promise.defer();
 
   bluetoothManager.addEventListener(aEventName, function onevent(aEvent) {
     bluetoothManager.removeEventListener(aEventName, onevent);
 
     ok(true, "BluetoothManager event '" + aEventName + "' got.");
     deferred.resolve(aEvent);
   });
 
   return deferred.promise;
 }
 
-/* Convenient function for setBluetoothEnabled and waitForManagerEvent
+/**
+ * Convenient function for setBluetoothEnabled and waitForManagerEvent
  * combined.
  *
  * Resolve if that named event occurs.  Reject if we can't set settings.
  *
- * Forfill params: the DOMEvent passed.
+ * Fulfill params: the DOMEvent passed.
  * Reject params: (none)
  *
  * @return A deferred promise.
  */
 function setBluetoothEnabledAndWait(aEnabled) {
   let promises = [];
 
   // Bug 969109 -  Intermittent test_dom_BluetoothManager_adapteradded.js
@@ -193,21 +200,22 @@ function setBluetoothEnabledAndWait(aEna
   // installing the event handler *before* we ever enable/disable Bluetooth. Or
   // we might just miss those events and get a timeout error.
   promises.push(waitForManagerEvent(aEnabled ? "enabled" : "disabled"));
   promises.push(setBluetoothEnabled(aEnabled));
 
   return Promise.all(promises);
 }
 
-/* Get default adapter.
+/**
+ * Get default adapter.
  *
  * Resolve if that default adapter is got, reject otherwise.
  *
- * Forfill params: a BluetoothAdapter instance.
+ * Fulfill params: a BluetoothAdapter instance.
  * Reject params: a DOMError, or null if if there is no adapter ready yet.
  *
  * @return A deferred promise.
  */
 function getDefaultAdapter() {
   let deferred = Promise.defer();
 
   let request = bluetoothManager.getDefaultAdapter();
@@ -232,17 +240,18 @@ function getDefaultAdapter() {
   request.onerror = function(aEvent) {
     ok(false, "Failed to get default adapter.");
     deferred.reject(aEvent.target.error);
   };
 
   return deferred.promise;
 }
 
-/* Flush permission settings and call |finish()|.
+/**
+ * Flush permission settings and call |finish()|.
  */
 function cleanUp() {
   SpecialPowers.flushPermissions(function() {
     // Use ok here so that we have at least one test run.
     ok(true, "permissions flushed");
 
     finish();
   });
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -694,17 +694,17 @@ nsDOMCameraControl::StartRecording(const
 
   NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
 
 #ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
       // Camera app will stop recording when it falls to the background, so no callback is necessary.
-      mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
+      mAudioChannelAgent->Init(mWindow, AUDIO_CHANNEL_CONTENT, nullptr);
       // Video recording doesn't output any sound, so it's not necessary to check canPlay.
       int32_t canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
     }
   }
 #endif
 
   nsCOMPtr<nsIDOMDOMRequest> request;
rename from dom/events/nsContentEventHandler.cpp
rename to dom/events/ContentEventHandler.cpp
--- a/dom/events/nsContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1,79 +1,84 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* 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/. */
 
-#include "nsContentEventHandler.h"
+#include "ContentEventHandler.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Element.h"
+#include "nsCaret.h"
 #include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCopySupport.h"
 #include "nsFocusManager.h"
-#include "nsPresContext.h"
+#include "nsFrameSelection.h"
+#include "nsIContentIterator.h"
 #include "nsIPresShell.h"
 #include "nsISelection.h"
-#include "nsIDOMRange.h"
-#include "nsRange.h"
-#include "nsCaret.h"
-#include "nsCopySupport.h"
-#include "nsFrameSelection.h"
-#include "nsIFrame.h"
-#include "nsView.h"
-#include "nsIContentIterator.h"
-#include "nsTextFragment.h"
-#include "nsTextFrame.h"
 #include "nsISelectionController.h"
 #include "nsISelectionPrivate.h"
-#include "nsContentUtils.h"
-#include "nsLayoutUtils.h"
+#include "nsIDOMRange.h"
+#include "nsIFrame.h"
 #include "nsIObjectFrame.h"
-#include "mozilla/dom/Element.h"
-#include "mozilla/IMEStateManager.h"
-#include "mozilla/TextEvents.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsRange.h"
+#include "nsTextFragment.h"
+#include "nsTextFrame.h"
+#include "nsView.h"
+
 #include <algorithm>
 
-using namespace mozilla;
-using namespace mozilla::dom;
-using namespace mozilla::widget;
+namespace mozilla {
+
+using namespace dom;
+using namespace widget;
 
 /******************************************************************/
-/* nsContentEventHandler                                          */
+/* ContentEventHandler                                            */
 /******************************************************************/
 
-nsContentEventHandler::nsContentEventHandler(
-                              nsPresContext* aPresContext) :
-  mPresContext(aPresContext),
-  mPresShell(aPresContext->GetPresShell()), mSelection(nullptr),
-  mFirstSelectedRange(nullptr), mRootContent(nullptr)
+ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext)
+  : mPresContext(aPresContext)
+  , mPresShell(aPresContext->GetPresShell())
+  , mSelection(nullptr)
+  , mFirstSelectedRange(nullptr)
+  , mRootContent(nullptr)
 {
 }
 
 nsresult
-nsContentEventHandler::InitCommon()
+ContentEventHandler::InitCommon()
 {
-  if (mSelection)
+  if (mSelection) {
     return NS_OK;
+  }
 
   NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_AVAILABLE);
 
   // If text frame which has overflowing selection underline is dirty,
   // we need to flush the pending reflow here.
   mPresShell->FlushPendingNotifications(Flush_Layout);
 
   // Flushing notifications can cause mPresShell to be destroyed (bug 577963).
   NS_ENSURE_TRUE(!mPresShell->IsDestroying(), NS_ERROR_FAILURE);
 
   nsCopySupport::GetSelectionForCopy(mPresShell->GetDocument(),
                                      getter_AddRefs(mSelection));
 
   nsCOMPtr<nsIDOMRange> firstRange;
   nsresult rv = mSelection->GetRangeAt(0, getter_AddRefs(firstRange));
   // This shell doesn't support selection.
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
+  }
   mFirstSelectedRange = static_cast<nsRange*>(firstRange.get());
 
   nsINode* startNode = mFirstSelectedRange->GetStartParent();
   NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
   nsINode* endNode = mFirstSelectedRange->GetEndParent();
   NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
 
   // See bug 537041 comment 5, the range could have removed node.
@@ -83,17 +88,17 @@ nsContentEventHandler::InitCommon()
                "mFirstSelectedRange crosses the document boundary");
 
   mRootContent = startNode->GetSelectionRootContent(mPresShell);
   NS_ENSURE_TRUE(mRootContent, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::Init(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::Init(WidgetQueryContentEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent must not be null");
 
   nsresult rv = InitCommon();
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mSucceeded = false;
 
@@ -112,51 +117,51 @@ nsContentEventHandler::Init(WidgetQueryC
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   aEvent->mReply.mFocusedWidget = frame->GetNearestWidget();
 
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::Init(WidgetSelectionEvent* aEvent)
+ContentEventHandler::Init(WidgetSelectionEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent must not be null");
 
   nsresult rv = InitCommon();
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mSucceeded = false;
 
   return NS_OK;
 }
 
 nsIContent*
-nsContentEventHandler::GetFocusedContent()
+ContentEventHandler::GetFocusedContent()
 {
   nsIDocument* doc = mPresShell->GetDocument();
   if (!doc) {
     return nullptr;
   }
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(doc->GetWindow());
   nsCOMPtr<nsPIDOMWindow> focusedWindow;
   return nsFocusManager::GetFocusedDescendant(window, true,
                                               getter_AddRefs(focusedWindow));
 }
 
 bool
-nsContentEventHandler::IsPlugin(nsIContent* aContent)
+ContentEventHandler::IsPlugin(nsIContent* aContent)
 {
   return aContent &&
          aContent->GetDesiredIMEState().mEnabled == IMEState::PLUGIN;
 }
 
 nsresult
-nsContentEventHandler::QueryContentRect(nsIContent* aContent,
-                                        WidgetQueryContentEvent* aEvent)
+ContentEventHandler::QueryContentRect(nsIContent* aContent,
+                                      WidgetQueryContentEvent* aEvent)
 {
   NS_PRECONDITION(aContent, "aContent must not be null");
 
   nsIFrame* frame = aContent->GetPrimaryFrame();
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
 
   // get rect for first frame
   nsRect resultRect(nsPoint(0, 0), frame->GetRect().Size());
@@ -205,41 +210,44 @@ static void ConvertToNativeNewlines(nsAF
 #endif
 }
 
 static void AppendString(nsAString& aString, nsIContent* aContent)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "aContent is not a text node!");
   const nsTextFragment* text = aContent->GetText();
-  if (!text)
+  if (!text) {
     return;
+  }
   text->AppendTo(aString);
 }
 
 static void AppendSubString(nsAString& aString, nsIContent* aContent,
                             uint32_t aXPOffset, uint32_t aXPLength)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "aContent is not a text node!");
   const nsTextFragment* text = aContent->GetText();
-  if (!text)
+  if (!text) {
     return;
+  }
   text->AppendTo(aString, int32_t(aXPOffset), int32_t(aXPLength));
 }
 
 #if defined(XP_WIN)
 static uint32_t CountNewlinesInXPLength(nsIContent* aContent,
                                         uint32_t aXPLength)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "aContent is not a text node!");
   const nsTextFragment* text = aContent->GetText();
-  if (!text)
+  if (!text) {
     return 0;
+  }
   // For automated tests, we should abort on debug build.
   NS_ABORT_IF_FALSE(
     (aXPLength == UINT32_MAX || aXPLength <= text->GetLength()),
     "aXPLength is out-of-bounds");
   const uint32_t length = std::min(aXPLength, text->GetLength());
   uint32_t newlines = 0;
   for (uint32_t i = 0; i < length; ++i) {
     if (text->CharAt(i) == '\n') {
@@ -274,37 +282,40 @@ static uint32_t CountNewlinesInNativeLen
       ++nativeOffset;
     }
   }
   return newlines;
 }
 #endif
 
 /* static */ uint32_t
-nsContentEventHandler::GetNativeTextLength(nsIContent* aContent, uint32_t aMaxLength)
+ContentEventHandler::GetNativeTextLength(nsIContent* aContent,
+                                         uint32_t aMaxLength)
 {
   if (aContent->IsNodeOfType(nsINode::eTEXT)) {
     uint32_t textLengthDifference =
 #if defined(XP_MACOSX)
       // On Mac, the length of a native newline ("\r") is equal to the length of
-      // the XP newline ("\n"), so the native length is the same as the XP length.
+      // the XP newline ("\n"), so the native length is the same as the XP
+      // length.
       0;
 #elif defined(XP_WIN)
-      // On Windows, the length of a native newline ("\r\n") is twice the length of
-      // the XP newline ("\n"), so XP length is equal to the length of the native
-      // offset plus the number of newlines encountered in the string.
+      // On Windows, the length of a native newline ("\r\n") is twice the length
+      // of the XP newline ("\n"), so XP length is equal to the length of the
+      // native offset plus the number of newlines encountered in the string.
       CountNewlinesInXPLength(aContent, aMaxLength);
 #else
       // On other platforms, the native and XP newlines are the same.
       0;
 #endif
 
     const nsTextFragment* text = aContent->GetText();
-    if (!text)
+    if (!text) {
       return 0;
+    }
     uint32_t length = std::min(text->GetLength(), aMaxLength);
     return length + textLengthDifference;
   } else if (IsContentBR(aContent)) {
 #if defined(XP_WIN)
     // Length of \r\n
     return 2;
 #else
     return 1;
@@ -349,112 +360,123 @@ static nsresult GenerateFlatTextContent(
                     aRange->EndOffset() - aRange->StartOffset());
     ConvertToNativeNewlines(aString);
     return NS_OK;
   }
 
   nsAutoString tmpStr;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node)
+    if (!node) {
       break;
-    if (!node->IsNodeOfType(nsINode::eCONTENT))
+    }
+    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
       continue;
+    }
     nsIContent* content = static_cast<nsIContent*>(node);
 
     if (content->IsNodeOfType(nsINode::eTEXT)) {
-      if (content == startNode)
+      if (content == startNode) {
         AppendSubString(aString, content, aRange->StartOffset(),
                         content->TextLength() - aRange->StartOffset());
-      else if (content == endNode)
+      } else if (content == endNode) {
         AppendSubString(aString, content, 0, aRange->EndOffset());
-      else
+      } else {
         AppendString(aString, content);
-    } else if (IsContentBR(content))
-        aString.Append(char16_t('\n'));
+      }
+    } else if (IsContentBR(content)) {
+      aString.Append(char16_t('\n'));
+    }
   }
   ConvertToNativeNewlines(aString);
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent,
-                                               bool aForward,
-                                               uint32_t* aXPOffset)
+ContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent,
+                                             bool aForward,
+                                             uint32_t* aXPOffset)
 {
   // XXX This method assumes that the frame boundaries must be cluster
   // boundaries. It's false, but no problem now, maybe.
   if (!aContent->IsNodeOfType(nsINode::eTEXT) ||
-      *aXPOffset == 0 || *aXPOffset == aContent->TextLength())
+      *aXPOffset == 0 || *aXPOffset == aContent->TextLength()) {
     return NS_OK;
+  }
 
   NS_ASSERTION(*aXPOffset <= aContent->TextLength(),
                "offset is out of range.");
 
   nsRefPtr<nsFrameSelection> fs = mPresShell->FrameSelection();
   int32_t offsetInFrame;
   nsFrameSelection::HINT hint =
     aForward ? nsFrameSelection::HINTLEFT : nsFrameSelection::HINTRIGHT;
   nsIFrame* frame = fs->GetFrameForNodeOffset(aContent, int32_t(*aXPOffset),
                                               hint, &offsetInFrame);
   if (!frame) {
     // This content doesn't have any frames, we only can check surrogate pair...
     const nsTextFragment* text = aContent->GetText();
     NS_ENSURE_TRUE(text, NS_ERROR_FAILURE);
     if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) &&
-        NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1)))
+        NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) {
       *aXPOffset += aForward ? 1 : -1;
+    }
     return NS_OK;
   }
   int32_t startOffset, endOffset;
   nsresult rv = frame->GetOffsets(startOffset, endOffset);
   NS_ENSURE_SUCCESS(rv, rv);
-  if (*aXPOffset == uint32_t(startOffset) || *aXPOffset == uint32_t(endOffset))
+  if (*aXPOffset == static_cast<uint32_t>(startOffset) ||
+      *aXPOffset == static_cast<uint32_t>(endOffset)) {
     return NS_OK;
-  if (frame->GetType() != nsGkAtoms::textFrame)
+  }
+  if (frame->GetType() != nsGkAtoms::textFrame) {
     return NS_ERROR_FAILURE;
+  }
   nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
   int32_t newOffsetInFrame = *aXPOffset - startOffset;
   newOffsetInFrame += aForward ? -1 : 1;
   textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame);
   *aXPOffset = startOffset + newOffsetInFrame;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::SetRangeFromFlatTextOffset(
-                              nsRange* aRange,
-                              uint32_t aNativeOffset,
-                              uint32_t aNativeLength,
-                              bool aExpandToClusterBoundaries,
-                              uint32_t* aNewNativeOffset)
+ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
+                                                uint32_t aNativeOffset,
+                                                uint32_t aNativeLength,
+                                                bool aExpandToClusterBoundaries,
+                                                uint32_t* aNewNativeOffset)
 {
   if (aNewNativeOffset) {
     *aNewNativeOffset = aNativeOffset;
   }
 
   nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
   nsresult rv = iter->Init(mRootContent);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint32_t nativeOffset = 0;
   uint32_t nativeEndOffset = aNativeOffset + aNativeLength;
   bool startSet = false;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node)
+    if (!node) {
       break;
-    if (!node->IsNodeOfType(nsINode::eCONTENT))
+    }
+    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
       continue;
+    }
     nsIContent* content = static_cast<nsIContent*>(node);
 
     uint32_t nativeTextLength;
     nativeTextLength = GetNativeTextLength(content);
-    if (nativeTextLength == 0)
+    if (nativeTextLength == 0) {
       continue;
+    }
 
     if (nativeOffset <= aNativeOffset &&
         aNativeOffset < nativeOffset + nativeTextLength) {
       nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(content));
       NS_ASSERTION(domNode, "aContent doesn't have nsIDOMNode!");
 
       uint32_t xpOffset =
         content->IsNodeOfType(nsINode::eTEXT) ?
@@ -490,31 +512,33 @@ nsContentEventHandler::SetRangeFromFlatT
           rv = ExpandToClusterBoundary(content, true, &xpOffset);
           NS_ENSURE_SUCCESS(rv, rv);
         }
       } else {
         // Use first position of next node, because the end node is ignored
         // by ContentIterator when the offset is zero.
         xpOffset = 0;
         iter->Next();
-        if (iter->IsDone())
+        if (iter->IsDone()) {
           break;
+        }
         domNode = do_QueryInterface(iter->GetCurrentNode());
       }
 
       rv = aRange->SetEnd(domNode, int32_t(xpOffset));
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
 
     nativeOffset += nativeTextLength;
   }
 
-  if (nativeOffset < aNativeOffset)
+  if (nativeOffset < aNativeOffset) {
     return NS_ERROR_FAILURE;
+  }
 
   nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mRootContent));
   NS_ASSERTION(domNode, "lastContent doesn't have nsIDOMNode!");
   if (!startSet) {
     MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT));
     rv = aRange->SetStart(domNode, int32_t(mRootContent->GetChildCount()));
     NS_ENSURE_SUCCESS(rv, rv);
     if (aNewNativeOffset) {
@@ -522,21 +546,22 @@ nsContentEventHandler::SetRangeFromFlatT
     }
   }
   rv = aRange->SetEnd(domNode, int32_t(mRootContent->GetChildCount()));
   NS_ASSERTION(NS_SUCCEEDED(rv), "nsIDOMRange::SetEnd failed");
   return rv;
 }
 
 nsresult
-nsContentEventHandler::OnQuerySelectedText(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQuerySelectedText(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
   rv = GetFlatTextOffsetOfRange(mRootContent,
                                 mFirstSelectedRange, &aEvent->mReply.mOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -565,21 +590,22 @@ nsContentEventHandler::OnQuerySelectedTe
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
   nsRefPtr<nsRange> range = new nsRange(mRootContent);
   rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
                                   aEvent->mInput.mLength, false,
                                   &aEvent->mReply.mOffset);
@@ -626,21 +652,22 @@ static nsresult GetFrameForTextRect(nsIN
   nsIFrame* frame = content->GetPrimaryFrame();
   NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
   int32_t childOffset = 0;
   return frame->GetChildFrameContainingOffset(aOffset, aHint, &childOffset,
                                               aReturnFrame);
 }
 
 nsresult
-nsContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryTextRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   nsRefPtr<nsRange> range = new nsRange(mRootContent);
   rv = SetRangeFromFlatTextOffset(range, aEvent->mInput.mOffset,
                                   aEvent->mInput.mLength, true,
                                   &aEvent->mReply.mOffset);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = GenerateFlatTextContent(range, aEvent->mReply.mString);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -679,20 +706,22 @@ nsContentEventHandler::OnQueryTextRect(W
 
   // iterate over all covered frames
   for (nsIFrame* frame = firstFrame; frame != lastFrame;) {
     frame = frame->GetNextContinuation();
     if (!frame) {
       do {
         iter->Next();
         node = iter->GetCurrentNode();
-        if (!node)
+        if (!node) {
           break;
-        if (!node->IsNodeOfType(nsINode::eCONTENT))
+        }
+        if (!node->IsNodeOfType(nsINode::eCONTENT)) {
           continue;
+        }
         frame = static_cast<nsIContent*>(node)->GetPrimaryFrame();
       } while (!frame && !iter->IsDone());
       if (!frame) {
         // this can happen when the end offset of the range is 0.
         frame = lastFrame;
       }
     }
     frameRect.SetRect(nsPoint(0, 0), frame->GetRect().Size());
@@ -716,35 +745,37 @@ nsContentEventHandler::OnQueryTextRect(W
   }
   aEvent->mReply.mRect =
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   nsIContent* focusedContent = GetFocusedContent();
   rv = QueryContentRect(IsPlugin(focusedContent) ?
                           focusedContent : mRootContent.get(), aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryCaretRect(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   nsRefPtr<nsCaret> caret = mPresShell->GetCaret();
   NS_ASSERTION(caret, "GetCaret returned null");
 
   // When the selection is collapsed and the queried offset is current caret
   // position, we should return the "real" caret rect.
   bool selectionIsCollapsed;
   rv = mSelection->GetIsCollapsed(&selectionIsCollapsed);
@@ -759,18 +790,19 @@ nsContentEventHandler::OnQueryCaretRect(
     // element offset properties. We need to match those or things break. 
     nsINode* startNode = mFirstSelectedRange->GetStartParent();
     if (startNode && startNode->IsNodeOfType(nsINode::eTEXT)) {
       offset = ConvertToXPOffset(static_cast<nsIContent*>(startNode), offset);
     }
     if (offset == aEvent->mInput.mOffset) {
       nsRect rect;
       nsIFrame* caretFrame = caret->GetGeometry(mSelection, &rect);
-      if (!caretFrame)
+      if (!caretFrame) {
         return NS_ERROR_FAILURE;
+      }
       rv = ConvertToRootViewRelativeOffset(caretFrame, rect);
       NS_ENSURE_SUCCESS(rv, rv);
       aEvent->mReply.mRect =
         rect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel());
       aEvent->mReply.mOffset = aEvent->mInput.mOffset;
       aEvent->mSucceeded = true;
       return NS_OK;
     }
@@ -802,57 +834,59 @@ nsContentEventHandler::OnQueryCaretRect(
 
   aEvent->mReply.mRect =
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryContentState(WidgetQueryContentEvent * aEvent)
+ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
-  
+  }
   aEvent->mSucceeded = true;
-
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQuerySelectionAsTransferable(
-                         WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQuerySelectionAsTransferable(
+                       WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   if (!aEvent->mReply.mHasSelection) {
     aEvent->mSucceeded = true;
     aEvent->mReply.mTransferable = nullptr;
     return NS_OK;
   }
 
   nsCOMPtr<nsIDocument> doc = mPresShell->GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
-  rv = nsCopySupport::GetTransferableForSelection(mSelection, doc, getter_AddRefs(aEvent->mReply.mTransferable));
+  rv = nsCopySupport::GetTransferableForSelection(
+         mSelection, doc, getter_AddRefs(aEvent->mReply.mTransferable));
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
   NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
   nsIWidget* rootWidget = rootFrame->GetNearestWidget();
   NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
 
   // The root frame's widget might be different, e.g., the event was fired on
   // a popup but the rootFrame is the document root.
@@ -866,17 +900,18 @@ nsContentEventHandler::OnQueryCharacterA
     NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
   }
 
   WidgetQueryContentEvent eventOnRoot(true, NS_QUERY_CHARACTER_AT_POINT,
                                       rootWidget);
   eventOnRoot.refPoint = aEvent->refPoint;
   if (rootWidget != aEvent->widget) {
     eventOnRoot.refPoint += LayoutDeviceIntPoint::FromUntyped(
-      aEvent->widget->WidgetToScreenOffset() - rootWidget->WidgetToScreenOffset());
+      aEvent->widget->WidgetToScreenOffset() -
+        rootWidget->WidgetToScreenOffset());
   }
   nsPoint ptInRoot =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
 
   nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
   if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame ||
       !targetFrame->GetContent() ||
       !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(),
@@ -909,21 +944,22 @@ nsContentEventHandler::OnQueryCharacterA
   // currently, we don't need to get the actual text.
   aEvent->mReply.mOffset = nativeOffset;
   aEvent->mReply.mRect = textRect.mReply.mRect;
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent)
+ContentEventHandler::OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     return rv;
+  }
 
   aEvent->mReply.mWidgetIsHit = false;
 
   NS_ENSURE_TRUE(aEvent->widget, NS_ERROR_FAILURE);
 
   nsIDocument* doc = mPresShell->GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
   nsIFrame* docFrame = mPresShell->GetRootFrame();
@@ -942,29 +978,30 @@ nsContentEventHandler::OnQueryDOMWidgetH
     nsIWidget* targetWidget = nullptr;
     nsIFrame* targetFrame = contentUnderMouse->GetPrimaryFrame();
     nsIObjectFrame* pluginFrame = do_QueryFrame(targetFrame);
     if (pluginFrame) {
       targetWidget = pluginFrame->GetWidget();
     } else if (targetFrame) {
       targetWidget = targetFrame->GetNearestWidget();
     }
-    if (aEvent->widget == targetWidget)
+    if (aEvent->widget == targetWidget) {
       aEvent->mReply.mWidgetIsHit = true;
+    }
   }
 
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                                nsINode* aNode,
-                                                int32_t aNodeOffset,
-                                                uint32_t* aNativeOffset)
+ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                              nsINode* aNode,
+                                              int32_t aNodeOffset,
+                                              uint32_t* aNativeOffset)
 {
   NS_ENSURE_STATE(aRootContent);
   NS_ASSERTION(aNativeOffset, "param is invalid");
 
   nsRefPtr<nsRange> prev = new nsRange(aRootContent);
   nsCOMPtr<nsIDOMNode> rootDOMNode(do_QueryInterface(aRootContent));
   prev->SetStart(rootDOMNode, 0);
 
@@ -976,86 +1013,91 @@ nsContentEventHandler::GetFlatTextOffset
   iter->Init(prev);
 
   nsCOMPtr<nsINode> startNode = do_QueryInterface(startDOMNode);
   nsINode* endNode = aNode;
 
   *aNativeOffset = 0;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node)
+    if (!node) {
       break;
-    if (!node->IsNodeOfType(nsINode::eCONTENT))
+    }
+    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
       continue;
+    }
     nsIContent* content = static_cast<nsIContent*>(node);
 
     if (node->IsNodeOfType(nsINode::eTEXT)) {
       // Note: our range always starts from offset 0
-      if (node == endNode)
+      if (node == endNode) {
         *aNativeOffset += GetNativeTextLength(content, aNodeOffset);
-      else
+      } else {
         *aNativeOffset += GetNativeTextLength(content);
+      }
     } else if (IsContentBR(content)) {
 #if defined(XP_WIN)
       // On Windows, the length of the newline is 2.
       *aNativeOffset += 2;
 #else
       // On other platforms, the length of the newline is 1.
       *aNativeOffset += 1;
 #endif
     }
   }
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                                nsRange* aRange,
-                                                uint32_t* aNativeOffset)
+ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
+                                              nsRange* aRange,
+                                              uint32_t* aNativeOffset)
 {
   nsINode* startNode = aRange->GetStartParent();
   NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
   int32_t startOffset = aRange->StartOffset();
   return GetFlatTextOffsetOfRange(aRootContent, startNode, startOffset,
                                   aNativeOffset);
 }
 
 nsresult
-nsContentEventHandler::GetStartFrameAndOffset(nsRange* aRange,
-                                              nsIFrame** aFrame,
-                                              int32_t* aOffsetInFrame)
+ContentEventHandler::GetStartFrameAndOffset(nsRange* aRange,
+                                            nsIFrame** aFrame,
+                                            int32_t* aOffsetInFrame)
 {
   NS_ASSERTION(aRange && aFrame && aOffsetInFrame, "params are invalid");
 
   nsIContent* content = nullptr;
   nsINode* node = aRange->GetStartParent();
-  if (node && node->IsNodeOfType(nsINode::eCONTENT))
+  if (node && node->IsNodeOfType(nsINode::eCONTENT)) {
     content = static_cast<nsIContent*>(node);
+  }
   NS_ASSERTION(content, "the start node doesn't have nsIContent!");
 
   nsRefPtr<nsFrameSelection> fs = mPresShell->FrameSelection();
   *aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(),
                                       fs->GetHint(), aOffsetInFrame);
   NS_ENSURE_TRUE((*aFrame), NS_ERROR_FAILURE);
   NS_ASSERTION((*aFrame)->GetType() == nsGkAtoms::textFrame,
                "The frame is not textframe");
   return NS_OK;
 }
 
 nsresult
-nsContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
-                                                       nsRect& aRect)
+ContentEventHandler::ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
+                                                     nsRect& aRect)
 {
   NS_ASSERTION(aFrame, "aFrame must not be null");
 
   nsView* view = nullptr;
   nsPoint posInView;
   aFrame->GetOffsetFromView(posInView, &view);
-  if (!view)
+  if (!view) {
     return NS_ERROR_FAILURE;
+  }
   aRect += posInView + view->GetOffsetTo(nullptr);
   return NS_OK;
 }
 
 static void AdjustRangeForSelection(nsIContent* aRoot,
                                     nsINode** aNode,
                                     int32_t* aOffset)
 {
@@ -1075,26 +1117,27 @@ static void AdjustRangeForSelection(nsIC
     } else {
       node = node->GetParent();
       offset = node->IndexOf(*aNode) + (offset ? 1 : 0);
     }
   }
 
   nsIContent* brContent = node->GetChildAt(offset - 1);
   while (brContent && brContent->IsHTML()) {
-    if (brContent->Tag() != nsGkAtoms::br || IsContentBR(brContent))
+    if (brContent->Tag() != nsGkAtoms::br || IsContentBR(brContent)) {
       break;
+    }
     brContent = node->GetChildAt(--offset - 1);
   }
   *aNode = node;
   *aOffset = std::max(offset, 0);
 }
 
 nsresult
-nsContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent)
+ContentEventHandler::OnSelectionEvent(WidgetSelectionEvent* aEvent)
 {
   aEvent->mSucceeded = false;
 
   // Get selection to manipulate
   // XXX why do we need to get them from ISM? This method should work fine
   //     without ISM.
   nsresult rv =
     IMEStateManager::GetFocusSelectionAndRoot(getter_AddRefs(mSelection),
@@ -1148,8 +1191,10 @@ nsContentEventHandler::OnSelectionEvent(
   NS_ENSURE_SUCCESS(rv, rv);
 
   selPrivate->ScrollIntoViewInternal(
     nsISelectionController::SELECTION_FOCUS_REGION,
     false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
+
+} // namespace mozilla
rename from dom/events/nsContentEventHandler.h
rename to dom/events/ContentEventHandler.h
--- a/dom/events/nsContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -1,71 +1,71 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#ifndef nsContentEventHandler_h__
-#define nsContentEventHandler_h__
+#ifndef mozilla_ContentEventHandler_h_
+#define mozilla_ContentEventHandler_h_
 
+#include "mozilla/EventForwards.h"
 #include "nsCOMPtr.h"
-
 #include "nsISelection.h"
 #include "nsRange.h"
-#include "mozilla/EventForwards.h"
 
-class nsCaret;
 class nsPresContext;
 
 struct nsRect;
 
+namespace mozilla {
+
 /*
  * Query Content Event Handler
- *   nsContentEventHandler is a helper class for nsEventStateManager.
+ *   ContentEventHandler is a helper class for nsEventStateManager.
  *   The platforms request some content informations, e.g., the selected text,
  *   the offset of the selected text and the text for specified range.
  *   This class answers to NS_QUERY_* events from actual contents.
  */
 
-class MOZ_STACK_CLASS nsContentEventHandler {
+class MOZ_STACK_CLASS ContentEventHandler
+{
 public:
-  nsContentEventHandler(nsPresContext *aPresContext);
+  ContentEventHandler(nsPresContext* aPresContext);
 
   // NS_QUERY_SELECTED_TEXT event handler
-  nsresult OnQuerySelectedText(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQuerySelectedText(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_TEXT_CONTENT event handler
-  nsresult OnQueryTextContent(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryTextContent(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_CARET_RECT event handler
-  nsresult OnQueryCaretRect(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryCaretRect(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_TEXT_RECT event handler
-  nsresult OnQueryTextRect(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryTextRect(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_EDITOR_RECT event handler
-  nsresult OnQueryEditorRect(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryEditorRect(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_CONTENT_STATE event handler
-  nsresult OnQueryContentState(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryContentState(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_SELECTION_AS_TRANSFERABLE event handler
-  nsresult OnQuerySelectionAsTransferable(
-             mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQuerySelectionAsTransferable(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_CHARACTER_AT_POINT event handler
-  nsresult OnQueryCharacterAtPoint(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent);
   // NS_QUERY_DOM_WIDGET_HITTEST event handler
-  nsresult OnQueryDOMWidgetHittest(mozilla::WidgetQueryContentEvent* aEvent);
+  nsresult OnQueryDOMWidgetHittest(WidgetQueryContentEvent* aEvent);
 
   // NS_SELECTION_* event
-  nsresult OnSelectionEvent(mozilla::WidgetSelectionEvent* aEvent);
+  nsresult OnSelectionEvent(WidgetSelectionEvent* aEvent);
 
 protected:
   nsPresContext* mPresContext;
   nsCOMPtr<nsIPresShell> mPresShell;
   nsCOMPtr<nsISelection> mSelection;
   nsRefPtr<nsRange> mFirstSelectedRange;
   nsCOMPtr<nsIContent> mRootContent;
 
-  nsresult Init(mozilla::WidgetQueryContentEvent* aEvent);
-  nsresult Init(mozilla::WidgetSelectionEvent* aEvent);
+  nsresult Init(WidgetQueryContentEvent* aEvent);
+  nsresult Init(WidgetSelectionEvent* aEvent);
 
   // InitCommon() is called from each Init().
   nsresult InitCommon();
 
 public:
   // FlatText means the text that is generated from DOM tree. The BR elements
   // are replaced to native linefeeds. Other elements are ignored.
 
@@ -82,17 +82,17 @@ public:
                                       uint32_t aMaxLength = UINT32_MAX);
 protected:
   // Returns focused content (including its descendant documents).
   nsIContent* GetFocusedContent();
   // Returns true if the content is a plugin host.
   bool IsPlugin(nsIContent* aContent);
   // QueryContentRect() sets the rect of aContent's frame(s) to aEvent.
   nsresult QueryContentRect(nsIContent* aContent,
-                            mozilla::WidgetQueryContentEvent* aEvent);
+                            WidgetQueryContentEvent* aEvent);
   // Make the DOM range from the offset of FlatText and the text length.
   // If aExpandToClusterBoundaries is true, the start offset and the end one are
   // expanded to nearest cluster boundaries.
   nsresult SetRangeFromFlatTextOffset(nsRange* aRange,
                                       uint32_t aNativeOffset,
                                       uint32_t aNativeLength,
                                       bool aExpandToClusterBoundaries,
                                       uint32_t* aNewNativeOffset = nullptr);
@@ -105,9 +105,11 @@ protected:
   nsresult ConvertToRootViewRelativeOffset(nsIFrame* aFrame,
                                            nsRect& aRect);
   // Expand aXPOffset to the nearest offset in cluster boundary. aForward is
   // true, it is expanded to forward.
   nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward,
                                    uint32_t* aXPOffset);
 };
 
-#endif // nsContentEventHandler_h__
+} // namespace mozilla
+
+#endif // mozilla_ContentEventHandler_h_
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -1,20 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* 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/. */
 
+#include "ContentEventHandler.h"
 #include "IMEContentObserver.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/dom/Element.h"
 #include "nsAutoPtr.h"
 #include "nsAsyncDOMEvent.h"
-#include "nsContentEventHandler.h"
 #include "nsContentUtils.h"
 #include "nsGkAtoms.h"
 #include "nsIAtom.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMRange.h"
 #include "nsIFrame.h"
@@ -365,19 +365,19 @@ IMEContentObserver::CharacterDataChanged
   if (causedByComposition &&
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
   uint32_t offset = 0;
   // get offsets of change and fire notification
   nsresult rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent,
-                                                    aInfo->mChangeStart,
-                                                    &offset);
+    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent,
+                                                  aInfo->mChangeStart,
+                                                  &offset);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   uint32_t oldEnd = offset + aInfo->mChangeEnd - aInfo->mChangeStart;
   uint32_t newEnd = offset + aInfo->mReplaceLength;
 
   nsContentUtils::AddScriptRunner(
     new TextChangeEvent(this, offset, oldEnd, newEnd, causedByComposition));
 }
@@ -390,26 +390,25 @@ IMEContentObserver::NotifyContentAdded(n
   bool causedByComposition = IsEditorHandlingEventForComposition();
   if (causedByComposition &&
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
   uint32_t offset = 0;
   nsresult rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer,
-                                                    aStartIndex, &offset);
+    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer,
+                                                  aStartIndex, &offset);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   // get offset at the end of the last added node
   nsIContent* childAtStart = aContainer->GetChildAt(aStartIndex);
   uint32_t addingLength = 0;
-  rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer,
-                                                    aEndIndex, &addingLength);
+  rv = ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer,
+                                                     aEndIndex, &addingLength);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   if (!addingLength) {
     return;
   }
 
   nsContentUtils::AddScriptRunner(
     new TextChangeEvent(this, offset, offset, offset + addingLength,
@@ -446,32 +445,31 @@ IMEContentObserver::ContentRemoved(nsIDo
   bool causedByComposition = IsEditorHandlingEventForComposition();
   if (causedByComposition &&
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
   uint32_t offset = 0;
   nsresult rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(mRootContent,
-                                                    NODE_FROM(aContainer,
-                                                              aDocument),
-                                                    aIndexInContainer, &offset);
+    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent,
+                                                  NODE_FROM(aContainer,
+                                                            aDocument),
+                                                  aIndexInContainer, &offset);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   // get offset at the end of the deleted node
   int32_t nodeLength =
     aChild->IsNodeOfType(nsINode::eTEXT) ?
       static_cast<int32_t>(aChild->TextLength()) :
       std::max(static_cast<int32_t>(aChild->GetChildCount()), 1);
   MOZ_ASSERT(nodeLength >= 0, "The node length is out of range");
   uint32_t textLength = 0;
-  rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild,
-                                                    nodeLength, &textLength);
+  rv = ContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild,
+                                                     nodeLength, &textLength);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   if (!textLength) {
     return;
   }
 
   nsContentUtils::AddScriptRunner(
     new TextChangeEvent(this, offset, offset + textLength, offset,
@@ -492,17 +490,17 @@ void
 IMEContentObserver::AttributeWillChange(nsIDocument* aDocument,
                                         dom::Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute,
                                         int32_t aModType)
 {
   nsIContent *content = GetContentBR(aElement);
   mPreAttrChangeLength = content ?
-    nsContentEventHandler::GetNativeTextLength(content) : 0;
+    ContentEventHandler::GetNativeTextLength(content) : 0;
 }
 
 void
 IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
                                      dom::Element* aElement,
                                      int32_t aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      int32_t aModType)
@@ -514,24 +512,24 @@ IMEContentObserver::AttributeChanged(nsI
   }
 
   nsIContent *content = GetContentBR(aElement);
   if (!content) {
     return;
   }
 
   uint32_t postAttrChangeLength =
-    nsContentEventHandler::GetNativeTextLength(content);
+    ContentEventHandler::GetNativeTextLength(content);
   if (postAttrChangeLength == mPreAttrChangeLength) {
     return;
   }
   uint32_t start;
   nsresult rv =
-    nsContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content,
-                                                    0, &start);
+    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, content,
+                                                  0, &start);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   nsContentUtils::AddScriptRunner(
     new TextChangeEvent(this, start, start + mPreAttrChangeLength,
                         start + postAttrChangeLength, causedByComposition));
 }
 
 } // namespace mozilla
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* 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/. */
 
+#include "ContentEventHandler.h"
 #include "TextComposition.h"
-#include "nsContentEventHandler.h"
 #include "nsContentUtils.h"
 #include "nsEventDispatcher.h"
 #include "nsIContent.h"
 #include "nsIEditor.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
@@ -222,17 +222,17 @@ TextComposition::CompositionEventDispatc
   }
 
   nsEventStatus status = nsEventStatus_eIgnore;
   switch (mEventMessage) {
     case NS_COMPOSITION_START: {
       WidgetCompositionEvent compStart(true, NS_COMPOSITION_START, mWidget);
       WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT,
                                            mWidget);
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQuerySelectedText(&selectedText);
       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
       compStart.data = selectedText.mReply.mString;
       IMEStateManager::DispatchCompositionEvent(mEventTarget, mPresContext,
                                                 &compStart, &status, nullptr);
       break;
     }
     case NS_COMPOSITION_UPDATE:
--- a/dom/events/WheelEvent.cpp
+++ b/dom/events/WheelEvent.cpp
@@ -12,19 +12,27 @@ namespace mozilla {
 namespace dom {
 
 WheelEvent::WheelEvent(EventTarget* aOwner,
                        nsPresContext* aPresContext,
                        WidgetWheelEvent* aWheelEvent)
   : MouseEvent(aOwner, aPresContext,
                aWheelEvent ? aWheelEvent :
                              new WidgetWheelEvent(false, 0, nullptr))
+  , mAppUnitsPerDevPixel(0)
 {
   if (aWheelEvent) {
     mEventIsInternal = false;
+    // If the delta mode is pixel, the WidgetWheelEvent's delta values are in
+    // device pixels.  However, JS contents need the delta values in CSS pixels.
+    // We should store the value of mAppUnitsPerDevPixel here because
+    // it might be changed by changing zoom or something.
+    if (aWheelEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
+      mAppUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+    }
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     mEvent->AsWheelEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
@@ -66,47 +74,59 @@ WheelEvent::InitWheelEvent(const nsAStri
   wheelEvent->deltaMode = aDeltaMode;
 
   return NS_OK;
 }
 
 double
 WheelEvent::DeltaX()
 {
-  return mEvent->AsWheelEvent()->deltaX;
+  if (!mAppUnitsPerDevPixel) {
+    return mEvent->AsWheelEvent()->deltaX;
+  }
+  return mEvent->AsWheelEvent()->deltaX *
+    mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel();
 }
 
 NS_IMETHODIMP
 WheelEvent::GetDeltaX(double* aDeltaX)
 {
   NS_ENSURE_ARG_POINTER(aDeltaX);
 
   *aDeltaX = DeltaX();
   return NS_OK;
 }
 
 double
 WheelEvent::DeltaY()
 {
-  return mEvent->AsWheelEvent()->deltaY;
+  if (!mAppUnitsPerDevPixel) {
+    return mEvent->AsWheelEvent()->deltaY;
+  }
+  return mEvent->AsWheelEvent()->deltaY *
+    mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel();
 }
 
 NS_IMETHODIMP
 WheelEvent::GetDeltaY(double* aDeltaY)
 {
   NS_ENSURE_ARG_POINTER(aDeltaY);
 
   *aDeltaY = DeltaY();
   return NS_OK;
 }
 
 double
 WheelEvent::DeltaZ()
 {
-  return mEvent->AsWheelEvent()->deltaZ;
+  if (!mAppUnitsPerDevPixel) {
+    return mEvent->AsWheelEvent()->deltaZ;
+  }
+  return mEvent->AsWheelEvent()->deltaZ *
+    mAppUnitsPerDevPixel / nsPresContext::AppUnitsPerCSSPixel();
 }
 
 NS_IMETHODIMP
 WheelEvent::GetDeltaZ(double* aDeltaZ)
 {
   NS_ENSURE_ARG_POINTER(aDeltaZ);
 
   *aDeltaZ = DeltaZ();
--- a/dom/events/WheelEvent.h
+++ b/dom/events/WheelEvent.h
@@ -38,18 +38,24 @@ public:
                                            ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return WheelEventBinding::Wrap(aCx, aScope, this);
   }
 
+  // NOTE: DeltaX(), DeltaY() and DeltaZ() return CSS pixels when deltaMode is
+  //       DOM_DELTA_PIXEL. (The internal event's delta values are device pixels
+  //       if it's dispatched by widget)
   double DeltaX();
   double DeltaY();
   double DeltaZ();
   uint32_t DeltaMode();
+
+private:
+  int32_t mAppUnitsPerDevPixel;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_WheelEvent_h_
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -66,16 +66,17 @@ if CONFIG['MOZ_WEBSPEECH']:
     EXPORTS.mozilla.dom += ['SpeechRecognitionError.h']
 
 UNIFIED_SOURCES += [
     'AnimationEvent.cpp',
     'BeforeUnloadEvent.cpp',
     'ClipboardEvent.cpp',
     'CommandEvent.cpp',
     'CompositionEvent.cpp',
+    'ContentEventHandler.cpp',
     'DataContainerEvent.cpp',
     'DataTransfer.cpp',
     'DeviceMotionEvent.cpp',
     'DragEvent.cpp',
     'Event.cpp',
     'EventTarget.cpp',
     'FocusEvent.cpp',
     'IMEContentObserver.cpp',
@@ -83,17 +84,16 @@ UNIFIED_SOURCES += [
     'KeyboardEvent.cpp',
     'MessageEvent.cpp',
     'MouseEvent.cpp',
     'MouseScrollEvent.cpp',
     'MutationEvent.cpp',
     'NotifyAudioAvailableEvent.cpp',
     'NotifyPaintEvent.cpp',
     'nsAsyncDOMEvent.cpp',
-    'nsContentEventHandler.cpp',
     'nsDOMEventTargetHelper.cpp',
     'nsEventDispatcher.cpp',
     'nsEventListenerManager.cpp',
     'nsEventListenerService.cpp',
     'nsJSEventListener.cpp',
     'nsPaintRequest.cpp',
     'PointerEvent.cpp',
     'ScrollAreaEvent.cpp',
--- a/dom/events/nsEventStateManager.cpp
+++ b/dom/events/nsEventStateManager.cpp
@@ -10,20 +10,21 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/UIEvent.h"
 
+#include "ContentEventHandler.h"
+
 #include "nsCOMPtr.h"
 #include "nsEventStateManager.h"
 #include "nsFocusManager.h"
-#include "nsContentEventHandler.h"
 #include "nsIContent.h"
 #include "nsINodeInfo.h"
 #include "nsIDocument.h"
 #include "nsIFrame.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
@@ -1211,85 +1212,87 @@ nsEventStateManager::PreHandleEvent(nsPr
         InitLineOrPageDelta(aTargetFrame, this, wheelEvent);
     }
     break;
   case NS_QUERY_SELECTED_TEXT:
     DoQuerySelectedText(aEvent->AsQueryContentEvent());
     break;
   case NS_QUERY_TEXT_CONTENT:
     {
-      if (RemoteQueryContentEvent(aEvent))
+      if (RemoteQueryContentEvent(aEvent)) {
         break;
-      nsContentEventHandler handler(mPresContext);
+      }
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryTextContent(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CARET_RECT:
     {
       if (RemoteQueryContentEvent(aEvent)) {
         break;
       }
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryCaretRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_TEXT_RECT:
     {
       if (RemoteQueryContentEvent(aEvent)) {
         break;
       }
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryTextRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_EDITOR_RECT:
     {
       // XXX remote event
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryEditorRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CONTENT_STATE:
     {
       // XXX remote event
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryContentState(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_SELECTION_AS_TRANSFERABLE:
     {
       // XXX remote event
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQuerySelectionAsTransferable(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CHARACTER_AT_POINT:
     {
       // XXX remote event
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryCharacterAtPoint(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_DOM_WIDGET_HITTEST:
     {
       // XXX remote event
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnQueryDOMWidgetHittest(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_SELECTION_SET:
     {
       WidgetSelectionEvent* selectionEvent = aEvent->AsSelectionEvent();
       if (IsTargetCrossProcess(selectionEvent)) {
         // Will not be handled locally, remote the event
-        if (GetCrossProcessTarget()->SendSelectionEvent(*selectionEvent))
+        if (GetCrossProcessTarget()->SendSelectionEvent(*selectionEvent)) {
           selectionEvent->mSucceeded = true;
+        }
         break;
       }
-      nsContentEventHandler handler(mPresContext);
+      ContentEventHandler handler(mPresContext);
       handler.OnSelectionEvent(selectionEvent);
     }
     break;
   case NS_CONTENT_COMMAND_CUT:
   case NS_CONTENT_COMMAND_COPY:
   case NS_CONTENT_COMMAND_PASTE:
   case NS_CONTENT_COMMAND_DELETE:
   case NS_CONTENT_COMMAND_UNDO:
@@ -5422,17 +5425,17 @@ nsEventStateManager::DoContentCommandScr
 }
 
 void
 nsEventStateManager::DoQuerySelectedText(WidgetQueryContentEvent* aEvent)
 {
   if (RemoteQueryContentEvent(aEvent)) {
     return;
   }
-  nsContentEventHandler handler(mPresContext);
+  ContentEventHandler handler(mPresContext);
   handler.OnQuerySelectedText(aEvent);
 }
 
 void
 nsEventStateManager::SetActiveManager(nsEventStateManager* aNewESM,
                                       nsIContent* aContent)
 {
   if (sActiveESM && aNewESM != sActiveESM) {
--- a/dom/events/test/window_wheel_default_action.html
+++ b/dom/events/test/window_wheel_default_action.html
@@ -1142,85 +1142,103 @@ function doTestZoom(aSettings, aCallback
       });
     }, 20);
   }
   doNextTest();
 }
 
 function doTestZoomedScroll(aCallback)
 {
+  var zoom = 1.0;
+  function setFullZoom(aWindow, aZoom)
+  {
+    zoom = aZoom;
+    SpecialPowers.setFullZoom(aWindow, aZoom);
+  }
+
   function prepareTestZoomedPixelScroll()
   {
     // Reset zoom and store the scroll amount into the data.
     synthesizeKey("0", { accelKey: true });
+    zoom = 1.0;
     onZoomReset(testZoomedPixelScroll);
   }
 
   function testZoomedPixelScroll()
   {
     gScrollableElement.scrollTop = 1000;
     gScrollableElement.scrollLeft = 1000;
     // Ensure not to be in reflow.
     hitEventLoop(function () {
-      function handler(aEvent)
+      function mousePixelScrollHandler(aEvent)
       {
         if (aEvent.axis == MouseScrollEvent.HORIZONTAL_AXIS) {
           is(aEvent.detail, 16,
              "doTestZoomedScroll: The detail of horizontal MozMousePixelScroll for pixel wheel event is wrong");
         } else if (aEvent.axis == MouseScrollEvent.VERTICAL_AXIS) {
           is(aEvent.detail, 16,
              "doTestZoomedScroll: The detail of vertical MozMousePixelScroll for pixel wheel event is wrong");
         } else {
           ok(false, "doTestZoomedScroll: The axis of MozMousePixelScroll for pixel wheel event is invalid, got " + aEvent.axis);
         }
       }
-      window.addEventListener("MozMousePixelScroll", handler, true);
+      function wheelHandler(aEvent)
+      {
+        is(aEvent.deltaX, 16.0 / zoom,
+           "doTestZoomedScroll: The deltaX of wheel for pixel is wrong");
+        is(aEvent.deltaY, 16.0 / zoom,
+           "doTestZoomedScroll: The deltaY of wheel for pixel is wrong");
+      }
+      window.addEventListener("MozMousePixelScroll", mousePixelScrollHandler, true);
+      window.addEventListener("wheel", wheelHandler, true);
       synthesizeWheel(gScrollableElement, 10, 10,
         { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
           deltaX: 16.0, deltaY: 16.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
       // wait scrolled actually.
       hitEventLoop(function () {
         var scrolledX = gScrollableElement.scrollLeft;
         var scrolledY = gScrollableElement.scrollTop;
         ok(scrolledX > 1000,
            "doTestZoomedScroll: scrolledX must be larger than 1000 for pixel wheel event, got " + scrolledX);
         ok(scrolledY > 1000,
            "doTestZoomedScroll: scrolledY must be larger than 1000 for pixel wheel event, got " + scrolledY);
 
         // Zoom
-        SpecialPowers.setFullZoom(window, 2.0);
+        setFullZoom(window, 2.0);
         // Ensure not to be in reflow.
         hitEventLoop(function () {
           gScrollableElement.scrollTop = 1000;
           gScrollableElement.scrollLeft = 1000;
           synthesizeWheel(gScrollableElement, 10, 10,
             { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
               deltaX: 16.0, deltaY: 16.0, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
           // wait scrolled actually.
           hitEventLoop(function () {
             ok(Math.abs(gScrollableElement.scrollLeft - (1000 + (scrolledX - 1000) / 2)) <= 1,
                "doTestZoomedScroll: zoomed horizontal scroll amount by pixel wheel event is different from normal, scrollLeft=" +
                  gScrollableElement.scrollLeft + ", scrolledX=" + scrolledX);
             ok(Math.abs(gScrollableElement.scrollTop - (1000 + (scrolledY - 1000) / 2)) <= 1,
                "doTestZoomedScroll: zoomed vertical scroll amount by pixel wheel event is different from normal, scrollTop=" +
                  gScrollableElement.scrollTop + ", scrolledY=" + scrolledY);
-            window.removeEventListener("MozMousePixelScroll", handler, true);
+            window.removeEventListener("MozMousePixelScroll", mousePixelScrollHandler, true);
+            window.removeEventListener("wheel", wheelHandler, true);
 
             synthesizeKey("0", { accelKey: true });
             onZoomReset(prepareTestZoomedLineScroll);
           }, 20);
         }, 20);
       }, 20);
     }, 20);
   }
 
   function prepareTestZoomedLineScroll()
   {
     // Reset zoom and store the scroll amount into the data.
     synthesizeKey("0", { accelKey: true });
+    zoom = 1.0;
     onZoomReset(testZoomedLineScroll);
   }
   function testZoomedLineScroll()
   {
     gScrollableElement.scrollTop = 1000;
     gScrollableElement.scrollLeft = 1000;
     // Ensure not to be in reflow.
     hitEventLoop(function () {
@@ -1256,17 +1274,17 @@ function doTestZoomedScroll(aCallback)
         var scrolledX = gScrollableElement.scrollLeft;
         var scrolledY = gScrollableElement.scrollTop;
         ok(scrolledX > 1000,
            "doTestZoomedScroll: scrolledX must be larger than 1000 for line wheel event, got " + scrolledX);
         ok(scrolledY > 1000,
            "doTestZoomedScroll: scrolledY must be larger than 1000 for line wheel event, got " + scrolledY);
 
         // Zoom
-        SpecialPowers.setFullZoom(window, 2.0);
+        setFullZoom(window, 2.0);
         // Ensure not to be in reflow.
         hitEventLoop(function () {
           gScrollableElement.scrollTop = 1000;
           gScrollableElement.scrollLeft = 1000;
           synthesizeWheel(gScrollableElement, 10, 10,
             { deltaMode: WheelEvent.DOM_DELTA_LINE,
               deltaX: 1.0, deltaY: 1.0, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
           // wait scrolled actually.
--- a/dom/filesystem/FileSystemTaskBase.cpp
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -36,17 +36,16 @@ FileSystemTaskBase::FileSystemTaskBase(F
              "Only call from parent process!");
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
   mFileSystem = do_GetWeakReference(aFileSystem);
 }
 
 FileSystemTaskBase::~FileSystemTaskBase()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
 }
 
 already_AddRefed<FileSystemBase>
 FileSystemTaskBase::GetFileSystem()
 {
   nsRefPtr<FileSystemBase> filesystem = do_QueryReferent(mFileSystem);
   return filesystem.forget();
 }
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -145,16 +145,17 @@ FMRadio::Init(nsPIDOMWindow *aWindow)
   // if preferences doesn't enable AudioChannelService.
   NS_ENSURE_TRUE_VOID(Preferences::GetBool("media.useAudioChannelService"));
 
   nsCOMPtr<nsIAudioChannelAgent> audioChannelAgent =
     do_CreateInstance("@mozilla.org/audiochannelagent;1");
   NS_ENSURE_TRUE_VOID(audioChannelAgent);
 
   audioChannelAgent->InitWithWeakCallback(
+    GetOwner(),
     nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_CONTENT,
     this);
 
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
   NS_ENSURE_TRUE_VOID(docshell);
 
   bool isActive = false;
   docshell->GetIsActive(&isActive);
@@ -383,16 +384,22 @@ FMRadio::EnableAudioChannelAgent()
 
 NS_IMETHODIMP
 FMRadio::CanPlayChanged(int32_t aCanPlay)
 {
   SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FMRadio::WindowVolumeChanged()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 void
 FMRadio::SetCanPlay(bool aCanPlay)
 {
   IFMRadioService::Singleton()->EnableAudio(aCanPlay);
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -29,16 +29,17 @@ class FMRadio MOZ_FINAL : public nsDOMEv
 
 {
   friend class FMRadioRequest;
 
 public:
   FMRadio();
 
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
   /* hal::SwitchObserver */
   virtual void Notify(const hal::SwitchEvent& aEvent) MOZ_OVERRIDE;
@@ -77,19 +78,16 @@ public:
 
   already_AddRefed<DOMRequest> CancelSeek();
 
   IMPL_EVENT_HANDLER(enabled);
   IMPL_EVENT_HANDLER(disabled);
   IMPL_EVENT_HANDLER(antennaavailablechange);
   IMPL_EVENT_HANDLER(frequencychange);
 
-  // nsIAudioChannelAgentCallback
-  NS_IMETHOD CanPlayChanged(int32_t aCanPlay);
-
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
 private:
   ~FMRadio();
 
   void SetCanPlay(bool aCanPlay);
   void EnableAudioChannelAgent();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -38,17 +38,17 @@ interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 
-[scriptable, uuid(27efada9-b8ea-4d70-a2e6-f46b9ba905f4)]
+[scriptable, uuid(ef70a299-033c-4adc-b214-6649aed9d828)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1577,9 +1577,22 @@ interface nsIDOMWindowUtils : nsISupport
 
    /*
     * Returns the value of a given property.  If the property is animated off
     * the main thread, this function will fetch the correct value from the
     * compositor.
     */
    AString getOMTAOrComputedStyle(in nsIDOMElement aElement,
                                   in AString aProperty);
+
+   /**
+    * With this it's possible to mute all the MediaElements in this window.
+    * We have audioMuted and audioVolume to preserve the volume across
+    * mute/umute.
+    */
+   attribute boolean audioMuted;
+
+    /**
+     * range: greater or equal to 0. The real volume level is affected by the
+     * volume of all ancestor windows.
+     */
+    attribute float audioVolume;
 };
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -67,16 +67,17 @@
 #include "PuppetWidget.h"
 #include "StructuredCloneUtils.h"
 #include "nsViewportInfo.h"
 #include "JavaScriptChild.h"
 #include "APZCCallbackHelper.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
+#include "UnitTransforms.h"
 
 #include "nsColorPickerProxy.h"
 
 #ifdef DEBUG
 #include "PCOMContentPermissionRequestChild.h"
 #endif /* DEBUG */
 
 #define BROWSER_ELEMENT_CHILD_SCRIPT \
@@ -311,17 +312,19 @@ TabChild::HandleEvent(nsIDOMEvent* aEven
 
 void
 TabChild::InitializeRootMetrics()
 {
   // Calculate a really simple resolution that we probably won't
   // be keeping, as well as putting the scroll offset back to
   // the top-left of the page.
   mLastRootMetrics.mViewport = CSSRect(CSSPoint(), kDefaultViewportSize);
-  mLastRootMetrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
+  mLastRootMetrics.mCompositionBounds = ParentLayerIntRect(
+      ParentLayerIntPoint(),
+      ViewAs<ParentLayerPixel>(mInnerSize, PixelCastJustification::ScreenToParentLayerForRoot));
   mLastRootMetrics.mZoom = mLastRootMetrics.CalculateIntrinsicScale();
   mLastRootMetrics.mDevPixelsPerCSSPixel = mWidget->GetDefaultScale();
   // We use ScreenToLayerScale(1) below in order to turn the
   // async zoom amount into the gecko zoom amount.
   mLastRootMetrics.mCumulativeResolution =
     mLastRootMetrics.mZoom / mLastRootMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
   // This is the root layer, so the cumulative resolution is the same
   // as the resolution.
@@ -583,17 +586,19 @@ TabChild::HandlePossibleViewportChange()
 
   float oldScreenWidth = mLastRootMetrics.mCompositionBounds.width;
   if (!oldScreenWidth) {
     oldScreenWidth = mInnerSize.width;
   }
 
   FrameMetrics metrics(mLastRootMetrics);
   metrics.mViewport = CSSRect(CSSPoint(), viewport);
-  metrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
+  metrics.mCompositionBounds = ParentLayerIntRect(
+      ParentLayerIntPoint(),
+      ViewAs<ParentLayerPixel>(mInnerSize, PixelCastJustification::ScreenToParentLayerForRoot));
 
   // This change to the zoom accounts for all types of changes I can conceive:
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
   //    viewport)
   // 3. screen size remains constant, but CSS viewport changes (meta viewport
   //    tag is added or removed)
@@ -626,17 +631,17 @@ TabChild::HandlePossibleViewportChange()
   }
 
   metrics.mCumulativeResolution = metrics.mZoom / metrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
   // This is the root layer, so the cumulative resolution is the same
   // as the resolution.
   metrics.mResolution = metrics.mCumulativeResolution / LayoutDeviceToParentLayerScale(1);
   utils->SetResolution(metrics.mResolution.scale, metrics.mResolution.scale);
 
-  CSSSize scrollPort = metrics.CalculateCompositedRectInCssPixels().Size();
+  CSSSize scrollPort = CSSSize(metrics.CalculateCompositedRectInCssPixels().Size());
   utils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height);
 
   // The call to GetPageSize forces a resize event to content, so we need to
   // make sure that we have the right CSS viewport and
   // scrollPositionClampingScrollPortSize set up before that happens.
 
   CSSSize pageSize = GetPageSize(document, viewport);
   if (!pageSize.width) {
@@ -1506,45 +1511,29 @@ TabChild::ProcessUpdateFrame(const Frame
         return true;
     }
 
     nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
 
     FrameMetrics newMetrics = aFrameMetrics;
     APZCCallbackHelper::UpdateRootFrame(utils, newMetrics);
 
-    CSSRect cssCompositedRect = newMetrics.CalculateCompositedRectInCssPixels();
+    CSSRect cssCompositedRect = CSSRect(newMetrics.CalculateCompositedRectInCssPixels());
     // The BrowserElementScrolling helper must know about these updated metrics
     // for other functions it performs, such as double tap handling.
     // Note, %f must not be used because it is locale specific!
     nsCString data;
     data.AppendPrintf("{ \"x\" : %d", NS_lround(newMetrics.mScrollOffset.x));
     data.AppendPrintf(", \"y\" : %d", NS_lround(newMetrics.mScrollOffset.y));
     data.AppendLiteral(", \"viewport\" : ");
         data.AppendLiteral("{ \"width\" : ");
         data.AppendFloat(newMetrics.mViewport.width);
         data.AppendLiteral(", \"height\" : ");
         data.AppendFloat(newMetrics.mViewport.height);
         data.AppendLiteral(" }");
-    data.AppendLiteral(", \"displayPort\" : ");
-        data.AppendLiteral("{ \"x\" : ");
-        data.AppendFloat(newMetrics.mDisplayPort.x);
-        data.AppendLiteral(", \"y\" : ");
-        data.AppendFloat(newMetrics.mDisplayPort.y);
-        data.AppendLiteral(", \"width\" : ");
-        data.AppendFloat(newMetrics.mDisplayPort.width);
-        data.AppendLiteral(", \"height\" : ");
-        data.AppendFloat(newMetrics.mDisplayPort.height);
-        data.AppendLiteral(" }");
-    data.AppendLiteral(", \"compositionBounds\" : ");
-        data.AppendPrintf("{ \"x\" : %d", newMetrics.mCompositionBounds.x);
-        data.AppendPrintf(", \"y\" : %d", newMetrics.mCompositionBounds.y);
-        data.AppendPrintf(", \"width\" : %d", newMetrics.mCompositionBounds.width);
-        data.AppendPrintf(", \"height\" : %d", newMetrics.mCompositionBounds.height);
-        data.AppendLiteral(" }");
     data.AppendLiteral(", \"cssPageRect\" : ");
         data.AppendLiteral("{ \"x\" : ");
         data.AppendFloat(newMetrics.mScrollableRect.x);
         data.AppendLiteral(", \"y\" : ");
         data.AppendFloat(newMetrics.mScrollableRect.y);
         data.AppendLiteral(", \"width\" : ");
         data.AppendFloat(newMetrics.mScrollableRect.width);
         data.AppendLiteral(", \"height\" : ");
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -709,22 +709,21 @@ TabParent::MapEventCoordinatesForChildPr
   }
 }
 
 bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetMouseEvent outEvent(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
-  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
+  MaybeForwardEventToRenderFrame(event, nullptr);
+  if (!MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
-  return PBrowserParent::SendRealMouseEvent(outEvent);
+  return PBrowserParent::SendRealMouseEvent(event);
 }
 
 CSSIntPoint TabParent::AdjustTapToChildWidget(const CSSIntPoint& aPoint)
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
 
   if (!content || !content->OwnerDoc()) {
     return aPoint;
@@ -777,35 +776,33 @@ bool TabParent::SendHandleDoubleTap(cons
   return PBrowserParent::SendHandleDoubleTap(AdjustTapToChildWidget(aPoint), aGuid);
 }
 
 bool TabParent::SendMouseWheelEvent(WidgetWheelEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetWheelEvent outEvent(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
-  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
+  MaybeForwardEventToRenderFrame(event, nullptr);
+  if (!MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
-  return PBrowserParent::SendMouseWheelEvent(outEvent);
+  return PBrowserParent::SendMouseWheelEvent(event);
 }
 
 bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  WidgetKeyboardEvent outEvent(event);
-  MaybeForwardEventToRenderFrame(event, nullptr, &outEvent);
-  if (!MapEventCoordinatesForChildProcess(&outEvent)) {
+  MaybeForwardEventToRenderFrame(event, nullptr);
+  if (!MapEventCoordinatesForChildProcess(&event)) {
     return false;
   }
-  return PBrowserParent::SendRealKeyEvent(outEvent);
+  return PBrowserParent::SendRealKeyEvent(event);
 }
 
 bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
   if (event.message == NS_TOUCH_START) {
@@ -836,34 +833,28 @@ bool TabParent::SendRealTouchEvent(Widge
   if (event.message == NS_TOUCH_END || event.message == NS_TOUCH_CANCEL) {
     for (int i = event.touches.Length() - 1; i >= 0; i--) {
       if (!event.touches[i]->mChanged) {
         event.touches.RemoveElementAt(i);
       }
     }
   }
 
-  // Create an out event for remote content that is identical to the event that
-  // we send to the render frame. The out event will be transformed in such a
-  // way that its async transform in the compositor is unapplied. The event that
-  // it is created from does not get mutated.
-  WidgetTouchEvent outEvent(event);
-
   ScrollableLayerGuid guid;
-  MaybeForwardEventToRenderFrame(event, &guid, &outEvent);
+  MaybeForwardEventToRenderFrame(event, &guid);
 
   if (mIsDestroyed) {
     return false;
   }
 
-  MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &outEvent);
+  MapEventCoordinatesForChildProcess(mChildProcessOffsetAtTouchStart, &event);
 
-  return (outEvent.message == NS_TOUCH_MOVE) ?
-    PBrowserParent::SendRealTouchMoveEvent(outEvent, guid) :
-    PBrowserParent::SendRealTouchEvent(outEvent, guid);
+  return (event.message == NS_TOUCH_MOVE) ?
+    PBrowserParent::SendRealTouchMoveEvent(event, guid) :
+    PBrowserParent::SendRealTouchEvent(event, guid);
 }
 
 /*static*/ TabParent*
 TabParent::GetEventCapturer()
 {
   return sEventCapturer;
 }
 
@@ -1806,22 +1797,21 @@ TabParent::UseAsyncPanZoom()
   bool usingOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
   bool asyncPanZoomEnabled =
     Preferences::GetBool("layers.async-pan-zoom.enabled", false);
   return (usingOffMainThreadCompositing && asyncPanZoomEnabled &&
           GetScrollingBehavior() == ASYNC_PAN_ZOOM);
 }
 
 void
-TabParent::MaybeForwardEventToRenderFrame(const WidgetInputEvent& aEvent,
-                                          ScrollableLayerGuid* aOutTargetGuid,
-                                          WidgetInputEvent* aOutEvent)
+TabParent::MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
+                                          ScrollableLayerGuid* aOutTargetGuid)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
-    rfp->NotifyInputEvent(aEvent, aOutTargetGuid, aOutEvent);
+    rfp->NotifyInputEvent(aEvent, aOutTargetGuid);
   }
 }
 
 bool
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       const nsString& aURL,
                                       const nsString& aName,
                                       const nsString& aFeatures,
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -362,23 +362,23 @@ private:
 
     CSSIntPoint AdjustTapToChildWidget(const CSSIntPoint& aPoint);
 
     // When true, we create a pan/zoom controller for our frame and
     // notify it of input events targeting us.
     bool UseAsyncPanZoom();
     // If we have a render frame currently, notify it that we're about
     // to dispatch |aEvent| to our child.  If there's a relevant
-    // transform in place, |aOutEvent| is the transformed |aEvent| to
-    // dispatch to content. |aOutTargetGuid| will contain the identifier
+    // transform in place, |aEvent| will be transformed in-place so that
+    // it is ready to be dispatched to content.
+    // |aOutTargetGuid| will contain the identifier
     // of the APZC instance that handled the event. aOutTargetGuid may be
-    // null but aOutEvent must not be.
-    void MaybeForwardEventToRenderFrame(const WidgetInputEvent& aEvent,
-                                        ScrollableLayerGuid* aOutTargetGuid,
-                                        WidgetInputEvent* aOutEvent);
+    // null.
+    void MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
+                                        ScrollableLayerGuid* aOutTargetGuid);
     // The offset for the child process which is sampled at touch start. This
     // means that the touch events are relative to where the frame was at the
     // start of the touch. We need to look for a better solution to this
     // problem see bug 872911.
     LayoutDeviceIntPoint mChildProcessOffsetAtTouchStart;
     // When true, we've initiated normal shutdown and notified our
     // managing PContent.
     bool mMarkedDestroying;
--- a/dom/locales/en-US/chrome/layout/layout_errors.properties
+++ b/dom/locales/en-US/chrome/layout/layout_errors.properties
@@ -2,8 +2,10 @@
 # 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/.
 
 ImageMapRectBoundsError=The "coords" attribute of the <area shape="rect"> tag is not in the "left,top,right,bottom" format.
 ImageMapCircleWrongNumberOfCoords=The "coords" attribute of the <area shape="circle"> tag is not in the "center-x,center-y,radius" format.
 ImageMapCircleNegativeRadius=The "coords" attribute of the <area shape="circle"> tag has a negative radius.
 ImageMapPolyWrongNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is not in the "x1,y1,x2,y2 …" format.
 ImageMapPolyOddNumberOfCoords=The "coords" attribute of the <area shape="poly"> tag is missing the last "y" coordinate (the correct format is "x1,y1,x2,y2 …").
+
+TablePartRelPosWarning=Relative positioning of table rows and row groups is now supported. This site may need to be updated because it may depend on this feature having no effect.
new file mode 100644
--- /dev/null
+++ b/dom/mobileconnection/tests/marionette/head.js
@@ -0,0 +1,399 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
+
+const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
+const SETTINGS_KEY_DATA_ROAMING_ENABLED = "ril.data.roaming_enabled";
+const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
+
+let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
+
+let _pendingEmulatorCmdCount = 0;
+
+/**
+ * Send emulator command with safe guard.
+ *
+ * We should only call |finish()| after all emulator command transactions
+ * end, so here comes with the pending counter.  Resolve when the emulator
+ * gives positive response, and reject otherwise.
+ *
+ * Fulfill params:
+ *   result -- an array of emulator response lines.
+ * Reject params:
+ *   result -- an array of emulator response lines.
+ *
+ * @param aCommand
+ *        A string command to be passed to emulator through its telnet console.
+ *
+ * @return A deferred promise.
+ */
+function runEmulatorCmdSafe(aCommand) {
+  let deferred = Promise.defer();
+
+  ++_pendingEmulatorCmdCount;
+  runEmulatorCmd(aCommand, function(aResult) {
+    --_pendingEmulatorCmdCount;
+
+    ok(true, "Emulator response: " + JSON.stringify(aResult));
+    if (Array.isArray(aResult) && aResult[0] === "OK") {
+      deferred.resolve(aResult);
+    } else {
+      deferred.reject(aResult);
+    }
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Get mozSettings value specified by @aKey.
+ *
+ * Resolve if that mozSettings value is retrieved successfully, reject
+ * otherwise.
+ *
+ * Fulfill params:
+ *   The corresponding mozSettings value of the key.
+ * Reject params: (none)
+ *
+ * @param aKey
+ *        A string.
+ * @param aAllowError [optional]
+ *        A boolean value.  If set to true, an error response won't be treated
+ *        as test failure.  Default: false.
+ *
+ * @return A deferred promise.
+ */
+function getSettings(aKey, aAllowError) {
+  let deferred = Promise.defer();
+
+  let request = navigator.mozSettings.createLock().get(aKey);
+  request.addEventListener("success", function(aEvent) {
+    ok(true, "getSettings(" + aKey + ") - success");
+    deferred.resolve(aEvent.target.result[aKey]);
+  });
+  request.addEventListener("error", function() {
+    ok(aAllowError, "getSettings(" + aKey + ") - error");
+    deferred.reject();
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Set mozSettings values.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aSettings
+ *        An object of format |{key1: value1, key2: value2, ...}|.
+ * @param aAllowError [optional]
+ *        A boolean value.  If set to true, an error response won't be treated
+ *        as test failure.  Default: false.
+ *
+ * @return A deferred promise.
+ */
+function setSettings(aSettings, aAllowError) {
+  let deferred = Promise.defer();
+
+  let request = navigator.mozSettings.createLock().set(aSettings);
+  request.addEventListener("success", function() {
+    ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
+    deferred.resolve();
+  });
+  request.addEventListener("error", function() {
+    ok(aAllowError, "setSettings(" + JSON.stringify(aSettings) + ")");
+    deferred.reject();
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Set mozSettings value with only one key.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aKey
+ *        A string key.
+ * @param aValue
+ *        An object value.
+ * @param aAllowError [optional]
+ *        A boolean value.  If set to true, an error response won't be treated
+ *        as test failure.  Default: false.
+ *
+ * @return A deferred promise.
+ */
+function setSettings1(aKey, aValue, aAllowError) {
+  let settings = {};
+  settings[aKey] = aValue;
+  return setSettings(settings, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_ENABLED.
+ */
+function getDataEnabled(aAllowError) {
+  return getSettings(SETTINGS_KEY_DATA_ENABLED, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_ENABLED.
+ */
+function setDataEnabled(aEnabled, aAllowError) {
+  return setSettings1(SETTINGS_KEY_DATA_ENABLED, aEnabled, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
+ */
+function getDataRoamingEnabled(aAllowError) {
+  return getSettings(SETTINGS_KEY_DATA_ROAMING_ENABLED, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
+ */
+function setDataRoamingEnabled(aEnabled, aAllowError) {
+  return setSettings1(SETTINGS_KEY_DATA_ROAMING_ENABLED, aEnabled, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_APN_SETTINGS.
+ */
+function getDataApnSettings(aAllowError) {
+  return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_APN_SETTINGS.
+ */
+function setDataApnSettings(aApnSettings, aAllowError) {
+  return setSettings1(SETTINGS_KEY_DATA_APN_SETTINGS, aApnSettings, aAllowError);
+}
+
+let mobileConnection;
+
+/**
+ * Push required permissions and test if
+ * |navigator.mozMobileConnections[<aServiceId>]| exists. Resolve if it does,
+ * reject otherwise.
+ *
+ * Fulfill params:
+ *   mobileConnection -- an reference to navigator.mozMobileMessage.
+ *
+ * Reject params: (none)
+ *
+ * @param aAdditonalPermissions [optional]
+ *        An array of permission strings other than "mobileconnection" to be
+ *        pushed. Default: empty string.
+ * @param aServiceId [optional]
+ *        A numeric DSDS service id. Default: 0.
+ *
+ * @return A deferred promise.
+ */
+function ensureMobileConnection(aAdditionalPermissions, aServiceId) {
+  let deferred = Promise.defer();
+
+  aAdditionalPermissions = aAdditionalPermissions || [];
+  aServiceId = aServiceId || 0;
+
+  if (aAdditionalPermissions.indexOf("mobileconnection") < 0) {
+    aAdditionalPermissions.push("mobileconnection");
+  }
+  let permissions = [];
+  for (let perm of aAdditionalPermissions) {
+    permissions.push({ "type": perm, "allow": 1, "context": document });
+  }
+
+  SpecialPowers.pushPermissions(permissions, function() {
+    ok(true, "permissions pushed: " + JSON.stringify(permissions));
+
+    // Permission changes can't change existing Navigator.prototype
+    // objects, so grab our objects from a new Navigator.
+    let ifr = document.createElement("iframe");
+    ifr.addEventListener("load", function load() {
+      ifr.removeEventListener("load", load);
+
+      mobileConnection =
+        ifr.contentWindow.navigator.mozMobileConnections[aServiceId];
+
+      if (mobileConnection) {
+        log("navigator.mozMobileConnections[" + aServiceId + "] is instance of " +
+            mobileConnection.constructor);
+      } else {
+        log("navigator.mozMobileConnections[" + aServiceId + "] is undefined");
+      }
+
+      if (mobileConnection instanceof MozMobileConnection) {
+        deferred.resolve(mobileConnection);
+      } else {
+        deferred.reject();
+      }
+    });
+
+    document.body.appendChild(ifr);
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Wait for one named MobileConnection event.
+ *
+ * Resolve if that named event occurs.  Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventName
+ *        A string event name.
+ *
+ * @return A deferred promise.
+ */
+function waitForManagerEvent(aEventName) {
+  let deferred = Promise.defer();
+
+  mobileConnection.addEventListener(aEventName, function onevent(aEvent) {
+    mobileConnection.removeEventListener(aEventName, onevent);
+
+    ok(true, "MobileConnection event '" + aEventName + "' got.");
+    deferred.resolve(aEvent);
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Set data connection enabling state and wait for "datachange" event.
+ *
+ * Resolve if data connection state changed to the expected one.  Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aEnabled
+ *        A boolean state.
+ *
+ * @return A deferred promise.
+ */
+function setDataEnabledAndWait(aEnabled) {
+  let deferred = Promise.defer();
+
+  let promises = [];
+  promises.push(waitForManagerEvent("datachange"));
+  promises.push(setDataEnabled(aEnabled));
+  Promise.all(promises).then(function keepWaiting() {
+    // To ignore some transient states, we only resolve that deferred promise
+    // when the |connected| state equals to the expected one and never rejects.
+    let connected = mobileConnection.data.connected;
+    if (connected == aEnabled) {
+      deferred.resolve();
+      return;
+    }
+
+    return waitForManagerEvent("datachange").then(keepWaiting);
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Set voice/data roaming emulation and wait for state change.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aRoaming
+ *        A boolean state.
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorRoamingAndWait(aRoaming) {
+  function doSetAndWait(aWhich, aRoaming) {
+    let promises = [];
+    promises.push(waitForManagerEvent(aWhich + "change"));
+
+    let cmd = "gsm " + aWhich + " " + (aRoaming ? "roaming" : "home");
+    promises.push(runEmulatorCmdSafe(cmd));
+    return Promise.all(promises)
+      .then(() => is(mobileConnection[aWhich].roaming, aRoaming,
+                     aWhich + ".roaming"));
+  }
+
+  // Set voice registration state first and then data registration state.
+  return doSetAndWait("voice", aRoaming)
+    .then(() => doSetAndWait("data", aRoaming));
+}
+
+let _networkManager;
+
+/**
+ * Get internal NetworkManager service.
+ */
+function getNetworkManager() {
+  if (!_networkManager) {
+    _networkManager = Cc["@mozilla.org/network/manager;1"]
+                    .getService(Ci.nsINetworkManager);
+    ok(_networkManager, "NetworkManager");
+  }
+
+  return _networkManager;
+}
+
+/**
+ * Flush permission settings and call |finish()|.
+ */
+function cleanUp() {
+  waitFor(function() {
+    SpecialPowers.flushPermissions(function() {
+      // Use ok here so that we have at least one test run.
+      ok(true, "permissions flushed");
+
+      finish();
+    });
+  }, function() {
+    return _pendingEmulatorCmdCount === 0;
+  });
+}
+
+/**
+ * Basic test routine helper for mobile connection tests.
+ *
+ * This helper does nothing but clean-ups.
+ *
+ * @param aTestCaseMain
+ *        A function that takes no parameter.
+ */
+function startTestBase(aTestCaseMain) {
+  Promise.resolve()
+    .then(aTestCaseMain)
+    .then(cleanUp, function() {
+      ok(false, 'promise rejects during test.');
+      cleanUp();
+    });
+}
+
+/**
+ * Common test routine helper for mobile connection tests.
+ *
+ * This function ensures global |mobileConnection| variable is available during
+ * the process and performs clean-ups as well.
+ *
+ * @param aTestCaseMain
+ *        A function that takes one parameter -- mobileConnection.
+ * @param aAdditonalPermissions [optional]
+ *        An array of permission strings other than "mobileconnection" to be
+ *        pushed. Default: empty string.
+ * @param aServiceId [optional]
+ *        A numeric DSDS service id. Default: 0.
+ */
+function startTestCommon(aTestCaseMain, aAdditionalPermissions, aServiceId) {
+  startTestBase(function() {
+    return ensureMobileConnection(aAdditionalPermissions, aServiceId)
+      .then(aTestCaseMain);
+  });
+}
--- a/dom/mobileconnection/tests/marionette/manifest.ini
+++ b/dom/mobileconnection/tests/marionette/manifest.ini
@@ -18,8 +18,10 @@ disabled = Bug 808783
 [test_mobile_roaming_preference.js]
 [test_call_barring_get_option.js]
 [test_call_barring_set_error.js]
 [test_call_barring_change_password.js]
 [test_mobile_set_radio.js]
 [test_mobile_last_known_network.js]
 [test_mobile_icc_change.js]
 [test_mobile_connections_array_uninitialized.js]
+[test_mobile_data_ipv6.js]
+disabled = Bug 978071
new file mode 100644
--- /dev/null
+++ b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+/**
+ * Test resulting IP address format with given APN settings.
+ *
+ * This test utility function performs following steps:
+ *
+ *   1) set "ril.data.apnSettings" to a given settings object,
+ *   2) enable data connection and wait for a "datachange" event,
+ *   3) check the IP address type of the active network interface,
+ *   4) disable data connection.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aApnSettings
+ *        An APN settings value.
+ * @param aIsIPv6
+ *        A boolean value indicating whether we're expecting an IPv6 address.
+ *
+ * @return A deferred promise.
+ */
+function doTest(aApnSettings, aIsIPv6) {
+  return setDataApnSettings([])
+    .then(() => setDataApnSettings(aApnSettings))
+    .then(() => setDataEnabledAndWait(true))
+    .then(function() {
+      let nm = getNetworkManager();
+      let active = nm.active;
+      ok(active, "Active network interface");
+
+      log("  Interface: " + active.name);
+      log("  Address: " + active.ip);
+      if (aIsIPv6) {
+        ok(active.ip.indexOf(":") > 0, "IPv6 address");
+      } else {
+        ok(active.ip.indexOf(":") < 0, "IPv4 address");
+      }
+    })
+    .then(() => setDataEnabledAndWait(false));
+}
+
+function doTestHome(aApnSettings, aProtocol) {
+  log("Testing \"" + aProtocol + "\"@HOME... ");
+
+  // aApnSettings is a double-array of per PDP context settings.  The first
+  // index is a DSDS service ID, and the second one is the index of pre-defined
+  // PDP context settings of a specified radio interface.  We use 0 for both as
+  // default here.
+  aApnSettings[0][0].protocol = aProtocol;
+  delete aApnSettings[0][0].roaming_protocol;
+
+  return doTest(aApnSettings, aProtocol === "IPV6");
+}
+
+function doTestRoaming(aApnSettings, aRoaminProtocol) {
+  log("Testing \"" + aRoaminProtocol + "\"@ROMAING... ");
+
+  // aApnSettings is a double-array of per PDP context settings.  The first
+  // index is a DSDS service ID, and the second one is the index of pre-defined
+  // PDP context settings of a specified radio interface.  We use 0 for both as
+  // default here.
+  delete aApnSettings[0][0].protocol;
+  aApnSettings[0][0].roaming_protocol = aRoaminProtocol;
+
+  return doTest(aApnSettings, aRoaminProtocol === "IPV6");
+}
+
+startTestCommon(function() {
+  let origApnSettings;
+
+  return setDataRoamingEnabled(true)
+    .then(getDataApnSettings)
+    .then(function(aResult) {
+      // If already set, then save original APN settings.
+      origApnSettings = JSON.parse(JSON.stringify(aResult));
+      return aResult;
+    }, function() {
+      // Return our own default value.
+      return [[{ "carrier": "T-Mobile US",
+                 "apn": "epc.tmobile.com",
+                 "mmsc": "http://mms.msg.eng.t-mobile.com/mms/wapenc",
+                 "types": ["default", "supl", "mms"] }]];
+    })
+
+    .then(function(aApnSettings) {
+      return Promise.resolve()
+
+        .then(() => doTestHome(aApnSettings, "NoSuchProtocol"))
+        .then(() => doTestHome(aApnSettings, "IP"))
+        .then(() => doTestHome(aApnSettings, "IPV6"))
+
+        .then(() => setEmulatorRoamingAndWait(true))
+
+        .then(() => doTestRoaming(aApnSettings, "NoSuchProtocol"))
+        .then(() => doTestRoaming(aApnSettings, "IP"))
+        .then(() => doTestRoaming(aApnSettings, "IPV6"))
+
+        .then(() => setEmulatorRoamingAndWait(false));
+    })
+
+    .then(() => setDataRoamingEnabled(false))
+    .then(function() {
+      if (origApnSettings) {
+        return setDataApnSettings(origApnSettings);
+      }
+    });
+}, ["settings-read", "settings-write"]);
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
 
 let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
 
-/* Push required permissions and test if |navigator.mozMobileMessage| exists.
+/**
+ * Push required permissions and test if |navigator.mozMobileMessage| exists.
  * Resolve if it does, reject otherwise.
  *
  * Fulfill params:
  *   manager -- an reference to navigator.mozMobileMessage.
  *
  * Reject params: (none)
  *
  * @return A deferred promise.
@@ -39,17 +40,18 @@ function ensureMobileMessage() {
     } else {
       deferred.reject();
     }
   });
 
   return deferred.promise;
 }
 
-/* Send a SMS message to a single receiver.  Resolve if it succeeds, reject
+/**
+ * Send a SMS message to a single receiver.  Resolve if it succeeds, reject
  * otherwise.
  *
  * Fulfill params:
  *   message -- the sent SmsMessage.
  *
  * Reject params:
  *   error -- a DOMError.
  *
@@ -67,17 +69,18 @@ function sendSmsWithSuccess(aReceiver, a
   };
   request.onerror = function(event) {
     deferred.reject(event.target.error);
   };
 
   return deferred.promise;
 }
 
-/* Send a MMS message with specified parameters.  Resolve if it fails, reject
+/**
+ * Send a MMS message with specified parameters.  Resolve if it fails, reject
  * otherwise.
  *
  * Fulfill params:
  *   message -- the failed MmsMessage
  *
  * Reject params: (none)
  *
  * @param aMmsParameters a MmsParameters instance.
@@ -95,17 +98,18 @@ function sendMmsWithFailure(aMmsParamete
   let request = manager.sendMMS(aMmsParameters);
   request.onsuccess = function(event) {
     deferred.reject();
   };
 
   return deferred.promise;
 }
 
-/* Retrieve messages from database.
+/**
+ * Retrieve messages from database.
  *
  * Fulfill params:
  *   messages -- an array of {Sms,Mms}Message instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @param aFilter an optional MozSmsFilter instance.
@@ -131,31 +135,33 @@ function getMessages(aFilter, aReverse) 
 
     deferred.resolve(messages);
   };
   cursor.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
-/* Retrieve all messages from database.
+/**
+ * Retrieve all messages from database.
  *
  * Fulfill params:
  *   messages -- an array of {Sms,Mms}Message instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @return A deferred promise.
  */
 function getAllMessages() {
   return getMessages(null, false);
 }
 
-/* Retrieve all threads from database.
+/**
+ * Retrieve all threads from database.
  *
  * Fulfill params:
  *   threads -- an array of MozMobileMessageThread instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @return A deferred promise.
@@ -174,17 +180,18 @@ function getAllThreads() {
 
     deferred.resolve(threads);
   };
   cursor.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
-/* Retrieve a single specified thread from database.
+/**
+ * Retrieve a single specified thread from database.
  *
  * Fulfill params:
  *   thread -- a MozMobileMessageThread instance.
  *
  * Reject params:
  *   event -- a DOMEvent if an error occurs in the retrieving process, or
  *            undefined if there's no such thread.
  *
@@ -199,17 +206,18 @@ function getThreadById(aThreadId) {
         if (thread.id === aThreadId) {
           return thread;
         }
       }
       throw undefined;
     });
 }
 
-/* Delete messages specified from database.
+/**
+ * Delete messages specified from database.
  *
  * Fulfill params:
  *   result -- an array of boolean values indicating whether delesion was
  *             actually performed on the message record with corresponding id.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
@@ -229,17 +237,18 @@ function deleteMessagesById(aMessageIds)
   request.onsuccess = function(event) {
     deferred.resolve(event.target.result);
   };
   request.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
-/* Delete messages specified from database.
+/**
+ * Delete messages specified from database.
  *
  * Fulfill params:
  *   result -- an array of boolean values indicating whether delesion was
  *             actually performed on the message record with corresponding id.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
@@ -247,34 +256,36 @@ function deleteMessagesById(aMessageIds)
  *
  * @return A deferred promise.
  */
 function deleteMessages(aMessages) {
   let ids = messagesToIds(aMessages);
   return deleteMessagesById(ids);
 }
 
-/* Delete all messages from database.
+/**
+ * Delete all messages from database.
  *
  * Fulfill params:
  *   ids -- an array of numeric values identifying those deleted
  *          {Sms,Mms}Messages.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
  * @return A deferred promise.
  */
 function deleteAllMessages() {
   return getAllMessages().then(deleteMessages);
 }
 
 let pendingEmulatorCmdCount = 0;
 
-/* Send emulator command with safe guard.
+/**
+ * Send emulator command with safe guard.
  *
  * We should only call |finish()| after all emulator command transactions
  * end, so here comes with the pending counter.  Resolve when the emulator
  * gives positive response, and reject otherwise.
  *
  * Fulfill params:
  *   result -- an array of emulator response lines.
  *
@@ -296,64 +307,68 @@ function runEmulatorCmdSafe(aCommand) {
     } else {
       deferred.reject(aResult);
     }
   });
 
   return deferred.promise;
 }
 
-/* Send simple text SMS to emulator.
+/**
+ * Send simple text SMS to emulator.
  *
  * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
 function sendTextSmsToEmulator(aFrom, aText) {
   let command = "sms send " + aFrom + " " + aText;
   return runEmulatorCmdSafe(command);
 }
 
-/* Send raw SMS TPDU to emulator.
+/**
+ * Send raw SMS TPDU to emulator.
  *
  * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
 function sendRawSmsToEmulator(aPdu) {
   let command = "sms pdu " + aPdu;
   return runEmulatorCmdSafe(command);
 }
 
-/* Name space for MobileMessageDB.jsm.  Only initialized after first call to
+/**
+ * Name space for MobileMessageDB.jsm.  Only initialized after first call to
  * newMobileMessageDB.
  */
 let MMDB;
 
 // Create a new MobileMessageDB instance.
 function newMobileMessageDB() {
   if (!MMDB) {
     MMDB = Cu.import("resource://gre/modules/MobileMessageDB.jsm", {});
     is(typeof MMDB.MobileMessageDB, "function", "MMDB.MobileMessageDB");
   }
 
   let mmdb = new MMDB.MobileMessageDB();
   ok(mmdb, "MobileMessageDB instance");
   return mmdb;
 }
 
-/* Initialize a MobileMessageDB.  Resolve if initialized with success, reject
+/**
+ * Initialize a MobileMessageDB.  Resolve if initialized with success, reject
  * otherwise.
  *
  * Fulfill params: a MobileMessageDB instance.
  * Reject params: a MobileMessageDB instance.
  *
  * @param aMmdb
  *        A MobileMessageDB instance.
  * @param aDbName
@@ -373,80 +388,101 @@ function initMobileMessageDB(aMmdb, aDbN
     } else {
       deferred.resolve(aMmdb);
     }
   });
 
   return deferred.promise;
 }
 
-/* Close a MobileMessageDB.
+/**
+ * Close a MobileMessageDB.
  *
  * @return The passed MobileMessageDB instance.
  */
 function closeMobileMessageDB(aMmdb) {
   aMmdb.close();
   return aMmdb;
 }
 
-/* Create a new array of id attribute of input messages.
+/**
+ * Create a new array of id attribute of input messages.
  *
  * @param aMessages an array of {Sms,Mms}Message instances.
  *
  * @return an array of numeric values.
  */
 function messagesToIds(aMessages) {
   let ids = [];
   for (let message of aMessages) {
     ids.push(message.id);
   }
   return ids;
 }
 
 // A reference to a nsIUUIDGenerator service.
 let uuidGenerator;
 
-/* Generate a new UUID.
+/**
+ * Generate a new UUID.
  *
  * @return A UUID string.
  */
 function newUUID() {
   if (!uuidGenerator) {
     uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                     .getService(Ci.nsIUUIDGenerator);
     ok(uuidGenerator, "uuidGenerator");
   }
 
   return uuidGenerator.generateUUID().toString();
 }
 
-/* Flush permission settings and call |finish()|.
+/**
+ * Flush permission settings and call |finish()|.
  */
 function cleanUp() {
   waitFor(function() {
     SpecialPowers.flushPermissions(function() {
       // Use ok here so that we have at least one test run.
       ok(true, "permissions flushed");
 
       finish();
     });
   }, function() {
     return pendingEmulatorCmdCount === 0;
   });
 }
 
+/**
+ * Basic test routine helper for mobile message tests.
+ *
+ * This helper does nothing but clean-ups.
+ *
+ * @param aTestCaseMain
+ *        A function that takes no parameter.
+ */
 function startTestBase(aTestCaseMain) {
   Promise.resolve()
          .then(aTestCaseMain)
          .then(cleanUp, function() {
            ok(false, 'promise rejects during test.');
            cleanUp();
          });
 }
 
+/**
+ * Common test routine helper for mobile message tests.
+ *
+ * This function ensures global |manager| variable is available during the
+ * process and performs clean-ups as well.
+ *
+ * @param aTestCaseMain
+ *        A function that takes no parameter.
+ */
 function startTestCommon(aTestCaseMain) {
   startTestBase(function() {
     return ensureMobileMessage()
       .then(deleteAllMessages)
       .then(aTestCaseMain)
       .then(deleteAllMessages);
   });
 }
--- a/dom/plugins/ipc/interpose/Makefile.in
+++ b/dom/plugins/ipc/interpose/Makefile.in
@@ -1,10 +1,5 @@
 # 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/.
 
 DIST_INSTALL     = 1
-
-
-EXTRA_DSO_LDOPTS += \
-  -framework Carbon \
-  $(NULL)
--- a/dom/plugins/ipc/interpose/moz.build
+++ b/dom/plugins/ipc/interpose/moz.build
@@ -8,8 +8,10 @@ LIBRARY_NAME = 'plugin_child_interpose'
 
 UNIFIED_SOURCES += [ "%s.mm" % (LIBRARY_NAME) ]
 
 UNIFIED_SOURCES += [
     'plugin_child_quirks.mm',
 ]
 
 FORCE_SHARED_LIB = True
+
+EXTRA_DSO_LDOPTS += ['-framework Carbon']
--- a/dom/plugins/test/testplugin/testplugin.mk
+++ b/dom/plugins/test/testplugin/testplugin.mk
@@ -32,19 +32,13 @@ INSTALL_TARGETS += \
 	$(NULL)
 else
 TEST_PLUGIN_DEST = $(DIST)/plugins
 INSTALL_TARGETS += TEST_PLUGIN
 endif
 
 include $(topsrcdir)/config/rules.mk
 
-ifndef __LP64__
-ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-EXTRA_DSO_LDOPTS += -framework Carbon
-endif
-endif
-
 ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 CXXFLAGS        += $(MOZ_GTK2_CFLAGS)
 CFLAGS          += $(MOZ_GTK2_CFLAGS)
 EXTRA_DSO_LDOPTS += $(MOZ_GTK2_LIBS) $(XLDFLAGS) $(XLIBS) $(XEXT_LIBS)
 endif
--- a/dom/plugins/test/testplugin/testplugin.mozbuild
+++ b/dom/plugins/test/testplugin/testplugin.mozbuild
@@ -44,8 +44,11 @@ USE_STATIC_LIBS = True
 
 # Don't use STL wrappers; nptest isn't Gecko code
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     RCFILE  = 'nptest.rc'
     RESFILE = 'nptest.res'
     DEFFILE = SRCDIR + '/nptest.def'
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' and '64' in CONFIG['OS_TEST']:
+    EXTRA_DSO_LDOPTS += ['-framework Carbon']
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -526,19 +526,19 @@ AudioManager::SetPhoneState(int32_t aSta
     mPhoneAudioAgent = nullptr;
   }
 
   if (aState == PHONE_STATE_IN_CALL || aState == PHONE_STATE_RINGTONE) {
     mPhoneAudioAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     MOZ_ASSERT(mPhoneAudioAgent);
     if (aState == PHONE_STATE_IN_CALL) {
       // Telephony doesn't be paused by any other channels.
-      mPhoneAudioAgent->Init(AUDIO_CHANNEL_TELEPHONY, nullptr);
+      mPhoneAudioAgent->Init(nullptr, AUDIO_CHANNEL_TELEPHONY, nullptr);
     } else {
-      mPhoneAudioAgent->Init(AUDIO_CHANNEL_RINGER, nullptr);
+      mPhoneAudioAgent->Init(nullptr, AUDIO_CHANNEL_RINGER, nullptr);
     }
 
     // Telephony can always play.
     int32_t canPlay;
     mPhoneAudioAgent->StartPlaying(&canPlay);
   }
 
   return NS_OK;
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -47,16 +47,20 @@ function debug(s) {
 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND =
   libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true";
 
 // Ril quirk to always turn the radio off for the client without SIM card
 // except hw default client.
 let RILQUIRKS_RADIO_OFF_WO_CARD =
   libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true";
 
+// Ril quirk to enable IPv6 protocol/roaming protocol in APN settings.
+let RILQUIRKS_HAVE_IPV6 =
+  libcutils.property_get("ro.moz.ril.ipv6", "false") == "true";
+
 const RADIOINTERFACELAYER_CID =
   Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
 const RADIOINTERFACE_CID =
   Components.ID("{6a7c91f0-a2b3-4193-8562-8969296c0b54}");
 const RILNETWORKINTERFACE_CID =
   Components.ID("{3bdd52a9-3965-4130-b569-0ac5afed045e}");
 const GSMICCINFO_CID =
   Components.ID("{d90c4261-a99d-47bc-8b05-b057bb7e8f8a}");
@@ -4520,22 +4524,35 @@ RILNetworkInterface.prototype = {
     // Use the default authType if the value in database is invalid.
     // For the case that user might not select the authentication type.
     if (authType == -1) {
       if (DEBUG) {
         this.debug("Invalid authType " + this.apnSetting.authtype);
       }
       authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT);
     }
+    let pdpType = RIL.GECKO_DATACALL_PDP_TYPE_IP;
+    if (RILQUIRKS_HAVE_IPV6) {
+      pdpType = !radioInterface.rilContext.data.roaming
+              ? this.apnSetting.protocol
+              : this.apnSetting.roaming_protocol;
+      if (RIL.RIL_DATACALL_PDP_TYPES.indexOf(pdpType) < 0) {
+        if (DEBUG) {
+          this.debug("Invalid pdpType '" + pdpType + "', using '" +
+                     RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'");
+        }
+        pdpType = RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT;
+      }
+    }
     radioInterface.setupDataCall(radioTechnology,
                                  this.apnSetting.apn,
                                  this.apnSetting.user,
                                  this.apnSetting.password,
                                  authType,
-                                 "IP");
+                                 pdpType);
     this.connecting = true;
   },
 
   reset: function() {
     let apnRetryTimer;
     this.connecting = false;
     // We will retry the connection in increasing times
     // based on the function: time = A * numer_of_retries^2 + B
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -2356,16 +2356,25 @@ this.GECKO_DATACALL_AUTH_PAP_OR_CHAP = "
 this.GECKO_DATACALL_AUTH_DEFAULT = GECKO_DATACALL_AUTH_PAP_OR_CHAP;
 this.RIL_DATACALL_AUTH_TO_GECKO = [
   GECKO_DATACALL_AUTH_NONE,         // DATACALL_AUTH_NONE
   GECKO_DATACALL_AUTH_PAP,          // DATACALL_AUTH_PAP
   GECKO_DATACALL_AUTH_CHAP,         // DATACALL_AUTH_CHAP
   GECKO_DATACALL_AUTH_PAP_OR_CHAP   // DATACALL_AUTH_PAP_OR_CHAP
 ];
 
+this.GECKO_DATACALL_PDP_TYPE_IP = "IP";
+this.GECKO_DATACALL_PDP_TYPE_IPV6 = "IPV6";