merge mozilla-inbound to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 23 Jan 2014 10:18:03 +0100
changeset 164794 d418cc97bacb83e47684134af5d496c583e67341
parent 164793 b9d9649e7ec015a5d68baa3606db9b53637a3a56 (current diff)
parent 164784 a90eb14cad0b541b9ee7c762ab1d9a216b71ac9b (diff)
child 164810 96e031fede3f9e663e2eae3d944488f2d95ae132
push id38817
push usercbook@mozilla.com
push dateThu, 23 Jan 2014 10:29:13 +0000
treeherdermozilla-inbound@7fdda1995af8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.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 mozilla-inbound to mozilla-central
js/src/jit-test/tests/basic/offThreadCompileScript.js
js/src/jit-test/tests/debug/Source-elementProperty.js
services/docs/healthreport.rst
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -326,28 +326,26 @@ HyperTextAccessible::DOMPointToHypertext
   Accessible* childAccAtOffset = nullptr;
   while (descendantAcc) {
     Accessible* parentAcc = descendantAcc->Parent();
     if (parentAcc == this) {
       childAccAtOffset = descendantAcc;
       break;
     }
 
-    // This offset no longer applies because the passed-in text object is not a child
-    // of the hypertext. This happens when there are nested hypertexts, e.g.
-    // <div>abc<h1>def</h1>ghi</div>
-    // If the passed-in DOM point was not on a direct child of the hypertext, we will
-    // return the offset for that entire hypertext
-    if (aIsEndOffset) {
-      // Not inclusive, the indicated char comes at index before this offset
-      // If the end offset is after the first character of the passed in object, use 1 for
-    // addTextOffset, to put us after the embedded object char. We'll only treat the offset as
-    // before the embedded object char if we end at the very beginning of the child.
-    addTextOffset = addTextOffset > 0;
-    } else
+    // This offset no longer applies because the passed-in text object is not
+    // a child of the hypertext. This happens when there are nested hypertexts,
+    // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
+    // to make it relative the hypertext.
+    // If the end offset is not supposed to be inclusive and the original point
+    // is not at 0 offset then the returned offset should be after an embedded
+    // character the original point belongs to.
+    if (aIsEndOffset)
+      addTextOffset = (addTextOffset > 0 || descendantAcc->IndexInParent() > 0) ? 1 : 0;
+    else
       addTextOffset = 0;
 
     descendantAcc = parentAcc;
   }
 
   // Loop through, adding offsets until we reach childAccessible
   // If childAccessible is null we will end up adding up the entire length of
   // the hypertext, which is good -- it just means our offset node
--- a/accessible/tests/mochitest/text/test_lineboundary.html
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -115,16 +115,22 @@
                        [ [ 0, 12, "Hello world ", 0, 12 ] ]);
 
       //////////////////////////////////////////////////////////////////////////
       // list items
 
       testTextAtOffset([ "li1" ], BOUNDARY_LINE_START,
                        [ [ 0, 5, kDiscBulletChar + "Item", 0, 5 ] ]);
 
+      //////////////////////////////////////////////////////////////////////////
+      // Nested hypertexts
+
+      testTextAtOffset(["ht_5" ], BOUNDARY_LINE_START,
+                       [ [ 0, 0, kEmbedChar, 0, 1 ] ]);
+
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
@@ -185,10 +191,17 @@ two words
   <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
 
   <p id="ht_4">Hello world
 </p>
 
   <ul>
     <li id="li1">Item</li>
   </ul>
+
+  <div id="ht_5">
+    <div>
+      <p>sectiounus</p>
+      <p>seciofarus</p>
+    </div>
+  </div>
 </body>
 </html>
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -23,16 +23,19 @@ Cu.import('resource://gre/modules/Networ
 
 // Identity
 Cu.import('resource://gre/modules/SignInToWebsite.jsm');
 SignInToWebsiteController.init();
 Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
 
 Cu.import('resource://gre/modules/DownloadsAPI.jsm');
 
+Cu.import('resource://gre/modules/Webapps.jsm');
+DOMApplicationRegistry.allAppsLaunchable = true;
+
 XPCOMUtils.defineLazyServiceGetter(Services, 'env',
                                    '@mozilla.org/process/environment;1',
                                    'nsIEnvironment');
 
 XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
                                    '@mozilla.org/content/style-sheet-service;1',
                                    'nsIStyleSheetService');
 
@@ -263,17 +266,16 @@ var shell = {
     }
 
     let homeURL = this.homeURL;
     if (!homeURL) {
       let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN';
       alert(msg);
       return;
     }
-
     let manifestURL = this.manifestURL;
     // <html:iframe id="systemapp"
     //              mozbrowser="true" allowfullscreen="true"
     //              style="overflow: hidden; height: 100%; width: 100%; border: none;"
     //              src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
     let systemAppFrame =
       document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
     systemAppFrame.setAttribute('id', 'systemapp');
@@ -608,19 +610,16 @@ var shell = {
   notifyContentStart: function shell_notifyContentStart() {
     this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
     this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true);
 
     let content = this.contentBrowser.contentWindow;
 
     this.reportCrash(true);
 
-    Cu.import('resource://gre/modules/Webapps.jsm');
-    DOMApplicationRegistry.allAppsLaunchable = true;
-
     this.sendEvent(window, 'ContentStart');
 
     Services.obs.notifyObservers(null, 'content-start', null);
 
 #ifdef MOZ_WIDGET_GONK
     Cu.import('resource://gre/modules/OperatorApps.jsm');
 #endif
 
@@ -663,23 +662,22 @@ Services.obs.addObserver(function onInte
                           peers: data.appsToSelect });
 }, 'inter-app-comm-select-app', false);
 
 Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
   shell.sendChromeEvent({ type: "fullscreenoriginchange",
                           fullscreenorigin: data });
 }, "fullscreen-origin-change", false);
 
-Services.obs.addObserver(function onWebappsStart(subject, topic, data) {
+DOMApplicationRegistry.registryStarted.then(function () {
   shell.sendChromeEvent({ type: 'webapps-registry-start' });
-}, 'webapps-registry-start', false);
-
-Services.obs.addObserver(function onWebappsReady(subject, topic, data) {
+});
+DOMApplicationRegistry.registryReady.then(function () {
   shell.sendChromeEvent({ type: 'webapps-registry-ready' });
-}, 'webapps-registry-ready', false);
+});
 
 Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) {
   shell.sendChromeEvent({
     type: "bluetooth-volumeset",
     value: data
   });
 }, 'bluetooth-volume-change', false);
 
--- a/browser/app/macbuild/Contents/_CodeSignature/CodeResources
+++ b/browser/app/macbuild/Contents/_CodeSignature/CodeResources
@@ -49,11 +49,23 @@
                 <real>10</real>
             </dict>
             <key>^MacOS/updates.xml$</key><dict>
                 <key>omit</key>
                 <true/>
                 <key>weight</key>
                 <real>10</real>
             </dict>
+            <key>^Updated.app/.*</key><dict>
+                <key>omit</key>
+                <true/>
+                <key>weight</key>
+                <real>10</real>
+            </dict>
+            <key>^updating/.*</key><dict>
+                <key>omit</key>
+                <true/>
+                <key>weight</key>
+                <real>10</real>
+            </dict>
         </dict>
     </dict>
 </plist>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3443,16 +3443,31 @@ var XULBrowserWindow = {
 
     if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
       url = trimURL(url);
 
     this.overLink = url;
     LinkTargetDisplay.update();
   },
 
+  showTooltip: function (x, y, tooltip) {
+    // The x,y coordinates are relative to the <browser> element using
+    // the chrome zoom level.
+    let elt = document.getElementById("remoteBrowserTooltip");
+    elt.label = tooltip;
+
+    let anchor = gBrowser.selectedBrowser;
+    elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null);
+  },
+
+  hideTooltip: function () {
+    let elt = document.getElementById("remoteBrowserTooltip");
+    elt.hidePopup();
+  },
+
   updateStatusField: function () {
     var text, type, types = ["overLink"];
     if (this._busyUI)
       types.push("status");
     types.push("defaultStatus");
     for (type of types) {
       text = this[type];
       if (text)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -124,16 +124,17 @@
 
     <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
          of this menupopup -->
     <menupopup id="backForwardMenu"
                onpopupshowing="return FillHistoryMenu(event.target);"
                oncommand="gotoHistoryIndex(event); event.stopPropagation();"
                onclick="checkForMiddleClick(this, event);"/>
     <tooltip id="aHTMLTooltip" page="true"/>
+    <tooltip id="remoteBrowserTooltip"/>
 
     <!-- for search and content formfill/pw manager -->
     <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
 
     <!-- for url bar autocomplete -->
     <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
 
     <!-- for select dropdowns -->
--- a/browser/metro/base/content/pages/config.js
+++ b/browser/metro/base/content/pages/config.js
@@ -1,17 +1,16 @@
 /* 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";
 
 const {classes: Cc, interfaces: Ci, manager: Cm, utils: Cu} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
 
-const PRIVATE_PREF_PREFIX = "capability.";   // Tag to prevent exposing private preferences
 const INITIAL_PAGE_DELAY = 500;   // Initial pause on program start for scroll alignment
 const PREFS_BUFFER_MAX = 100;   // Max prefs buffer size for getPrefsBuffer()
 const PAGE_SCROLL_TRIGGER = 200;     // Triggers additional getPrefsBuffer() on user scroll-to-bottom
 const FILTER_CHANGE_TRIGGER = 200;     // Delay between responses to filterInput changes
 const INNERHTML_VALUE_DELAY = 100;    // Delay before providing prefs innerHTML value
 
 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/config.properties");
 let gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
@@ -81,22 +80,16 @@ var NewPrefDialog = {
   // As new pref name is initially displayed, re-focused, or modifed during user input
   _updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
     this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
     this._positiveButton.setAttribute("disabled", true);
     if (aPrefName == "") {
       return;
     }
 
-    // Prevent addition of new "private" preferences
-    if (aPrefName.startsWith(PRIVATE_PREF_PREFIX)) {
-      this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.privateButton");
-      return;
-    }
-
     // If item already in list, it's being changed, else added
     let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
     if (item) {
       this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
     } else {
       this._positiveButton.removeAttribute("disabled");
     }
   },
@@ -201,20 +194,17 @@ var AboutConfig = {
   _list: null,
 
   // Init the main AboutConfig dialog
   init: function AC_init() {
     this.filterInput = document.getElementById("filter-input");
     this._prefsContainer = document.getElementById("prefs-container");
     this._loadingContainer = document.getElementById("loading-container");
 
-    let list = Services.prefs.getChildList("", {}).filter(function(aElement) {
-      // Prevent display of "private" preferences
-      return !aElement.startsWith(PRIVATE_PREF_PREFIX);
-    });
+    let list = Services.prefs.getChildList("");
     this._list = list.sort().map( function AC_getMapPref(aPref) {
       return new Pref(aPref);
     }, this);
 
     // Display the current prefs list (retains searchFilter value)
     this.bufferFilterInput();
 
     // Setup the prefs observers
@@ -456,18 +446,18 @@ var AboutConfig = {
 
     pref.value += aInt;
   },
 
   // Observe preference changes
   observe: function AC_observe(aSubject, aTopic, aPrefName) {
     let pref = new Pref(aPrefName);
 
-    // Ignore uninteresting preference changes, and external changes to "private" preferences
-    if ((aTopic != "nsPref:changed") || pref.name.startsWith(PRIVATE_PREF_PREFIX)) {
+    // Ignore uninteresting changes, and avoid "private" preferences
+    if (aTopic != "nsPref:changed") {
       return;
     }
 
     // If pref type invalid, refresh display as user reset/removed an item from the list
     if (pref.type == Services.prefs.PREF_INVALID) {
       document.location.reload();
       return;
     }
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -629,60 +629,18 @@ class Automation(object):
         raise
 
   def dumpScreen(self, utilityPath):
     if self.haveDumpedScreen:
       self.log.info("Not taking screenshot here: see the one that was previously logged")
       return
 
     self.haveDumpedScreen = True;
-
-    # Need to figure out what tool and whether it write to a file or stdout
-    if self.UNIXISH:
-      utility = [os.path.join(utilityPath, "screentopng")]
-      imgoutput = 'stdout'
-    elif self.IS_MAC:
-      utility = ['/usr/sbin/screencapture', '-C', '-x', '-t', 'png']
-      imgoutput = 'file'
-    elif self.IS_WIN32:
-      utility = [os.path.join(utilityPath, "screenshot.exe")]
-      imgoutput = 'file'
+    automationutils.dumpScreen(utilityPath)
 
-    # Run the capture correctly for the type of capture
-    try:
-      if imgoutput == 'file':
-        tmpfd, imgfilename = tempfile.mkstemp(prefix='mozilla-test-fail_')
-        os.close(tmpfd)
-        dumper = self.Process(utility + [imgfilename])
-      elif imgoutput == 'stdout':
-        dumper = self.Process(utility, bufsize=-1,
-                              stdout=subprocess.PIPE, close_fds=True)
-    except OSError, err:
-      self.log.info("Failed to start %s for screenshot: %s",
-                    utility[0], err.strerror)
-      return
-
-    # Check whether the capture utility ran successfully
-    dumper_out, dumper_err = dumper.communicate()
-    if dumper.returncode != 0:
-      self.log.info("%s exited with code %d", utility, dumper.returncode)
-      return
-
-    try:
-      if imgoutput == 'stdout':
-        image = dumper_out
-      elif imgoutput == 'file':
-        with open(imgfilename, 'rb') as imgfile:
-          image = imgfile.read()
-    except IOError, err:
-        self.log.info("Failed to read image from %s", imgoutput)
-
-    import base64
-    encoded = base64.b64encode(image)
-    self.log.info("SCREENSHOT: data:image/png;base64,%s", encoded)
 
   def killAndGetStack(self, processPID, utilityPath, debuggerInfo):
     """Kill the process, preferrably in a way that gets us a stack trace.
        Also attempts to obtain a screenshot before killing the process."""
     if not debuggerInfo:
       self.dumpScreen(utilityPath)
     self.killAndGetStackNoScreenshot(processPID, utilityPath, debuggerInfo)
 
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -479,68 +479,50 @@ def environment(xrePath, env=None, crash
       log.info("Failed determine available memory, disabling ASan low-memory configuration: %s", err.strerror)
     except:
       log.info("Failed determine available memory, disabling ASan low-memory configuration")
     else:
       log.info(message)
 
   return env
 
+def dumpScreen(utilityPath):
+  """dumps a screenshot of the entire screen to a directory specified by
+  the MOZ_UPLOAD_DIR environment variable"""
+  import mozfile
 
-def dumpScreen(utilityPath):
-  """dumps the screen to the log file as a data URI"""
-
-  # Need to figure out what tool and whether it write to a file or stdout
+  # Need to figure out which OS-dependent tool to use
   if mozinfo.isUnix:
     utility = [os.path.join(utilityPath, "screentopng")]
-    imgoutput = 'stdout'
   elif mozinfo.isMac:
     utility = ['/usr/sbin/screencapture', '-C', '-x', '-t', 'png']
-    imgoutput = 'file'
   elif mozinfo.isWin:
     utility = [os.path.join(utilityPath, "screenshot.exe")]
-    imgoutput = 'file'
-  else:
-    log.warn("Unable to dump screen on platform '%s'", sys.platform)
+
+  # Get dir where to write the screenshot file
+  parent_dir = os.environ.get('MOZ_UPLOAD_DIR', None)
+  if not parent_dir:
+    log.info('Failed to retrieve MOZ_UPLOAD_DIR env var')
+    return
 
-  # Run the capture correctly for the type of capture
-  kwargs = {'stdout': subprocess.PIPE}
-  if imgoutput == 'file':
-    tmpfd, imgfilename = tempfile.mkstemp(prefix='mozilla-test-fail_')
-    os.close(tmpfd)
-    utility.append(imgfilename)
-  elif imgoutput == 'stdout':
-    kwargs.update(dict(bufsize=-1, close_fds=True))
+  # Run the capture
   try:
-    dumper = subprocess.Popen(utility, **kwargs)
+    with mozfile.NamedTemporaryFile(suffix='.png',
+                                    prefix='mozilla-test-fail-screenshot_',
+                                    dir=parent_dir,
+                                    delete=False) as f:
+      returncode = subprocess.call(utility + [f.name])
   except OSError, err:
     log.info("Failed to start %s for screenshot: %s",
              utility[0], err.strerror)
     return
 
   # Check whether the capture utility ran successfully
-  stdout, _ = dumper.communicate()
-  returncode = dumper.poll()
-  if returncode:
+  if returncode != 0:
     log.info("%s exited with code %d", utility, returncode)
-    return
-
-  try:
-    if imgoutput == 'stdout':
-      image = stdout
-    elif imgoutput == 'file':
-      with open(imgfilename, 'rb') as imgfile:
-        image = imgfile.read()
-  except IOError, err:
-    log.info("Failed to read image from %s", imgoutput)
-
-  encoded = base64.b64encode(image)
-  uri = "data:image/png;base64,%s" %  encoded
-  log.info("SCREENSHOT: %s", uri)
-  return uri
 
 class ShutdownLeaks(object):
   """
   Parses the mochitest run log when running a debug build, assigns all leaked
   DOM windows (that are still around after test suite shutdown, despite running
   the GC) to the tests that created them and prints leak statistics.
   """
 
--- a/build/docs/mozinfo.rst
+++ b/build/docs/mozinfo.rst
@@ -54,16 +54,22 @@ bits
 
    Universal Mac builds do not have this key defined.
 
    Unkown processor architectures (see ``processor`` below) may not have
    this key defined.
 
    Optional.
 
+buildapp
+   The path to the XUL application being built.
+
+   For desktop Firefox, this is ``browser``. For Fennec, it's
+   ``mobile/android``. For B2G, it's ``b2g``.
+
 crashreporter
    Whether the crash reporter is enabled for this build.
 
    Values are ``true`` and ``false``.
 
    Always defined.
 
 datareporting
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -301,17 +301,17 @@ CSPRep.fromString = function(aStr, self,
 
   var dirs = aStr.split(";");
 
   directive:
   for each(var dir in dirs) {
     dir = dir.trim();
     if (dir.length < 1) continue;
 
-    var dirname = dir.split(/\s+/)[0];
+    var dirname = dir.split(/\s+/)[0].toLowerCase();
     var dirvalue = dir.substring(dirname.length).trim();
 
     if (aCSPR._directives.hasOwnProperty(dirname)) {
       // Check for (most) duplicate directives
       cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
                                                 [dirname]));
       CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
       continue directive;
@@ -559,17 +559,17 @@ CSPRep.fromStringSpecCompliant = functio
   }
 
   var dirs_list = aStr.split(";");
   var dirs = {};
   for each(var dir in dirs_list) {
     dir = dir.trim();
     if (dir.length < 1) continue;
 
-    var dirname = dir.split(/\s+/)[0];
+    var dirname = dir.split(/\s+/)[0].toLowerCase();
     var dirvalue = dir.substring(dirname.length).trim();
     dirs[dirname] = dirvalue;
   }
 
   // Spec compliant policies have different default behavior for inline
   // scripts, styles, and eval. Bug 885433
   aCSPR._allowEval = true;
   aCSPR._allowInlineScripts = true;
@@ -998,17 +998,17 @@ CSPSourceList.fromString = function(aStr
   if (self && !(self instanceof CSPSource)) {
      self = CSPSource.create(self, aCSPRep);
   }
 
   var slObj = new CSPSourceList();
   slObj._CSPRep = aCSPRep;
   aStr = aStr.trim();
   // w3 specifies case insensitive equality
-  if (aStr.toUpperCase() === "'NONE'") {
+  if (aStr.toLowerCase() === "'none'") {
     slObj._permitAllSources = false;
     return slObj;
   }
 
   var tokens = aStr.split(/\s+/);
   for (var i in tokens) {
     if (!R_SOURCEEXP.test(tokens[i])) {
       cspWarn(aCSPRep,
@@ -1066,17 +1066,17 @@ CSPSourceList.prototype = {
       return false;
     }
     if (that._sources.length != this._sources.length) {
       return false;
     }
     // sort both arrays and compare like a zipper
     // XXX (sid): I think we can make this more efficient
     var sortfn = function(a,b) {
-      return a.toString() > b.toString();
+      return a.toString.toLowerCase() > b.toString.toLowerCase();
     };
     var a_sorted = this._sources.sort(sortfn);
     var b_sorted = that._sources.sort(sortfn);
     for (var i in a_sorted) {
       if (!a_sorted[i].equals(b_sorted[i])) {
         return false;
       }
     }
@@ -1413,34 +1413,34 @@ CSPSource.fromString = function(aStr, aC
   if (R_NONCESRC.test(aStr))
     return CSPNonceSource.fromString(aStr, aCSPRep);
 
   // check for a hash-source match
   if (R_HASHSRC.test(aStr))
     return CSPHashSource.fromString(aStr, aCSPRep);
 
   // check for 'self' (case insensitive)
-  if (aStr.toUpperCase() === "'SELF'") {
+  if (aStr.toLowerCase() === "'self'") {
     if (!self) {
       cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
       return null;
     }
     sObj._self = self.clone();
     sObj._isSelf = true;
     return sObj;
   }
 
   // check for 'unsafe-inline' (case insensitive)
-  if (aStr.toUpperCase() === "'UNSAFE-INLINE'"){
+  if (aStr.toLowerCase() === "'unsafe-inline'"){
     sObj._allowUnsafeInline = true;
     return sObj;
   }
 
   // check for 'unsafe-eval' (case insensitive)
-  if (aStr.toUpperCase() === "'UNSAFE-EVAL'"){
+  if (aStr.toLowerCase() === "'unsafe-eval'"){
     sObj._allowUnsafeEval = true;
     return sObj;
   }
 
   cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
                                               [aStr]));
   return null;
 };
@@ -1551,17 +1551,17 @@ CSPSource.prototype = {
   permits:
   function(aSource) {
     if (!aSource) return false;
 
     if (!(aSource instanceof CSPSource))
       aSource = CSPSource.create(aSource, this._CSPRep);
 
     // verify scheme
-    if (this.scheme != aSource.scheme)
+    if (this.scheme.toLowerCase() != aSource.scheme.toLowerCase())
       return false;
 
     // port is defined in 'this' (undefined means it may not be relevant
     // to the scheme) AND this port (implicit or explicit) matches
     // aSource's port
     if (this.port && this.port !== "*" && this.port != aSource.port)
       return false;
 
@@ -1587,24 +1587,24 @@ CSPSource.prototype = {
    *        true if they have the same data
    */
   equals:
   function(that, resolveSelf) {
     // 1. schemes match
     // 2. ports match
     // 3. either both hosts are undefined, or one equals the other.
     if (resolveSelf)
-      return this.scheme === that.scheme
-          && this.port   === that.port
+      return this.scheme.toLowerCase() === that.scheme.toLowerCase()
+          && this.port === that.port
           && (!(this.host || that.host) ||
                (this.host && this.host.equals(that.host)));
 
     // otherwise, compare raw (non-self-resolved values)
-    return this._scheme === that._scheme
-        && this._port   === that._port
+    return this._scheme.toLowerCase() === that._scheme.toLowerCase()
+        && this._port === that._port
         && (!(this._host || that._host) ||
               (this._host && this._host.equals(that._host)));
   },
 
 };
 
 //////////////////////////////////////////////////////////////////////
 /**
@@ -1683,17 +1683,19 @@ CSPHost.prototype = {
    * Returns true if this host accepts the provided host (or the other way
    * around).
    * @param aHost
    *        the FQDN in question (CSPHost or String)
    * @returns
    */
   permits:
   function(aHost) {
-    if (!aHost) aHost = CSPHost.fromString("*");
+    if (!aHost) {
+      aHost = CSPHost.fromString("*");
+    }
 
     if (!(aHost instanceof CSPHost)) {
       // -- compare CSPHost to String
       aHost =  CSPHost.fromString(aHost);
     }
     var thislen = this._segments.length;
     var thatlen = aHost._segments.length;
 
@@ -1709,17 +1711,18 @@ CSPHost.prototype = {
     }
 
     // Given the wildcard condition (from above),
     // only necessary to compare elements that are present
     // in this host.  Extra tokens in aHost are ok.
     // * Compare from right to left.
     for (var i=1; i <= thislen; i++) {
       if (this._segments[thislen-i] != "*" &&
-          (this._segments[thislen-i] != aHost._segments[thatlen-i])) {
+          (this._segments[thislen-i].toLowerCase() !=
+           aHost._segments[thatlen-i].toLowerCase())) {
         return false;
       }
     }
 
     // at this point, all conditions are met, so the host is allowed
     return true;
   },
 
@@ -1732,18 +1735,20 @@ CSPHost.prototype = {
    *        true if they have the same data
    */
   equals:
   function(that) {
     if (this._segments.length != that._segments.length)
       return false;
 
     for (var i=0; i<this._segments.length; i++) {
-      if (this._segments[i] != that._segments[i])
+      if (this._segments[i].toLowerCase() !=
+          that._segments[i].toLowerCase()) {
         return false;
+      }
     }
     return true;
   }
 };
 
 this.CSPNonceSource = function CSPNonceSource() {
   this._nonce = undefined;
 }
--- a/content/base/test/unit/test_csputils.js
+++ b/content/base/test/unit/test_csputils.js
@@ -911,16 +911,76 @@ test(
                                 URI("http://self.com/foo.js"),
                                 Ci.nsIContentPolicy.TYPE_SCRIPT));
       do_check_false(testPermits(cspObjOld,
                                  URI("http://bar.com/foo.js"),
                                  Ci.nsIContentPolicy.TYPE_SCRIPT));
 
     });
 
+test(function test_equals_does_case_insensitive_comparison() {
+      // NOTE: For scheme, host and keyword-host:
+      // (1) compare the same lower-case in two distinct objects
+      // (2) compare upper-case with lower-case inputs
+      // to test case insensitivity.
+
+      // CSPSource equals ignores case
+      var upperCaseHost = "http://FOO.COM";
+      var lowerCaseHost = "http://foo.com";
+      src1 = CSPSource.fromString(lowerCaseHost);
+      src2 = CSPSource.fromString(lowerCaseHost);
+      do_check_true(src1.equals(src2))
+      src3 = CSPSource.fromString(upperCaseHost);
+      do_check_true(src1.equals(src3))
+
+      // CSPHost equals ignores case
+      var upperCaseScheme = "HTTP";
+      var lowerCaseScheme = "http";
+      src1 = CSPHost.fromString(lowerCaseScheme);
+      src2 = CSPHost.fromString(lowerCaseScheme);
+      do_check_true(src1.equals(src2));
+      src3 = CSPHost.fromString(upperCaseScheme);
+      do_check_true(src1.equals(src3));
+
+      // CSPSourceList equals (mainly for testing keywords)
+      var upperCaseKeywords = "'SELF'";
+      var lowerCaseKeywords = "'self'";
+      src1 = CSPSourceList.fromString(lowerCaseKeywords);
+      src2 = CSPSourceList.fromString(lowerCaseKeywords);
+      do_check_true(src1.equals(src2))
+      src3 = CSPSourceList.fromString(upperCaseKeywords);
+      do_check_true(src1.equals(src3))
+
+  });
+
+test(function test_csp_permits_case_insensitive() {
+      var cspr;
+      var SD = CSPRep.SRC_DIRECTIVES_NEW;
+
+      // checks directives can be case-insensitive
+      var selfHost = "http://self.com";
+      var testPolicy1 = "DEFAULT-src 'self';";
+      cspr = CSPRep.fromString(testPolicy1, URI(selfHost));
+      do_check_true(cspr.permits(URI("http://self.com"), SD.DEFAULT_SRC));
+
+      // checks hosts can be case-insensitive
+      var testPolicy2 = "default-src 'self' http://FOO.COM";
+      cspr = CSPRep.fromString(testPolicy2, URI(selfHost));
+      do_check_true(cspr.permits(URI("http://foo.com"), SD.DEFAULT_SRC));
+
+      // checks schemes can be case-insensitive
+      var testPolicy3 = "default-src 'self' HTTP://foo.com";
+      cspr = CSPRep.fromString(testPolicy3, URI(selfHost));
+      do_check_true(cspr.permits(URI("http://foo.com"), SD.DEFAULT_SRC));
+
+      // checks keywords can be case-insensitive
+      var testPolicy4 = "default-src 'NONE'";
+      cspr = CSPRep.fromString(testPolicy4, URI(selfHost));
+      do_check_false(cspr.permits(URI("http://foo.com"), SD.DEFAULT_SRC));
+  });
 /*
 
 test(function test_CSPRep_fromPolicyURI_failswhenmixed() {
         var cspr;
         var self = "http://localhost:" + POLICY_PORT;
         var closed_policy = CSPRep.fromString("allow 'none'");
         var my_uri_policy = "policy-uri " + POLICY_URI;
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -34,20 +34,20 @@
 #include "ForceDiscreteGPUHelperCGL.h"
 #endif
 
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/ErrorResult.h"
 
 class nsIDocShell;
 
-/* 
+/*
  * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25
  *   https://bugzilla.mozilla.org/show_bug.cgi?id=686732
- * 
+ *
  * Exceptions: some of the following values are set to higher values than in the spec because
  * the values in the spec are ridiculously low. They are explicitly marked below
 */
 #define MINVALUE_GL_MAX_TEXTURE_SIZE                  1024  // Different from the spec, which sets it to 64 on page 162
 #define MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE         512   // Different from the spec, which sets it to 16 on page 162
 #define MINVALUE_GL_MAX_VERTEX_ATTRIBS                8     // Page 164
 #define MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS      16    // Page 164
 #define MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS        128   // Page 164
@@ -225,17 +225,18 @@ public:
     bool IsPremultAlpha() const { return mOptions.premultipliedAlpha; }
 
     bool PresentScreenBuffer();
 
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
     uint32_t Generation() { return mGeneration.value(); }
 
-    const WebGLRectangleObject *FramebufferRectangleObject() const;
+    // Returns null if the current bound FB is not likely complete.
+    const WebGLRectangleObject* CurValidFBRectObject() const;
 
     static const size_t sMaxColorAttachments = 16;
 
     // This is similar to GLContext::ClearSafely, but tries to minimize the
     // amount of work it does.
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
     void ForceClearFramebufferWithDefaultValues(GLbitfield mask, const bool colorAttachmentsMask[sMaxColorAttachments]);
@@ -437,17 +438,17 @@ public:
                                -1, srcFormat, mPixelStorePremultiplyAlpha);
     }
     void TexParameterf(GLenum target, GLenum pname, GLfloat param) {
         TexParameter_base(target, pname, nullptr, &param);
     }
     void TexParameteri(GLenum target, GLenum pname, GLint param) {
         TexParameter_base(target, pname, &param, nullptr);
     }
-    
+
     void TexSubImage2D(GLenum target, GLint level,
                        GLint xoffset, GLint yoffset,
                        GLsizei width, GLsizei height, GLenum format,
                        GLenum type,
                        const Nullable<dom::ArrayBufferView> &pixels,
                        ErrorResult& rv);
     void TexSubImage2D(GLenum target, GLint level,
                        GLint xoffset, GLint yoffset, GLenum format,
@@ -471,33 +472,33 @@ public:
 
         gfx::IntSize size = data->GetSize();
         uint32_t byteLength = data->Stride() * size.height;
         return TexSubImage2D_base(target, level, xoffset, yoffset,
                                   size.width, size.height,
                                   data->Stride(), format, type,
                                   data->GetData(), byteLength,
                                   -1, srcFormat, mPixelStorePremultiplyAlpha);
-        
+
     }
 
     void Uniform1i(WebGLUniformLocation* location, GLint x);
     void Uniform2i(WebGLUniformLocation* location, GLint x, GLint y);
     void Uniform3i(WebGLUniformLocation* location, GLint x, GLint y,
                    GLint z);
     void Uniform4i(WebGLUniformLocation* location, GLint x, GLint y,
                    GLint z, GLint w);
 
     void Uniform1f(WebGLUniformLocation* location, GLfloat x);
     void Uniform2f(WebGLUniformLocation* location, GLfloat x, GLfloat y);
     void Uniform3f(WebGLUniformLocation* location, GLfloat x, GLfloat y,
                    GLfloat z);
     void Uniform4f(WebGLUniformLocation* location, GLfloat x, GLfloat y,
                    GLfloat z, GLfloat w);
-    
+
     void Uniform1iv(WebGLUniformLocation* location,
                     const dom::Int32Array& arr) {
         Uniform1iv_base(location, arr.Length(), arr.Data());
     }
     void Uniform1iv(WebGLUniformLocation* location,
                     const dom::Sequence<GLint>& arr) {
         Uniform1iv_base(location, arr.Length(), arr.Elements());
     }
@@ -520,17 +521,17 @@ public:
         Uniform3iv_base(location, arr.Length(), arr.Data());
     }
     void Uniform3iv(WebGLUniformLocation* location,
                     const dom::Sequence<GLint>& arr) {
         Uniform3iv_base(location, arr.Length(), arr.Elements());
     }
     void Uniform3iv_base(WebGLUniformLocation* location, uint32_t arrayLength,
                          const GLint* data);
-    
+
     void Uniform4iv(WebGLUniformLocation* location,
                     const dom::Int32Array& arr) {
         Uniform4iv_base(location, arr.Length(), arr.Data());
     }
     void Uniform4iv(WebGLUniformLocation* location,
                     const dom::Sequence<GLint>& arr) {
         Uniform4iv_base(location, arr.Length(), arr.Elements());
     }
@@ -564,17 +565,17 @@ public:
         Uniform3fv_base(location, arr.Length(), arr.Data());
     }
     void Uniform3fv(WebGLUniformLocation* location,
                     const dom::Sequence<GLfloat>& arr) {
         Uniform3fv_base(location, arr.Length(), arr.Elements());
     }
     void Uniform3fv_base(WebGLUniformLocation* location, uint32_t arrayLength,
                          const GLfloat* data);
-    
+
     void Uniform4fv(WebGLUniformLocation* location,
                     const dom::Float32Array& arr) {
         Uniform4fv_base(location, arr.Length(), arr.Data());
     }
     void Uniform4fv(WebGLUniformLocation* location,
                     const dom::Sequence<GLfloat>& arr) {
         Uniform4fv_base(location, arr.Length(), arr.Elements());
     }
@@ -812,18 +813,18 @@ protected:
     void BindFakeBlackTextures();
     void UnbindFakeBlackTextures();
 
     WebGLVertexAttrib0Status WhatDoesVertexAttrib0Need();
     bool DoFakeVertexAttrib0(GLuint vertexCount);
     void UndoFakeVertexAttrib0();
     void InvalidateFakeVertexAttrib0();
 
-    static CheckedUint32 GetImageSize(GLsizei height, 
-                                      GLsizei width, 
+    static CheckedUint32 GetImageSize(GLsizei height,
+                                      GLsizei width,
                                       uint32_t pixelSize,
                                       uint32_t alignment);
 
     // Returns x rounded to the next highest multiple of y.
     static CheckedUint32 RoundedToNextMultipleOf(CheckedUint32 x, CheckedUint32 y) {
         return ((x + y - 1) / y) * y;
     }
 
@@ -947,17 +948,17 @@ protected:
     bool ValidateComparisonEnum(GLenum target, const char *info);
     bool ValidateStencilOpEnum(GLenum action, const char *info);
     bool ValidateFaceEnum(GLenum face, const char *info);
     bool ValidateTexFormatAndType(GLenum format, GLenum type, int jsArrayType,
                                       uint32_t *texelSize, const char *info);
     bool ValidateDrawModeEnum(GLenum mode, const char *info);
     bool ValidateAttribIndex(GLuint index, const char *info);
     bool ValidateStencilParamsForDrawCall();
-    
+
     bool ValidateGLSLVariableName(const nsAString& name, const char *info);
     bool ValidateGLSLCharacter(char16_t c);
     bool ValidateGLSLString(const nsAString& string, const char *info);
     bool ValidateTexImage2DFormat(GLenum format, const char* info);
     bool ValidateTexImage2DTarget(GLenum target, GLsizei width, GLsizei height, const char* info);
     bool ValidateCompressedTextureSize(GLenum target, GLint level, GLenum format, GLsizei width, GLsizei height, uint32_t byteLength, const char* info);
     bool ValidateLevelWidthHeightForTarget(GLenum target, GLint level, GLsizei width, GLsizei height, const char* info);
 
@@ -1043,17 +1044,17 @@ private:
     // to not be null already.
     template<class ObjectType>
     bool ValidateObjectAssumeNonNull(const char* info, ObjectType *aObject);
 
 protected:
     int32_t MaxTextureSizeForTarget(GLenum target) const {
         return target == LOCAL_GL_TEXTURE_2D ? mGLMaxTextureSize : mGLMaxCubeMapTextureSize;
     }
-    
+
     /** like glBufferData but if the call may change the buffer size, checks any GL error generated
      * by this glBufferData call and returns it */
     GLenum CheckedBufferData(GLenum target,
                              GLsizeiptr size,
                              const GLvoid *data,
                              GLenum usage);
     /** like glTexImage2D but if the call may change the texture size, checks any GL error generated
      * by this glTexImage2D call and returns it */
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -48,19 +48,32 @@
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gl;
 using namespace mozilla::gfx;
 
 static bool BaseTypeAndSizeFromUniformType(GLenum uType, GLenum *baseType, GLint *unitSize);
 static GLenum InternalFormatForFormatAndType(GLenum format, GLenum type, bool isGLES2);
 
-const WebGLRectangleObject *WebGLContext::FramebufferRectangleObject() const {
-    return mBoundFramebuffer ? mBoundFramebuffer->RectangleObject()
-                             : static_cast<const WebGLRectangleObject*>(this);
+const WebGLRectangleObject*
+WebGLContext::CurValidFBRectObject() const
+{
+    const WebGLRectangleObject* rect = nullptr;
+
+    if (mBoundFramebuffer) {
+        // We don't really need to ask the driver.
+        // Use 'precheck' to just check that our internal state looks good.
+        GLenum precheckStatus = mBoundFramebuffer->PrecheckFramebufferStatus();
+        if (precheckStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE)
+            rect = &mBoundFramebuffer->RectangleObject();
+    } else {
+        rect = static_cast<const WebGLRectangleObject*>(this);
+    }
+
+    return rect;
 }
 
 WebGLContext::FakeBlackTexture::FakeBlackTexture(GLContext *gl, GLenum target, GLenum format)
     : mGL(gl)
     , mGLName(0)
 {
   MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D || target == LOCAL_GL_TEXTURE_CUBE_MAP);
   MOZ_ASSERT(format == LOCAL_GL_RGB || format == LOCAL_GL_RGBA);
@@ -103,17 +116,17 @@ WebGLContext::FakeBlackTexture::~FakeBla
 
 //
 //  WebGL API
 //
 
 void
 WebGLContext::ActiveTexture(GLenum texture)
 {
-    if (IsContextLost()) 
+    if (IsContextLost())
         return;
 
     if (texture < LOCAL_GL_TEXTURE0 ||
         texture >= LOCAL_GL_TEXTURE0 + uint32_t(mGLMaxTextureUnits))
     {
         return ErrorInvalidEnum(
             "ActiveTexture: texture unit %d out of range. "
             "Accepted values range from TEXTURE0 to TEXTURE0 + %d. "
@@ -163,17 +176,17 @@ WebGLContext::BindAttribLocation(WebGLPr
         return;
 
     if (!ValidateAttribIndex(location, "bindAttribLocation"))
         return;
 
     NS_LossyConvertUTF16toASCII cname(name);
     nsCString mappedName;
     prog->MapIdentifier(cname, &mappedName);
-    
+
     MakeContextCurrent();
     gl->fBindAttribLocation(progname, location, mappedName.get());
 }
 
 void
 WebGLContext::BindFramebuffer(GLenum target, WebGLFramebuffer *wfb)
 {
     if (IsContextLost())
@@ -342,79 +355,42 @@ WebGLContext::BlendFuncSeparate(GLenum s
     MakeContextCurrent();
     gl->fBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
 }
 
 GLenum
 WebGLContext::CheckFramebufferStatus(GLenum target)
 {
     if (IsContextLost())
-    {
         return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
-    }
-
-    MakeContextCurrent();
+
     if (target != LOCAL_GL_FRAMEBUFFER) {
         ErrorInvalidEnum("checkFramebufferStatus: target must be FRAMEBUFFER");
         return 0;
     }
 
     if (!mBoundFramebuffer)
         return LOCAL_GL_FRAMEBUFFER_COMPLETE;
-    if(mBoundFramebuffer->HasDepthStencilConflict())
-        return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
-
-    bool hasImages = false;
-    hasImages |= mBoundFramebuffer->DepthAttachment().IsDefined();
-    hasImages |= mBoundFramebuffer->StencilAttachment().IsDefined();
-    hasImages |= mBoundFramebuffer->DepthStencilAttachment().IsDefined();
-
-    if (!hasImages) {
-        int32_t colorAttachmentCount = mBoundFramebuffer->mColorAttachments.Length();
-
-        for(int32_t i = 0; i < colorAttachmentCount; i++) {
-            if (mBoundFramebuffer->ColorAttachment(i).IsDefined()) {
-                hasImages = true;
-                break;
-            }
-        }
-
-        /* http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf section 4.4.5 (page 118)
-         GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
-         No images are attached to the framebuffer.
-         */
-        if (!hasImages) {
-            return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
-        }
-    }
-
-    if(mBoundFramebuffer->HasIncompleteAttachment())
-        return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
-    if(mBoundFramebuffer->HasAttachmentsOfMismatchedDimensions())
-        return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
-
-    // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
-    mBoundFramebuffer->FinalizeAttachments();
-
-    return gl->fCheckFramebufferStatus(target);
+
+    return mBoundFramebuffer->CheckFramebufferStatus();
 }
 
 void
 WebGLContext::CopyTexSubImage2D_base(GLenum target,
                                      GLint level,
                                      GLenum internalformat,
                                      GLint xoffset,
                                      GLint yoffset,
                                      GLint x,
                                      GLint y,
                                      GLsizei width,
                                      GLsizei height,
                                      bool sub)
 {
-    const WebGLRectangleObject *framebufferRect = FramebufferRectangleObject();
+    const WebGLRectangleObject* framebufferRect = CurValidFBRectObject();
     GLsizei framebufferWidth = framebufferRect ? framebufferRect->Width() : 0;
     GLsizei framebufferHeight = framebufferRect ? framebufferRect->Height() : 0;
 
     const char *info = sub ? "copyTexSubImage2D" : "copyTexImage2D";
 
     if (!ValidateLevelWidthHeightForTarget(target, level, width, height, info)) {
         return;
     }
@@ -438,17 +414,17 @@ WebGLContext::CopyTexSubImage2D_base(GLe
         /*** first, we initialize the texture as black ***/
 
         // first, compute the size of the buffer we should allocate to initialize the texture as black
 
         uint32_t texelSize = 0;
         if (!ValidateTexFormatAndType(internalformat, LOCAL_GL_UNSIGNED_BYTE, -1, &texelSize, info))
             return;
 
-        CheckedUint32 checked_neededByteLength = 
+        CheckedUint32 checked_neededByteLength =
             GetImageSize(height, width, texelSize, mPixelStoreUnpackAlignment);
 
         if (!checked_neededByteLength.isValid())
             return ErrorInvalidOperation("%s: integer overflow computing the needed buffer size", info);
 
         uint32_t bytesNeeded = checked_neededByteLength.value();
 
         // now that the size is known, create the buffer
@@ -909,17 +885,17 @@ WebGLContext::DoFakeVertexAttrib0(GLuint
 
     CheckedUint32 checked_dataSize = CheckedUint32(vertexCount) * 4 * sizeof(GLfloat);
 
     if (!checked_dataSize.isValid()) {
         ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0 array for a draw-operation "
                          "with %d vertices. Try reducing the number of vertices.", vertexCount);
         return false;
     }
-    
+
     GLuint dataSize = checked_dataSize.value();
 
     if (!mFakeVertexAttrib0BufferObject) {
         gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
     }
 
     // if the VBO status is already exactly what we need, or if the only difference is that it's initialized and
     // we don't need it to be, then consider it OK
@@ -955,30 +931,30 @@ WebGLContext::DoFakeVertexAttrib0(GLuint
                 array[4 * i + 2] = mVertexAttrib0Vector[2];
                 array[4 * i + 3] = mVertexAttrib0Vector[3];
             }
             gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, array, LOCAL_GL_DYNAMIC_DRAW);
         } else {
             gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW);
         }
         UpdateWebGLErrorAndClearGLError(&error);
-        
+
         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0);
 
         // note that we do this error checking and early return AFTER having restored the buffer binding above
         if (error) {
             ErrorOutOfMemory("Ran out of memory trying to construct a fake vertex attrib 0 array for a draw-operation "
                              "with %d vertices. Try reducing the number of vertices.", vertexCount);
             return false;
         }
     }
 
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
     gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0);
-    
+
     return true;
 }
 
 void
 WebGLContext::UndoFakeVertexAttrib0()
 {
     WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 
@@ -1190,24 +1166,24 @@ WebGLContext::GenerateMipmap(GLenum targ
 
     if (!ValidateTextureTargetEnum(target, "generateMipmap"))
         return;
 
     WebGLTexture *tex = activeBoundTextureForTarget(target);
 
     if (!tex)
         return ErrorInvalidOperation("generateMipmap: No texture is bound to this target.");
-    
+
     GLenum imageTarget = (target == LOCAL_GL_TEXTURE_2D) ? LOCAL_GL_TEXTURE_2D
                                                          : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
     if (!tex->HasImageInfoAt(imageTarget, 0))
     {
         return ErrorInvalidOperation("generateMipmap: Level zero of texture is not defined.");
     }
-    
+
     if (!tex->IsFirstImagePowerOfTwo())
         return ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height.");
 
     GLenum format = tex->ImageInfoAt(imageTarget, 0).InternalFormat();
     if (IsTextureFormatCompressed(format))
         return ErrorInvalidOperation("generateMipmap: Texture data at level zero is compressed.");
 
     if (IsExtensionEnabled(WEBGL_depth_texture) &&
@@ -1316,17 +1292,17 @@ WebGLContext::GetAttribLocation(WebGLPro
 {
     if (IsContextLost())
         return -1;
 
     if (!ValidateObject("getAttribLocation: program", prog))
         return -1;
 
     if (!ValidateGLSLVariableName(name, "getAttribLocation"))
-        return -1; 
+        return -1;
 
     NS_LossyConvertUTF16toASCII cname(name);
     nsCString mappedName;
     prog->MapIdentifier(cname, &mappedName);
 
     GLuint progname = prog->GLName();
 
     MakeContextCurrent();
@@ -1625,17 +1601,17 @@ WebGLContext::GetProgramInfoLog(WebGLPro
         retval.SetIsVoid(true);
         return;
     }
 
     if (!ValidateObject("getProgramInfoLog: program", prog)) {
         retval.Truncate();
         return;
     }
-        
+
     GLuint progname = prog->GLName();
 
     MakeContextCurrent();
 
     GLint k = -1;
     gl->fGetProgramiv(progname, LOCAL_GL_INFO_LOG_LENGTH, &k);
     if (k == -1) {
         // If GetProgramiv doesn't modify |k|,
@@ -2170,17 +2146,17 @@ WebGLContext::LinkProgram(WebGLProgram *
 
             nsAutoCString log;
 
             bool alreadyReportedShaderInfoLog = false;
 
             for (size_t i = 0; i < program->AttachedShaders().Length(); i++) {
 
                 WebGLShader* shader = program->AttachedShaders()[i];
-                
+
                 if (shader->CompileStatus())
                     continue;
 
                 const char *shaderTypeName = nullptr;
                 if (shader->ShaderType() == LOCAL_GL_VERTEX_SHADER) {
                     shaderTypeName = "vertex";
                 } else if (shader->ShaderType() == LOCAL_GL_FRAGMENT_SHADER) {
                     shaderTypeName = "fragment";
@@ -2264,17 +2240,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
     }
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("readPixels: negative size passed");
 
     if (pixels.IsNull())
         return ErrorInvalidValue("readPixels: null destination buffer");
 
-    const WebGLRectangleObject *framebufferRect = FramebufferRectangleObject();
+    const WebGLRectangleObject* framebufferRect = CurValidFBRectObject();
     GLsizei framebufferWidth = framebufferRect ? framebufferRect->Width() : 0;
     GLsizei framebufferHeight = framebufferRect ? framebufferRect->Height() : 0;
 
     uint32_t channels = 0;
 
     // Check the format param
     switch (format) {
         case LOCAL_GL_ALPHA:
@@ -2402,17 +2378,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
             return ErrorInvalidOperation("readPixels: integer overflow computing clipped rect size");
 
         // now we know that subrect_width is in the [0..width] interval, and same for heights.
 
         // now, same computation as above to find the size of the intermediate buffer to allocate for the subrect
         // no need to check again for integer overflow here, since we already know the sizes aren't greater than before
         uint32_t subrect_plainRowSize = subrect_width * bytesPerPixel;
     // There are checks above to ensure that this doesn't overflow.
-        uint32_t subrect_alignedRowSize = 
+        uint32_t subrect_alignedRowSize =
             RoundedToNextMultipleOf(subrect_plainRowSize, mPixelStorePackAlignment).value();
         uint32_t subrect_byteLength = (subrect_height-1)*subrect_alignedRowSize + subrect_plainRowSize;
 
         // create subrect buffer, call glReadPixels, copy pixels into destination buffer, delete subrect buffer
         GLubyte *subrect_data = new GLubyte[subrect_byteLength];
         gl->fReadPixels(subrect_x, subrect_y, subrect_width, subrect_height, format, type, subrect_data);
 
         // notice that this for loop terminates because we already checked that subrect_height is at most height
@@ -2465,17 +2441,17 @@ WebGLContext::ReadPixels(GLint x, GLint 
                     }
 
                     row += checked_alignedRowSize.value();
                 }
             } else {
                 NS_WARNING("Unhandled case, how'd we get here?");
                 return rv.Throw(NS_ERROR_FAILURE);
             }
-        }            
+        }
     }
 }
 
 void
 WebGLContext::RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
 {
     if (IsContextLost())
         return;
@@ -3121,32 +3097,32 @@ WebGLContext::CompileShader(WebGLShader 
 #ifdef XP_MACOSX
             if (gl->Vendor() == gl::GLVendor::NVIDIA) {
                 // Work around bug 890432
                 resources.MaxExpressionComplexity = 1000;
             }
 #endif
         }
 
-        // We're storing an actual instance of StripComments because, if we don't, the 
+        // We're storing an actual instance of StripComments because, if we don't, the
         // cleanSource nsAString instance will be destroyed before the reference is
         // actually used.
         StripComments stripComments(shader->Source());
         const nsAString& cleanSource = Substring(stripComments.result().Elements(), stripComments.length());
         if (!ValidateGLSLString(cleanSource, "compileShader"))
             return;
 
         // shaderSource() already checks that the source stripped of comments is in the
         // 7-bit ASCII range, so we can skip the NS_IsAscii() check.
         NS_LossyConvertUTF16toASCII sourceCString(cleanSource);
 
         if (gl->WorkAroundDriverBugs()) {
             const uint32_t maxSourceLength = 0x3ffff;
             if (sourceCString.Length() > maxSourceLength)
-                return ErrorInvalidValue("compileShader: source has more than %d characters", 
+                return ErrorInvalidValue("compileShader: source has more than %d characters",
                                          maxSourceLength);
         }
 
         const char *s = sourceCString.get();
 
 #define WEBGL2_BYPASS_ANGLE
 #ifdef WEBGL2_BYPASS_ANGLE
         /*
@@ -3653,17 +3629,17 @@ void
 WebGLContext::ShaderSource(WebGLShader *shader, const nsAString& source)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("shaderSource: shader", shader))
         return;
 
-    // We're storing an actual instance of StripComments because, if we don't, the 
+    // We're storing an actual instance of StripComments because, if we don't, the
     // cleanSource nsAString instance will be destroyed before the reference is
     // actually used.
     StripComments stripComments(source);
     const nsAString& cleanSource = Substring(stripComments.result().Elements(), stripComments.length());
     if (!ValidateGLSLString(cleanSource, "compileShader"))
         return;
 
     shader->SetSource(source);
@@ -3680,25 +3656,25 @@ GLenum WebGLContext::CheckedTexImage2D(G
                                        GLenum format,
                                        GLenum type,
                                        const GLvoid *data)
 {
     WebGLTexture *tex = activeBoundTextureForTarget(target);
     MOZ_ASSERT(tex != nullptr, "no texture bound");
 
     bool sizeMayChange = true;
-    
+
     if (tex->HasImageInfoAt(target, level)) {
         const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(target, level);
         sizeMayChange = width != imageInfo.Width() ||
                         height != imageInfo.Height() ||
                         format != imageInfo.InternalFormat() ||
                         type != imageInfo.Type();
     }
-    
+
     if (sizeMayChange) {
         UpdateWebGLErrorAndClearGLError();
         gl->fTexImage2D(target, level, internalFormat, width, height, border, format, type, data);
         GLenum error = LOCAL_GL_NO_ERROR;
         UpdateWebGLErrorAndClearGLError(&error);
         return error;
     } else {
         gl->fTexImage2D(target, level, internalFormat, width, height, border, format, type, data);
@@ -3759,17 +3735,17 @@ WebGLContext::TexImage2D_base(GLenum tar
     if (!ValidateTexFormatAndType(format, type, jsArrayType, &dstTexelSize, "texImage2D"))
         return;
 
     WebGLTexelFormat dstFormat = GetWebGLTexelFormat(format, type);
     WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;
 
     uint32_t srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(actualSrcFormat);
 
-    CheckedUint32 checked_neededByteLength = 
+    CheckedUint32 checked_neededByteLength =
         GetImageSize(height, width, srcTexelSize, mPixelStoreUnpackAlignment);
 
     CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize;
 
     CheckedUint32 checked_alignedRowSize =
         RoundedToNextMultipleOf(checked_plainRowSize.value(), mPixelStoreUnpackAlignment);
 
     if (!checked_neededByteLength.isValid())
@@ -3885,17 +3861,17 @@ WebGLContext::TexImage2D(GLenum target, 
 {
     if (IsContextLost())
         return;
 
     if (!pixels) {
         // Spec says to generate an INVALID_VALUE error
         return ErrorInvalidValue("texImage2D: null ImageData");
     }
-    
+
     Uint8ClampedArray arr(pixels->GetDataObject());
     return TexImage2D_base(target, level, internalformat, pixels->Width(),
                            pixels->Height(), 4*pixels->Width(), 0,
                            format, type, arr.Data(), arr.Length(), -1,
                            WebGLTexelFormat::RGBA8, false);
 }
 
 
@@ -3943,44 +3919,44 @@ WebGLContext::TexSubImage2D_base(GLenum 
     WebGLTexelFormat dstFormat = GetWebGLTexelFormat(format, type);
     WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;
 
     uint32_t srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(actualSrcFormat);
 
     if (width == 0 || height == 0)
         return; // ES 2.0 says it has no effect, we better return right now
 
-    CheckedUint32 checked_neededByteLength = 
+    CheckedUint32 checked_neededByteLength =
         GetImageSize(height, width, srcTexelSize, mPixelStoreUnpackAlignment);
 
     CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize;
 
-    CheckedUint32 checked_alignedRowSize = 
+    CheckedUint32 checked_alignedRowSize =
         RoundedToNextMultipleOf(checked_plainRowSize.value(), mPixelStoreUnpackAlignment);
 
     if (!checked_neededByteLength.isValid())
         return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");
 
     uint32_t bytesNeeded = checked_neededByteLength.value();
- 
+
     if (byteLength < bytesNeeded)
         return ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength);
 
     WebGLTexture *tex = activeBoundTextureForTarget(target);
 
     if (!tex)
         return ErrorInvalidOperation("texSubImage2D: no texture is bound to this target");
 
     if (!tex->HasImageInfoAt(target, level))
         return ErrorInvalidOperation("texSubImage2D: no texture image previously defined for this level and face");
-    
+
     const WebGLTexture::ImageInfo &imageInfo = tex->ImageInfoAt(target, level);
     if (!CanvasUtils::CheckSaneSubrectSize(xoffset, yoffset, width, height, imageInfo.Width(), imageInfo.Height()))
         return ErrorInvalidValue("texSubImage2D: subtexture rectangle out of bounds");
-    
+
     // Require the format and type in texSubImage2D to match that of the existing texture as created by texImage2D
     if (imageInfo.InternalFormat() != format || imageInfo.Type() != type)
         return ErrorInvalidOperation("texSubImage2D: format or type doesn't match the existing texture");
 
     if (imageInfo.HasUninitializedImageData()) {
         tex->DoDeferredImageInitialization(target, level);
     }
 
@@ -4231,18 +4207,18 @@ InternalFormatForFormatAndType(GLenum fo
     if (isGLES2)
         return format;
 
     if (format == LOCAL_GL_DEPTH_COMPONENT) {
         if (type == LOCAL_GL_UNSIGNED_SHORT)
             return LOCAL_GL_DEPTH_COMPONENT16;
         else if (type == LOCAL_GL_UNSIGNED_INT)
             return LOCAL_GL_DEPTH_COMPONENT32;
-    } 
-    
+    }
+
     if (format == LOCAL_GL_DEPTH_STENCIL) {
         if (type == LOCAL_GL_UNSIGNED_INT_24_8_EXT)
             return LOCAL_GL_DEPTH24_STENCIL8;
     }
 
     switch (type) {
     case LOCAL_GL_UNSIGNED_BYTE:
     case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
--- a/content/canvas/src/WebGLContextVertices.cpp
+++ b/content/canvas/src/WebGLContextVertices.cpp
@@ -730,17 +730,17 @@ void WebGLContext::Draw_cleanup()
             if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
                 gl->fFlush();
                 mDrawCallsSinceLastFlush = 0;
             }
         }
     }
 
     // Let's check the viewport
-    const WebGLRectangleObject* rect = FramebufferRectangleObject();
+    const WebGLRectangleObject* rect = CurValidFBRectObject();
     if (rect) {
         if (mViewportWidth > rect->Width() ||
             mViewportHeight > rect->Height())
         {
             if (!mAlreadyWarnedAboutViewportLargerThanDest) {
                 GenerateWarning("Drawing to a destination rect smaller than the viewport rect. "
                                 "(This warning will only be given once)");
                 mAlreadyWarnedAboutViewportLargerThanDest = true;
--- a/content/canvas/src/WebGLFramebuffer.cpp
+++ b/content/canvas/src/WebGLFramebuffer.cpp
@@ -7,111 +7,134 @@
 #include "WebGLFramebuffer.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLTexture.h"
 #include "WebGLRenderbuffer.h"
 #include "GLContext.h"
+#include "WebGLContextUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gl;
 
 JSObject*
-WebGLFramebuffer::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope) {
+WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> scope)
+{
     return dom::WebGLFramebufferBinding::Wrap(cx, scope, this);
 }
 
-WebGLFramebuffer::WebGLFramebuffer(WebGLContext *context)
+WebGLFramebuffer::WebGLFramebuffer(WebGLContext* context)
     : WebGLContextBoundObject(context)
     , mHasEverBeenBound(false)
     , mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
 {
     SetIsDOMBinding();
     mContext->MakeContextCurrent();
     mContext->gl->fGenFramebuffers(1, &mGLName);
     mContext->mFramebuffers.insertBack(this);
 
     mColorAttachments.SetLength(1);
     mColorAttachments[0].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0;
 }
 
 bool
-WebGLFramebuffer::Attachment::IsDeleteRequested() const {
+WebGLFramebuffer::Attachment::IsDeleteRequested() const
+{
     return Texture() ? Texture()->IsDeleteRequested()
          : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
          : false;
 }
 
 bool
-WebGLFramebuffer::Attachment::HasAlpha() const {
+WebGLFramebuffer::Attachment::HasAlpha() const
+{
     GLenum format = 0;
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
         format = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).InternalFormat();
     else if (Renderbuffer())
         format = Renderbuffer()->InternalFormat();
     return FormatHasAlpha(format);
 }
 
 void
-WebGLFramebuffer::Attachment::SetTexImage(WebGLTexture *tex, GLenum target, GLint level) {
+WebGLFramebuffer::Attachment::SetTexImage(WebGLTexture* tex, GLenum target, GLint level)
+{
     mTexturePtr = tex;
     mRenderbufferPtr = nullptr;
     mTexImageTarget = target;
     mTexImageLevel = level;
 }
 
 bool
-WebGLFramebuffer::Attachment::HasUninitializedImageData() const {
-    if (mRenderbufferPtr) {
-        return mRenderbufferPtr->HasUninitializedImageData();
-    } else if (mTexturePtr) {
-        if (!mTexturePtr->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
-            return false;
-        return mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).HasUninitializedImageData();
-    } else {
+WebGLFramebuffer::Attachment::HasUninitializedImageData() const
+{
+    if (!HasImage())
         return false;
+
+    if (Renderbuffer()) {
+        return Renderbuffer()->HasUninitializedImageData();
+    } else if (Texture()) {
+        MOZ_ASSERT(Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
+        return Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).HasUninitializedImageData();
     }
+
+    MOZ_ASSERT(false, "Should not get here.");
+    return false;
 }
 
 void
-WebGLFramebuffer::Attachment::SetImageDataStatus(WebGLImageDataStatus newStatus) {
+WebGLFramebuffer::Attachment::SetImageDataStatus(WebGLImageDataStatus newStatus)
+{
+    if (!HasImage())
+        return;
+
     if (mRenderbufferPtr) {
         mRenderbufferPtr->SetImageDataStatus(newStatus);
+        return;
     } else if (mTexturePtr) {
         mTexturePtr->SetImageDataStatus(mTexImageTarget, mTexImageLevel, newStatus);
-    } else {
-        MOZ_ASSERT(false); // should not get here, worth crashing a debug build.
+        return;
     }
-}
 
-const WebGLRectangleObject*
-WebGLFramebuffer::Attachment::RectangleObject() const {
-    if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
-        return &Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-    else if (Renderbuffer())
-        return Renderbuffer();
-    else
-        return nullptr;
+    MOZ_ASSERT(false, "Should not get here.");
 }
 
 bool
-WebGLFramebuffer::Attachment::HasSameDimensionsAs(const Attachment& other) const {
-    const WebGLRectangleObject *thisRect = RectangleObject();
-    const WebGLRectangleObject *otherRect = other.RectangleObject();
-    return thisRect &&
-           otherRect &&
-           thisRect->HasSameDimensionsAs(*otherRect);
+WebGLFramebuffer::Attachment::HasImage() const
+{
+    if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
+        return true;
+    else if (Renderbuffer())
+        return true;
+
+    return false;
+}
+
+const WebGLRectangleObject&
+WebGLFramebuffer::Attachment::RectangleObject() const
+{
+    MOZ_ASSERT(HasImage(), "Make sure it has an image before requesting the rectangle.");
+
+    if (Texture()) {
+        MOZ_ASSERT(Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
+        return Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
+    } else if (Renderbuffer()) {
+        return *Renderbuffer();
+    }
+
+    MOZ_CRASH("Should not get here.");
 }
 
 static inline bool
-IsValidAttachedTextureColorFormat(GLenum format) {
+IsValidAttachedTextureColorFormat(GLenum format)
+{
     return (
         /* linear 8-bit formats */
         format == LOCAL_GL_ALPHA ||
         format == LOCAL_GL_LUMINANCE ||
         format == LOCAL_GL_LUMINANCE_ALPHA ||
         format == LOCAL_GL_RGB ||
         format == LOCAL_GL_RGBA ||
         /* sRGB 8-bit formats */
@@ -121,71 +144,76 @@ IsValidAttachedTextureColorFormat(GLenum
         format ==  LOCAL_GL_ALPHA32F_ARB ||
         format ==  LOCAL_GL_LUMINANCE32F_ARB ||
         format ==  LOCAL_GL_LUMINANCE_ALPHA32F_ARB ||
         format ==  LOCAL_GL_RGB32F_ARB ||
         format ==  LOCAL_GL_RGBA32F_ARB);
 }
 
 bool
-WebGLFramebuffer::Attachment::IsComplete() const {
-    const WebGLRectangleObject *thisRect = RectangleObject();
-
-    if (!thisRect ||
-        !thisRect->Width() ||
-        !thisRect->Height())
+WebGLFramebuffer::Attachment::IsComplete() const
+{
+    if (!HasImage())
         return false;
 
+    const WebGLRectangleObject& rect = RectangleObject();
+
+    if (!rect.Width() ||
+        !rect.Height())
+    {
+        return false;
+    }
+
     if (mTexturePtr) {
-        if (!mTexturePtr->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
-            return false;
-
+        MOZ_ASSERT(mTexturePtr->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
         GLenum format = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).InternalFormat();
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT) {
             return format == LOCAL_GL_DEPTH_COMPONENT;
-        }
-        else if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        } else if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
             return format == LOCAL_GL_DEPTH_STENCIL;
-        }
-        else if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-                 mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments)) {
+        } else if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+                   mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments))
+        {
             return IsValidAttachedTextureColorFormat(format);
         }
-        MOZ_CRASH("Invalid WebGL attachment poin?");
+        MOZ_ASSERT(false, "Invalid WebGL attachment point?");
+        return false;
     }
 
     if (mRenderbufferPtr) {
         GLenum format = mRenderbufferPtr->InternalFormat();
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT) {
             return format == LOCAL_GL_DEPTH_COMPONENT16;
-        }
-        else if (mAttachmentPoint == LOCAL_GL_STENCIL_ATTACHMENT) {
+        } else if (mAttachmentPoint == LOCAL_GL_STENCIL_ATTACHMENT) {
             return format == LOCAL_GL_STENCIL_INDEX8;
-        }
-        else if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        } else if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
             return format == LOCAL_GL_DEPTH_STENCIL;
+        } else if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+                   mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments))
+        {
+            return format == LOCAL_GL_RGB565 ||
+                   format == LOCAL_GL_RGB5_A1 ||
+                   format == LOCAL_GL_RGBA4 ||
+                   format == LOCAL_GL_SRGB8_ALPHA8_EXT;
         }
-        else if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-                 mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments)) {
-            return (format == LOCAL_GL_RGB565 ||
-                    format == LOCAL_GL_RGB5_A1 ||
-                    format == LOCAL_GL_RGBA4 ||
-                    format == LOCAL_GL_SRGB8_ALPHA8_EXT);
-        }
-        MOZ_CRASH("Invalid WebGL attachment poin?");
+        MOZ_ASSERT(false, "Invalid WebGL attachment point?");
+        return false;
     }
 
-    MOZ_ASSERT(false); // should never get there
+    MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
 void
-WebGLFramebuffer::Attachment::FinalizeAttachment(GLenum attachmentLoc) const {
+WebGLFramebuffer::Attachment::FinalizeAttachment(GLenum attachmentLoc) const
+{
+    MOZ_ASSERT(HasImage());
+
     if (Texture()) {
         GLContext* gl = Texture()->Context()->gl;
         if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
             gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                       TexImageTarget(), Texture()->GLName(), TexImageLevel());
             gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
                                       TexImageTarget(), Texture()->GLName(), TexImageLevel());
         } else {
@@ -195,42 +223,41 @@ WebGLFramebuffer::Attachment::FinalizeAt
         return;
     }
 
     if (Renderbuffer()) {
         Renderbuffer()->FramebufferRenderbuffer(attachmentLoc);
         return;
     }
 
-    // Neither?
-    MOZ_ASSERT(false, "FB attachment without a tex or RB.");
+    MOZ_ASSERT(false, "Should not get here.");
 }
 
 void
-WebGLFramebuffer::Delete() {
+WebGLFramebuffer::Delete()
+{
     mColorAttachments.Clear();
     mDepthAttachment.Reset();
     mStencilAttachment.Reset();
     mDepthStencilAttachment.Reset();
+
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 }
 
 void
 WebGLFramebuffer::FramebufferRenderbuffer(GLenum target,
-                             GLenum attachment,
-                             GLenum rbtarget,
-                             WebGLRenderbuffer *wrb)
+                                          GLenum attachment,
+                                          GLenum rbtarget,
+                                          WebGLRenderbuffer* wrb)
 {
     MOZ_ASSERT(mContext->mBoundFramebuffer == this);
     if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", wrb))
-    {
         return;
-    }
 
     if (target != LOCAL_GL_FRAMEBUFFER)
         return mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: target", target);
 
     if (rbtarget != LOCAL_GL_RENDERBUFFER)
         return mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: renderbuffer target:", rbtarget);
 
     switch (attachment) {
@@ -253,35 +280,37 @@ WebGLFramebuffer::FramebufferRenderbuffe
         EnsureColorAttachments(colorAttachmentId);
         mColorAttachments[colorAttachmentId].SetRenderbuffer(wrb);
         break;
     }
 }
 
 void
 WebGLFramebuffer::FramebufferTexture2D(GLenum target,
-                          GLenum attachment,
-                          GLenum textarget,
-                          WebGLTexture *wtex,
-                          GLint level)
+                                       GLenum attachment,
+                                       GLenum textarget,
+                                       WebGLTexture* wtex,
+                                       GLint level)
 {
     MOZ_ASSERT(mContext->mBoundFramebuffer == this);
     if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture",
                                            wtex))
     {
         return;
     }
 
     if (target != LOCAL_GL_FRAMEBUFFER)
         return mContext->ErrorInvalidEnumInfo("framebufferTexture2D: target", target);
 
     if (textarget != LOCAL_GL_TEXTURE_2D &&
         (textarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
          textarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z))
+    {
         return mContext->ErrorInvalidEnumInfo("framebufferTexture2D: invalid texture target", textarget);
+    }
 
     if (wtex) {
         bool isTexture2D = wtex->Target() == LOCAL_GL_TEXTURE_2D;
         bool isTexTarget2D = textarget == LOCAL_GL_TEXTURE_2D;
         if (isTexture2D != isTexTarget2D) {
             return mContext->ErrorInvalidOperation("framebufferTexture2D: mismatched texture and texture target");
         }
     }
@@ -295,263 +324,351 @@ WebGLFramebuffer::FramebufferTexture2D(G
         break;
     case LOCAL_GL_STENCIL_ATTACHMENT:
         mStencilAttachment.SetTexImage(wtex, textarget, level);
         break;
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
         mDepthStencilAttachment.SetTexImage(wtex, textarget, level);
         break;
     default:
-        if (!CheckColorAttachementNumber(attachment, "framebufferTexture2D")){
+        if (!CheckColorAttachementNumber(attachment, "framebufferTexture2D"))
             return;
-        }
 
         size_t colorAttachmentId = size_t(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
         EnsureColorAttachments(colorAttachmentId);
         mColorAttachments[colorAttachmentId].SetTexImage(wtex, textarget, level);
         break;
     }
 }
 
-bool
-WebGLFramebuffer::HasIncompleteAttachment() const {
-    int32_t colorAttachmentCount = mColorAttachments.Length();
-
-    for (int32_t i = 0; i < colorAttachmentCount; i++)
-    {
-        if (mColorAttachments[i].IsDefined() && !mColorAttachments[i].IsComplete())
-        {
-            return true;
-        }
-    }
-
-    return ((mDepthAttachment.IsDefined() && !mDepthAttachment.IsComplete()) ||
-            (mStencilAttachment.IsDefined() && !mStencilAttachment.IsComplete()) ||
-            (mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.IsComplete()));
-}
-
-bool
-WebGLFramebuffer::HasAttachmentsOfMismatchedDimensions() const {
-    int32_t colorAttachmentCount = mColorAttachments.Length();
-
-    for (int32_t i = 1; i < colorAttachmentCount; i++)
-    {
-        if (mColorAttachments[i].IsDefined() && !mColorAttachments[i].HasSameDimensionsAs(mColorAttachments[0]))
-        {
-            return true;
-        }
-    }
-
-    return ((mDepthAttachment.IsDefined() && !mDepthAttachment.HasSameDimensionsAs(mColorAttachments[0])) ||
-            (mStencilAttachment.IsDefined() && !mStencilAttachment.HasSameDimensionsAs(mColorAttachments[0])) ||
-            (mDepthStencilAttachment.IsDefined() && !mDepthStencilAttachment.HasSameDimensionsAs(mColorAttachments[0])));
-}
-
 const WebGLFramebuffer::Attachment&
-WebGLFramebuffer::GetAttachment(GLenum attachment) const {
+WebGLFramebuffer::GetAttachment(GLenum attachment) const
+{
     if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
         return mDepthStencilAttachment;
     if (attachment == LOCAL_GL_DEPTH_ATTACHMENT)
         return mDepthAttachment;
     if (attachment == LOCAL_GL_STENCIL_ATTACHMENT)
         return mStencilAttachment;
 
     if (!CheckColorAttachementNumber(attachment, "getAttachment")) {
         MOZ_ASSERT(false);
         return mColorAttachments[0];
     }
 
-    uint32_t colorAttachmentId = uint32_t(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
-
+    size_t colorAttachmentId = attachment - LOCAL_GL_COLOR_ATTACHMENT0;
     if (colorAttachmentId >= mColorAttachments.Length()) {
         MOZ_ASSERT(false);
         return mColorAttachments[0];
     }
 
     return mColorAttachments[colorAttachmentId];
 }
 
 void
-WebGLFramebuffer::DetachTexture(const WebGLTexture *tex) {
-    int32_t colorAttachmentCount = mColorAttachments.Length();
-
-    for (int32_t i = 0; i < colorAttachmentCount; i++) {
+WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
+{
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
         if (mColorAttachments[i].Texture() == tex) {
             FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_TEXTURE_2D, nullptr, 0);
             // a texture might be attached more that once while editing the framebuffer
         }
     }
 
     if (mDepthAttachment.Texture() == tex)
         FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nullptr, 0);
     if (mStencilAttachment.Texture() == tex)
         FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nullptr, 0);
     if (mDepthStencilAttachment.Texture() == tex)
         FramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, LOCAL_GL_TEXTURE_2D, nullptr, 0);
 }
 
 void
-WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer *rb) {
-    int32_t colorAttachmentCount = mColorAttachments.Length();
-
-    for (int32_t i = 0; i < colorAttachmentCount; i++) {
+WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
+{
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
         if (mColorAttachments[0].Renderbuffer() == rb) {
             FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_RENDERBUFFER, nullptr);
             // a renderbuffer might be attached more that once while editing the framebuffer
         }
     }
 
     if (mDepthAttachment.Renderbuffer() == rb)
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
     if (mStencilAttachment.Renderbuffer() == rb)
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
     if (mDepthStencilAttachment.Renderbuffer() == rb)
         FramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, nullptr);
 }
 
 bool
-WebGLFramebuffer::CheckAndInitializeAttachments()
+WebGLFramebuffer::HasDefinedAttachments() const
+{
+    bool hasAttachments = false;
+
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
+        hasAttachments |= mColorAttachments[i].IsDefined();
+    }
+
+    hasAttachments |= mDepthAttachment.IsDefined();
+    hasAttachments |= mStencilAttachment.IsDefined();
+    hasAttachments |= mDepthStencilAttachment.IsDefined();
+
+    return hasAttachments;
+}
+
+
+static bool
+IsIncomplete(const WebGLFramebuffer::Attachment& cur)
+{
+  return cur.IsDefined() && !cur.IsComplete();
+}
+
+bool
+WebGLFramebuffer::HasIncompleteAttachments() const
+{
+    bool hasIncomplete = false;
+
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
+        hasIncomplete |= IsIncomplete(mColorAttachments[i]);
+    }
+
+    hasIncomplete |= IsIncomplete(mDepthAttachment);
+    hasIncomplete |= IsIncomplete(mStencilAttachment);
+    hasIncomplete |= IsIncomplete(mDepthStencilAttachment);
+
+    return hasIncomplete;
+}
+
+
+const WebGLRectangleObject&
+WebGLFramebuffer::GetAnyRectObject() const
+{
+    MOZ_ASSERT(HasDefinedAttachments());
+
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].HasImage())
+            return mColorAttachments[i].RectangleObject();
+    }
+
+    if (mDepthAttachment.HasImage())
+        return mDepthAttachment.RectangleObject();
+
+    if (mStencilAttachment.HasImage())
+        return mStencilAttachment.RectangleObject();
+
+    if (mDepthStencilAttachment.HasImage())
+        return mDepthStencilAttachment.RectangleObject();
+
+    MOZ_CRASH("Should not get here.");
+}
+
+
+static bool
+RectsMatch(const WebGLFramebuffer::Attachment& attachment,
+           const WebGLRectangleObject& rect)
+{
+    return attachment.RectangleObject().HasSameDimensionsAs(rect);
+}
+
+bool
+WebGLFramebuffer::AllImageRectsMatch() const
+{
+    MOZ_ASSERT(HasDefinedAttachments());
+    MOZ_ASSERT(!HasIncompleteAttachments());
+
+    const WebGLRectangleObject& rect = GetAnyRectObject();
+
+    // Alright, we have *a* rect, let's check all the others.
+    bool imageRectsMatch = true;
+
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].HasImage())
+            imageRectsMatch &= RectsMatch(mColorAttachments[i], rect);
+    }
+
+    if (mDepthAttachment.HasImage())
+        imageRectsMatch &= RectsMatch(mDepthAttachment, rect);
+
+    if (mStencilAttachment.HasImage())
+        imageRectsMatch &= RectsMatch(mStencilAttachment, rect);
+
+    if (mDepthStencilAttachment.HasImage())
+        imageRectsMatch &= RectsMatch(mDepthStencilAttachment, rect);
+
+    return imageRectsMatch;
+}
+
+
+const WebGLRectangleObject&
+WebGLFramebuffer::RectangleObject() const
+{
+    // If we're using this as the RectObj of an FB, we need to be sure the FB
+    // has a consistent rect.
+    MOZ_ASSERT(AllImageRectsMatch(), "Did you mean `GetAnyRectObject`?");
+    return GetAnyRectObject();
+}
+
+GLenum
+WebGLFramebuffer::PrecheckFramebufferStatus() const
 {
     MOZ_ASSERT(mContext->mBoundFramebuffer == this);
-    // enforce WebGL section 6.5 which is WebGL-specific, hence OpenGL itself would not
-    // generate the INVALID_FRAMEBUFFER_OPERATION that we need here
+
+    if (!HasDefinedAttachments())
+        return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No attachments
+
+    if (HasIncompleteAttachments())
+        return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
+
+    if (!AllImageRectsMatch())
+        return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // No consistent size
+
     if (HasDepthStencilConflict())
-        return false;
+        return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
+
+    return LOCAL_GL_FRAMEBUFFER_COMPLETE;
+}
 
-    if (HasIncompleteAttachment())
-        return false;
+GLenum
+WebGLFramebuffer::CheckFramebufferStatus() const
+{
+    GLenum precheckStatus = PrecheckFramebufferStatus();
+    if (precheckStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+        return precheckStatus;
 
+    // Looks good on our end. Let's ask the driver.
     mContext->MakeContextCurrent();
 
     // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
     FinalizeAttachments();
 
+    return mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+}
+
+
+
+bool
+WebGLFramebuffer::CheckAndInitializeAttachments()
+{
+    MOZ_ASSERT(mContext->mBoundFramebuffer == this);
+
+    if (CheckFramebufferStatus() != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+        return false;
+
+    // Cool! We've checked out ok. Just need to initialize.
     size_t colorAttachmentCount = size_t(mColorAttachments.Length());
 
+    // Check if we need to initialize anything
     {
-        bool hasUnitializedAttachments = false;
+        bool hasUninitializedAttachments = false;
 
         for (size_t i = 0; i < colorAttachmentCount; i++) {
-            hasUnitializedAttachments |= mColorAttachments[i].HasUninitializedImageData();
+            if (mColorAttachments[i].HasImage())
+                hasUninitializedAttachments |= mColorAttachments[i].HasUninitializedImageData();
         }
 
-        if (!hasUnitializedAttachments &&
-            !mDepthAttachment.HasUninitializedImageData() &&
-            !mStencilAttachment.HasUninitializedImageData() &&
-            !mDepthStencilAttachment.HasUninitializedImageData())
-        {
+        if (mDepthAttachment.HasImage())
+            hasUninitializedAttachments |= mDepthAttachment.HasUninitializedImageData();
+        if (mStencilAttachment.HasImage())
+            hasUninitializedAttachments |= mStencilAttachment.HasUninitializedImageData();
+        if (mDepthStencilAttachment.HasImage())
+            hasUninitializedAttachments |= mDepthStencilAttachment.HasUninitializedImageData();
+
+        if (!hasUninitializedAttachments)
             return true;
-        }
     }
 
-    // ensure INVALID_FRAMEBUFFER_OPERATION in zero-size case
-    const WebGLRectangleObject *rect = mColorAttachments[0].RectangleObject();
-    if (!rect ||
-        !rect->Width() ||
-        !rect->Height())
-        return false;
-
-    GLenum status = mContext->CheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
-    if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
-        return false;
-
+    // Get buffer-bit-mask and color-attachment-mask-list
     uint32_t mask = 0;
     bool colorAttachmentsMask[WebGLContext::sMaxColorAttachments] = { false };
-
-    MOZ_ASSERT( colorAttachmentCount <= WebGLContext::sMaxColorAttachments );
+    MOZ_ASSERT(colorAttachmentCount <= WebGLContext::sMaxColorAttachments);
 
-    for (size_t i = 0; i < colorAttachmentCount; i++)
-    {
-        colorAttachmentsMask[i] = mColorAttachments[i].HasUninitializedImageData();
-
-        if (colorAttachmentsMask[i]) {
-            mask |= LOCAL_GL_COLOR_BUFFER_BIT;
+    for (size_t i = 0; i < colorAttachmentCount; i++) {
+        if (mColorAttachments[i].HasUninitializedImageData()) {
+          colorAttachmentsMask[i] = true;
+          mask |= LOCAL_GL_COLOR_BUFFER_BIT;
         }
     }
 
     if (mDepthAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_DEPTH_BUFFER_BIT;
     }
 
     if (mStencilAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_STENCIL_BUFFER_BIT;
     }
 
+    // Clear!
     mContext->ForceClearFramebufferWithDefaultValues(mask, colorAttachmentsMask);
 
-    for (size_t i = 0; i < colorAttachmentCount; i++)
-    {
+    // Mark all the uninitialized images as initialized.
+    for (size_t i = 0; i < colorAttachmentCount; i++) {
         if (mColorAttachments[i].HasUninitializedImageData())
             mColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     }
 
     if (mDepthAttachment.HasUninitializedImageData())
         mDepthAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mStencilAttachment.HasUninitializedImageData())
         mStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mDepthStencilAttachment.HasUninitializedImageData())
         mDepthStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
 
     return true;
 }
 
-bool WebGLFramebuffer::CheckColorAttachementNumber(GLenum attachment, const char * functionName) const
+bool WebGLFramebuffer::CheckColorAttachementNumber(GLenum attachment, const char* functionName) const
 {
     const char* const errorFormating = "%s: attachment: invalid enum value 0x%x";
 
-    if (mContext->IsExtensionEnabled(WebGLContext::WEBGL_draw_buffers))
-    {
+    if (mContext->IsExtensionEnabled(WebGLContext::WEBGL_draw_buffers)) {
         if (attachment < LOCAL_GL_COLOR_ATTACHMENT0 ||
             attachment >= GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + mContext->mGLMaxColorAttachments))
         {
             mContext->ErrorInvalidEnum(errorFormating, functionName, attachment);
             return false;
         }
-    }
-    else if (attachment != LOCAL_GL_COLOR_ATTACHMENT0)
-    {
+    } else if (attachment != LOCAL_GL_COLOR_ATTACHMENT0) {
         if (attachment > LOCAL_GL_COLOR_ATTACHMENT0 &&
             attachment <= LOCAL_GL_COLOR_ATTACHMENT15)
         {
             mContext->ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x. "
                                        "Try the WEBGL_draw_buffers extension if supported.", functionName, attachment);
             return false;
-        }
-        else
-        {
+        } else {
             mContext->ErrorInvalidEnum(errorFormating, functionName, attachment);
             return false;
         }
     }
 
     return true;
 }
 
-void WebGLFramebuffer::EnsureColorAttachments(size_t colorAttachmentId) {
-    size_t currentAttachmentCount = mColorAttachments.Length();
+void WebGLFramebuffer::EnsureColorAttachments(size_t colorAttachmentId)
+{
+    MOZ_ASSERT(colorAttachmentId < WebGLContext::sMaxColorAttachments);
 
-    if (mColorAttachments.Length() > colorAttachmentId) {
+    size_t currentAttachmentCount = mColorAttachments.Length();
+    if (colorAttachmentId < currentAttachmentCount)
         return;
-    }
-
-    MOZ_ASSERT( colorAttachmentId < WebGLContext::sMaxColorAttachments );
 
     mColorAttachments.SetLength(colorAttachmentId + 1);
 
     for (size_t i = colorAttachmentId; i >= currentAttachmentCount; i--) {
         mColorAttachments[i].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0 + i;
     }
 }
 
 void
-WebGLFramebuffer::FinalizeAttachments() const {
+WebGLFramebuffer::FinalizeAttachments() const
+{
     for (size_t i = 0; i < ColorAttachmentCount(); i++) {
         if (ColorAttachment(i).IsDefined())
             ColorAttachment(i).FinalizeAttachment(LOCAL_GL_COLOR_ATTACHMENT0 + i);
     }
 
     if (DepthAttachment().IsDefined())
         DepthAttachment().FinalizeAttachment(LOCAL_GL_DEPTH_ATTACHMENT);
 
--- a/content/canvas/src/WebGLFramebuffer.h
+++ b/content/canvas/src/WebGLFramebuffer.h
@@ -22,17 +22,17 @@ namespace gl {
 
 class WebGLFramebuffer MOZ_FINAL
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
 {
 public:
-    WebGLFramebuffer(WebGLContext *context);
+    WebGLFramebuffer(WebGLContext* context);
 
     ~WebGLFramebuffer() {
         DeleteOnce();
     }
 
     struct Attachment
     {
         // deleting a texture or renderbuffer immediately detaches it
@@ -49,31 +49,31 @@ public:
         bool IsDefined() const {
             return Texture() || Renderbuffer();
         }
 
         bool IsDeleteRequested() const;
 
         bool HasAlpha() const;
 
-        void SetTexImage(WebGLTexture *tex, GLenum target, GLint level);
-        void SetRenderbuffer(WebGLRenderbuffer *rb) {
+        void SetTexImage(WebGLTexture* tex, GLenum target, GLint level);
+        void SetRenderbuffer(WebGLRenderbuffer* rb) {
             mTexturePtr = nullptr;
             mRenderbufferPtr = rb;
         }
-        const WebGLTexture *Texture() const {
+        const WebGLTexture* Texture() const {
             return mTexturePtr;
         }
-        WebGLTexture *Texture() {
+        WebGLTexture* Texture() {
             return mTexturePtr;
         }
-        const WebGLRenderbuffer *Renderbuffer() const {
+        const WebGLRenderbuffer* Renderbuffer() const {
             return mRenderbufferPtr;
         }
-        WebGLRenderbuffer *Renderbuffer() {
+        WebGLRenderbuffer* Renderbuffer() {
             return mRenderbufferPtr;
         }
         GLenum TexImageTarget() const {
             return mTexImageTarget;
         }
         GLint TexImageLevel() const {
             return mTexImageLevel;
         }
@@ -81,55 +81,61 @@ public:
         bool HasUninitializedImageData() const;
         void SetImageDataStatus(WebGLImageDataStatus x);
 
         void Reset() {
             mTexturePtr = nullptr;
             mRenderbufferPtr = nullptr;
         }
 
-        const WebGLRectangleObject* RectangleObject() const;
-        bool HasSameDimensionsAs(const Attachment& other) const;
+        const WebGLRectangleObject& RectangleObject() const;
 
+        bool HasImage() const;
         bool IsComplete() const;
 
         void FinalizeAttachment(GLenum attachmentLoc) const;
     };
 
     void Delete();
 
     bool HasEverBeenBound() { return mHasEverBeenBound; }
     void SetHasEverBeenBound(bool x) { mHasEverBeenBound = x; }
     GLuint GLName() { return mGLName; }
 
     void FramebufferRenderbuffer(GLenum target,
                                  GLenum attachment,
                                  GLenum rbtarget,
-                                 WebGLRenderbuffer *wrb);
+                                 WebGLRenderbuffer* wrb);
 
     void FramebufferTexture2D(GLenum target,
                               GLenum attachment,
                               GLenum textarget,
-                              WebGLTexture *wtex,
+                              WebGLTexture* wtex,
                               GLint level);
 
-    bool HasIncompleteAttachment() const;
+private:
+    const WebGLRectangleObject& GetAnyRectObject() const;
+
+public:
+    bool HasDefinedAttachments() const;
+    bool HasIncompleteAttachments() const;
+    bool AllImageRectsMatch() const;
+    GLenum PrecheckFramebufferStatus() const;
+    GLenum CheckFramebufferStatus() const;
 
     bool HasDepthStencilConflict() const {
         return int(mDepthAttachment.IsDefined()) +
                int(mStencilAttachment.IsDefined()) +
                int(mDepthStencilAttachment.IsDefined()) >= 2;
     }
 
-    bool HasAttachmentsOfMismatchedDimensions() const;
-
-    const size_t ColorAttachmentCount() const {
+    size_t ColorAttachmentCount() const {
         return mColorAttachments.Length();
     }
-    const Attachment& ColorAttachment(uint32_t colorAttachmentId) const {
+    const Attachment& ColorAttachment(size_t colorAttachmentId) const {
         return mColorAttachments[colorAttachmentId];
     }
 
     const Attachment& DepthAttachment() const {
         return mDepthAttachment;
     }
 
     const Attachment& StencilAttachment() const {
@@ -137,39 +143,37 @@ public:
     }
 
     const Attachment& DepthStencilAttachment() const {
         return mDepthStencilAttachment;
     }
 
     const Attachment& GetAttachment(GLenum attachment) const;
 
-    void DetachTexture(const WebGLTexture *tex);
+    void DetachTexture(const WebGLTexture* tex);
 
-    void DetachRenderbuffer(const WebGLRenderbuffer *rb);
+    void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
-    const WebGLRectangleObject *RectangleObject() {
-        return mColorAttachments[0].RectangleObject();
-    }
+    const WebGLRectangleObject& RectangleObject() const;
 
-    WebGLContext *GetParentObject() const {
+    WebGLContext* GetParentObject() const {
         return Context();
     }
 
     void FinalizeAttachments() const;
 
-    virtual JSObject* WrapObject(JSContext *cx,
+    virtual JSObject* WrapObject(JSContext* cx,
                                  JS::Handle<JSObject*> scope) MOZ_OVERRIDE;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
 
     bool CheckAndInitializeAttachments();
 
-    bool CheckColorAttachementNumber(GLenum attachment, const char * functionName) const;
+    bool CheckColorAttachementNumber(GLenum attachment, const char* functionName) const;
 
     GLuint mGLName;
     bool mHasEverBeenBound;
 
     void EnsureColorAttachments(size_t colorAttachmentId);
 
     // we only store pointers to attached renderbuffers, not to attached textures, because
     // we will only need to initialize renderbuffers. Textures are already initialized.
--- a/content/html/content/src/HTMLTrackElement.cpp
+++ b/content/html/content/src/HTMLTrackElement.cpp
@@ -303,16 +303,16 @@ HTMLTrackElement::UnbindFromTree(bool aD
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 uint16_t
 HTMLTrackElement::ReadyState() const
 {
   if (!mTrack) {
-    return NONE;
+    return READY_STATE_NONE;
   }
 
   return mTrack->ReadyState();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/HTMLTrackElement.h
+++ b/content/html/content/src/HTMLTrackElement.h
@@ -92,20 +92,20 @@ public:
   }
   void SetDefault(bool aDefault, ErrorResult& aError)
   {
     SetHTMLBoolAttr(nsGkAtoms::_default, aDefault, aError);
   }
 
   // Constants for numeric readyState property values.
   enum {
-    NONE = 0U,
-    LOADING = 1U,
-    LOADED = 2U,
-    ERROR = 3U
+    READY_STATE_NONE = 0U,
+    READY_STATE_LOADING = 1U,
+    READY_STATE_LOADED = 2U,
+    READY_STATE_ERROR = 3U
   };
   uint16_t ReadyState() const;
 
   TextTrack* Track();
 
   virtual nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const MOZ_OVERRIDE;
 
   // For Track, ItemValue reflects the src attribute
--- a/content/html/content/src/TextTrackManager.cpp
+++ b/content/html/content/src/TextTrackManager.cpp
@@ -56,17 +56,17 @@ TextTrackManager::TextTracks() const
 }
 
 already_AddRefed<TextTrack>
 TextTrackManager::AddTextTrack(TextTrackKind aKind, const nsAString& aLabel,
                                const nsAString& aLanguage)
 {
   nsRefPtr<TextTrack> ttrack =
     mTextTracks->AddTextTrack(mMediaElement, aKind, aLabel, aLanguage);
-  ttrack->SetReadyState(HTMLTrackElement::LOADED);
+  ttrack->SetReadyState(HTMLTrackElement::READY_STATE_LOADED);
   AddCues(ttrack);
   return ttrack.forget();
 }
 
 void
 TextTrackManager::AddTextTrack(TextTrack* aTextTrack)
 {
   mTextTracks->AddTextTrack(aTextTrack);
@@ -150,16 +150,16 @@ TextTrackManager::AddCue(TextTrackCue& a
 void
 TextTrackManager::PopulatePendingList()
 {
   uint32_t len = mTextTracks->Length();
   bool dummy;
   for (uint32_t index = 0; index < len; ++index) {
     TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy);
     if (ttrack && ttrack->Mode() != TextTrackMode::Disabled &&
-        ttrack->ReadyState() == HTMLTrackElement::LOADING) {
+        ttrack->ReadyState() == HTMLTrackElement::READY_STATE_LOADING) {
       mPendingTextTracks->AddTextTrack(ttrack);
     }
   }
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/SharedThreadPool.cpp
@@ -0,0 +1,227 @@
+/* -*- 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 "SharedThreadPool.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPtr.h"
+#include "nsDataHashtable.h"
+#include "VideoUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Preferences.h"
+
+#ifdef XP_WIN
+// Required to init MSCOM by MSCOMInitThreadPoolListener.
+#include <Objbase.h>
+#endif
+
+namespace mozilla {
+
+// Created and destroyed on the main thread.
+static StaticAutoPtr<ReentrantMonitor> sMonitor;
+
+// Hashtable, maps thread pool name to SharedThreadPool instance.
+// Modified only on the main thread.
+static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools;
+
+static already_AddRefed<nsIThreadPool>
+CreateThreadPool(const nsCString& aName);
+
+static void
+DestroySharedThreadPoolHashTable();
+
+void
+SharedThreadPool::EnsureInitialized()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (sMonitor || sPools) {
+    // Already initalized.
+    return;
+  }
+  sMonitor = new ReentrantMonitor("SharedThreadPool");
+  sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>();
+}
+
+class ShutdownPoolsEvent : public nsRunnable {
+public:
+  NS_IMETHODIMP Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    DestroySharedThreadPoolHashTable();
+    return NS_OK;
+  }
+};
+
+static void
+DestroySharedThreadPoolHashTable()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sMonitor && sPools);
+  if (!sPools->Count()) {
+    // No more SharedThreadPool singletons. Delete the hash table.
+    // Note we don't need to lock sMonitor, since we only modify the
+    // hash table on the main thread, and if the hash table is empty
+    // there are no external references into its contents.
+    sPools = nullptr;
+    sMonitor = nullptr;
+  }
+}
+
+TemporaryRef<SharedThreadPool>
+SharedThreadPool::Get(const nsCString& aName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EnsureInitialized();
+  MOZ_ASSERT(sMonitor);
+  ReentrantMonitorAutoEnter mon(*sMonitor);
+  SharedThreadPool* pool = nullptr;
+  if (!sPools->Get(aName, &pool)) {
+    nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
+    NS_ENSURE_TRUE(threadPool, nullptr);
+    pool = new SharedThreadPool(aName, threadPool);
+    sPools->Put(aName, pool);
+  }
+  MOZ_ASSERT(pool);
+  RefPtr<SharedThreadPool> instance(pool);
+  return instance.forget();
+}
+
+NS_IMETHODIMP_(nsrefcnt) SharedThreadPool::AddRef(void)
+{
+  MOZ_ASSERT(sMonitor);
+  ReentrantMonitorAutoEnter mon(*sMonitor);
+  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+  nsrefcnt count = ++mRefCnt;
+  NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
+  return count;
+}
+
+NS_IMETHODIMP_(nsrefcnt) SharedThreadPool::Release(void)
+{
+  MOZ_ASSERT(sMonitor);
+  bool dispatchShutdownEvent;
+  {
+    ReentrantMonitorAutoEnter mon(*sMonitor);
+    nsrefcnt count = --mRefCnt;
+    NS_LOG_RELEASE(this, count, "SharedThreadPool");
+    if (count) {
+      return count;
+    }
+
+    // Zero refcount. Must shutdown and then delete the thread pool.
+
+    // First, dispatch an event to the main thread to call Shutdown() on
+    // the nsIThreadPool. The Runnable here  will add a refcount to the pool,
+    // and when the Runnable releases the nsIThreadPool it will be deleted.
+    RefPtr<nsIRunnable> r = NS_NewRunnableMethod(mPool, &nsIThreadPool::Shutdown);
+    NS_DispatchToMainThread(r);
+
+    // Remove SharedThreadPool from table of pools.
+    sPools->Remove(mName);
+    MOZ_ASSERT(!sPools->Get(mName));
+
+    // Stabilize refcount, so that if something in the dtor QIs,
+    // it won't explode.
+    mRefCnt = 1;
+
+    delete this;
+
+    dispatchShutdownEvent = sPools->Count() == 0;
+  }
+  if (dispatchShutdownEvent) {
+    // No more SharedThreadPools alive. Destroy the hash table.
+    // Ensure that we only run on the main thread.
+    // Do this in an event so that if something holds the monitor we won't
+    // be deleting the monitor while it's held.
+    NS_DispatchToMainThread(new ShutdownPoolsEvent(), NS_DISPATCH_NORMAL);
+  }
+  return 0;
+}
+
+NS_IMPL_QUERY_INTERFACE1(SharedThreadPool, nsIThreadPool)
+
+SharedThreadPool::SharedThreadPool(const nsCString& aName,
+                                   nsIThreadPool* aPool)
+  : mName(aName)
+  , mPool(aPool)
+  , mRefCnt(0)
+{
+  mEventTarget = do_QueryInterface(aPool);
+}
+
+SharedThreadPool::~SharedThreadPool()
+{
+}
+
+#ifdef XP_WIN
+
+// Thread pool listener which ensures that MSCOM is initialized and
+// deinitialized on the thread pool thread. We may call into WMF or
+// DirectShow on this thread, so we need MSCOM working.
+class MSCOMInitThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener {
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSITHREADPOOLLISTENER
+};
+
+NS_IMPL_ISUPPORTS1(MSCOMInitThreadPoolListener, nsIThreadPoolListener)
+
+NS_IMETHODIMP
+MSCOMInitThreadPoolListener::OnThreadCreated()
+{
+  HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
+  if (FAILED(hr)) {
+    NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread.");
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MSCOMInitThreadPoolListener::OnThreadShuttingDown()
+{
+  CoUninitialize();
+  return NS_OK;
+}
+
+#endif // XP_WIN
+
+static already_AddRefed<nsIThreadPool>
+CreateThreadPool(const nsCString& aName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv;
+  nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  rv = pool->SetName(aName);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  // We limit the number of threads that we use for media. Note that the
+  // default thread limit is the same as the idle limit so that we're not
+  // constantly creating and destroying threads (see Bug 881954). When the
+  // thread pool threads shutdown they dispatch an event to the main thread
+  // to call nsIThread::Shutdown(), and if we're very busy that can take a
+  // while to run, and we end up with dozens of extra threads. Note that
+  // threads that are idle for 60 seconds are shutdown naturally.
+  rv = pool->SetThreadLimit(
+    Preferences::GetUint("media.thread-pool.thread-limit", 4));
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  rv = pool->SetIdleThreadLimit(
+    Preferences::GetUint("media.thread-pool.idle-thread-limit", 4));
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+#ifdef XP_WIN
+  // Ensure MSCOM is initialized on the thread pools threads.
+  nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
+  rv = pool->SetListener(listener);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+#endif
+
+  return pool.forget();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/SharedThreadPool.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#ifndef SharedThreadPool_h_
+#define SharedThreadPool_h_
+
+#include <queue>
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nsIThreadPool.h"
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+
+// Wrapper that makes an nsIThreadPool a singleton, and provides a
+// consistent threadsafe interface to get instances. Callers simply get a
+// SharedThreadPool by the name of its nsIThreadPool. All get requests of
+// the same name get the same SharedThreadPool. Users must store a reference
+// to the pool, and when the last reference to a SharedThreadPool is dropped
+// the pool is shutdown and deleted. Users aren't required to manually
+// shutdown the pool, and can release references on any thread. On Windows
+// all threads in the pool have MSCOM initialized with COINIT_MULTITHREADED.
+class SharedThreadPool : public nsIThreadPool {
+public:
+
+  // Gets (possibly creating) the shared thread pool singleton instance with
+  // thread pool named aName.
+  // *Must* be called on the main thread.
+  static TemporaryRef<SharedThreadPool> Get(const nsCString& aName);
+
+  // We implement custom threadsafe AddRef/Release pair, that destroys the
+  // the shared pool singleton when the refcount drops to 0. The addref/release
+  // are implemented using locking, so it's not recommended that you use them
+  // in a tight loop.
+  NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
+  NS_IMETHOD_(nsrefcnt) AddRef(void);
+  NS_IMETHOD_(nsrefcnt) Release(void);
+
+  // Forward behaviour to wrapped thread pool implementation.
+  NS_FORWARD_SAFE_NSITHREADPOOL(mPool);
+  NS_FORWARD_SAFE_NSIEVENTTARGET(mEventTarget);
+
+private:
+
+  // Creates necessary statics.
+  // Main thread only.
+  static void EnsureInitialized();
+
+  // Creates a singleton SharedThreadPool wrapper around aPool.
+  // aName is the name of the aPool, and is used to lookup the
+  // SharedThreadPool in the hash table of all created pools.
+  SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool);
+  virtual ~SharedThreadPool();
+
+  // Name of mPool.
+  const nsCString mName;
+
+  // Thread pool being wrapped.
+  nsCOMPtr<nsIThreadPool> mPool;
+
+  // Refcount. We implement custom ref counting so that the thread pool is
+  // shutdown in a threadsafe manner and singletonness is preserved.
+  nsrefcnt mRefCnt;
+
+  // mPool QI'd to nsIEventTarget. We cache this, so that we can use
+  // NS_FORWARD_SAFE_NSIEVENTTARGET above.
+  nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+} // namespace mozilla
+
+#endif // SharedThreadPool_h_
\ No newline at end of file
--- a/content/media/TextTrack.cpp
+++ b/content/media/TextTrack.cpp
@@ -64,17 +64,17 @@ TextTrack::SetDefaultSettings()
 {
   mKind = TextTrackKind::Subtitles;
   mMode = TextTrackMode::Hidden;
   mCueList = new TextTrackCueList(mParent);
   mActiveCueList = new TextTrackCueList(mParent);
   mRegionList = new TextTrackRegionList(mParent);
   mCuePos = 0;
   mDirty = false;
-  mReadyState = HTMLTrackElement::NONE;
+  mReadyState = HTMLTrackElement::READY_STATE_NONE;
 }
 
 JSObject*
 TextTrack::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return TextTrackBinding::Wrap(aCx, aScope, this);
 }
 
@@ -190,16 +190,16 @@ TextTrack::ReadyState() const
 {
   return mReadyState;
 }
 
 void
 TextTrack::SetReadyState(uint16_t aState)
 {
   mReadyState = aState;
-  if (mMediaElement && (mReadyState == HTMLTrackElement::LOADED ||
-      mReadyState == HTMLTrackElement::ERROR)) {
+  if (mMediaElement && (mReadyState == HTMLTrackElement::READY_STATE_LOADED ||
+      mReadyState == HTMLTrackElement::READY_STATE_ERROR)) {
     mMediaElement->RemoveTextTrack(this, true);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/media/WebVTTListener.cpp
+++ b/content/media/WebVTTListener.cpp
@@ -73,17 +73,17 @@ WebVTTListener::LoadResource()
 
   nsPIDOMWindow* window = mElement->OwnerDoc()->GetWindow();
   rv = mParserWrapper->LoadParser(window);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mParserWrapper->Watch(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mElement->mTrack->SetReadyState(HTMLTrackElement::LOADING);
+  mElement->mTrack->SetReadyState(HTMLTrackElement::READY_STATE_LOADING);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                        nsIChannel* aNewChannel,
                                        uint32_t aFlags,
                                        nsIAsyncVerifyRedirectCallback* cb)
@@ -101,19 +101,19 @@ WebVTTListener::OnStartRequest(nsIReques
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStopRequest(nsIRequest* aRequest,
                               nsISupports* aContext,
                               nsresult aStatus)
 {
-  if (mElement->ReadyState() != HTMLTrackElement::ERROR) {
+  if (mElement->ReadyState() != HTMLTrackElement::READY_STATE_ERROR) {
     TextTrack* track = mElement->Track();
-    track->SetReadyState(HTMLTrackElement::LOADED);
+    track->SetReadyState(HTMLTrackElement::READY_STATE_LOADED);
   }
   // Attempt to parse any final data the parser might still have.
   mParserWrapper->Flush();
   return NS_OK;
 }
 
 NS_METHOD
 WebVTTListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -80,16 +80,17 @@ EXPORTS += [
     'MediaMetadataManager.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MP3FrameParser.h',
     'RtspMediaResource.h',
     'SharedBuffer.h',
+    'SharedThreadPool.h',
     'StreamBuffer.h',
     'TimeVarying.h',
     'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoSegment.h',
     'VideoUtils.h',
     'VorbisUtils.h',
 ]
@@ -126,16 +127,17 @@ UNIFIED_SOURCES += [
     'MediaDecoderStateMachine.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
     'MP3FrameParser.cpp',
     'RtspMediaResource.cpp',
+    'SharedThreadPool.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
     'TextTrackRegionList.cpp',
     'VideoFrameContainer.cpp',
--- a/content/media/wmf/WMFByteStream.cpp
+++ b/content/media/wmf/WMFByteStream.cpp
@@ -14,97 +14,29 @@
 #include "WMFUtils.h"
 #include "MediaResource.h"
 #include "nsISeekableStream.h"
 #include "mozilla/RefPtr.h"
 #include "nsIThreadPool.h"
 #include "nsXPCOMCIDInternal.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/DebugOnly.h"
+#include "SharedThreadPool.h"
 #include <algorithm>
 #include <cassert>
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gWMFByteStreamLog = nullptr;
 #define WMF_BS_LOG(...) PR_LOG(gWMFByteStreamLog, PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define WMF_BS_LOG(...)
 #endif
 
-// Limit the number of threads that we use for IO.
-static const uint32_t NumWMFIoThreads = 4;
-
-// Thread pool listener which ensures that MSCOM is initialized and
-// deinitialized on the thread pool thread. We can call back into WMF
-// on this thread, so we need MSCOM working.
-class ThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener {
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSITHREADPOOLLISTENER
-};
-
-NS_IMPL_ISUPPORTS1(ThreadPoolListener, nsIThreadPoolListener)
-
-NS_IMETHODIMP
-ThreadPoolListener::OnThreadCreated()
-{
-  HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
-  if (FAILED(hr)) {
-    NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread.");
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ThreadPoolListener::OnThreadShuttingDown()
-{
-  CoUninitialize();
-  return NS_OK;
-}
-
-// Thread pool on which read requests are processed.
-// This is created and destroyed on the main thread only.
-static nsIThreadPool* sThreadPool = nullptr;
-
-// Counter of the number of WMFByteStreams that are instantiated and that need
-// the thread pool. This is read/write on the main thread only.
-static int32_t sThreadPoolRefCnt = 0;
-
-class ReleaseWMFByteStreamResourcesEvent MOZ_FINAL : public nsRunnable {
-public:
-  ReleaseWMFByteStreamResourcesEvent(already_AddRefed<MediaResource> aResource)
-    : mResource(aResource) {}
-  virtual ~ReleaseWMFByteStreamResourcesEvent() {}
-  NS_IMETHOD Run() {
-    NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
-    // Explicitly release the MediaResource reference. We *must* do this on
-    // the main thread, so we must explicitly release it here, we can't rely
-    // on the destructor to release it, since if this event runs before its
-    // dispatch call returns the destructor may run on the non-main thread.
-    mResource = nullptr;
-    NS_ASSERTION(sThreadPoolRefCnt > 0, "sThreadPoolRefCnt Should be non-negative");
-    sThreadPoolRefCnt--;
-    if (sThreadPoolRefCnt == 0) {
-      NS_ASSERTION(sThreadPool != nullptr, "Should have thread pool ref if sThreadPoolRefCnt==0.");
-      // Note: store ref to thread pool, then clear global ref, then
-      // Shutdown() using the stored ref. Events can run during the Shutdown()
-      // call, so if we release after calling Shutdown(), another event may
-      // have incremented the refcnt in the meantime, and have a dangling
-      // pointer to the now destroyed threadpool!
-      nsCOMPtr<nsIThreadPool> pool = sThreadPool;
-      NS_IF_RELEASE(sThreadPool);
-      pool->Shutdown();
-    }
-    return NS_OK;
-  }
-  nsRefPtr<MediaResource> mResource;
-};
-
 WMFByteStream::WMFByteStream(MediaResource* aResource,
                              WMFSourceReaderCallback* aSourceReaderCallback)
   : mSourceReaderCallback(aSourceReaderCallback),
     mResource(aResource),
     mReentrantMonitor("WMFByteStream.Data"),
     mOffset(0),
     mIsShutdown(false)
 {
@@ -118,63 +50,26 @@ WMFByteStream::WMFByteStream(MediaResour
 #endif
   WMF_BS_LOG("[%p] WMFByteStream CTOR", this);
   MOZ_COUNT_CTOR(WMFByteStream);
 }
 
 WMFByteStream::~WMFByteStream()
 {
   MOZ_COUNT_DTOR(WMFByteStream);
-  // The WMFByteStream can be deleted from a thread pool thread, so we
-  // dispatch an event to the main thread to deref the thread pool and
-  // deref the MediaResource.
-  nsCOMPtr<nsIRunnable> event =
-    new ReleaseWMFByteStreamResourcesEvent(mResource.forget());
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   WMF_BS_LOG("[%p] WMFByteStream DTOR", this);
 }
 
 nsresult
 WMFByteStream::Init()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
 
-  if (!sThreadPool) {
-    nsresult rv;
-    nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    sThreadPool = pool;
-    NS_ADDREF(sThreadPool);
-
-    rv = sThreadPool->SetName(NS_LITERAL_CSTRING("WMFByteStream Async Read Pool"));
-    NS_ENSURE_SUCCESS(rv, rv);
-    
-    // We limit the number of threads that we use for IO. Note that the thread
-    // limit is the same as the idle limit so that we're not constantly creating
-    // and destroying threads. When the thread pool threads shutdown they
-    // dispatch an event to the main thread to call nsIThread::Shutdown(),
-    // and if we're very busy that can take a while to run, and we end up with
-    // dozens of extra threads. Note that threads that are idle for 60 seconds
-    // are shutdown naturally.
-    rv = sThreadPool->SetThreadLimit(NumWMFIoThreads);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = sThreadPool->SetIdleThreadLimit(NumWMFIoThreads);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsIThreadPoolListener> listener = new ThreadPoolListener();
-    rv = sThreadPool->SetListener(listener);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  sThreadPoolRefCnt++;
-
-  // Store a ref to the thread pool, so that we keep the pool alive as long as
-  // we're alive.
-  mThreadPool = sThreadPool;
+  mThreadPool = SharedThreadPool::Get(NS_LITERAL_CSTRING("WMFByteStream IO"));
+  NS_ENSURE_TRUE(mThreadPool, NS_ERROR_FAILURE);
 
   NS_ConvertUTF8toUTF16 contentTypeUTF16(mResource->GetContentType());
   if (!contentTypeUTF16.IsEmpty()) {
     HRESULT hr = wmf::MFCreateAttributes(byRef(mAttributes), 1);
     NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
     hr = mAttributes->SetString(MF_BYTESTREAM_CONTENT_TYPE,
                                 contentTypeUTF16.get());
--- a/content/media/wmf/WMFByteStream.h
+++ b/content/media/wmf/WMFByteStream.h
@@ -10,23 +10,22 @@
 
 #include "nsISupportsImpl.h"
 #include "nsCOMPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Attributes.h"
 #include "nsAutoPtr.h"
 #include "mozilla/RefPtr.h"
 
-class nsIThreadPool;
-
 namespace mozilla {
 
 class MediaResource;
 class ReadRequest;
 class WMFSourceReaderCallback;
+class SharedThreadPool;
 
 // Wraps a MediaResource around an IMFByteStream interface, so that it can
 // be used by the IMFSourceReader. Each WMFByteStream creates a WMF Work Queue
 // on which blocking I/O is performed. The SourceReader requests reads
 // asynchronously using {Begin,End}Read(), and more rarely synchronously
 // using Read().
 //
 // Note: This implementation attempts to be bug-compatible with Windows Media
@@ -118,17 +117,17 @@ private:
   // call this function.
   nsresult Read(ReadRequest* aRequestState);
 
   // Returns true if the current position of the stream is at end of stream.
   bool IsEOS();
 
   // Reference to the thread pool in which we perform the reads asynchronously.
   // Note this is pool is shared amongst all active WMFByteStreams.
-  nsCOMPtr<nsIThreadPool> mThreadPool;
+  RefPtr<SharedThreadPool> mThreadPool;
 
   // Reference to the source reader's callback. We use this reference to
   // notify threads waiting on a ReadSample() callback to stop waiting
   // if the stream is closed, which happens when the media element is
   // shutdown.
   RefPtr<WMFSourceReaderCallback> mSourceReaderCallback;
 
   // Resource we're wrapping.
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -66,17 +66,17 @@ this.AppsUtils = {
   // Clones a app, without the manifest.
   cloneAppObject: function cloneAppObject(aApp) {
     return {
       name: aApp.name,
       csp: aApp.csp,
       installOrigin: aApp.installOrigin,
       origin: aApp.origin,
 #ifdef MOZ_ANDROID_SYNTHAPKS
-      packageName: aApp.packageName,
+      apkPackageName: aApp.apkPackageName,
 #endif
       receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
       installTime: aApp.installTime,
       manifestURL: aApp.manifestURL,
       appStatus: aApp.appStatus,
       removable: aApp.removable,
       id: aApp.id,
       localId: aApp.localId,
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -236,26 +236,38 @@ this.DOMApplicationRegistry = {
         // will be empty so we can't just apply a staged update.
         app.downloading = false;
         app.readyToApplyDownload = false;
       }
     });
   },
 
   // Notify we are starting with registering apps.
+  _registryStarted: Promise.defer(),
   notifyAppsRegistryStart: function notifyAppsRegistryStart() {
     Services.obs.notifyObservers(this, "webapps-registry-start", null);
+    this._registryStarted.resolve();
+  },
+
+  get registryStarted() {
+    return this._registryStarted.promise;
   },
 
   // Notify we are done with registering apps and save a copy of the registry.
+  _registryReady: Promise.defer(),
   notifyAppsRegistryReady: function notifyAppsRegistryReady() {
+    this._registryReady.resolve();
     Services.obs.notifyObservers(this, "webapps-registry-ready", null);
     this._saveApps();
   },
 
+  get registryReady() {
+    return this._registryReady.promise;
+  },
+
   // Ensure that the .to property in redirects is a relative URL.
   sanitizeRedirects: function sanitizeRedirects(aSource) {
     if (!aSource) {
       return null;
     }
 
     let res = [];
     for (let i = 0; i < aSource.length; i++) {
@@ -962,19 +974,27 @@ this.DOMApplicationRegistry = {
 
     try {
       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
       file.initWithPath(aPath);
       let channel = NetUtil.newChannel(file);
       channel.contentType = "application/json";
       NetUtil.asyncFetch(channel, function(aStream, aResult) {
         if (!Components.isSuccessCode(aResult)) {
+          deferred.resolve(null);
+
+          if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
+            // We expect this under certain circumstances, like for webapps.json
+            // on firstrun, so we return early without reporting an error.
+            return;
+          }
+
           Cu.reportError("DOMApplicationRegistry: Could not read from json file "
                          + aPath);
-          deferred.resolve(null);
+          return;
         }
 
         try {
           // Obtain a converter to read from a UTF-8 encoded input stream.
           let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                           .createInstance(Ci.nsIScriptableUnicodeConverter);
           converter.charset = "UTF-8";
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -208,16 +208,23 @@ parent:
 
     /**
      * Used to set the current text of the status tooltip.
      * Nowadays this is mainly used for link locations on hover.
      */
     SetStatus(uint32_t type, nsString status);
 
     /**
+     * Show/hide a tooltip when the mouse hovers over an element in the content
+     * document.
+     */
+    ShowTooltip(uint32_t x, uint32_t y, nsString tooltip);
+    HideTooltip();
+
+    /**
      * Initiates an asynchronous request for permission for the
      * provided principal.
      *
      * @param aType
      *   The type of permission to request.
      * @param aAccess
      *   Access type. "read" for example.
      * @param aPrincipal
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -730,16 +730,17 @@ NS_INTERFACE_MAP_BEGIN(TabChild)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
   NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
   NS_INTERFACE_MAP_ENTRY(nsITabChild)
   NS_INTERFACE_MAP_ENTRY(nsIDialogCreator)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsSupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY(nsITooltipListener)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(TabChild)
 NS_IMPL_RELEASE(TabChild)
 
 NS_IMETHODIMP
 TabChild::SetStatus(uint32_t aStatusType, const char16_t* aStatus)
 {
@@ -2507,16 +2508,30 @@ TabChild::GetFrom(nsIPresShell* aPresShe
   nsIDocument* doc = aPresShell->GetDocument();
   if (!doc) {
       return nullptr;
   }
   nsCOMPtr<nsIDocShell> docShell(doc->GetDocShell());
   return GetFrom(docShell);
 }
 
+NS_IMETHODIMP
+TabChild::OnShowTooltip(int32_t aXCoords, int32_t aYCoords, const char16_t *aTipText)
+{
+    nsString str(aTipText);
+    SendShowTooltip(aXCoords, aYCoords, str);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::OnHideTooltip()
+{
+    SendHideTooltip();
+    return NS_OK;
+}
 
 TabChildGlobal::TabChildGlobal(TabChild* aTabChild)
 : mTabChild(aTabChild)
 {
 }
 
 void
 TabChildGlobal::Init()
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -24,16 +24,17 @@
 #include "nsFrameMessageManager.h"
 #include "nsIWebProgressListener.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDialogCreator.h"
 #include "nsIPresShell.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsWeakReference.h"
 #include "nsITabChild.h"
+#include "nsITooltipListener.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/CompositorTypes.h"
 
 class nsICachedFileDescriptorListener;
 class nsIDOMWindowUtils;
 
@@ -152,17 +153,18 @@ class TabChild : public PBrowserChild,
                  public nsIWindowProvider,
                  public nsIDOMEventListener,
                  public nsIWebProgressListener,
                  public nsSupportsWeakReference,
                  public nsIDialogCreator,
                  public nsITabChild,
                  public nsIObserver,
                  public ipc::MessageManagerCallback,
-                 public TabContext
+                 public TabContext,
+                 public nsITooltipListener
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
     typedef mozilla::layout::RenderFrameChild RenderFrameChild;
     typedef mozilla::layout::ScrollingBehavior ScrollingBehavior;
 
 public:
     /** 
      * This is expected to be called off the critical path to content
@@ -186,16 +188,17 @@ public:
     NS_DECL_NSIWEBBROWSERCHROMEFOCUS
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSIWINDOWPROVIDER
     NS_DECL_NSIDOMEVENTLISTENER
     NS_DECL_NSIWEBPROGRESSLISTENER
     NS_DECL_NSIDIALOGCREATOR
     NS_DECL_NSITABCHILD
     NS_DECL_NSIOBSERVER
+    NS_DECL_NSITOOLTIPLISTENER
 
     /**
      * MessageManagerCallback methods that we override.
      */
     virtual bool DoSendBlockingMessage(JSContext* aCx,
                                        const nsAString& aMessage,
                                        const mozilla::dom::StructuredCloneData& aData,
                                        JS::Handle<JSObject *> aCpows,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -928,46 +928,85 @@ bool
 TabParent::RecvSetBackgroundColor(const nscolor& aColor)
 {
   if (RenderFrameParent* frame = GetRenderFrame()) {
     frame->SetBackgroundColor(aColor);
   }
   return true;
 }
 
+nsIXULBrowserWindow*
+TabParent::GetXULBrowserWindow()
+{
+  nsCOMPtr<nsIContent> frame = do_QueryInterface(mFrameElement);
+  if (!frame) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell = frame->OwnerDoc()->GetDocShell();
+  if (!docShell) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+  docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+  if (!treeOwner) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIXULWindow> window = do_GetInterface(treeOwner);
+  if (!window) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+  window->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+  return xulBrowserWindow;
+}
+
 bool
 TabParent::RecvSetStatus(const uint32_t& aType, const nsString& aStatus)
 {
-  nsCOMPtr<nsIContent> frame = do_QueryInterface(mFrameElement);
-  if (frame) {
-    nsCOMPtr<nsIDocShell> docShell = frame->OwnerDoc()->GetDocShell();
-    if (!docShell)
-      return true;
-    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
-    docShell->GetTreeOwner(getter_AddRefs(treeOwner));
-    if (!treeOwner)
-      return true;
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow();
+  if (!xulBrowserWindow) {
+    return true;
+  }
+
+  switch (aType) {
+   case nsIWebBrowserChrome::STATUS_SCRIPT:
+    xulBrowserWindow->SetJSStatus(aStatus);
+    break;
+   case nsIWebBrowserChrome::STATUS_LINK:
+    xulBrowserWindow->SetOverLink(aStatus, nullptr);
+    break;
+  }
+  return true;
+}
 
-    nsCOMPtr<nsIXULWindow> window = do_GetInterface(treeOwner);
-    if (window) {
-      nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
-      window->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
-      if (xulBrowserWindow) {
-        switch (aType)
-        {
-        case nsIWebBrowserChrome::STATUS_SCRIPT:
-          xulBrowserWindow->SetJSStatus(aStatus);
-          break;
-        case nsIWebBrowserChrome::STATUS_LINK:
-          xulBrowserWindow->SetOverLink(aStatus, nullptr);
-          break;
-        }
-      }
-    }
+bool
+TabParent::RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip)
+{
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow();
+  if (!xulBrowserWindow) {
+    return true;
   }
+
+  xulBrowserWindow->ShowTooltip(aX, aY, aTooltip);
+  return true;
+}
+
+bool
+TabParent::RecvHideTooltip()
+{
+  nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow();
+  if (!xulBrowserWindow) {
+    return true;
+  }
+
+  xulBrowserWindow->HideTooltip();
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMEFocus(const bool& aFocus,
                               nsIMEUpdatePreference* aPreference,
                               uint32_t* aSeqno)
 {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -12,16 +12,17 @@
 #include "mozilla/dom/PContentDialogParent.h"
 #include "mozilla/dom/TabContext.h"
 #include "nsCOMPtr.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsIBrowserDOMWindow.h"
 #include "nsIDialogParamBlock.h"
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
+#include "nsIXULBrowserWindow.h"
 #include "Units.h"
 #include "js/TypeDecls.h"
 
 class nsFrameLoader;
 class nsIContent;
 class nsIPrincipal;
 class nsIURI;
 class nsIWidget;
@@ -78,16 +79,18 @@ public:
 
     nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
 
     already_AddRefed<nsILoadContext> GetLoadContext();
 
+    nsIXULBrowserWindow* GetXULBrowserWindow();
+
     /**
      * Return the TabParent that has decided it wants to capture an
      * event series for fast-path dispatch to its subprocess, if one
      * has.
      *
      * DOM event dispatch and widget are free to ignore capture
      * requests from TabParents; the end result wrt remote content is
      * (must be) always the same, albeit usually slower without
@@ -161,16 +164,18 @@ public:
                                      const nsString& aInputmode,
                                      const nsString& aActionHint,
                                      const int32_t& aCause,
                                      const int32_t& aFocusChange) MOZ_OVERRIDE;
     virtual bool RecvRequestFocus(const bool& aCanRaise) MOZ_OVERRIDE;
     virtual bool RecvSetCursor(const uint32_t& aValue) MOZ_OVERRIDE;
     virtual bool RecvSetBackgroundColor(const nscolor& aValue) MOZ_OVERRIDE;
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus) MOZ_OVERRIDE;
+    virtual bool RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip);
+    virtual bool RecvHideTooltip();
     virtual bool RecvGetDPI(float* aValue) MOZ_OVERRIDE;
     virtual bool RecvGetDefaultScale(double* aValue) MOZ_OVERRIDE;
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue) MOZ_OVERRIDE;
     virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
                                 const ViewID& aViewId,
                                 const CSSRect& aRect) MOZ_OVERRIDE;
     virtual bool RecvUpdateZoomConstraints(const uint32_t& aPresShellId,
                                            const ViewID& aViewId,
--- a/dom/xbl/nsXBLWindowKeyHandler.cpp
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -9,38 +9,35 @@
 #include "nsIContent.h"
 #include "nsIAtom.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsXBLService.h"
 #include "nsIServiceManager.h"
 #include "nsGkAtoms.h"
 #include "nsXBLDocumentInfo.h"
 #include "nsIDOMElement.h"
-#include "nsINativeKeyBindings.h"
-#include "nsIController.h"
 #include "nsFocusManager.h"
-#include "nsPIWindowRoot.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsContentUtils.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Element.h"
 #include "nsEventStateManager.h"
+#include "nsIEditor.h"
+#include "nsIHTMLEditor.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-static nsINativeKeyBindings *sNativeEditorBindings = nullptr;
-
 class nsXBLSpecialDocInfo : public nsIObserver
 {
 public:
   nsRefPtr<nsXBLDocumentInfo> mHTMLBindings;
   nsRefPtr<nsXBLDocumentInfo> mUserHTMLBindings;
 
   static const char sHTMLBindingStr[];
   static const char sUserHTMLBindingStr[];
@@ -225,108 +222,62 @@ BuildHandlerChain(nsIContent* aContent, 
 
 //
 // EnsureHandlers
 //    
 // Lazily load the XBL handlers. Overridden to handle being attached
 // to a particular element rather than the document
 //
 nsresult
-nsXBLWindowKeyHandler::EnsureHandlers(bool *aIsEditor)
+nsXBLWindowKeyHandler::EnsureHandlers()
 {
   nsCOMPtr<Element> el = GetElement();
   NS_ENSURE_STATE(!mWeakPtrForElement || el);
   if (el) {
     // We are actually a XUL <keyset>.
-    if (aIsEditor)
-      *aIsEditor = false;
-
     if (mHandler)
       return NS_OK;
 
     nsCOMPtr<nsIContent> content(do_QueryInterface(el));
     BuildHandlerChain(content, &mHandler);
   } else { // We are an XBL file of handlers.
     if (!sXBLSpecialDocInfo) {
       sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
       NS_ADDREF(sXBLSpecialDocInfo);
     }
     sXBLSpecialDocInfo->LoadDocInfo();
 
     // Now determine which handlers we should be using.
-    bool isEditor = IsEditor();
-    if (isEditor) {
+    if (IsHTMLEditableFieldFocused()) {
       sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler);
     }
     else {
       sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler);
     }
-
-    if (aIsEditor)
-      *aIsEditor = isEditor;
   }
 
   return NS_OK;
 }
 
-static nsINativeKeyBindings*
-GetEditorKeyBindings()
-{
-  static bool noBindings = false;
-  if (!sNativeEditorBindings && !noBindings) {
-    CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "editor",
-                   &sNativeEditorBindings);
-
-    if (!sNativeEditorBindings) {
-      noBindings = true;
-    }
-  }
-
-  return sNativeEditorBindings;
-}
-
-static void
-DoCommandCallback(const char *aCommand, void *aData)
-{
-  nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(static_cast<EventTarget*>(aData));
-  if (!root) {
-    return;
-  }
-
-  nsCOMPtr<nsIController> controller;
-  root->GetControllerForCommand(aCommand, getter_AddRefs(controller));
-  if (!controller) {
-    return;
-  }
-
-  bool commandEnabled;
-  nsresult rv = controller->IsCommandEnabled(aCommand, &commandEnabled);
-  NS_ENSURE_SUCCESS_VOID(rv);
-  if (commandEnabled) {
-    controller->DoCommand(aCommand);
-  }
-}
-
 nsresult
 nsXBLWindowKeyHandler::WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType)
 {
   bool prevent;
   aKeyEvent->GetDefaultPrevented(&prevent);
   if (prevent)
     return NS_OK;
 
   bool trustedEvent = false;
   // Don't process the event if it was not dispatched from a trusted source
   aKeyEvent->GetIsTrusted(&trustedEvent);
 
   if (!trustedEvent)
     return NS_OK;
 
-  bool isEditor;
-  nsresult rv = EnsureHandlers(&isEditor);
+  nsresult rv = EnsureHandlers();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<Element> el = GetElement();
   if (!el) {
     if (mUserHandler) {
       WalkHandlersInternal(aKeyEvent, aEventType, mUserHandler);
       aKeyEvent->GetDefaultPrevented(&prevent);
       if (prevent)
@@ -338,55 +289,16 @@ nsXBLWindowKeyHandler::WalkHandlers(nsID
   // skip keysets that are disabled
   if (content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
                                       nsGkAtoms::_true, eCaseMatters)) {
     return NS_OK;
   }
 
   WalkHandlersInternal(aKeyEvent, aEventType, mHandler);
 
-  aKeyEvent->GetDefaultPrevented(&prevent);
-  if (prevent) {
-    return NS_OK;
-  }
-
-  // XXX Shouldn't we prefer the native key binding rather than our key
-  //     bindings?  I.e., should we call WalkHandlersInternal() after this
-  //     block?
-  if (isEditor && GetEditorKeyBindings()) {
-    WidgetKeyboardEvent* keyEvent =
-      aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
-    MOZ_ASSERT(keyEvent,
-               "DOM key event's internal event must be WidgetKeyboardEvent");
-    bool handled = false;
-    switch (keyEvent->message) {
-      case NS_KEY_PRESS:
-        handled = sNativeEditorBindings->KeyPress(*keyEvent,
-                                                  DoCommandCallback,
-                                                  mTarget);
-        break;
-      case NS_KEY_UP:
-        handled = sNativeEditorBindings->KeyUp(*keyEvent,
-                                               DoCommandCallback,
-                                               mTarget);
-        break;
-      case NS_KEY_DOWN:
-        handled = sNativeEditorBindings->KeyDown(*keyEvent,
-                                                 DoCommandCallback,
-                                                 mTarget);
-        break;
-      default:
-        MOZ_CRASH("Unknown key message");
-    }
-
-    if (handled)
-      aKeyEvent->PreventDefault();
-
-  }
-  
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aEvent));
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
@@ -417,50 +329,55 @@ nsXBLWindowKeyHandler::EventMatched(nsXB
                                     nsIAtom* inEventType,
                                     nsIDOMKeyEvent* inEvent,
                                     uint32_t aCharCode, bool aIgnoreShiftKey)
 {
   return inHandler->KeyEventMatched(inEventType, inEvent, aCharCode,
                                     aIgnoreShiftKey);
 }
 
-/* static */ void
-nsXBLWindowKeyHandler::ShutDown()
+bool
+nsXBLWindowKeyHandler::IsHTMLEditableFieldFocused()
 {
-  NS_IF_RELEASE(sNativeEditorBindings);
-}
-
-//
-// IsEditor
-//
-// Determine if the document we're working with is Editor or Browser
-//
-bool
-nsXBLWindowKeyHandler::IsEditor()
-{
-  // XXXndeakin even though this is only used for key events which should be
-  // going to the focused frame anyway, this doesn't seem like the right way
-  // to determine if something is an editor.
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm)
     return false;
 
   nsCOMPtr<nsIDOMWindow> focusedWindow;
   fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
   if (!focusedWindow)
     return false;
 
   nsCOMPtr<nsPIDOMWindow> piwin(do_QueryInterface(focusedWindow));
   nsIDocShell *docShell = piwin->GetDocShell();
-  nsCOMPtr<nsIPresShell> presShell;
-  if (docShell)
-    presShell = docShell->GetPresShell();
+  if (!docShell) {
+    return false;
+  }
+
+  nsCOMPtr<nsIEditor> editor;
+  docShell->GetEditor(getter_AddRefs(editor));
+  nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(editor);
+  if (!htmlEditor) {
+    return false;
+  }
 
-  if (presShell) {
-    return presShell->GetSelectionFlags() == nsISelectionDisplay::DISPLAY_ALL;
+  nsCOMPtr<nsIDOMElement> focusedElement;
+  fm->GetFocusedElement(getter_AddRefs(focusedElement));
+  nsCOMPtr<nsINode> focusedNode = do_QueryInterface(focusedElement);
+  if (focusedNode) {
+    // If there is a focused element, make sure it's in the active editing host.
+    // Note that GetActiveEditingHost finds the current editing host based on
+    // the document's selection.  Even though the document selection is usually
+    // collapsed to where the focus is, but the page may modify the selection
+    // without our knowledge, in which case this check will do something useful.
+    nsCOMPtr<Element> activeEditingHost = htmlEditor->GetActiveEditingHost();
+    if (!activeEditingHost) {
+      return false;
+    }
+    return nsContentUtils::ContentIsDescendantOf(focusedNode, activeEditingHost);
   }
 
   return false;
 }
 
 //
 // WalkHandlersInternal and WalkHandlersAndExecute
 //
--- a/dom/xbl/nsXBLWindowKeyHandler.h
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -26,43 +26,40 @@ class nsXBLWindowKeyHandler : public nsI
 {
 public:
   nsXBLWindowKeyHandler(nsIDOMElement* aElement, mozilla::dom::EventTarget* aTarget);
   virtual ~nsXBLWindowKeyHandler();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMEVENTLISTENER
 
-  // release globals
-  static NS_HIDDEN_(void) ShutDown();
-
 protected:
   nsresult WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType);
 
   // walk the handlers, looking for one to handle the event
   nsresult WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent,
                                 nsIAtom* aEventType, 
                                 nsXBLPrototypeHandler* aHandler);
 
   // walk the handlers for aEvent, aCharCode and aIgnoreShiftKey
   bool WalkHandlersAndExecute(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType,
                                 nsXBLPrototypeHandler* aHandler,
                                 uint32_t aCharCode, bool aIgnoreShiftKey);
 
   // lazily load the handlers. Overridden to handle being attached
   // to a particular element rather than the document
-  nsresult EnsureHandlers(bool *aIsEditor);
+  nsresult EnsureHandlers();
 
   // check if the given handler cares about the given key event
   bool EventMatched(nsXBLPrototypeHandler* inHandler, nsIAtom* inEventType,
                       nsIDOMKeyEvent* inEvent, uint32_t aCharCode,
                       bool aIgnoreShiftKey);
 
-  // are we working with editor or browser?
-  bool IsEditor() ;
+  // Is an HTML editable element focused
+  bool IsHTMLEditableFieldFocused();
 
   // Returns the element which was passed as a parameter to the constructor,
   // unless the element has been removed from the document.
   already_AddRefed<mozilla::dom::Element> GetElement();
   // Using weak pointer to the DOM Element.
   nsWeakPtr              mWeakPtrForElement;
   mozilla::dom::EventTarget* mTarget; // weak ref
 
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -12,16 +12,17 @@
 #include "nsDebug.h"                    // for NS_ENSURE_TRUE, etc
 #include "nsEditor.h"                   // for nsEditor, etc
 #include "nsEditorEventListener.h"
 #include "nsEventListenerManager.h"     // for nsEventListenerManager
 #include "nsFocusManager.h"             // for nsFocusManager
 #include "nsGkAtoms.h"                  // for nsGkAtoms, nsGkAtoms::input
 #include "nsIClipboard.h"               // for nsIClipboard, etc
 #include "nsIContent.h"                 // for nsIContent
+#include "nsIController.h"              // for nsIController
 #include "nsID.h"
 #include "nsIDOMDOMStringList.h"        // for nsIDOMDOMStringList
 #include "nsIDOMDataTransfer.h"         // for nsIDOMDataTransfer
 #include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMDragEvent.h"            // for nsIDOMDragEvent
 #include "nsIDOMElement.h"              // for nsIDOMElement
 #include "nsIDOMEvent.h"                // for nsIDOMEvent
 #include "nsIDOMEventTarget.h"          // for nsIDOMEventTarget
@@ -30,39 +31,87 @@
 #include "nsIDOMNode.h"                 // for nsIDOMNode
 #include "nsIDOMRange.h"                // for nsIDOMRange
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditor.h"                  // for nsEditor::GetSelection, etc
 #include "nsIEditorIMESupport.h"
 #include "nsIEditorMailSupport.h"       // for nsIEditorMailSupport
 #include "nsIFocusManager.h"            // for nsIFocusManager
 #include "nsIFormControl.h"             // for nsIFormControl, etc
+#include "nsIHTMLEditor.h"              // for nsIHTMLEditor
 #include "nsIMEStateManager.h"          // for nsIMEStateManager
+#include "nsINativeKeyBindings.h"       // for nsINativeKeyBindings
 #include "nsINode.h"                    // for nsINode, ::NODE_IS_EDITABLE, etc
 #include "nsIPlaintextEditor.h"         // for nsIPlaintextEditor, etc
 #include "nsIPresShell.h"               // for nsIPresShell
 #include "nsIPrivateTextEvent.h"        // for nsIPrivateTextEvent
 #include "nsIPrivateTextRange.h"        // for nsIPrivateTextRangeList
 #include "nsISelection.h"               // for nsISelection
 #include "nsISelectionController.h"     // for nsISelectionController, etc
 #include "nsISelectionPrivate.h"        // for nsISelectionPrivate
 #include "nsITransferable.h"            // for kFileMime, kHTMLMime, etc
 #include "nsLiteralString.h"            // for NS_LITERAL_STRING
+#include "nsPIWindowRoot.h"             // for nsPIWindowRoot
 #include "nsServiceManagerUtils.h"      // for do_GetService
 #include "nsString.h"                   // for nsAutoString
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
 #include "nsContentUtils.h"             // for nsContentUtils, etc
 #include "nsIBidiKeyboard.h"            // for nsIBidiKeyboard
 #endif
 
 class nsPresContext;
 
 using namespace mozilla;
 using mozilla::dom::EventTarget;
 
+static nsINativeKeyBindings *sNativeEditorBindings = nullptr;
+
+static nsINativeKeyBindings*
+GetEditorKeyBindings()
+{
+  static bool noBindings = false;
+  if (!sNativeEditorBindings && !noBindings) {
+    CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "editor",
+                   &sNativeEditorBindings);
+
+    if (!sNativeEditorBindings) {
+      noBindings = true;
+    }
+  }
+
+  return sNativeEditorBindings;
+}
+
+static void
+DoCommandCallback(const char *aCommand, void *aData)
+{
+  nsIDocument* doc = static_cast<nsIDocument*>(aData);
+  nsPIDOMWindow* win = doc->GetWindow();
+  if (!win) {
+    return;
+  }
+  nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
+  if (!root) {
+    return;
+  }
+
+  nsCOMPtr<nsIController> controller;
+  root->GetControllerForCommand(aCommand, getter_AddRefs(controller));
+  if (!controller) {
+    return;
+  }
+
+  bool commandEnabled;
+  nsresult rv = controller->IsCommandEnabled(aCommand, &commandEnabled);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (commandEnabled) {
+    controller->DoCommand(aCommand);
+  }
+}
+
 nsEditorEventListener::nsEditorEventListener() :
   mEditor(nullptr), mCommitText(false),
   mInTransaction(false)
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
   , mHaveBidiKeyboards(false)
   , mShouldSwitchTextDirection(false)
   , mSwitchToRTL(false)
 #endif
@@ -72,16 +121,22 @@ nsEditorEventListener::nsEditorEventList
 nsEditorEventListener::~nsEditorEventListener() 
 {
   if (mEditor) {
     NS_WARNING("We're not uninstalled");
     Disconnect();
   }
 }
 
+/* static */ void
+nsEditorEventListener::ShutDown()
+{
+  NS_IF_RELEASE(sNativeEditorBindings);
+}
+
 nsresult
 nsEditorEventListener::Connect(nsEditor* aEditor)
 {
   NS_ENSURE_ARG(aEditor);
 
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
   nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
   if (bidiKeyboard) {
@@ -444,17 +499,43 @@ nsEditorEventListener::KeyPress(nsIDOMEv
   }
 
   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
   if (!keyEvent) {
     //non-key event passed to keypress.  bad things.
     return NS_OK;
   }
 
-  return mEditor->HandleKeyPressEvent(keyEvent);
+  nsresult rv = mEditor->HandleKeyPressEvent(keyEvent);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aKeyEvent->GetDefaultPrevented(&defaultPrevented);
+  if (defaultPrevented) {
+    return NS_OK;
+  }
+
+  if (GetEditorKeyBindings() && ShouldHandleNativeKeyBindings(aKeyEvent)) {
+    // Now, ask the native key bindings to handle the event.
+    // XXX Note that we're not passing the keydown/keyup events to the native
+    // key bindings, which should be OK since those events are only handled on
+    // Windows for now, where we don't have native key bindings.
+    WidgetKeyboardEvent* keyEvent =
+      aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
+    MOZ_ASSERT(keyEvent,
+               "DOM key event's internal event must be WidgetKeyboardEvent");
+    nsCOMPtr<nsIDocument> doc = mEditor->GetDocument();
+    bool handled = sNativeEditorBindings->KeyPress(*keyEvent,
+                                                   DoCommandCallback,
+                                                   doc);
+    if (handled) {
+      aKeyEvent->PreventDefault();
+    }
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEditorEventListener::MouseClick(nsIDOMEvent* aMouseEvent)
 {
   NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
 
   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
@@ -924,8 +1005,40 @@ nsEditorEventListener::IsFileControlText
       nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(parent);
       MOZ_ASSERT(formControl);
       return formControl->GetType() == NS_FORM_INPUT_FILE;
     }
   }
   return false;
 }
 
+bool
+nsEditorEventListener::ShouldHandleNativeKeyBindings(nsIDOMEvent* aKeyEvent)
+{
+  // Only return true if the target of the event is a desendant of the active
+  // editing host in order to match the similar decision made in
+  // nsXBLWindowKeyHandler.
+  // Note that IsAcceptableInputEvent doesn't check for the active editing
+  // host for keyboard events, otherwise this check would have been
+  // unnecessary.  IsAcceptableInputEvent currently makes a similar check for
+  // mouse events.
+
+  nsCOMPtr<nsIDOMEventTarget> target;
+  aKeyEvent->GetTarget(getter_AddRefs(target));
+  nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
+  if (!targetContent) {
+    return false;
+  }
+
+  nsCOMPtr<nsIHTMLEditor> htmlEditor =
+    do_QueryInterface(static_cast<nsIEditor*>(mEditor));
+  if (!htmlEditor) {
+    return false;
+  }
+
+  nsIContent* editingHost = htmlEditor->GetActiveEditingHost();
+  if (!editingHost) {
+    return false;
+  }
+
+  return nsContentUtils::ContentIsDescendantOf(targetContent, editingHost);
+}
+
--- a/editor/libeditor/base/nsEditorEventListener.h
+++ b/editor/libeditor/base/nsEditorEventListener.h
@@ -54,29 +54,32 @@ public:
   NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent);
   NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; }
   NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
   NS_IMETHOD Focus(nsIDOMEvent* aEvent);
   NS_IMETHOD Blur(nsIDOMEvent* aEvent);
 
   void SpellCheckIfNeeded();
 
+  static NS_HIDDEN_(void) ShutDown();
+
 protected:
   nsresult InstallToEditor();
   void UninstallFromEditor();
 
   bool CanDrop(nsIDOMDragEvent* aEvent);
   nsresult DragEnter(nsIDOMDragEvent* aDragEvent);
   nsresult DragOver(nsIDOMDragEvent* aDragEvent);
   nsresult DragExit(nsIDOMDragEvent* aDragEvent);
   nsresult Drop(nsIDOMDragEvent* aDragEvent);
   nsresult DragGesture(nsIDOMDragEvent* aDragEvent);
   void CleanupDragDropCaret();
   already_AddRefed<nsIPresShell> GetPresShell();
   bool IsFileControlTextBox();
+  bool ShouldHandleNativeKeyBindings(nsIDOMEvent* aKeyEvent);
 
 protected:
   nsEditor* mEditor; // weak
   nsRefPtr<nsCaret> mCaret;
   bool mCommitText;
   bool mInTransaction;
 #ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
   bool mHaveBidiKeyboards;
--- a/editor/libeditor/html/tests/mochitest.ini
+++ b/editor/libeditor/html/tests/mochitest.ini
@@ -60,16 +60,18 @@ support-files =
   data/cfhtml-nocontext.txt
   file_bug549262.html
   file_bug674770-1.html
   file_select_all_without_body.html
   green.png
 
 [test_CF_HTML_clipboard.html]
 [test_bug200416.html]
+[test_bug289384.html]
+skip-if = os != "mac"
 [test_bug290026.html]
 [test_bug291780.html]
 [test_bug316447.html]
 [test_bug332636.html]
 [test_bug332636.html^headers^]
 [test_bug372345.html]
 [test_bug404320.html]
 [test_bug410986.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/html/tests/test_bug289384.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=289384
+-->
+<head>
+  <title>Test for Bug 289384</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=289384">Mozilla Bug 289384</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+  var win = window.open("data:text/html,<a href=\"data:text/html,<body contenteditable onload='opener.continueTest(window);'>foo bar</body>\">link</a>", "", "test-289384");
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad);
+    win.document.querySelector("a").click();
+  }, false);
+});
+
+function continueTest(win) {
+  SimpleTest.waitForFocus(function() {
+    var doc = win.document;
+    var sel = win.getSelection();
+    doc.body.focus();
+    sel.collapse(doc.body.firstChild, 3);
+    SimpleTest.executeSoon(function() {
+      synthesizeKey("VK_LEFT", {accelKey: true}, win);
+      ok(sel.isCollapsed, "The selection must be collapsed");
+      is(sel.anchorNode, doc.body.firstChild, "The anchor node should be the body element's text node");
+      is(sel.anchorOffset, 0, "The anchor offset should be 0");
+      win.close();
+      SimpleTest.finish();
+    });
+  }, win);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
+++ b/editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
@@ -168,45 +168,45 @@ function runTests()
     check(aDescription + "Shift+Backspace", true, true, true);
 
     reset("");
     synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
     check(aDescription + "Ctrl+Backspace", true, true, aIsReadonly);
 
     reset("");
     synthesizeKey("VK_BACK_SPACE", { altKey: true });
-    check(aDescription + "Alt+Backspace", true, true, aIsReadonly);
+    check(aDescription + "Alt+Backspace", true, true, aIsReadonly || kIsMac);
 
     reset("");
     synthesizeKey("VK_BACK_SPACE", { metaKey: true });
     check(aDescription + "Meta+Backspace", true, true, aIsReadonly);
 
     reset("");
     synthesizeKey("VK_BACK_SPACE", { osKey: true });
     check(aDescription + "OS+Backspace", true, true, aIsReadonly);
 
     // Delete key:
     //   If editor is readonly, it doesn't consume.
     //   If editor is editable, delete is consumed.
     //   Otherwise, editor doesn't consume the event.
     reset("");
     synthesizeKey("VK_DELETE", { });
-    check(aDescription + "Delete", true, true, !aIsReadonly);
+    check(aDescription + "Delete", true, true, !aIsReadonly || kIsMac);
 
     reset("");
     synthesizeKey("VK_DELETE", { shiftKey: true });
-    check(aDescription + "Shift+Delete", true, true, false);
+    check(aDescription + "Shift+Delete", true, true, kIsMac);
 
     reset("");
     synthesizeKey("VK_DELETE", { ctrlKey: true });
     check(aDescription + "Ctrl+Delete", true, true, false);
 
     reset("");
     synthesizeKey("VK_DELETE", { altKey: true });
-    check(aDescription + "Alt+Delete", true, true, false);
+    check(aDescription + "Alt+Delete", true, true, kIsMac);
 
     reset("");
     synthesizeKey("VK_DELETE", { metaKey: true });
     check(aDescription + "Meta+Delete", true, true, false);
 
     reset("");
     synthesizeKey("VK_DELETE", { osKey: true });
     check(aDescription + "OS+Delete", true, true, false);
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -875,17 +875,17 @@ APZCTreeManager::GetAPZCAtPoint(AsyncPan
     return aApzc;
   }
   return nullptr;
 }
 
 /* This function sets the aTransformToApzcOut and aTransformToGeckoOut out-parameters
    to some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
-   is the layer that corresponds to the returned APZC instance, and layer R is the root
+   is the layer that corresponds to the argument |aApzc|, and layer R is the root
    of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
    When layer L is displayed to the screen by the compositor, the set of transforms that
    are applied to L are (in order from top to bottom):
 
         L's transient async transform       (hereafter referred to as transform matrix LT)
         L's nontransient async transform    (hereafter referred to as transform matrix LN)
         L's CSS transform                   (hereafter referred to as transform matrix LC)
         M's transient async transform       (hereafter referred to as transform matrix MT)
@@ -913,50 +913,65 @@ APZCTreeManager::GetAPZCAtPoint(AsyncPan
         MT.Inverse()
         LC.Inverse()
         LN.Inverse()
    This combined transformation is returned in the aTransformToApzcOut out-parameter.
 
    Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip
    out all of the async transforms that are involved in this chain. This is because async
    transforms are stored only in the compositor and gecko does not account for them when
-   doing display-list-based hit-testing for event dispatching. Therefore, given a user input
-   in screen space, the following transforms need to be applied (in order from top to bottom):
+   doing display-list-based hit-testing for event dispatching.
+   Furthermore, because these input events are processed by Gecko in a FIFO queue that
+   includes other things (specifically paint requests), it is possible that by time the
+   input event reaches gecko, it will have painted something else. Therefore, we need to
+   apply another transform to the input events to account for the possible disparity between
+   what we know gecko last painted and the last paint request we sent to gecko. Let this
+   transform be represented by LD, MD, ... RD.
+   Therefore, given a user input in screen space, the following transforms need to be applied
+   (in order from top to bottom):
         RC.Inverse()
         RN.Inverse()
         RT.Inverse()
         ...
         MC.Inverse()
         MN.Inverse()
         MT.Inverse()
         LC.Inverse()
         LN.Inverse()
         LT.Inverse()
+        LD
         LC
+        MD
         MC
         ...
+        RD
         RC
    This sequence can be simplified and refactored to the following:
         aTransformToApzcOut
         LT.Inverse()
+        LD
         LC
+        MD
         MC
         ...
+        RD
         RC
    Since aTransformToApzcOut is already one of the out-parameters, we set aTransformToGeckoOut
-   to the remaining transforms (LT.Inverse() * LC * ... * RC), so that the caller code can
+   to the remaining transforms (LT.Inverse() * LD * ... * RC), so that the caller code can
    combine it with aTransformToApzcOut to get the final transform required in this case.
 
    Note that for many of these layers, there will be no AsyncPanZoomController attached, and
    so the async transform will be the identity transform. So, in the example above, if layers
-   L and P have APZC instances attached, MT, MN, NT, NN, OT, ON, QT, QN, RT and RN will be
-   identity transforms.
+   L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT,
+   RN and RD will be identity transforms.
    Additionally, for space-saving purposes, each APZC instance stores its layer's individual
    CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for
    layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
+   The APZC instances track the last dispatched paint request and so are able to calculate LD and
+   PD using those internally stored values.
    The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations
    required can be generated.
  */
 void
 APZCTreeManager::GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
                                     gfx3DMatrix& aTransformToGeckoOut)
 {
   MonitorAutoLock lock(mTreeLock);
@@ -973,35 +988,35 @@ APZCTreeManager::GetInputTransforms(Asyn
   gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
   // nontransientAsyncTransform is LN
   gfx3DMatrix nontransientAsyncTransform = aApzc->GetNontransientAsyncTransform();
   // transientAsyncUntransform is LT.Inverse()
   gfx3DMatrix transientAsyncUntransform = nontransientAsyncTransform * asyncUntransform;
 
   // aTransformToApzcOut is initialized to OC.Inverse() * NC.Inverse() * MC.Inverse() * LC.Inverse() * LN.Inverse()
   aTransformToApzcOut = ancestorUntransform * aApzc->GetCSSTransform().Inverse() * nontransientAsyncTransform.Inverse();
-  // aTransformToGeckoOut is initialized to LT.Inverse() * LC * MC * NC * OC
-  aTransformToGeckoOut = transientAsyncUntransform * aApzc->GetCSSTransform() * aApzc->GetAncestorTransform();
+  // aTransformToGeckoOut is initialized to LT.Inverse() * LD * LC * MC * NC * OC
+  aTransformToGeckoOut = transientAsyncUntransform * aApzc->GetTransformToLastDispatchedPaint() * aApzc->GetCSSTransform() * aApzc->GetAncestorTransform();
 
   for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) {
     // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent == P
     ancestorUntransform = parent->GetAncestorTransform().Inverse();
     // asyncUntransform is updated to PA.Inverse() when parent == P
     asyncUntransform = gfx3DMatrix(parent->GetCurrentAsyncTransform()).Inverse();
     // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PC.Inverse() * PA.Inverse()
     gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * parent->GetCSSTransform().Inverse() * asyncUntransform;
 
     // aTransformToApzcOut is RC.Inverse() * QC.Inverse() * PC.Inverse() * PA.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse() * LC.Inverse() * LN.Inverse()
     aTransformToApzcOut = untransformSinceLastApzc * aTransformToApzcOut;
-    // aTransformToGeckoOut is LT.Inverse() * LC * MC * NC * OC * PC * QC * RC
-    aTransformToGeckoOut = aTransformToGeckoOut * parent->GetCSSTransform() * parent->GetAncestorTransform();
+    // aTransformToGeckoOut is LT.Inverse() * LD * LC * MC * NC * OC * PD * PC * QC * RC
+    aTransformToGeckoOut = aTransformToGeckoOut * parent->GetTransformToLastDispatchedPaint() * parent->GetCSSTransform() * parent->GetAncestorTransform();
 
     // The above values for aTransformToApzcOut and aTransformToGeckoOut when parent == P match
-    // the required output as explained in the comment above GetTargetAPZC. Note that any missing terms
-    // are async transforms that are guaranteed to be identity transforms.
+    // the required output as explained in the comment above this method. Note that any missing
+    // terms are guaranteed to be identity transforms.
   }
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2)
 {
   MonitorAutoLock lock(mTreeLock);
   nsRefPtr<AsyncPanZoomController> ancestor;
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -244,18 +244,23 @@ void TextureHost::Finalize()
   }
 }
 
 void
 TextureHost::PrintInfo(nsACString& aTo, const char* aPrefix)
 {
   aTo += aPrefix;
   aTo += nsPrintfCString("%s (0x%p)", Name(), this);
-  AppendToString(aTo, GetSize(), " [size=", "]");
-  AppendToString(aTo, GetFormat(), " [format=", "]");
+  // Note: the TextureHost needs to be locked before it is safe to call
+  //       GetSize() and GetFormat() on it.
+  if (Lock()) {
+    AppendToString(aTo, GetSize(), " [size=", "]");
+    AppendToString(aTo, GetFormat(), " [format=", "]");
+    Unlock();
+  }
   AppendToString(aTo, mFlags, " [flags=", "]");
 }
 
 void
 TextureSource::SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData)
 {
     mCompositableBackendData = aBackendData;
 }
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -1333,68 +1333,71 @@ const CSSRect AsyncPanZoomController::Ca
 
 void AsyncPanZoomController::ScheduleComposite() {
   if (mCompositorParent) {
     mCompositorParent->ScheduleRenderOnCompositorThread();
   }
 }
 
 void AsyncPanZoomController::RequestContentRepaint() {
-  mFrameMetrics.mDisplayPort =
-    CalculatePendingDisplayPort(mFrameMetrics,
+  RequestContentRepaint(mFrameMetrics);
+}
+
+void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics) {
+  aFrameMetrics.mDisplayPort =
+    CalculatePendingDisplayPort(aFrameMetrics,
                                 GetVelocityVector(),
                                 GetAccelerationVector(),
                                 mPaintThrottler.AverageDuration().ToSeconds());
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   CSSRect oldDisplayPort = mLastPaintRequestMetrics.mDisplayPort
                          + mLastPaintRequestMetrics.mScrollOffset;
-  CSSRect newDisplayPort = mFrameMetrics.mDisplayPort
-                         + mFrameMetrics.mScrollOffset;
+  CSSRect newDisplayPort = aFrameMetrics.mDisplayPort
+                         + aFrameMetrics.mScrollOffset;
 
   if (fabsf(oldDisplayPort.x - newDisplayPort.x) < EPSILON &&
       fabsf(oldDisplayPort.y - newDisplayPort.y) < EPSILON &&
       fabsf(oldDisplayPort.width - newDisplayPort.width) < EPSILON &&
       fabsf(oldDisplayPort.height - newDisplayPort.height) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.mScrollOffset.x -
-            mFrameMetrics.mScrollOffset.x) < EPSILON &&
+            aFrameMetrics.mScrollOffset.x) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.mScrollOffset.y -
-            mFrameMetrics.mScrollOffset.y) < EPSILON &&
-      mFrameMetrics.mZoom == mLastPaintRequestMetrics.mZoom &&
-      fabsf(mFrameMetrics.mViewport.width - mLastPaintRequestMetrics.mViewport.width) < EPSILON &&
-      fabsf(mFrameMetrics.mViewport.height - mLastPaintRequestMetrics.mViewport.height) < EPSILON) {
+            aFrameMetrics.mScrollOffset.y) < EPSILON &&
+      aFrameMetrics.mZoom == mLastPaintRequestMetrics.mZoom &&
+      fabsf(aFrameMetrics.mViewport.width - mLastPaintRequestMetrics.mViewport.width) < EPSILON &&
+      fabsf(aFrameMetrics.mViewport.height - mLastPaintRequestMetrics.mViewport.height) < EPSILON) {
     return;
   }
 
   SendAsyncScrollEvent();
-  ScheduleContentRepaint(mFrameMetrics);
+  mPaintThrottler.PostTask(
+    FROM_HERE,
+    NewRunnableMethod(this,
+                      &AsyncPanZoomController::DispatchRepaintRequest,
+                      aFrameMetrics),
+    GetFrameTime());
+
+  aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId;
+  mLastPaintRequestMetrics = aFrameMetrics;
 }
 
 void
-AsyncPanZoomController::ScheduleContentRepaint(FrameMetrics &aFrameMetrics) {
-  // This message is compressed, so fire whether or not we already have a paint
-  // queued up. We need to know whether or not a paint was requested anyways,
-  // for the purposes of content calling window.scrollTo().
+AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
 
     LogRendertraceRect(GetGuid(), "requested displayport", "yellow",
         aFrameMetrics.mDisplayPort + aFrameMetrics.mScrollOffset);
 
-    mPaintThrottler.PostTask(
-      FROM_HERE,
-      NewRunnableMethod(controller.get(),
-                        &GeckoContentController::RequestContentRepaint,
-                        aFrameMetrics),
-      GetFrameTime());
+    controller->RequestContentRepaint(aFrameMetrics);
+    mLastDispatchedPaintMetrics = aFrameMetrics;
   }
-  aFrameMetrics.mPresShellId = mLastContentPaintMetrics.mPresShellId;
-  mLastPaintRequestMetrics = aFrameMetrics;
 }
 
 void
 AsyncPanZoomController::FireAsyncScrollOnTimeout()
 {
   if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
     ReentrantMonitorAutoEnter lock(mMonitor);
     SendAsyncScrollEvent();
@@ -1525,16 +1528,24 @@ ViewTransform AsyncPanZoomController::Ge
 
 gfx3DMatrix AsyncPanZoomController::GetNontransientAsyncTransform() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   return gfx3DMatrix::ScalingMatrix(mLastContentPaintMetrics.mResolution.scale,
                                     mLastContentPaintMetrics.mResolution.scale,
                                     1.0f);
 }
 
+gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  CSSPoint scrollChange = mLastContentPaintMetrics.mScrollOffset - mLastDispatchedPaintMetrics.mScrollOffset;
+  float zoomChange = mLastContentPaintMetrics.mZoom.scale / mLastDispatchedPaintMetrics.mZoom.scale;
+  return gfx3DMatrix::Translation(scrollChange.x, scrollChange.y, 0) *
+         gfx3DMatrix::ScalingMatrix(zoomChange, zoomChange, 1);
+}
+
 void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   mLastContentPaintMetrics = aLayerMetrics;
 
   bool isDefault = mFrameMetrics.IsDefault();
   mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
   APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint);
@@ -1552,25 +1563,28 @@ void AsyncPanZoomController::NotifyLayer
     if (mFrameMetrics.mViewport.width != aLayerMetrics.mViewport.width ||
         mFrameMetrics.mViewport.height != aLayerMetrics.mViewport.height) {
       needContentRepaint = true;
     }
     mFrameMetrics.mViewport = aLayerMetrics.mViewport;
   }
 
   if (aIsFirstPaint || isDefault) {
+    // Initialize our internal state to something sane when the content
+    // that was just painted is something we knew nothing about previously
     mPaintThrottler.ClearHistory();
     mPaintThrottler.SetMaxDurations(gNumPaintDurationSamples);
 
     mX.CancelTouch();
     mY.CancelTouch();
+    SetState(NOTHING);
 
     mFrameMetrics = aLayerMetrics;
+    mLastDispatchedPaintMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
-    SetState(NOTHING);
   } else {
     // If we're not taking the aLayerMetrics wholesale we still need to pull
     // in some things into our local mFrameMetrics because these things are
     // determined by Gecko and our copy in mFrameMetrics may be stale.
     mFrameMetrics.mScrollableRect = aLayerMetrics.mScrollableRect;
     mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds;
     float parentResolutionChange = aLayerMetrics.GetParentResolution().scale
                                  / mFrameMetrics.GetParentResolution().scale;
@@ -1692,17 +1706,17 @@ void AsyncPanZoomController::ZoomToRect(
     StartAnimation(new ZoomAnimation(
         mFrameMetrics.mScrollOffset,
         mFrameMetrics.mZoom,
         endZoomToMetrics.mScrollOffset,
         endZoomToMetrics.mZoom));
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
-    ScheduleContentRepaint(endZoomToMetrics);
+    RequestContentRepaint(endZoomToMetrics);
   }
 }
 
 void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
   mPreventDefaultSet = true;
   mPreventDefault = aPreventDefault;
   CheckContentResponse();
 }
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -218,16 +218,25 @@ public:
   /**
    * Returns the part of the async transform that will remain once Gecko does a
    * repaint at the desired metrics. That is, in the steady state:
    * gfx3DMatrix(GetCurrentAsyncTransform()) === GetNontransientAsyncTransform()
    */
   gfx3DMatrix GetNontransientAsyncTransform();
 
   /**
+   * Returns the transform to take something from the coordinate space of the
+   * last thing we know gecko painted, to the coordinate space of the last thing
+   * we asked gecko to paint. In cases where that last request has not yet been
+   * processed, this is needed to transform input events properly into a space
+   * gecko will understand.
+   */
+  gfx3DMatrix GetTransformToLastDispatchedPaint();
+
+  /**
    * Recalculates the displayport. Ideally, this should paint an area bigger
    * than the composite-to dimensions so that when you scroll down, you don't
    * checkerboard immediately. This includes a bunch of logic, including
    * algorithms to bias painting in the direction of the velocity.
    */
   static const CSSRect CalculatePendingDisplayPort(
     const FrameMetrics& aFrameMetrics,
     const ScreenPoint& aVelocity,
@@ -477,17 +486,22 @@ protected:
    * gets discarded.
    */
   void RequestContentRepaint();
 
   /**
    * Tell the paint throttler to request a content repaint with the given
    * metrics.  (Helper function used by RequestContentRepaint.)
    */
-  void ScheduleContentRepaint(FrameMetrics &aFrameMetrics);
+  void RequestContentRepaint(FrameMetrics& aFrameMetrics);
+
+  /**
+   * Actually send the next pending paint request to gecko.
+   */
+  void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics);
 
   /**
    * Advances a fling by an interpolated amount based on the passed in |aDelta|.
    * This should be called whenever sampling the content transform for this
    * frame. Returns true if the fling animation should be advanced by one frame,
    * or false if there is no fling or the fling has ended.
    */
   bool DoFling(const TimeDuration& aDelta);
@@ -630,16 +644,21 @@ private:
   // values we were notified of in NotifyLayersUpdate(). Since it represents
   // the Gecko state, it should be used as a basis for untransformation when
   // sending messages back to Gecko.
   FrameMetrics mLastContentPaintMetrics;
   // The last metrics that we requested a paint for. These are used to make sure
   // that we're not requesting a paint of the same thing that's already drawn.
   // If we don't do this check, we don't get a ShadowLayersUpdated back.
   FrameMetrics mLastPaintRequestMetrics;
+  // The last metrics that we actually sent to Gecko. This allows us to transform
+  // inputs into a coordinate space that Gecko knows about. This assumes the pipe
+  // through which input events and repaint requests are sent to Gecko operates
+  // in a FIFO manner.
+  FrameMetrics mLastDispatchedPaintMetrics;
 
   nsTArray<MultiTouchInput> mTouchQueue;
 
   CancelableTask* mContentResponseTimeoutTask;
 
   AxisX mX;
   AxisY mY;
 
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -216,16 +216,18 @@ void DoPanTest(bool aShouldTriggerScroll
 
   // Pan back
   apzc->SetAllowedTouchBehavior(values);
   ApzcPan(apzc, tm, time, touchEnd, touchStart, !aShouldTriggerScroll);
   apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut);
 
   EXPECT_EQ(pointOut, ScreenPoint());
   EXPECT_EQ(viewTransformOut, ViewTransform());
+
+  apzc->Destroy();
 }
 
 static void
 ApzcPinch(AsyncPanZoomController* aApzc, int aFocusX, int aFocusY, float aScale) {
   aApzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
                                             0,
                                             ScreenPoint(aFocusX, aFocusY),
                                             10.0,
@@ -310,16 +312,18 @@ TEST(AsyncPanZoomController, Pinch) {
 
   ApzcPinch(apzc, 250, 300, 0.5);
 
   // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
   fm = apzc->GetFrameMetrics();
   EXPECT_EQ(fm.mZoom.scale, 1.0f);
   EXPECT_EQ(fm.mScrollOffset.x, 880);
   EXPECT_EQ(fm.mScrollOffset.y, 0);
+
+  apzc->Destroy();
 }
 
 TEST(AsyncPanZoomController, PinchWithTouchActionNone) {
   nsRefPtr<MockContentController> mcc = new NiceMock<MockContentController>();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
 
   FrameMetrics fm;
   fm.mViewport = CSSRect(0, 0, 980, 480);
@@ -873,33 +877,63 @@ TEST(APZCTreeManager, HitTesting2) {
   // and transformToGecko should reapply it
   EXPECT_EQ(gfxPoint(75, 75), transformToGecko.Transform(gfxPoint(37.5, 75)));
 
   // Pan the root layer upward by 50 pixels.
   // This causes layers[1] to scroll out of view, and an async transform
   // of -50 to be set on the root layer.
   int time = 0;
   // Silence GMock warnings about "uninteresting mock function calls".
-  EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(1);
+  EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
+  // Since this paint request is in the queue to Gecko, transformToGecko will
+  // take it into account.
   ApzcPan(apzcroot, manager, time, 100, 50);
 
   // Hit where layers[3] used to be. It should now hit the root.
   hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko);
   EXPECT_EQ(apzcroot, hit.get());
   // transformToApzc doesn't unapply the root's own async transform
   EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75)));
-  // but transformToGecko does
-  EXPECT_EQ(gfxPoint(75, 125), transformToGecko.Transform(gfxPoint(75, 75)));
+  // and transformToGecko unapplies it and then reapplies it, because by the
+  // time the event being transformed reaches Gecko the new paint request will
+  // have been handled.
+  EXPECT_EQ(gfxPoint(75, 75), transformToGecko.Transform(gfxPoint(75, 75)));
 
   // Hit where layers[1] used to be and where layers[3] should now be.
   hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko);
   EXPECT_EQ(apzc3, hit.get());
   // transformToApzc unapplies both layers[2]'s css transform and the root's
-  // async trasnform
+  // async transform
   EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 25)));
-  // transformToGecko reapplies the css transform only (since Gecko doesn't
-  // know about async transforms)
-  EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(12.5, 75)));
+  // transformToGecko reapplies both the css transform and the async transform
+  // because we have already issued a paint request with it.
+  EXPECT_EQ(gfxPoint(25, 25), transformToGecko.Transform(gfxPoint(12.5, 75)));
+
+  // This second pan will move the APZC by another 50 pixels but since the paint
+  // request dispatched above has not "completed", we will not dispatch another
+  // one yet. Now we have an async transform on top of the pending paint request
+  // transform.
+  ApzcPan(apzcroot, manager, time, 100, 50);
+
+  // Hit where layers[3] used to be. It should now hit the root.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75)));
+  // transformToGecko unapplies the full async transform of -100 pixels, and then
+  // reapplies the "D" transform of -50 leading to an overall adjustment of +50
+  EXPECT_EQ(gfxPoint(75, 125), transformToGecko.Transform(gfxPoint(75, 75)));
+
+  // Hit where layers[1] used to be. It should now hit the root.
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(gfxPoint(25, 25), transformToApzc.Transform(gfxPoint(25, 25)));
+  // transformToGecko unapplies the full async transform of -100 pixels, and then
+  // reapplies the "D" transform of -50 leading to an overall adjustment of +50
+  EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(25, 25)));
 
   manager->ClearTree();
 }
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -632,17 +632,17 @@ NS_IMETHODIMP imgRequest::OnStartRequest
       // Image object not created until OnDataAvailable, so forward to static
       // DecodePool directly.
       nsCOMPtr<nsIEventTarget> target = RasterImage::GetEventTarget();
       rv = retargetable->RetargetDeliveryTo(target);
     }
     PR_LOG(GetImgLog(), PR_LOG_WARNING,
            ("[this=%p] imgRequest::OnStartRequest -- "
             "RetargetDeliveryTo rv %d=%s\n",
-            this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv));
+            this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
   }
 
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 {
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -2,16 +2,17 @@
  *
  * 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 imgRequestProxy_h__
 #define imgRequestProxy_h__
 
+#include "mozilla/WeakPtr.h"
 #include "imgIRequest.h"
 #include "nsISecurityInfoProvider.h"
 
 #include "nsILoadGroup.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
@@ -38,17 +39,18 @@ namespace image {
 class Image;
 class ImageURL;
 } // namespace image
 } // namespace mozilla
 
 class imgRequestProxy : public imgIRequest,
                         public nsISupportsPriority,
                         public nsISecurityInfoProvider,
-                        public nsITimedChannel
+                        public nsITimedChannel,
+                        public mozilla::SupportsWeakPtr<imgRequestProxy>
 {
 public:
   typedef mozilla::image::ImageURL ImageURL;
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIREQUEST
   NS_DECL_NSIREQUEST
   NS_DECL_NSISUPPORTSPRIORITY
   NS_DECL_NSISECURITYINFOPROVIDER
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -13,16 +13,17 @@
 #include "ImageLogging.h"
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Services.h"
 
 using namespace mozilla::image;
+using mozilla::WeakPtr;
 
 class imgStatusTrackerObserver : public imgDecoderObserver
 {
 public:
   imgStatusTrackerObserver(imgStatusTracker* aTracker)
   : mTracker(aTracker->asWeakPtr())
   {
     MOZ_ASSERT(aTracker);
@@ -138,17 +139,17 @@ public:
   {
     LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnError");
     nsRefPtr<imgStatusTracker> tracker = mTracker.get();
     if (!tracker) { return; }
     tracker->RecordError();
   }
 
 private:
-  mozilla::WeakPtr<imgStatusTracker> mTracker;
+  WeakPtr<imgStatusTracker> mTracker;
 };
 
 // imgStatusTracker methods
 
 imgStatusTracker::imgStatusTracker(Image* aImage)
   : mImage(aImage),
     mState(0),
     mImageStatus(imgIRequest::STATUS_NONE),
@@ -352,27 +353,27 @@ imgStatusTracker::NotifyCurrentState(img
 
   // We don't keep track of
   nsCOMPtr<nsIRunnable> ev = new imgStatusNotifyRunnable(this, proxy);
   NS_DispatchToCurrentThread(ev);
 }
 
 #define NOTIFY_IMAGE_OBSERVERS(func) \
   do { \
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(proxies); \
+    ProxyArray::ForwardIterator iter(proxies); \
     while (iter.HasMore()) { \
-      nsRefPtr<imgRequestProxy> proxy = iter.GetNext(); \
-      if (!proxy->NotificationsDeferred()) { \
+      nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get(); \
+      if (proxy && !proxy->NotificationsDeferred()) { \
         proxy->func; \
       } \
     } \
   } while (false);
 
 /* static */ void
-imgStatusTracker::SyncNotifyState(nsTObserverArray<imgRequestProxy*>& proxies,
+imgStatusTracker::SyncNotifyState(ProxyArray& proxies,
                                   bool hasImage, uint32_t state,
                                   nsIntRect& dirtyRect, bool hadLastPart)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // OnStartRequest
   if (state & stateRequestStarted)
     NOTIFY_IMAGE_OBSERVERS(OnStartRequest());
 
@@ -500,23 +501,23 @@ imgStatusTracker::SyncNotifyDifference(c
 
   nsIntRect invalidRect = mInvalidRect.Union(diff.invalidRect);
 
   SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect, mHadLastPart);
 
   mInvalidRect.SetEmpty();
 
   if (diff.unblockedOnload) {
-    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+    ProxyArray::ForwardIterator iter(mConsumers);
     while (iter.HasMore()) {
       // Hold on to a reference to this proxy, since notifying the state can
       // cause it to disappear.
-      nsRefPtr<imgRequestProxy> proxy = iter.GetNext();
+      nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
 
-      if (!proxy->NotificationsDeferred()) {
+      if (proxy && !proxy->NotificationsDeferred()) {
         SendUnblockOnload(proxy);
       }
     }
   }
 
   if (diff.foundError) {
     FireFailureNotification();
   }
@@ -545,18 +546,18 @@ imgStatusTracker::SyncNotify(imgRequestP
 
   nsIntRect r;
   if (mImage) {
     // XXX - Should only send partial rects here, but that needs to
     // wait until we fix up the observer interface
     r = mImage->FrameRect(imgIContainer::FRAME_CURRENT);
   }
 
-  nsTObserverArray<imgRequestProxy*> array;
-  array.AppendElement(proxy);
+  ProxyArray array;
+  array.AppendElement(proxy->asWeakPtr());
   SyncNotifyState(array, !!mImage, mState, r, mHadLastPart);
 }
 
 void
 imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy,
                                          nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread(),
@@ -577,17 +578,17 @@ imgStatusTracker::EmulateRequestFinished
     aProxy->OnStopRequest(true);
   }
 }
 
 void
 imgStatusTracker::AddConsumer(imgRequestProxy* aConsumer)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mConsumers.AppendElementUnlessExists(aConsumer);
+  mConsumers.AppendElementUnlessExists(aConsumer->asWeakPtr());
 }
 
 // XXX - The last argument should go away.
 bool
 imgStatusTracker::RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Remove the proxy from the list.
@@ -605,16 +606,30 @@ imgStatusTracker::RemoveConsumer(imgRequ
   if (aConsumer->NotificationsDeferred() && runnable) {
     runnable->RemoveProxy(aConsumer);
     aConsumer->SetNotificationsDeferred(false);
   }
 
   return removed;
 }
 
+bool
+imgStatusTracker::FirstConsumerIs(imgRequestProxy* aConsumer)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
+  ProxyArray::ForwardIterator iter(mConsumers);
+  while (iter.HasMore()) {
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      return proxy.get() == aConsumer;
+    }
+  }
+  return false;
+}
+
 void
 imgStatusTracker::RecordCancel()
 {
   if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL))
     mImageStatus = imgIRequest::STATUS_ERROR;
 }
 
 void
@@ -776,19 +791,22 @@ imgStatusTracker::SendUnlockedDraw(imgRe
     aProxy->OnUnlockedDraw();
 }
 
 void
 imgStatusTracker::OnUnlockedDraw()
 {
   MOZ_ASSERT(NS_IsMainThread());
   RecordUnlockedDraw();
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendUnlockedDraw(iter.GetNext());
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendUnlockedDraw(proxy);
+    }
   }
 }
 
 void
 imgStatusTracker::RecordFrameChanged(const nsIntRect* aDirtyRect)
 {
   NS_ABORT_IF_FALSE(mImage,
                     "RecordFrameChanged called before we have an Image");
@@ -833,19 +851,22 @@ imgStatusTracker::SendStartRequest(imgRe
     aProxy->OnStartRequest();
 }
 
 void
 imgStatusTracker::OnStartRequest()
 {
   MOZ_ASSERT(NS_IsMainThread());
   RecordStartRequest();
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendStartRequest(iter.GetNext());
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendStartRequest(proxy);
+    }
   }
 }
 
 void
 imgStatusTracker::RecordStopRequest(bool aLastPart,
                                     nsresult aStatus)
 {
   mHadLastPart = aLastPart;
@@ -904,80 +925,95 @@ imgStatusTracker::OnStopRequest(bool aLa
     NS_DispatchToMainThread(
       new OnStopRequestEvent(this, aLastPart, aStatus));
     return;
   }
   bool preexistingError = mImageStatus == imgIRequest::STATUS_ERROR;
 
   RecordStopRequest(aLastPart, aStatus);
   /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mConsumers);
+  ProxyArray::ForwardIterator srIter(mConsumers);
   while (srIter.HasMore()) {
-    SendStopRequest(srIter.GetNext(), aLastPart, aStatus);
+    nsRefPtr<imgRequestProxy> proxy = srIter.GetNext().get();
+    if (proxy) {
+      SendStopRequest(proxy, aLastPart, aStatus);
+    }
   }
 
   if (NS_FAILED(aStatus) && !preexistingError) {
     FireFailureNotification();
   }
 }
 
 void
 imgStatusTracker::OnDiscard()
 {
   MOZ_ASSERT(NS_IsMainThread());
   RecordDiscard();
 
   /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendDiscard(iter.GetNext());
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendDiscard(proxy);
+    }
   }
 }
 
 void
 imgStatusTracker::FrameChanged(const nsIntRect* aDirtyRect)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RecordFrameChanged(aDirtyRect);
 
   /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendFrameChanged(iter.GetNext(), aDirtyRect);
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendFrameChanged(proxy, aDirtyRect);
+    }
   }
 }
 
 void
 imgStatusTracker::OnStopFrame()
 {
   MOZ_ASSERT(NS_IsMainThread());
   RecordStopFrame();
 
   /* notify the kids */
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendStopFrame(iter.GetNext());
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendStopFrame(proxy);
+    }
   }
 }
 
 void
 imgStatusTracker::OnDataAvailable()
 {
   if (!NS_IsMainThread()) {
     // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
     // so subsequent calls or dispatches which Unlock or Decrement~ should
     // be issued after this to avoid race conditions.
     NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &imgStatusTracker::OnDataAvailable));
     return;
   }
   // Notify any imgRequestProxys that are observing us that we have an Image.
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    iter.GetNext()->SetHasImage();
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      proxy->SetHasImage();
+    }
   }
 }
 
 void
 imgStatusTracker::RecordBlockOnload()
 {
   MOZ_ASSERT(!(mState & stateBlockingOnload));
   mState |= stateBlockingOnload;
@@ -1016,19 +1052,22 @@ imgStatusTracker::MaybeUnblockOnload()
     return;
   }
   if (!(mState & stateBlockingOnload)) {
     return;
   }
 
   RecordUnblockOnload();
 
-  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
+  ProxyArray::ForwardIterator iter(mConsumers);
   while (iter.HasMore()) {
-    SendUnblockOnload(iter.GetNext());
+    nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
+    if (proxy) {
+      SendUnblockOnload(proxy);
+    }
   }
 }
 
 void
 imgStatusTracker::RecordError()
 {
   mImageStatus = imgIRequest::STATUS_ERROR;
 }
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -4,28 +4,28 @@
  * 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 imgStatusTracker_h__
 #define imgStatusTracker_h__
 
 class imgDecoderObserver;
 class imgIContainer;
-class imgRequestProxy;
 class imgStatusNotifyRunnable;
 class imgRequestNotifyRunnable;
 class imgStatusTrackerObserver;
 class nsIRunnable;
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTObserverArray.h"
 #include "nsThreadUtils.h"
 #include "nsRect.h"
+#include "imgRequestProxy.h"
 
 namespace mozilla {
 namespace image {
 
 class Image;
 
 struct ImageStatusDiff
 {
@@ -168,20 +168,17 @@ public:
   size_t ConsumerCount() const {
     MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
     return mConsumers.Length();
   }
 
   // This is intentionally non-general because its sole purpose is to support an
   // some obscure network priority logic in imgRequest. That stuff could probably
   // be improved, but it's too scary to mess with at the moment.
-  bool FirstConsumerIs(imgRequestProxy* aConsumer) {
-    MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
-    return mConsumers.SafeElementAt(0, nullptr) == aConsumer;
-  }
+  bool FirstConsumerIs(imgRequestProxy* aConsumer);
 
   void AdoptConsumers(imgStatusTracker* aTracker) {
     MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
     MOZ_ASSERT(aTracker);
     mConsumers = aTracker->mConsumers;
   }
 
   // Returns whether we are in the process of loading; that is, whether we have
@@ -290,44 +287,45 @@ public:
   // Notify for the changes captured in an ImageStatusDiff. Because this may
   // result in recursive notifications, no decoding locks may be held.
   // Called on the main thread only.
   void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff);
 
   nsIntRect GetInvalidRect() const { return mInvalidRect; }
 
 private:
+  typedef nsTObserverArray<mozilla::WeakPtr<imgRequestProxy>> ProxyArray;
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
   friend class imgStatusTrackerObserver;
   friend class imgStatusTrackerInit;
   imgStatusTracker(const imgStatusTracker& aOther);
 
   // Main thread only because it deals with the observer service.
   void FireFailureNotification();
 
   // Main thread only, since imgRequestProxy calls are expected on the main
   // thread, and mConsumers is not threadsafe.
-  static void SyncNotifyState(nsTObserverArray<imgRequestProxy*>& proxies,
+  static void SyncNotifyState(ProxyArray& proxies,
                               bool hasImage, uint32_t state,
                               nsIntRect& dirtyRect, bool hadLastPart);
 
   nsCOMPtr<nsIRunnable> mRequestRunnable;
 
   // The invalid area of the most recent frame we know about. (All previous
   // frames are assumed to be fully valid.)
   nsIntRect mInvalidRect;
 
   // This weak ref should be set null when the image goes out of scope.
   mozilla::image::Image* mImage;
 
   // List of proxies attached to the image. Each proxy represents a consumer
   // using the image. Array and/or individual elements should only be accessed
   // on the main thread.
-  nsTObserverArray<imgRequestProxy*> mConsumers;
+  ProxyArray mConsumers;
 
   mozilla::RefPtr<imgDecoderObserver> mTrackerObserver;
 
   uint32_t mState;
   uint32_t mImageStatus;
   bool mIsMultipart    : 1;
   bool mHadLastPart    : 1;
   bool mHasBeenDecoded : 1;
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -912,16 +912,71 @@ class SkipRoot
     {
         init(PerThreadDataFriendFields::get(pt), ptr, count);
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
+/*
+ * RootedGeneric<T> allows a class to instantiate its own Rooted type by
+ * including the following two methods:
+ *
+ *    static inline js::ThingRootKind rootKind() { return js::THING_ROOT_CUSTOM; }
+ *    void trace(JSTracer *trc);
+ *
+ * The trace() method must trace all of the class's fields.
+ *
+ * Implementation:
+ *
+ * RootedGeneric<T> works by placing a pointer to its 'rooter' field into the
+ * usual list of rooters when it is instantiated. When marking, it backs up
+ * from this pointer to find a vtable containing a type-appropriate trace()
+ * method.
+ */
+template <typename GCType>
+class JS_PUBLIC_API(RootedGeneric)
+{
+  public:
+    JS::Rooted<GCType> rooter;
+    SkipRoot skip;
+
+    RootedGeneric(js::ContextFriendFields *cx)
+        : rooter(cx), skip(cx, rooter.address())
+    {
+    }
+
+    RootedGeneric(js::ContextFriendFields *cx, const GCType &initial)
+        : rooter(cx, initial), skip(cx, rooter.address())
+    {
+    }
+
+    virtual inline void trace(JSTracer *trc);
+
+    operator const GCType&() const { return rooter.get(); }
+    GCType operator->() const { return rooter.get(); }
+};
+
+template <typename GCType>
+inline void RootedGeneric<GCType>::trace(JSTracer *trc)
+{
+    rooter->trace(trc);
+}
+
+// We will instantiate RootedGeneric<void*> in RootMarking.cpp, and MSVC will
+// notice that void*s have no trace() method defined on them and complain (even
+// though it's never called.) MSVC's complaint is not unreasonable, so
+// specialize for void*.
+template <>
+inline void RootedGeneric<void*>::trace(JSTracer *trc)
+{
+    MOZ_ASSUME_UNREACHABLE("RootedGeneric<void*>::trace()");
+}
+
 /* Interface substitute for Rooted<T> which does not root the variable's memory. */
 template <typename T>
 class FakeRooted : public RootedBase<T>
 {
   public:
     template <typename CX>
     FakeRooted(CX *cx
                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
--- a/js/src/devtools/rootAnalysis/analyzeRoots.js
+++ b/js/src/devtools/rootAnalysis/analyzeRoots.js
@@ -47,19 +47,19 @@ for (var line of text) {
 text = null;
 
 var match;
 var gcThings = {};
 var gcPointers = {};
 
 text = snarf(gcTypesFile).split("\n");
 for (var line of text) {
-    if (match = /GCThing: (.*)/.exec(line))
+    if (match = /^GCThing: (.*)/.exec(line))
         gcThings[match[1]] = true;
-    if (match = /GCPointer: (.*)/.exec(line))
+    if (match = /^GCPointer: (.*)/.exec(line))
         gcPointers[match[1]] = true;
 }
 text = null;
 
 function isUnrootedType(type)
 {
     if (type.Kind == "Pointer") {
         var target = type.Type;
--- a/js/src/devtools/rootAnalysis/computeGCTypes.js
+++ b/js/src/devtools/rootAnalysis/computeGCTypes.js
@@ -6,48 +6,49 @@ loadRelativeToScript('utility.js');
 loadRelativeToScript('annotations.js');
 
 function processCSU(csu, body)
 {
     if (!("DataField" in body))
         return;
     for (var field of body.DataField) {
         var type = field.Field.Type;
+        var fieldName = field.Field.Name[0];
         if (type.Kind == "Pointer") {
             var target = type.Type;
             if (target.Kind == "CSU")
-                addNestedPointer(csu, target.Name);
+                addNestedPointer(csu, target.Name, fieldName);
         }
         if (type.Kind == "CSU") {
             // Ignore nesting in classes which are AutoGCRooters. We only consider
             // types with fields that may not be properly rooted.
             if (type.Name == "JS::AutoGCRooter" || type.Name == "JS::CustomAutoRooter")
                 return;
-            addNestedStructure(csu, type.Name);
+            addNestedStructure(csu, type.Name, fieldName);
         }
     }
 }
 
-function addNestedStructure(csu, inner)
+var structureParents = {}; // Map from field => list of <parent, fieldName>
+var pointerParents = {}; // Map from field => list of <parent, fieldName>
+
+function addNestedStructure(csu, inner, field)
 {
     if (!(inner in structureParents))
         structureParents[inner] = [];
-    structureParents[inner].push(csu);
+    structureParents[inner].push([ csu, field ]);
 }
 
-function addNestedPointer(csu, inner)
+function addNestedPointer(csu, inner, field)
 {
     if (!(inner in pointerParents))
         pointerParents[inner] = [];
-    pointerParents[inner].push(csu);
+    pointerParents[inner].push([ csu, field ]);
 }
 
-var structureParents = {};
-var pointerParents = {};
-
 var xdb = xdbLibrary();
 xdb.open("src_comp.xdb");
 
 var minStream = xdb.min_data_stream();
 var maxStream = xdb.max_data_stream();
 
 for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
     var csu = xdb.read_key(csuIndex);
@@ -55,46 +56,109 @@ for (var csuIndex = minStream; csuIndex 
     var json = JSON.parse(data.readString());
     assert(json.length == 1);
     processCSU(csu.readString(), json[0]);
 
     xdb.free_string(csu);
     xdb.free_string(data);
 }
 
-function addGCType(name)
+var gcTypes = {}; // map from parent struct => Set of GC typed children
+var gcPointers = {}; // map from parent struct => Set of GC typed children
+var gcFields = {};
+
+function addGCType(name, child, why)
 {
+    if (!why)
+        why = '<annotation>';
+    if (!child)
+        child = 'annotation';
+
     if (isRootedTypeName(name))
         return;
 
-    print("GCThing: " + name);
+    if (!(name in gcTypes))
+        gcTypes[name] = Set();
+    gcTypes[name].add(why);
+
+    if (!(name in gcFields))
+        gcFields[name] = Map();
+    gcFields[name].set(why, child);
+
     if (name in structureParents) {
-        for (var holder of structureParents[name])
-            addGCType(holder);
+        for (var field of structureParents[name]) {
+            var [ holder, fieldName ] = field;
+            addGCType(holder, name, fieldName);
+        }
     }
     if (name in pointerParents) {
-        for (var holder of pointerParents[name])
-            addGCPointer(holder);
+        for (var field of pointerParents[name]) {
+            var [ holder, fieldName ] = field;
+            addGCPointer(holder, name, fieldName);
+        }
     }
 }
 
-function addGCPointer(name)
+function addGCPointer(name, child, why)
 {
-    // Ignore types which are properly rooted.
+    if (!why)
+        why = '<annotation>';
+    if (!child)
+        child = 'annotation';
+
+    // Ignore types that are properly rooted.
     if (isRootedPointerTypeName(name))
         return;
 
-    print("GCPointer: " + name);
+    if (!(name in gcPointers))
+        gcPointers[name] = Set();
+    gcPointers[name].add(why);
+
+    if (!(name in gcFields))
+        gcFields[name] = Map();
+    gcFields[name].set(why, child);
+
     if (name in structureParents) {
-        for (var holder of structureParents[name])
-            addGCPointer(holder);
+        for (var field of structureParents[name]) {
+            var [ holder, fieldName ] = field;
+            addGCPointer(holder, name, fieldName);
+        }
     }
 }
 
 addGCType('js::ObjectImpl');
 addGCType('JSString');
 addGCType('js::Shape');
 addGCType('js::BaseShape');
 addGCType('JSScript');
 addGCType('js::LazyScript');
 addGCType('js::ion::IonCode');
 addGCPointer('JS::Value');
 addGCPointer('jsid');
+
+function explain(csu, indent, seen) {
+    if (!seen)
+        seen = Set();
+    seen.add(csu);
+    if (!(csu in gcFields))
+        return;
+    if (gcFields[csu].has('<annotation>')) {
+        print(indent + "because I said so");
+        return;
+    }
+    for (var [ field, child ] of gcFields[csu]) {
+        var inherit = "";
+        if (field == "field:0")
+            inherit = " (probably via inheritance)";
+        print(indent + "contains field '" + field + "' of type " + child + inherit);
+        if (!seen.has(child))
+            explain(child, indent + "  ", seen);
+    }
+}
+
+for (var csu in gcTypes) {
+    print("GCThing: " + csu);
+    explain(csu, "  ");
+}
+for (var csu in gcPointers) {
+    print("GCPointer: " + csu);
+    explain(csu, "  ");
+}
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -309,16 +309,22 @@ EmitBackPatchOp(ExclusiveContext *cx, By
 
     offset = bce->offset();
     delta = offset - *lastp;
     *lastp = offset;
     JS_ASSERT(delta > 0);
     return EmitJump(cx, bce, JSOP_BACKPATCH, delta);
 }
 
+static inline unsigned
+LengthOfSetLine(unsigned line)
+{
+    return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
+}
+
 /* Updates line number notes, not column notes. */
 static inline bool
 UpdateLineNumberNotes(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t offset)
 {
     TokenStream *ts = &bce->parser->tokenStream;
     if (!ts->srcCoords.isOnThisLine(offset, bce->currentLine())) {
         unsigned line = ts->srcCoords.lineNum(offset);
         unsigned delta = line - bce->currentLine();
@@ -331,17 +337,17 @@ UpdateLineNumberNotes(ExclusiveContext *
          * NB: We handle backward line number deltas (possible with for
          * loops where the update part is emitted after the body, but its
          * line number is <= any line number in the body) here by letting
          * unsigned delta_ wrap to a very large number, which triggers a
          * SRC_SETLINE.
          */
         bce->current->currentLine = line;
         bce->current->lastColumn  = 0;
-        if (delta >= (unsigned)(2 + ((line > SN_3BYTE_OFFSET_MASK)<<1))) {
+        if (delta >= LengthOfSetLine(line)) {
             if (NewSrcNote2(cx, bce, SRC_SETLINE, (ptrdiff_t)line) < 0)
                 return false;
         } else {
             do {
                 if (NewSrcNote(cx, bce, SRC_NEWLINE) < 0)
                     return false;
             } while (--delta != 0);
         }
@@ -6707,74 +6713,77 @@ SetSrcNoteOffset(ExclusiveContext *cx, B
 
     SrcNotesVector &notes = bce->notes();
 
     /* Find the offset numbered which (i.e., skip exactly which offsets). */
     jssrcnote *sn = notes.begin() + index;
     JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
     JS_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
     for (sn++; which; sn++, which--) {
-        if (*sn & SN_3BYTE_OFFSET_FLAG)
-            sn += 2;
+        if (*sn & SN_4BYTE_OFFSET_FLAG)
+            sn += 3;
     }
 
     /*
      * See if the new offset requires three bytes either by being too big or if
      * the offset has already been inflated (in which case, we need to stay big
      * to not break the srcnote encoding if this isn't the last srcnote).
      */
-    if (offset > (ptrdiff_t)SN_3BYTE_OFFSET_MASK || (*sn & SN_3BYTE_OFFSET_FLAG)) {
+    if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) {
         /* Maybe this offset was already set to a three-byte value. */
-        if (!(*sn & SN_3BYTE_OFFSET_FLAG)) {
+        if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
             /* Insert two dummy bytes that will be overwritten shortly. */
             jssrcnote dummy = 0;
             if (!(sn = notes.insert(sn, dummy)) ||
+                !(sn = notes.insert(sn, dummy)) ||
                 !(sn = notes.insert(sn, dummy)))
             {
                 js_ReportOutOfMemory(cx);
                 return false;
             }
         }
-        *sn++ = (jssrcnote)(SN_3BYTE_OFFSET_FLAG | (offset >> 16));
+        *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24));
+        *sn++ = (jssrcnote)(offset >> 16);
         *sn++ = (jssrcnote)(offset >> 8);
     }
     *sn = (jssrcnote)offset;
     return true;
 }
 
-#ifdef DEBUG_notme
-#define DEBUG_srcnotesize
-#endif
-
-#ifdef DEBUG_srcnotesize
-#define NBINS 10
-static uint32_t hist[NBINS];
-
-static void
-DumpSrcNoteSizeHist()
-{
-    static FILE *fp;
-    int i, n;
-
-    if (!fp) {
-        fp = fopen("/tmp/srcnotes.hist", "w");
-        if (!fp)
-            return;
-        setvbuf(fp, nullptr, _IONBF, 0);
-    }
-    fprintf(fp, "SrcNote size histogram:\n");
-    for (i = 0; i < NBINS; i++) {
-        fprintf(fp, "%4u %4u ", JS_BIT(i), hist[i]);
-        for (n = (int) JS_HOWMANY(hist[i], 10); n > 0; --n)
-            fputc('*', fp);
-        fputc('\n', fp);
-    }
-    fputc('\n', fp);
-}
-#endif
+/*
+ * Finish taking source notes in cx's notePool, copying final notes to the new
+ * stable store allocated by the caller and passed in via notes. Return false
+ * on malloc failure, which means this function reported an error.
+ *
+ * Use this to compute the number of jssrcnotes to allocate and pass in via
+ * notes. This method knows a lot about details of FinishTakingSrcNotes, so
+ * DON'T CHANGE js::frontend::FinishTakingSrcNotes WITHOUT CHECKING WHETHER
+ * THIS METHOD NEEDS CORRESPONDING CHANGES!
+ */
+ptrdiff_t
+BytecodeEmitter::countFinalSourceNotes()
+{
+    ptrdiff_t diff = prologOffset() - prolog.lastNoteOffset;
+    ptrdiff_t cnt = prolog.notes.length() + main.notes.length() + 1;
+    if (prolog.notes.length() && prolog.currentLine != firstLine) {
+        if (diff > SN_DELTA_MASK)
+            cnt += JS_HOWMANY(diff - SN_DELTA_MASK, SN_XDELTA_MASK);
+        cnt += LengthOfSetLine(firstLine);
+    } else if (diff > 0) {
+        if (main.notes.length()) {
+            jssrcnote *sn = main.notes.begin();
+            diff -= SN_IS_XDELTA(sn)
+                    ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK)
+                    : SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
+        }
+        if (diff > 0)
+            cnt += JS_HOWMANY(diff, SN_XDELTA_MASK);
+    }
+    return cnt;
+}
 
 /*
  * Fill in the storage at notes with prolog and main srcnotes; the space at
  * notes was allocated using the BytecodeEmitter::countFinalSourceNotes()
  * method from BytecodeEmitter.h. SO DON'T CHANGE THIS FUNCTION WITHOUT AT
  * LEAST CHECKING WHETHER BytecodeEmitter::countFinalSourceNotes() NEEDS
  * CORRESPONDING CHANGES!
  */
@@ -7027,31 +7036,32 @@ SrcNoteArity(jssrcnote *sn)
 JS_FRIEND_API(unsigned)
 js_SrcNoteLength(jssrcnote *sn)
 {
     unsigned arity;
     jssrcnote *base;
 
     arity = SrcNoteArity(sn);
     for (base = sn++; arity; sn++, arity--) {
-        if (*sn & SN_3BYTE_OFFSET_FLAG)
-            sn += 2;
+        if (*sn & SN_4BYTE_OFFSET_FLAG)
+            sn += 3;
     }
     return sn - base;
 }
 
 JS_FRIEND_API(ptrdiff_t)
 js_GetSrcNoteOffset(jssrcnote *sn, unsigned which)
 {
     /* Find the offset numbered which (i.e., skip exactly which offsets). */
     JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
     JS_ASSERT((int) which < SrcNoteArity(sn));
     for (sn++; which; sn++, which--) {
-        if (*sn & SN_3BYTE_OFFSET_FLAG)
-            sn += 2;
-    }
-    if (*sn & SN_3BYTE_OFFSET_FLAG) {
-        return (ptrdiff_t)(((uint32_t)(sn[0] & SN_3BYTE_OFFSET_MASK) << 16)
-                           | (sn[1] << 8)
-                           | sn[2]);
+        if (*sn & SN_4BYTE_OFFSET_FLAG)
+            sn += 3;
+    }
+    if (*sn & SN_4BYTE_OFFSET_FLAG) {
+        return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24)
+                           | (sn[1] << 16)
+                           | (sn[2] << 8)
+                           | sn[3]);
     }
     return (ptrdiff_t)*sn;
 }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -211,17 +211,17 @@ struct BytecodeEmitter
     void switchToMain() { current = &main; }
     void switchToProlog() { current = &prolog; }
 
     SrcNotesVector &notes() const { return current->notes; }
     ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; }
     unsigned currentLine() const { return current->currentLine; }
     unsigned lastColumn() const { return current->lastColumn; }
 
-    inline ptrdiff_t countFinalSourceNotes();
+    ptrdiff_t countFinalSourceNotes();
 
     bool reportError(ParseNode *pn, unsigned errorNumber, ...);
     bool reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...);
     bool reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...);
 };
 
 /*
  * Emit one bytecode.
@@ -277,44 +277,12 @@ NewSrcNote3(ExclusiveContext *cx, Byteco
 
 /* NB: this function can add at most one extra extended delta note. */
 bool
 AddToSrcNoteDelta(ExclusiveContext *cx, BytecodeEmitter *bce, jssrcnote *sn, ptrdiff_t delta);
 
 bool
 FinishTakingSrcNotes(ExclusiveContext *cx, BytecodeEmitter *bce, jssrcnote *notes);
 
-/*
- * Finish taking source notes in cx's notePool, copying final notes to the new
- * stable store allocated by the caller and passed in via notes. Return false
- * on malloc failure, which means this function reported an error.
- *
- * Use this to compute the number of jssrcnotes to allocate and pass in via
- * notes. This method knows a lot about details of FinishTakingSrcNotes, so
- * DON'T CHANGE js::frontend::FinishTakingSrcNotes WITHOUT CHECKING WHETHER
- * THIS METHOD NEEDS CORRESPONDING CHANGES!
- */
-inline ptrdiff_t
-BytecodeEmitter::countFinalSourceNotes()
-{
-    ptrdiff_t diff = prologOffset() - prolog.lastNoteOffset;
-    ptrdiff_t cnt = prolog.notes.length() + main.notes.length() + 1;
-    if (prolog.notes.length() && prolog.currentLine != firstLine) {
-        if (diff > SN_DELTA_MASK)
-            cnt += JS_HOWMANY(diff - SN_DELTA_MASK, SN_XDELTA_MASK);
-        cnt += 2 + ((firstLine > SN_3BYTE_OFFSET_MASK) << 1);
-    } else if (diff > 0) {
-        if (main.notes.length()) {
-            jssrcnote *sn = main.notes.begin();
-            diff -= SN_IS_XDELTA(sn)
-                    ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK)
-                    : SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
-        }
-        if (diff > 0)
-            cnt += JS_HOWMANY(diff, SN_XDELTA_MASK);
-    }
-    return cnt;
-}
-
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_BytecodeEmitter_h */
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -125,36 +125,36 @@ SN_IS_TERMINATOR(jssrcnote *sn)
                                  ? SN_MAKE_XDELTA(sn, delta)                  \
                                  : SN_MAKE_NOTE(sn, SN_TYPE(sn), delta))
 
 #define SN_DELTA_LIMIT          ((ptrdiff_t)JS_BIT(SN_DELTA_BITS))
 #define SN_XDELTA_LIMIT         ((ptrdiff_t)JS_BIT(SN_XDELTA_BITS))
 
 /*
  * Offset fields follow certain notes and are frequency-encoded: an offset in
- * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffff] takes three, and
+ * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffffff] takes four, and
  * the high bit of the first byte is set.
  */
-#define SN_3BYTE_OFFSET_FLAG    0x80
-#define SN_3BYTE_OFFSET_MASK    0x7f
+#define SN_4BYTE_OFFSET_FLAG    0x80
+#define SN_4BYTE_OFFSET_MASK    0x7f
 
 /*
  * Negative SRC_COLSPAN offsets are rare, but can arise with for(;;) loops and
  * other constructs that generate code in non-source order. They can also arise
  * due to failure to update pn->pn_pos.end to be the last child's end -- such
  * failures are bugs to fix.
  *
  * Source note offsets in general must be non-negative and less than 0x800000,
- * per the above SN_3BYTE_* definitions. To encode negative colspans, we bias
+ * per the above SN_4BYTE_* definitions. To encode negative colspans, we bias
  * them by the offset domain size and restrict non-negative colspans to less
  * than half this domain.
  */
-#define SN_COLSPAN_DOMAIN       ptrdiff_t(SN_3BYTE_OFFSET_FLAG << 16)
+#define SN_COLSPAN_DOMAIN       ptrdiff_t(1 << 23)
 
-#define SN_MAX_OFFSET ((size_t)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16) - 1)
+#define SN_MAX_OFFSET ((size_t)((ptrdiff_t)SN_4BYTE_OFFSET_FLAG << 24) - 1)
 
 #define SN_LENGTH(sn)           ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \
                                  : js_SrcNoteLength(sn))
 #define SN_NEXT(sn)             ((sn) + SN_LENGTH(sn))
 
 struct JSSrcNoteSpec {
     const char      *name;      /* name for disassembly/debugging output */
     int8_t          arity;      /* number of offset operands */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -60,16 +60,23 @@ MarkExactStackRoot(JSTracer *trc, Rooted
       case THING_ROOT_TYPE:        MarkTypeRoot(trc, (types::Type *)addr, "exact-type"); break;
       case THING_ROOT_TYPE_OBJECT: MarkTypeObjectRoot(trc, (types::TypeObject **)addr, "exact-typeobject"); break;
       case THING_ROOT_JIT_CODE:    MarkJitCodeRoot(trc, (jit::JitCode **)addr, "exact-jitcode"); break;
       case THING_ROOT_VALUE:       MarkValueRoot(trc, (Value *)addr, "exact-value"); break;
       case THING_ROOT_ID:          MarkIdRoot(trc, (jsid *)addr, "exact-id"); break;
       case THING_ROOT_PROPERTY_ID: MarkIdRoot(trc, &((js::PropertyId *)addr)->asId(), "exact-propertyid"); break;
       case THING_ROOT_BINDINGS:    ((Bindings *)addr)->trace(trc); break;
       case THING_ROOT_PROPERTY_DESCRIPTOR: ((JSPropertyDescriptor *)addr)->trace(trc); break;
+      case THING_ROOT_CUSTOM: {
+          // 'rooter' is a member within a class containing a vtable. Back up
+          // to the vtable and call trace() through it.
+          const size_t rooterOffset = offsetof(RootedGeneric<void*>, rooter);
+          reinterpret_cast< RootedGeneric<void*>* >(uintptr_t(rooter) - rooterOffset)->trace(trc);
+          break;
+      }
       default: MOZ_ASSUME_UNREACHABLE("Invalid THING_ROOT kind"); break;
     }
 }
 
 static inline void
 MarkExactStackRootList(JSTracer *trc, Rooted<void*> *rooter, ThingRootKind kind)
 {
     while (rooter) {
@@ -588,21 +595,21 @@ AutoGCRooter::traceAllWrappers(JSTracer 
 
 void
 AutoHashableValueRooter::trace(JSTracer *trc)
 {
     MarkValueRoot(trc, reinterpret_cast<Value*>(&value), "AutoHashableValueRooter");
 }
 
 void
-StackShape::AutoRooter::trace(JSTracer *trc)
+StackShape::trace(JSTracer *trc)
 {
-    if (shape->base)
-        MarkBaseShapeRoot(trc, (BaseShape**) &shape->base, "StackShape::AutoRooter base");
-    MarkIdRoot(trc, (jsid*) &shape->propid, "StackShape::AutoRooter id");
+    if (base)
+        MarkBaseShapeRoot(trc, (BaseShape**) &base, "StackShape base");
+    MarkIdRoot(trc, (jsid*) &propid, "StackShape id");
 }
 
 void
 JSPropertyDescriptor::trace(JSTracer *trc)
 {
     if (obj)
         MarkObjectRoot(trc, &obj, "Descriptor::obj");
     MarkValueRoot(trc, &value, "Descriptor::value");
rename from js/src/jit-test/tests/basic/offThreadCompileScript.js
rename to js/src/jit-test/tests/basic/offThreadCompileScript-01.js
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/offThreadCompileScript-02.js
@@ -0,0 +1,17 @@
+// Test offThreadCompileScript option handling.
+
+offThreadCompileScript('Error()');
+assertEq(!!runOffThreadScript().stack.match(/^@<string>:1\n/), true);
+
+offThreadCompileScript('Error()',
+                       { fileName: "candelabra", lineNumber: 6502 });
+assertEq(!!runOffThreadScript().stack.match(/^@candelabra:6502\n/), true);
+
+var element = {};
+offThreadCompileScript('Error()', { element: element }); // shouldn't crash
+runOffThreadScript();
+
+var elementAttributeName = "molybdenum";
+elementAttributeName += elementAttributeName + elementAttributeName + elementAttributeName;
+offThreadCompileScript('Error()', { elementAttributeName: elementAttributeName }); // shouldn't crash
+runOffThreadScript();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-findScripts-19.js
@@ -0,0 +1,5 @@
+var g = newGlobal();
+var dbg = new Debugger(g);
+try { g.eval('function drag(ev) {'); } catch (ex) { }
+for (s of dbg.findScripts())
+  s.lineCount;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-01.js
@@ -0,0 +1,20 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+
+var g = newGlobal();
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+  log += 'd';
+  assertEq(frame.script.global, gw);
+}
+
+g.eval('debugger;');
+assertEq(log, 'd');
+
+g.eval('function f() { debugger; }');
+g.f();
+assertEq(log, 'dd');
+
+assertEq(gw.getOwnPropertyDescriptor('f').value.global, gw);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Script-global-02.js
@@ -0,0 +1,40 @@
+// Debugger.Script.prototype.script returns the global the script runs in.
+// Multi-global version.
+
+var dbg = new Debugger;
+
+var g1 = newGlobal();
+var g1w = dbg.addDebuggee(g1);
+
+var g2 = newGlobal();
+var g2w = dbg.addDebuggee(g2);
+
+var g3 = newGlobal();
+var g3w = dbg.addDebuggee(g3);
+
+var log = '';
+dbg.onDebuggerStatement = function (frame) {
+  log += 'd';
+  assertEq(frame.script.global, g1w);
+  assertEq(frame.older.script.global, g2w);
+  assertEq(frame.older.older.script.global, g3w);
+  assertEq(frame.older.older.older.script.global, g1w);
+}
+
+g1.eval('function f() { debugger; }');
+
+g2.g1 = g1;
+g2.eval('function g() { g1.f(); }');
+
+g3.g2 = g2;
+g3.eval('function h() { g2.g(); }');
+
+g1.g3 = g3;
+g1.eval('function i() { g3.h(); }');
+
+g1.i();
+assertEq(log, 'd');
+
+assertEq(g1w.getOwnPropertyDescriptor('f').value.global, g1w);
+assertEq(g2w.getOwnPropertyDescriptor('g').value.global, g2w);
+assertEq(g3w.getOwnPropertyDescriptor('h').value.global, g3w);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Source-element-02.js
@@ -0,0 +1,6 @@
+// Specifying an owning element in a cross-global evaluation shouldn't crash.
+// That is, when 'evaluate' switches compartments, it should properly wrap
+// the CompileOptions members that will become cross-compartment
+// references.
+
+evaluate('42 + 1729', { global: newGlobal(), element: {} });
rename from js/src/jit-test/tests/debug/Source-elementProperty.js
rename to js/src/jit-test/tests/debug/Source-elementAttributeName.js
--- a/js/src/jit-test/tests/debug/Source-elementProperty.js
+++ b/js/src/jit-test/tests/debug/Source-elementAttributeName.js
@@ -1,11 +1,11 @@
-// Source.prototype.elementProperty can be a string or undefined.
+// Source.prototype.elementAttributeName can be a string or undefined.
 
 var g = newGlobal('new-compartment');
 var dbg = new Debugger;
 var gw = dbg.addDebuggee(g);
-g.evaluate("function f(x) { return 2*x; }", {elementProperty: "src"});
+g.evaluate("function f(x) { return 2*x; }", {elementAttributeName: "src"});
 var fw = gw.getOwnPropertyDescriptor('f').value;
-assertEq(fw.script.source.elementProperty, "src");
+assertEq(fw.script.source.elementAttributeName, "src");
 g.evaluate("function f(x) { return 2*x; }");
 var fw = gw.getOwnPropertyDescriptor('f').value;
-assertEq(fw.script.source.elementProperty, undefined);
+assertEq(fw.script.source.elementAttributeName, undefined);
--- a/js/src/jit-test/tests/debug/Source-surfaces.js
+++ b/js/src/jit-test/tests/debug/Source-surfaces.js
@@ -18,16 +18,16 @@ assertThrowsInstanceOf(function () {
 assertThrowsInstanceOf(function () {
     Debugger.Source.prototype.element.call({})
 }, TypeError);
 assertThrowsInstanceOf(function () {
     Debugger.Source.prototype.element.call(Debugger.Source.prototype)
 }, TypeError);
 
 assertThrowsInstanceOf(function () {
-    Debugger.Source.prototype.elementProperty.call(42)
+    Debugger.Source.prototype.elementAttributeName.call(42)
 }, TypeError);
 assertThrowsInstanceOf(function () {
-    Debugger.Source.prototype.elementProperty.call({})
+    Debugger.Source.prototype.elementAttributeName.call({})
 }, TypeError);
 assertThrowsInstanceOf(function () {
-    Debugger.Source.prototype.elementProperty.call(Debugger.Source.prototype)
+    Debugger.Source.prototype.elementAttributeName.call(Debugger.Source.prototype)
 }, TypeError);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -4095,16 +4095,18 @@ IonBuilder::selectInliningTargets(Object
           case InliningDecision_Error:
             return false;
           case InliningDecision_DontInline:
             inlineable = false;
             break;
           case InliningDecision_Inline:
             inlineable = true;
             break;
+          default:
+            MOZ_ASSUME_UNREACHABLE("Unhandled InliningDecision value!");
         }
 
         // Enforce a maximum inlined bytecode limit at the callsite.
         if (inlineable && target->isInterpreted()) {
             totalSize += target->nonLazyScript()->length();
             if (totalSize > optimizationInfo().inlineMaxTotalBytecodeLength())
                 inlineable = false;
         }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4304,17 +4304,17 @@ JS::ReadOnlyCompileOptions::originPrinci
 {
     return NormalizeOriginPrincipals(principals_, originPrincipals_);
 }
 
 JS::OwningCompileOptions::OwningCompileOptions(JSContext *cx)
     : ReadOnlyCompileOptions(),
       runtime(GetRuntime(cx)),
       elementRoot(cx),
-      elementPropertyRoot(cx)
+      elementAttributeNameRoot(cx)
 {
 }
 
 JS::OwningCompileOptions::~OwningCompileOptions()
 {
     if (principals_)
         JS_DropPrincipals(runtime, principals_);
     if (originPrincipals_)
@@ -4334,29 +4334,38 @@ JS::OwningCompileOptions::copy(JSContext
     setOriginPrincipals(rhs.originPrincipals());
     setElement(rhs.element());
 
     return (setFileAndLine(cx, rhs.filename(), rhs.lineno) &&
             setSourceMapURL(cx, rhs.sourceMapURL()));
 }
 
 bool
-JS::OwningCompileOptions::setFileAndLine(JSContext *cx, const char *f, unsigned l)
+JS::OwningCompileOptions::setFile(JSContext *cx, const char *f)
 {
     char *copy = nullptr;
     if (f) {
         copy = JS_strdup(cx, f);
         if (!copy)
             return false;
     }
 
     // OwningCompileOptions always owns filename_, so this cast is okay.
     js_free(const_cast<char *>(filename_));
 
     filename_ = copy;
+    return true;
+}
+
+bool
+JS::OwningCompileOptions::setFileAndLine(JSContext *cx, const char *f, unsigned l)
+{
+    if (!setFile(cx, f))
+        return false;
+
     lineno = l;
     return true;
 }
 
 bool
 JS::OwningCompileOptions::setSourceMapURL(JSContext *cx, const jschar *s)
 {
     jschar *copy = nullptr;
@@ -4368,29 +4377,53 @@ JS::OwningCompileOptions::setSourceMapUR
 
     // OwningCompileOptions always owns sourceMapURL_, so this cast is okay.
     js_free(const_cast<jschar *>(sourceMapURL_));
 
     sourceMapURL_ = copy;
     return true;
 }
 
+bool
+JS::OwningCompileOptions::wrap(JSContext *cx, JSCompartment *compartment)
+{
+    if (!compartment->wrap(cx, &elementRoot))
+        return false;
+    if (elementAttributeNameRoot) {
+        if (!compartment->wrap(cx, elementAttributeNameRoot.address()))
+            return false;
+    }
+    return true;
+}
+
 JS::CompileOptions::CompileOptions(JSContext *cx, JSVersion version)
-    : ReadOnlyCompileOptions(), elementRoot(cx), elementPropertyRoot(cx)
+    : ReadOnlyCompileOptions(), elementRoot(cx), elementAttributeNameRoot(cx)
 {
     this->version = (version != JSVERSION_UNKNOWN) ? version : cx->findVersion();
 
     compileAndGo = false;
     noScriptRval = cx->options().noScriptRval();
     strictOption = cx->options().strictMode();
     extraWarningsOption = cx->options().extraWarnings();
     werrorOption = cx->options().werror();
     asmJSOption = cx->options().asmJS();
 }
 
+bool
+JS::CompileOptions::wrap(JSContext *cx, JSCompartment *compartment)
+{
+    if (!compartment->wrap(cx, &elementRoot))
+        return false;
+    if (elementAttributeNameRoot) {
+        if (!compartment->wrap(cx, elementAttributeNameRoot.address()))
+            return false;
+    }
+    return true;
+}
+
 JSScript *
 JS::Compile(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options,
             const jschar *chars, size_t length)
 {
     JS_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3477,17 +3477,17 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
   public:
     // Read-only accessors for non-POD options. The proper way to set these
     // depends on the derived type.
     JSPrincipals *principals() const { return principals_; }
     JSPrincipals *originPrincipals() const;
     const char *filename() const { return filename_; }
     const jschar *sourceMapURL() const { return sourceMapURL_; }
     virtual JSObject *element() const = 0;
-    virtual JSString *elementProperty() const = 0;
+    virtual JSString *elementAttributeName() const = 0;
 
     // POD options.
     JSVersion version;
     bool versionSet;
     bool utf8;
     unsigned lineno;
     unsigned column;
     bool compileAndGo;
@@ -3501,16 +3501,20 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
     bool asmJSOption;
     bool forceAsync;
     enum SourcePolicy {
         NO_SOURCE,
         LAZY_SOURCE,
         SAVE_SOURCE
     } sourcePolicy;
 
+    // Wrap any compilation option values that need it as appropriate for
+    // use from |compartment|.
+    virtual bool wrap(JSContext *cx, JSCompartment *compartment) = 0;
+
   private:
     static JSObject * const nullObjectPtr;
     void operator=(const ReadOnlyCompileOptions &) MOZ_DELETE;
 };
 
 /*
  * Compilation options, with dynamic lifetime. An instance of this type
  * makes a copy of / holds / roots all dynamically allocated resources
@@ -3523,39 +3527,47 @@ class JS_FRIEND_API(ReadOnlyCompileOptio
  * or indirectly, by a JavaScript object: if any value that this roots ever
  * comes to refer to the object that owns this, then the whole cycle, and
  * anything else it entrains, will never be freed.
  */
 class JS_FRIEND_API(OwningCompileOptions) : public ReadOnlyCompileOptions
 {
     JSRuntime *runtime;
     PersistentRootedObject elementRoot;
-    PersistentRootedString elementPropertyRoot;
+    PersistentRootedString elementAttributeNameRoot;
 
   public:
     // A minimal constructor, for use with OwningCompileOptions::copy. This
     // leaves |this.version| set to JSVERSION_UNKNOWN; the instance
     // shouldn't be used until we've set that to something real (as |copy|
     // will).
     explicit OwningCompileOptions(JSContext *cx);
     ~OwningCompileOptions();
 
     JSObject *element() const MOZ_OVERRIDE { return elementRoot; }
-    JSString *elementProperty() const MOZ_OVERRIDE { return elementPropertyRoot; }
+    JSString *elementAttributeName() const MOZ_OVERRIDE { return elementAttributeNameRoot; }
 
     // Set this to a copy of |rhs|. Return false on OOM.
     bool copy(JSContext *cx, const ReadOnlyCompileOptions &rhs);
 
     /* These setters make copies of their string arguments, and are fallible. */
+    bool setFile(JSContext *cx, const char *f);
     bool setFileAndLine(JSContext *cx, const char *f, unsigned l);
     bool setSourceMapURL(JSContext *cx, const jschar *s);
 
     /* These setters are infallible, and can be chained. */
-    OwningCompileOptions &setElement(JSObject *e)         { elementRoot = e;         return *this; }
-    OwningCompileOptions &setElementProperty(JSString *p) { elementPropertyRoot = p; return *this; }
+    OwningCompileOptions &setLine(unsigned l)             { lineno = l;              return *this; }
+    OwningCompileOptions &setElement(JSObject *e) {
+        elementRoot = e;
+        return *this;
+    }
+    OwningCompileOptions &setElementAttributeName(JSString *p) {
+        elementAttributeNameRoot = p;
+        return *this;
+    }
     OwningCompileOptions &setPrincipals(JSPrincipals *p) {
         if (p) JS_HoldPrincipals(p);
         if (principals_) JS_DropPrincipals(runtime, principals_);
         principals_ = p;
         return *this;
     }
     OwningCompileOptions &setOriginPrincipals(JSPrincipals *p) {
         if (p) JS_HoldPrincipals(p);
@@ -3571,55 +3583,65 @@ class JS_FRIEND_API(OwningCompileOptions
     OwningCompileOptions &setUTF8(bool u) { utf8 = u; return *this; }
     OwningCompileOptions &setColumn(unsigned c) { column = c; return *this; }
     OwningCompileOptions &setCompileAndGo(bool cng) { compileAndGo = cng; return *this; }
     OwningCompileOptions &setForEval(bool eval) { forEval = eval; return *this; }
     OwningCompileOptions &setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; }
     OwningCompileOptions &setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; }
     OwningCompileOptions &setCanLazilyParse(bool clp) { canLazilyParse = clp; return *this; }
     OwningCompileOptions &setSourcePolicy(SourcePolicy sp) { sourcePolicy = sp; return *this; }
+
+    virtual bool wrap(JSContext *cx, JSCompartment *compartment) MOZ_OVERRIDE;
 };
 
 /*
  * Compilation options stored on the stack. An instance of this type
  * simply holds references to dynamically allocated resources (element;
  * filename; source map URL) that are owned by something else. If you
  * create an instance of this type, it's up to you to guarantee that
  * everything you store in it will outlive it.
  */
 class MOZ_STACK_CLASS JS_FRIEND_API(CompileOptions) : public ReadOnlyCompileOptions
 {
     RootedObject elementRoot;
-    RootedString elementPropertyRoot;
+    RootedString elementAttributeNameRoot;
 
   public:
     explicit CompileOptions(JSContext *cx, JSVersion version = JSVERSION_UNKNOWN);
     CompileOptions(js::ContextFriendFields *cx, const ReadOnlyCompileOptions &rhs)
-      : ReadOnlyCompileOptions(), elementRoot(cx), elementPropertyRoot(cx)
+      : ReadOnlyCompileOptions(), elementRoot(cx), elementAttributeNameRoot(cx)
     {
         copyPODOptions(rhs);
 
         principals_ = rhs.principals();
         originPrincipals_ = rhs.originPrincipals();
         filename_ = rhs.filename();
         sourceMapURL_ = rhs.sourceMapURL();
         elementRoot = rhs.element();
-        elementPropertyRoot = rhs.elementProperty();
+        elementAttributeNameRoot = rhs.elementAttributeName();
     }
 
     JSObject *element() const MOZ_OVERRIDE { return elementRoot; }
-    JSString *elementProperty() const MOZ_OVERRIDE { return elementPropertyRoot; }
-
+    JSString *elementAttributeName() const MOZ_OVERRIDE { return elementAttributeNameRoot; }
+
+    CompileOptions &setFile(const char *f) { filename_ = f; return *this; }
+    CompileOptions &setLine(unsigned l) { lineno = l; return *this; }
     CompileOptions &setFileAndLine(const char *f, unsigned l) {
         filename_ = f; lineno = l; return *this;
     }
     CompileOptions &setSourceMapURL(const jschar *s) { sourceMapURL_ = s;       return *this; }
     CompileOptions &setElement(JSObject *e)          { elementRoot = e;         return *this; }
-    CompileOptions &setElementProperty(JSString *p)  { elementPropertyRoot = p; return *this; }
-    CompileOptions &setPrincipals(JSPrincipals *p)   { principals_ = p;         return *this; }
+    CompileOptions &setElementAttributeName(JSString *p) {
+        elementAttributeNameRoot = p;
+        return *this;
+    }
+    CompileOptions &setPrincipals(JSPrincipals *p) {
+        principals_ = p;
+        return *this;
+    }
     CompileOptions &setOriginPrincipals(JSPrincipals *p) {
         originPrincipals_ = p;
         return *this;
     }
     CompileOptions &setVersion(JSVersion v) {
         version = v;
         versionSet = true;
         return *this;
@@ -3627,16 +3649,18 @@ class MOZ_STACK_CLASS JS_FRIEND_API(Comp
     CompileOptions &setUTF8(bool u) { utf8 = u; return *this; }
     CompileOptions &setColumn(unsigned c) { column = c; return *this; }
     CompileOptions &setCompileAndGo(bool cng) { compileAndGo = cng; return *this; }
     CompileOptions &setForEval(bool eval) { forEval = eval; return *this; }
     CompileOptions &setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; }
     CompileOptions &setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; }
     CompileOptions &setCanLazilyParse(bool clp) { canLazilyParse = clp; return *this; }
     CompileOptions &setSourcePolicy(SourcePolicy sp) { sourcePolicy = sp; return *this; }
+
+    virtual bool wrap(JSContext *cx, JSCompartment *compartment) MOZ_OVERRIDE;
 };
 
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::HandleObject obj, const ReadOnlyCompileOptions &options,
         const char *bytes, size_t length);
 
 extern JS_PUBLIC_API(JSScript *)
 Compile(JSContext *cx, JS::HandleObject obj, const ReadOnlyCompileOptions &options,
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1125,24 +1125,24 @@ JSObject::sealOrFreeze(JSContext *cx, Ha
         AutoShapeVector shapes(cx);
         for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) {
             if (!shapes.append(&r.front()))
                 return false;
         }
         Reverse(shapes.begin(), shapes.end());
 
         for (size_t i = 0; i < shapes.length(); i++) {
-            StackShape child(shapes[i]);
-            StackShape::AutoRooter rooter(cx, &child);
-            child.attrs |= getSealedOrFrozenAttributes(child.attrs, it);
-
-            if (!JSID_IS_EMPTY(child.propid) && it == FREEZE)
-                MarkTypePropertyNonWritable(cx, obj, child.propid);
-
-            last = cx->compartment()->propertyTree.getChild(cx, last, child);
+            StackShape unrootedChild(shapes[i]);
+            RootedGeneric<StackShape*> child(cx, &unrootedChild);
+            child->attrs |= getSealedOrFrozenAttributes(child->attrs, it);
+
+            if (!JSID_IS_EMPTY(child->propid) && it == FREEZE)
+                MarkTypePropertyNonWritable(cx, obj, child->propid);
+
+            last = cx->compartment()->propertyTree.getChild(cx, last, *child);
             if (!last)
                 return false;
         }
 
         JS_ASSERT(obj->lastProperty()->slotSpan() == last->slotSpan());
         JS_ALWAYS_TRUE(setLastProperty(cx, obj, last));
     } else {
         RootedId id(cx);
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -182,18 +182,18 @@ SET_UINT32_INDEX(jsbytecode *pc, uint32_
                                   (uint32_t((pc)[2]) << 16) |                 \
                                   (uint32_t((pc)[3]) << 8)  |                 \
                                   uint32_t((pc)[4])))
 #define SET_INT32(pc,i)         ((pc)[1] = (jsbytecode)(uint32_t(i) >> 24),   \
                                  (pc)[2] = (jsbytecode)(uint32_t(i) >> 16),   \
                                  (pc)[3] = (jsbytecode)(uint32_t(i) >> 8),    \
                                  (pc)[4] = (jsbytecode)uint32_t(i))
 
-/* Index limit is determined by SN_3BYTE_OFFSET_FLAG, see frontend/BytecodeEmitter.h. */
-#define INDEX_LIMIT_LOG2        23
+/* Index limit is determined by SN_4BYTE_OFFSET_FLAG, see frontend/BytecodeEmitter.h. */
+#define INDEX_LIMIT_LOG2        31
 #define INDEX_LIMIT             (uint32_t(1) << INDEX_LIMIT_LOG2)
 
 #define ARGC_HI(argc)           UINT16_HI(argc)
 #define ARGC_LO(argc)           UINT16_LO(argc)
 #define GET_ARGC(pc)            GET_UINT16(pc)
 #define ARGC_LIMIT              UINT16_LIMIT
 
 #define GET_ARGNO(pc)           GET_UINT16(pc)
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -121,17 +121,17 @@ Shape::removeChild(Shape *child)
         Shape *otherChild = r.front();
         JS_ASSERT((r.popFront(), r.empty()));    /* No more elements! */
         kidp->setShape(otherChild);
         js_delete(hash);
     }
 }
 
 Shape *
-PropertyTree::getChild(ExclusiveContext *cx, Shape *parentArg, const StackShape &child)
+PropertyTree::getChild(ExclusiveContext *cx, Shape *parentArg, StackShape &unrootedChild)
 {
     RootedShape parent(cx, parentArg);
     JS_ASSERT(parent);
 
     Shape *existingShape = nullptr;
 
     /*
      * The property tree has extremely low fan-out below its root in
@@ -139,21 +139,21 @@ PropertyTree::getChild(ExclusiveContext 
      * defining closures that capture a constructor's environment as
      * getters or setters on the new object that is passed in as
      * |this| can significantly increase fan-out below the property
      * tree root -- see bug 335700 for details.
      */
     KidsPointer *kidp = &parent->kids;
     if (kidp->isShape()) {
         Shape *kid = kidp->toShape();
-        if (kid->matches(child))
-            existingShape = kid;
+        if (kid->matches(unrootedChild))
+        existingShape = kid;
     } else if (kidp->isHash()) {
-        if (KidsHash::Ptr p = kidp->toHash()->lookup(child))
-            existingShape = *p;
+        if (KidsHash::Ptr p = kidp->toHash()->lookup(unrootedChild))
+        existingShape = *p;
     } else {
         /* If kidp->isNull(), we always insert. */
     }
 
 #ifdef JSGC_INCREMENTAL
     if (existingShape) {
         JS::Zone *zone = existingShape->zone();
         if (zone->needsBarrier()) {
@@ -176,23 +176,23 @@ PropertyTree::getChild(ExclusiveContext 
             existingShape = nullptr;
         }
     }
 #endif
 
     if (existingShape)
         return existingShape;
 
-    StackShape::AutoRooter childRoot(cx, &child);
+    RootedGeneric<StackShape*> child(cx, &unrootedChild);
 
     Shape *shape = newShape(cx);
     if (!shape)
         return nullptr;
 
-    new (shape) Shape(child, parent->numFixedSlots());
+    new (shape) Shape(*child, parent->numFixedSlots());
 
     if (!insertChild(cx, parent, shape))
         return nullptr;
 
     return shape;
 }
 
 Shape *
--- a/js/src/jspropertytree.h
+++ b/js/src/jspropertytree.h
@@ -92,15 +92,15 @@ class PropertyTree
     PropertyTree(JSCompartment *comp)
         : compartment_(comp)
     {
     }
 
     JSCompartment *compartment() { return compartment_; }
 
     Shape *newShape(ExclusiveContext *cx);
-    Shape *getChild(ExclusiveContext *cx, Shape *parent, const StackShape &child);
+    Shape *getChild(ExclusiveContext *cx, Shape *parent, StackShape &child);
     Shape *lookupChild(ThreadSafeContext *cx, Shape *parent, const StackShape &child);
 };
 
 } /* namespace js */
 
 #endif /* jspropertytree_h */
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -305,16 +305,17 @@ enum ThingRootKind
     THING_ROOT_SCRIPT,
     THING_ROOT_LAZY_SCRIPT,
     THING_ROOT_ID,
     THING_ROOT_PROPERTY_ID,
     THING_ROOT_VALUE,
     THING_ROOT_TYPE,
     THING_ROOT_BINDINGS,
     THING_ROOT_PROPERTY_DESCRIPTOR,
+    THING_ROOT_CUSTOM,
     THING_ROOT_LIMIT
 };
 
 /*
  * This list enumerates the different types of conceptual stacks we have in
  * SpiderMonkey. In reality, they all share the C stack, but we allow different
  * stack limits depending on the type of code running.
  */
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -569,17 +569,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         RootedScriptSource sourceObject(cx);
         if (scriptBits & (1 << OwnSource)) {
             ScriptSource *ss = cx->new_<ScriptSource>(xdr->originPrincipals());
             if (!ss)
                 return false;
             /*
              * We use this CompileOptions only to initialize the
              * ScriptSourceObject. Most CompileOptions fields aren't used by
-             * ScriptSourceObject, and those that are (element; elementProperty)
+             * ScriptSourceObject, and those that are (element; elementAttributeName)
              * aren't preserved by XDR. So this can be simple.
              */
             CompileOptions options(cx);
             sourceObject = ScriptSourceObject::create(cx, ss, options);
             if (!sourceObject)
                 return false;
         } else {
             JS_ASSERT(enclosingScript);
@@ -985,18 +985,25 @@ ScriptSourceObject::setSource(ScriptSour
 }
 
 JSObject *
 ScriptSourceObject::element() const
 {
     return getReservedSlot(ELEMENT_SLOT).toObjectOrNull();
 }
 
+void
+ScriptSourceObject::initElement(HandleObject element)
+{
+    JS_ASSERT(getReservedSlot(ELEMENT_SLOT).isNull());
+    setReservedSlot(ELEMENT_SLOT, ObjectOrNullValue(element));
+}
+
 const Value &
-ScriptSourceObject::elementProperty() const
+ScriptSourceObject::elementAttributeName() const
 {
     const Value &prop = getReservedSlot(ELEMENT_PROPERTY_SLOT);
     JS_ASSERT(prop.isUndefined() || prop.isString());
     return prop;
 }
 
 void
 ScriptSourceObject::finalize(FreeOp *fop, JSObject *obj)
@@ -1026,18 +1033,18 @@ ScriptSourceObject::create(ExclusiveCont
     RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, nullptr, cx->global()));
     if (!object)
         return nullptr;
     RootedScriptSource sourceObject(cx, &object->as<ScriptSourceObject>());
 
     source->incref();
     sourceObject->initSlot(SOURCE_SLOT, PrivateValue(source));
     sourceObject->initSlot(ELEMENT_SLOT, ObjectOrNullValue(options.element()));
-    if (options.elementProperty())
-        sourceObject->initSlot(ELEMENT_PROPERTY_SLOT, StringValue(options.elementProperty()));
+    if (options.elementAttributeName())
+        sourceObject->initSlot(ELEMENT_PROPERTY_SLOT, StringValue(options.elementAttributeName()));
     else
         sourceObject->initSlot(ELEMENT_PROPERTY_SLOT, UndefinedValue());
 
     return sourceObject;
 }
 
 static const unsigned char emptySource[] = "";
 
@@ -2276,26 +2283,23 @@ js::PCToLineNumber(JSScript *script, jsb
 {
     /* Cope with StackFrame.pc value prior to entering js_Interpret. */
     if (!pc)
         return 0;
 
     return PCToLineNumber(script->lineno(), script->notes(), script->code(), pc, columnp);
 }
 
-/* The line number limit is the same as the jssrcnote offset limit. */
-#define SN_LINE_LIMIT   (SN_3BYTE_OFFSET_FLAG << 16)
-
 jsbytecode *
 js_LineNumberToPC(JSScript *script, unsigned target)
 {
     ptrdiff_t offset = 0;
     ptrdiff_t best = -1;
     unsigned lineno = script->lineno();
-    unsigned bestdiff = SN_LINE_LIMIT;
+    unsigned bestdiff = SN_MAX_OFFSET;
     for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
         /*
          * Exact-match only if offset is not in the prolog; otherwise use
          * nearest greater-or-equal line number match.
          */
         if (lineno == target && offset >= ptrdiff_t(script->mainOffset()))
             goto out;
         if (lineno >= target) {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -492,17 +492,18 @@ class ScriptSourceObject : public JSObje
         AutoThreadSafeAccess ts1(lastProperty());
         AutoThreadSafeAccess ts2(lastProperty()->base());
         return static_cast<ScriptSource *>(getReservedSlot(SOURCE_SLOT).toPrivate());
     }
 
     void setSource(ScriptSource *source);
 
     JSObject *element() const;
-    const Value &elementProperty() const;
+    void initElement(HandleObject element);
+    const Value &elementAttributeName() const;
 
   private:
     static const uint32_t SOURCE_SLOT = 0;
     static const uint32_t ELEMENT_SLOT = 1;
     static const uint32_t ELEMENT_PROPERTY_SLOT = 2;
     static const uint32_t RESERVED_SLOTS = 3;
 };
 
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -185,48 +185,56 @@ static const JSClass workerGlobalClass =
     JS_EnumerateStub, JS_ResolveStub,
     JS_ConvertStub,   nullptr
 };
 
 ParseTask::ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
                      const jschar *chars, size_t length, JSObject *scopeChain,
                      JS::OffThreadCompileCallback callback, void *callbackData)
   : cx(cx), options(initCx), chars(chars), length(length),
-    alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(scopeChain),
-    exclusiveContextGlobal(exclusiveContextGlobal), callback(callback),
+    alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), scopeChain(initCx, scopeChain),
+    exclusiveContextGlobal(initCx, exclusiveContextGlobal), optionsElement(initCx), callback(callback),
     callbackData(callbackData), script(nullptr), errors(cx), overRecursed(false)
 {
-    JSRuntime *rt = scopeChain->runtimeFromMainThread();
-
-    if (!AddObjectRoot(rt, &this->scopeChain, "ParseTask::scopeChain"))
-        MOZ_CRASH();
-    if (!AddObjectRoot(rt, &this->exclusiveContextGlobal, "ParseTask::exclusiveContextGlobal"))
-        MOZ_CRASH();
 }
 
 bool
 ParseTask::init(JSContext *cx, const ReadOnlyCompileOptions &options)
 {
-    return this->options.copy(cx, options);
+    if (!this->options.copy(cx, options))
+        return false;
+
+    // Save those compilation options that the ScriptSourceObject can't
+    // point at while it's in the compilation's temporary compartment.
+    optionsElement = this->options.element();
+    this->options.setElement(nullptr);
+
+    return true;
 }
 
 void
 ParseTask::activate(JSRuntime *rt)
 {
     rt->setUsedByExclusiveThread(exclusiveContextGlobal->zone());
     cx->enterCompartment(exclusiveContextGlobal->compartment());
 }
 
+void
+ParseTask::finish()
+{
+    if (script) {
+        // Initialize the ScriptSourceObject slots that we couldn't while the SSO
+        // was in the temporary compartment.
+        ScriptSourceObject &sso = script->sourceObject()->as<ScriptSourceObject>();
+        sso.initElement(optionsElement);
+    }
+}
+
 ParseTask::~ParseTask()
 {
-    JSRuntime *rt = scopeChain->runtimeFromMainThread();
-
-    JS_RemoveObjectRootRT(rt, &scopeChain);
-    JS_RemoveObjectRootRT(rt, &exclusiveContextGlobal);
-
     // ParseTask takes over ownership of its input exclusive context.
     js_delete(cx);
 
     for (size_t i = 0; i < errors.length(); i++)
         js_delete(errors[i]);
 }
 
 bool
@@ -646,16 +654,17 @@ WorkerThreadState::finishParseTask(JSCon
 
         // Note: this is safe to do without requiring the compilation lock, as
         // the new type is not yet available to compilation threads.
         object->setProtoUnchecked(newProto);
     }
 
     // Move the parsed script and all its contents into the desired compartment.
     gc::MergeCompartments(parseTask->cx->compartment(), parseTask->scopeChain->compartment());
+    parseTask->finish();
 
     RootedScript script(rt, parseTask->script);
 
     // If we have a context, report any error or warnings generated during the
     // parse, and inform the debugger about the compiled scripts.
     if (maybecx) {
         AutoCompartment ac(maybecx, parseTask->scopeChain);
         for (size_t i = 0; i < parseTask->errors.length(); i++)
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -337,20 +337,27 @@ struct ParseTask
     OwningCompileOptions options;
     const jschar *chars;
     size_t length;
     LifoAlloc alloc;
 
     // Rooted pointer to the scope in the target compartment which the
     // resulting script will be merged into. This is not safe to use off the
     // main thread.
-    JSObject *scopeChain;
+    PersistentRootedObject scopeChain;
 
     // Rooted pointer to the global object used by 'cx'.
-    JSObject *exclusiveContextGlobal;
+    PersistentRootedObject exclusiveContextGlobal;
+
+    // Saved GC-managed CompileOptions fields that will populate slots in
+    // the ScriptSourceObject. We create the ScriptSourceObject in the
+    // compilation's temporary compartment, so storing these values there
+    // at that point would create cross-compartment references. Instead we
+    // hold them here, and install them after merging the compartments.
+    PersistentRootedObject optionsElement;
 
     // Callback invoked off the main thread when the parse finishes.
     JS::OffThreadCompileCallback callback;
     void *callbackData;
 
     // Holds the final script between the invocation of the callback and the
     // point where FinishOffThreadScript is called, which will destroy the
     // ParseTask.
@@ -362,16 +369,17 @@ struct ParseTask
     bool overRecursed;
 
     ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
               const jschar *chars, size_t length, JSObject *scopeChain,
               JS::OffThreadCompileCallback callback, void *callbackData);
     bool init(JSContext *cx, const ReadOnlyCompileOptions &options);
 
     void activate(JSRuntime *rt);
+    void finish();
 
     ~ParseTask();
 };
 
 // Return whether, if a new parse task was started, it would need to wait for
 // an in-progress GC to complete before starting.
 extern bool
 OffThreadParsingMustWaitForGC(JSRuntime *rt);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -760,16 +760,97 @@ Load(JSContext *cx, unsigned argc, jsval
 }
 
 static bool
 LoadScriptRelativeToScript(JSContext *cx, unsigned argc, jsval *vp)
 {
     return LoadScript(cx, argc, vp, true);
 }
 
+// Populate |options| with the options given by |opts|'s properties. If we
+// need to convert a filename to a C string, let fileNameBytes own the
+// bytes.
+static bool
+ParseCompileOptions(JSContext *cx, CompileOptions &options, HandleObject opts,
+                    JSAutoByteString &fileNameBytes)
+{
+    RootedValue v(cx);
+    RootedString s(cx);
+
+    if (!JS_GetProperty(cx, opts, "compileAndGo", &v))
+        return false;
+    if (!v.isUndefined())
+        options.setCompileAndGo(ToBoolean(v));
+
+    if (!JS_GetProperty(cx, opts, "noScriptRval", &v))
+        return false;
+    if (!v.isUndefined())
+        options.setNoScriptRval(ToBoolean(v));
+
+    if (!JS_GetProperty(cx, opts, "fileName", &v))
+        return false;
+    if (v.isNull()) {
+        options.setFile(nullptr);
+    } else if (!v.isUndefined()) {
+        s = ToString(cx, v);
+        if (!s)
+            return false;
+        char *fileName = fileNameBytes.encodeLatin1(cx, s);
+        if (!fileName)
+            return false;
+        options.setFile(fileName);
+    }
+
+    if (!JS_GetProperty(cx, opts, "element", &v))
+        return false;
+    if (v.isObject())
+        options.setElement(&v.toObject());
+
+    if (!JS_GetProperty(cx, opts, "elementAttributeName", &v))
+        return false;
+    if (!v.isUndefined()) {
+        s = ToString(cx, v);
+        if (!s)
+            return false;
+        options.setElementAttributeName(s);
+    }
+
+    if (!JS_GetProperty(cx, opts, "lineNumber", &v))
+        return false;
+    if (!v.isUndefined()) {
+        uint32_t u;
+        if (!ToUint32(cx, v, &u))
+            return false;
+        options.setLine(u);
+    }
+
+    if (!JS_GetProperty(cx, opts, "sourcePolicy", &v))
+        return false;
+    if (!v.isUndefined()) {
+        JSString *s = ToString(cx, v);
+        if (!s)
+            return false;
+        char *policy = JS_EncodeStringToUTF8(cx, s);
+        if (!policy)
+            return false;
+        if (strcmp(policy, "NO_SOURCE") == 0) {
+            options.setSourcePolicy(CompileOptions::NO_SOURCE);
+        } else if (strcmp(policy, "LAZY_SOURCE") == 0) {
+            options.setSourcePolicy(CompileOptions::LAZY_SOURCE);
+        } else if (strcmp(policy, "SAVE_SOURCE") == 0) {
+            options.setSourcePolicy(CompileOptions::SAVE_SOURCE);
+        } else {
+            JS_ReportError(cx, "bad 'sourcePolicy' option: '%s'", policy);
+            return false;
+        }
+    }
+
+    return true;
+}
+
 class AutoNewContext
 {
   private:
     JSContext *oldcx;
     JSContext *newcx;
     Maybe<JSAutoRequest> newRequest;
     Maybe<AutoCompartment> newCompartment;
 
@@ -844,106 +925,60 @@ Evaluate(JSContext *cx, unsigned argc, j
                              "evaluate");
         return false;
     }
     if (!args[0].isString() || (args.length() == 2 && args[1].isPrimitive())) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
         return false;
     }
 
+    CompileOptions options(cx);
+    JSAutoByteString fileNameBytes;
     bool newContext = false;
-    bool compileAndGo = true;
-    bool noScriptRval = false;
-    const char *fileName = "@evaluate";
-    RootedObject element(cx);
-    RootedString elementProperty(cx);
-    JSAutoByteString fileNameBytes;
     RootedString displayURL(cx);
     RootedString sourceMapURL(cx);
-    unsigned lineNumber = 1;
     RootedObject global(cx, nullptr);
     bool catchTermination = false;
     bool saveFrameChain = false;
     RootedObject callerGlobal(cx, cx->global());
-    CompileOptions::SourcePolicy sourcePolicy = CompileOptions::SAVE_SOURCE;
+
+    options.setFileAndLine("@evaluate", 1);
 
     global = JS_GetGlobalForObject(cx, &args.callee());
     if (!global)
         return false;
 
     if (args.length() == 2) {
         RootedObject opts(cx, &args[1].toObject());
         RootedValue v(cx);
 
+        if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
+            return false;
+
         if (!JS_GetProperty(cx, opts, "newContext", &v))
             return false;
         if (!v.isUndefined())
             newContext = ToBoolean(v);
 
-        if (!JS_GetProperty(cx, opts, "compileAndGo", &v))
-            return false;
-        if (!v.isUndefined())
-            compileAndGo = ToBoolean(v);
-
-        if (!JS_GetProperty(cx, opts, "noScriptRval", &v))
-            return false;
-        if (!v.isUndefined())
-            noScriptRval = ToBoolean(v);
-
-        if (!JS_GetProperty(cx, opts, "fileName", &v))
-            return false;
-        if (v.isNull()) {
-            fileName = nullptr;
-        } else if (!v.isUndefined()) {
-            JSString *s = ToString(cx, v);
-            if (!s)
-                return false;
-            fileName = fileNameBytes.encodeLatin1(cx, s);
-            if (!fileName)
-                return false;
-        }
-
-        if (!JS_GetProperty(cx, opts, "element", &v))
-            return false;
-        if (v.isObject())
-            element = &v.toObject();
-
-        if (!JS_GetProperty(cx, opts, "elementProperty", &v))
-            return false;
-        if (!v.isUndefined()) {
-            elementProperty = ToString(cx, v);
-            if (!elementProperty)
-                return false;
-        }
-
         if (!JS_GetProperty(cx, opts, "displayURL", &v))
             return false;
         if (!v.isUndefined()) {
             displayURL = ToString(cx, v);
             if (!displayURL)
                 return false;
         }
 
         if (!JS_GetProperty(cx, opts, "sourceMapURL", &v))
             return false;
         if (!v.isUndefined()) {
             sourceMapURL = ToString(cx, v);
             if (!sourceMapURL)
                 return false;
         }
 
-        if (!JS_GetProperty(cx, opts, "lineNumber", &v))
-            return false;
-        if (!v.isUndefined()) {
-            uint32_t u;
-            if (!ToUint32(cx, v, &u))
-                return false;
-            lineNumber = u;
-        }
-
         if (!JS_GetProperty(cx, opts, "global", &v))
             return false;
         if (!v.isUndefined()) {
             if (v.isObject()) {
                 global = js::UncheckedUnwrap(&v.toObject());
                 if (!global)
                     return false;
             }
@@ -958,38 +993,16 @@ Evaluate(JSContext *cx, unsigned argc, j
             return false;
         if (!v.isUndefined())
             catchTermination = ToBoolean(v);
 
         if (!JS_GetProperty(cx, opts, "saveFrameChain", &v))
             return false;
         if (!v.isUndefined())
             saveFrameChain = ToBoolean(v);
-
-        if (!JS_GetProperty(cx, opts, "sourcePolicy", &v))
-            return false;
-        if (!v.isUndefined()) {
-            JSString *s = ToString(cx, v);
-            if (!s)
-                return false;
-            char *policy = JS_EncodeStringToUTF8(cx, s);
-            if (!policy)
-                return false;
-            if (strcmp(policy, "NO_SOURCE") == 0) {
-                sourcePolicy = CompileOptions::NO_SOURCE;
-            } else if (strcmp(policy, "LAZY_SOURCE") == 0) {
-                sourcePolicy = CompileOptions::LAZY_SOURCE;
-            } else if (strcmp(policy, "SAVE_SOURCE") == 0) {
-                sourcePolicy = CompileOptions::SAVE_SOURCE;
-            } else {
-                JS_ReportError(cx, "bad 'sourcePolicy' option passed to 'evaluate': '%s'",
-                               policy);
-                return false;
-            }
-        }
     }
 
     RootedString code(cx, args[0].toString());
 
     size_t codeLength;
     const jschar *codeChars = JS_GetStringCharsAndLength(cx, code, &codeLength);
     if (!codeChars)
         return false;
@@ -1004,26 +1017,22 @@ Evaluate(JSContext *cx, unsigned argc, j
     {
         AutoSaveFrameChain asfc(cx);
         if (saveFrameChain && !asfc.save())
             return false;
 
         JSAutoCompartment ac(cx, global);
         RootedScript script(cx);
 
+        if (!options.wrap(cx, cx->compartment()))
+            return false;
+
         {
             JS::AutoSaveContextOptions asco(cx);
-            JS::ContextOptionsRef(cx).setNoScriptRval(noScriptRval);
-
-            CompileOptions options(cx);
-            options.setFileAndLine(fileName, lineNumber)
-                   .setElement(element)
-                   .setElementProperty(elementProperty)
-                   .setSourcePolicy(sourcePolicy)
-                   .setCompileAndGo(compileAndGo);
+            JS::ContextOptionsRef(cx).setNoScriptRval(options.noScriptRval);
 
             script = JS::Compile(cx, global, options, codeChars, codeLength);
             if (!script)
                 return false;
         }
 
         if (displayURL && !script->scriptSource()->hasDisplayURL()) {
             const jschar *durl = JS_GetStringCharsZ(cx, displayURL);
@@ -3293,26 +3302,40 @@ OffThreadCompileScript(JSContext *cx, un
         return false;
     }
     if (!args[0].isString()) {
         const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0]));
         JS_ReportError(cx, "expected string to parse, got %s", typeName);
         return false;
     }
 
-    JSString *scriptContents = args[0].toString();
+    JSAutoByteString fileNameBytes;
     CompileOptions options(cx);
-    options.setFileAndLine("<string>", 1)
-           .setCompileAndGo(true)
+    options.setFileAndLine("<string>", 1);
+
+    if (args.length() >= 2) {
+        if (args[1].isPrimitive()) {
+            JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
+            return false;
+        }
+
+        RootedObject opts(cx, &args[1].toObject());
+        if (!ParseCompileOptions(cx, options, opts, fileNameBytes))
+            return false;
+    }
+
+    // These option settings must override whatever the caller requested.
+    options.setCompileAndGo(true)
            .setSourcePolicy(CompileOptions::SAVE_SOURCE);
 
     // We assume the caller wants caching if at all possible, ignoring
     // heuristics that make sense for a real browser.
     options.forceAsync = true;
 
+    JSString *scriptContents = args[0].toString();
     const jschar *chars = JS_GetStringCharsZ(cx, scriptContents);
     if (!chars)
         return false;
     size_t length = JS_GetStringLength(scriptContents);
 
     if (!JS::CanCompileOffThread(cx, options, length)) {
         JS_ReportError(cx, "cannot compile code on worker thread");
         return false;
@@ -4036,23 +4059,23 @@ static const JSFunctionSpecWithHelp shel
 "      lineNumber: starting line number for error messages and debug info\n"
 "      global: global in which to execute the code\n"
 "      newContext: if true, create and use a new cx (default: false)\n"
 "      saveFrameChain: if true, save the frame chain before evaluating code\n"
 "         and restore it afterwards\n"
 "      catchTermination: if true, catch termination (failure without\n"
 "         an exception value, as for slow scripts or out-of-memory)\n"
 "         and return 'terminated'\n"
-"      element: if present with value |v|, convert |v| to an object |o| mark\n"
-"         the source as being attached to the DOM element |o|. If the\n"
+"      element: if present with value |v|, convert |v| to an object |o| and\n"
+"         mark the source as being attached to the DOM element |o|. If the\n"
 "         property is omitted or |v| is null, don't attribute the source to\n"
 "         any DOM element.\n"
-"      elementProperty: if present and not undefined, the name of property\n"
-"         of 'element' that holds this code. This is what Debugger.Source\n"
-"         .prototype.elementProperty returns.\n"
+"      elementAttributeName: if present and not undefined, the name of\n"
+"         property of 'element' that holds this code. This is what\n"
+"         Debugger.Source.prototype.elementAttributeName returns.\n"
 "      sourceMapURL: if present with value |v|, convert |v| to a string, and\n"
 "         provide that as the code's source map URL. If omitted, attach no\n"
 "         source map URL to the code (although the code may provide one itself,\n"
 "         via a //#sourceMappingURL comment).\n"
 "      sourcePolicy: if present, the value converted to a string must be either\n"
 "         'NO_SOURCE', 'LAZY_SOURCE', or 'SAVE_SOURCE'; use the given source\n"
 "         retention policy for this compilation.\n"),
 
@@ -4231,24 +4254,36 @@ static const JSFunctionSpecWithHelp shel
 "  Parses a string, potentially throwing."),
 
     JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
 "syntaxParse(code)",
 "  Check the syntax of a string, returning success value"),
 
 #ifdef JS_THREADSAFE
     JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
-"offThreadCompileScript(code)",
-"  Trigger an off thread parse/emit for the input string"),
+"offThreadCompileScript(code[, options])",
+"  Compile |code| on a helper thread. To wait for the compilation to finish\n"
+"  and run the code, call |runOffThreadScript|. If present, |options| may\n"
+"  have properties saying how the code should be compiled:\n"
+"      noScriptRval: use the no-script-rval compiler option (default: false)\n"
+"      fileName: filename for error messages and debug info\n"
+"      lineNumber: starting line number for error messages and debug info\n"
+"      element: if present with value |v|, convert |v| to an object |o| and\n"
+"         mark the source as being attached to the DOM element |o|. If the\n"
+"         property is omitted or |v| is null, don't attribute the source to\n"
+"         any DOM element.\n"
+"      elementAttributeName: if present and not undefined, the name of\n"
+"         property of 'element' that holds this code. This is what\n"
+"         Debugger.Source.prototype.elementAttributeName returns.\n"),
 
     JS_FN_HELP("runOffThreadScript", runOffThreadScript, 0, 0,
 "runOffThreadScript()",
 "  Wait for off-thread compilation to complete. If an error occurred,\n"
 "  throw the appropriate exception; otherwise, run the script and return\n"
-               "  its value."),
+"  its value."),
 
 #endif
 
     JS_FN_HELP("timeout", Timeout, 1, 0,
 "timeout([seconds], [func])",
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.\n"
 "  If a second argument is provided, it will be invoked when the timer elapses.\n"),
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2999,16 +2999,29 @@ DebuggerScript_getSourceMapUrl(JSContext
     } else {
         args.rval().setNull();
     }
 
     return true;
 }
 
 static bool
+DebuggerScript_getGlobal(JSContext *cx, unsigned argc, Value *vp)
+{
+    THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get global)", args, obj, script);
+    Debugger *dbg = Debugger::fromChildJSObject(obj);
+
+    RootedValue v(cx, ObjectValue(script->global()));
+    if (!dbg->wrapDebuggeeValue(cx, &v))
+        return false;
+    args.rval().set(v);
+    return true;
+}
+
+static bool
 DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getChildScripts", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
 
     RootedObject result(cx, NewDenseEmptyArray(cx));
     if (!result)
         return false;
@@ -3661,16 +3674,17 @@ static const JSPropertySpec DebuggerScri
     JS_PSG("url", DebuggerScript_getUrl, 0),
     JS_PSG("startLine", DebuggerScript_getStartLine, 0),
     JS_PSG("lineCount", DebuggerScript_getLineCount, 0),
     JS_PSG("source", DebuggerScript_getSource, 0),
     JS_PSG("sourceStart", DebuggerScript_getSourceStart, 0),
     JS_PSG("sourceLength", DebuggerScript_getSourceLength, 0),
     JS_PSG("staticLevel", DebuggerScript_getStaticLevel, 0),
     JS_PSG("sourceMapURL", DebuggerScript_getSourceMapUrl, 0),
+    JS_PSG("global", DebuggerScript_getGlobal, 0),
     JS_PS_END
 };
 
 static const JSFunctionSpec DebuggerScript_methods[] = {
     JS_FN("getChildScripts", DebuggerScript_getChildScripts, 0, 0),
     JS_FN("getAllOffsets", DebuggerScript_getAllOffsets, 0, 0),
     JS_FN("getAllColumnOffsets", DebuggerScript_getAllColumnOffsets, 0, 0),
     JS_FN("getLineOffsets", DebuggerScript_getLineOffsets, 1, 0),
@@ -3873,27 +3887,27 @@ DebuggerSource_getElement(JSContext *cx,
         args.rval().setUndefined();
     }
     return true;
 }
 
 static bool
 DebuggerSource_getElementProperty(JSContext *cx, unsigned argc, Value *vp)
 {
-    THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementProperty)", args, obj, sourceObject);
-    args.rval().set(sourceObject->elementProperty());
+    THIS_DEBUGSOURCE_REFERENT(cx, argc, vp, "(get elementAttributeName)", args, obj, sourceObject);
+    args.rval().set(sourceObject->elementAttributeName());
     return Debugger::fromChildJSObject(obj)->wrapDebuggeeValue(cx, args.rval());
 }
 
 static const JSPropertySpec DebuggerSource_properties[] = {
     JS_PSG("text", DebuggerSource_getText, 0),
     JS_PSG("url", DebuggerSource_getUrl, 0),
     JS_PSG("element", DebuggerSource_getElement, 0),
     JS_PSG("displayURL", DebuggerSource_getDisplayURL, 0),
-    JS_PSG("elementProperty", DebuggerSource_getElementProperty, 0),
+    JS_PSG("elementAttributeName", DebuggerSource_getElementProperty, 0),
     JS_PS_END
 };
 
 static const JSFunctionSpec DebuggerSource_methods[] = {
     JS_FS_END
 };
 
 
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -229,27 +229,11 @@ AutoRooterGetterSetter::AutoRooterGetter
                                                PropertyOp *pgetter, StrictPropertyOp *psetter
                                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
 {
     if (attrs & (JSPROP_GETTER | JSPROP_SETTER))
         inner.construct(cx, attrs, pgetter, psetter);
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-inline
-StackBaseShape::AutoRooter::AutoRooter(ThreadSafeContext *cx, const StackBaseShape *base_
-                                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : CustomAutoRooter(cx), base(base_), skip(cx, base_)
-{
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-}
-
-inline
-StackShape::AutoRooter::AutoRooter(ThreadSafeContext *cx, const StackShape *shape_
-                                   MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : CustomAutoRooter(cx), shape(shape_), skip(cx, shape_)
-{
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-}
-
 } /* namespace js */
 
 #endif /* vm_Shape_inl_h */
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -301,17 +301,17 @@ ShapeTable::grow(ThreadSafeContext *cx)
     if (!change(delta, cx) && entryCount + removedCount == size - 1) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     return true;
 }
 
 /* static */ Shape *
-Shape::replaceLastProperty(ExclusiveContext *cx, const StackBaseShape &base,
+Shape::replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base,
                            TaggedProto proto, HandleShape shape)
 {
     JS_ASSERT(!shape->inDictionary());
 
     if (!shape->parent) {
         /* Treat as resetting the initial property of the shape hierarchy. */
         AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
         return EmptyShape::getInitialShape(cx, base.clasp, proto,
@@ -358,61 +358,61 @@ JSObject::getChildPropertyOnDictionary(T
                       child.slot() == parent->maybeSlot() + 1);
         }
     }
 
     RootedShape shape(cx);
 
     if (obj->inDictionaryMode()) {
         JS_ASSERT(parent == obj->lastProperty());
-        StackShape::AutoRooter childRoot(cx, &child);
+        RootedGeneric<StackShape*> childRoot(cx, &child);
         shape = js_NewGCShape(cx);
         if (!shape)
             return nullptr;
-        if (child.hasSlot() && child.slot() >= obj->lastProperty()->base()->slotSpan()) {
-            if (!JSObject::setSlotSpan(cx, obj, child.slot() + 1))
+        if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) {
+            if (!JSObject::setSlotSpan(cx, obj, childRoot->slot() + 1))
                 return nullptr;
         }
-        shape->initDictionaryShape(child, obj->numFixedSlots(), &obj->shape_);
+        shape->initDictionaryShape(*childRoot, obj->numFixedSlots(), &obj->shape_);
     }
 
     return shape;
 }
 
 /* static */ Shape *
 JSObject::getChildProperty(ExclusiveContext *cx,
-                           HandleObject obj, HandleShape parent, StackShape &child)
+                           HandleObject obj, HandleShape parent, StackShape &unrootedChild)
 {
-    StackShape::AutoRooter childRoot(cx, &child);
-    RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, child));
+    RootedGeneric<StackShape*> child(cx, &unrootedChild);
+    RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child));
 
     if (!obj->inDictionaryMode()) {
-        shape = cx->compartment()->propertyTree.getChild(cx, parent, child);
+        shape = cx->compartment()->propertyTree.getChild(cx, parent, *child);
         if (!shape)
             return nullptr;
         //JS_ASSERT(shape->parent == parent);
         //JS_ASSERT_IF(parent != lastProperty(), parent == lastProperty()->parent);
         if (!JSObject::setLastProperty(cx, obj, shape))
             return nullptr;
     }
 
     return shape;
 }
 
 /* static */ Shape *
 JSObject::lookupChildProperty(ThreadSafeContext *cx,
-                              HandleObject obj, HandleShape parent, StackShape &child)
+                              HandleObject obj, HandleShape parent, StackShape &unrootedChild)
 {
-    StackShape::AutoRooter childRoot(cx, &child);
+    RootedGeneric<StackShape*> child(cx, &unrootedChild);
     JS_ASSERT(cx->isThreadLocal(obj));
 
-    RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, child));
+    RootedShape shape(cx, getChildPropertyOnDictionary(cx, obj, parent, *child));
 
     if (!obj->inDictionaryMode()) {
-        shape = cx->compartment_->propertyTree.lookupChild(cx, parent, child);
+        shape = cx->compartment_->propertyTree.lookupChild(cx, parent, *child);
         if (!shape)
             return nullptr;
         if (!JSObject::setLastProperty(cx, obj, shape))
             return nullptr;
     }
 
     return shape;
 }
@@ -541,17 +541,17 @@ ShouldConvertToDictionary(JSObject *obj)
     if (obj->hadElementsAccess())
         return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
     return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
 }
 
 template <ExecutionMode mode>
 static inline UnownedBaseShape *
 GetOrLookupUnownedBaseShape(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
-                            const StackBaseShape &base)
+                            StackBaseShape &base)
 {
     if (mode == ParallelExecution)
         return BaseShape::lookupUnowned(cx, base);
     return BaseShape::getUnowned(cx->asExclusiveContext(), base);
 }
 
 template <ExecutionMode mode>
 /* static */ Shape *
@@ -1434,59 +1434,59 @@ StackBaseShape::match(UnownedBaseShape *
         && key->clasp == lookup->clasp
         && key->parent == lookup->parent
         && key->metadata == lookup->metadata
         && key->rawGetter == lookup->rawGetter
         && key->rawSetter == lookup->rawSetter;
 }
 
 void
-StackBaseShape::AutoRooter::trace(JSTracer *trc)
+StackBaseShape::trace(JSTracer *trc)
 {
-    if (base->parent) {
-        gc::MarkObjectRoot(trc, (JSObject**)&base->parent,
-                           "StackBaseShape::AutoRooter parent");
+    if (parent) {
+        gc::MarkObjectRoot(trc, (JSObject**)&parent,
+                           "StackBaseShape parent");
     }
-    if (base->metadata) {
-        gc::MarkObjectRoot(trc, (JSObject**)&base->metadata,
-                           "StackBaseShape::AutoRooter metadata");
+    if (metadata) {
+        gc::MarkObjectRoot(trc, (JSObject**)&metadata,
+                           "StackBaseShape metadata");
     }
-    if ((base->flags & BaseShape::HAS_GETTER_OBJECT) && base->rawGetter) {
-        gc::MarkObjectRoot(trc, (JSObject**)&base->rawGetter,
-                           "StackBaseShape::AutoRooter getter");
+    if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) {
+        gc::MarkObjectRoot(trc, (JSObject**)&rawGetter,
+                           "StackBaseShape getter");
     }
-    if ((base->flags & BaseShape::HAS_SETTER_OBJECT) && base->rawSetter) {
-        gc::MarkObjectRoot(trc, (JSObject**)&base->rawSetter,
-                           "StackBaseShape::AutoRooter setter");
+    if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) {
+        gc::MarkObjectRoot(trc, (JSObject**)&rawSetter,
+                           "StackBaseShape setter");
     }
 }
 
 /* static */ UnownedBaseShape*
-BaseShape::getUnowned(ExclusiveContext *cx, const StackBaseShape &base)
+BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base)
 {
     BaseShapeSet &table = cx->compartment()->baseShapes;
 
     if (!table.initialized() && !table.init())
         return nullptr;
 
     DependentAddPtr<BaseShapeSet> p(cx, table, &base);
     if (p)
         return *p;
 
-    StackBaseShape::AutoRooter root(cx, &base);
+    RootedGeneric<StackBaseShape*> root(cx, &base);
 
     BaseShape *nbase_ = js_NewGCBaseShape<CanGC>(cx);
     if (!nbase_)
         return nullptr;
 
-    new (nbase_) BaseShape(base);
+    new (nbase_) BaseShape(*root);
 
     UnownedBaseShape *nbase = static_cast<UnownedBaseShape *>(nbase_);
 
-    if (!p.add(cx, table, &base, nbase))
+    if (!p.add(cx, table, root, nbase))
         return nullptr;
 
     return nbase;
 }
 
 /* static */ UnownedBaseShape *
 BaseShape::lookupUnowned(ThreadSafeContext *cx, const StackBaseShape &base)
 {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -710,17 +710,17 @@ class BaseShape : public gc::BarrieredCe
     void setSlotSpan(uint32_t slotSpan) { JS_ASSERT(isOwned()); slotSpan_ = slotSpan; }
 
     JSCompartment *compartment() const { return compartment_; }
 
     /*
      * Lookup base shapes from the compartment's baseShapes table, adding if
      * not already found.
      */
-    static UnownedBaseShape* getUnowned(ExclusiveContext *cx, const StackBaseShape &base);
+    static UnownedBaseShape* getUnowned(ExclusiveContext *cx, StackBaseShape &base);
 
     /*
      * Lookup base shapes from the compartment's baseShapes table, returning
      * nullptr if not found.
      */
     static UnownedBaseShape *lookupUnowned(ThreadSafeContext *cx, const StackBaseShape &base);
 
     /* Get the canonical base shape. */
@@ -842,29 +842,19 @@ struct StackBaseShape
 
         this->rawGetter = rawGetter;
         this->rawSetter = rawSetter;
     }
 
     static inline HashNumber hash(const StackBaseShape *lookup);
     static inline bool match(UnownedBaseShape *key, const StackBaseShape *lookup);
 
-    class AutoRooter : private JS::CustomAutoRooter
-    {
-      public:
-        inline AutoRooter(ThreadSafeContext *cx, const StackBaseShape *base_
-                          MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-      private:
-        virtual void trace(JSTracer *trc);
-
-        const StackBaseShape *base;
-        SkipRoot skip;
-        MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-    };
+    // For RootedGeneric<StackBaseShape*>
+    static inline js::ThingRootKind rootKind() { return js::THING_ROOT_CUSTOM; }
+    void trace(JSTracer *trc);
 };
 
 inline
 BaseShape::BaseShape(const StackBaseShape &base)
 {
     mozilla::PodZero(this);
     this->clasp = base.clasp;
     this->parent = base.parent;
@@ -956,17 +946,17 @@ class Shape : public gc::BarrieredCell<S
         new (this) Shape(child, nfixed);
         this->flags |= IN_DICTIONARY;
 
         this->listp = nullptr;
         insertIntoDictionary(dictp);
     }
 
     /* Replace the base shape of the last shape in a non-dictionary lineage with base. */
-    static Shape *replaceLastProperty(ExclusiveContext *cx, const StackBaseShape &base,
+    static Shape *replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base,
                                       TaggedProto proto, HandleShape shape);
 
     /*
      * This function is thread safe if every shape in the lineage of |shape|
      * is thread local, which is the case when we clone the entire shape
      * lineage in preparation for converting an object to dictionary mode.
      */
     static bool hashify(ThreadSafeContext *cx, Shape *shape);
@@ -1551,29 +1541,19 @@ struct StackShape
         hash = mozilla::RotateLeft(hash, 4) ^ (flags & Shape::PUBLIC_FLAGS);
         hash = mozilla::RotateLeft(hash, 4) ^ attrs;
         hash = mozilla::RotateLeft(hash, 4) ^ shortid;
         hash = mozilla::RotateLeft(hash, 4) ^ slot_;
         hash = mozilla::RotateLeft(hash, 4) ^ JSID_BITS(propid);
         return hash;
     }
 
-    class AutoRooter : private JS::CustomAutoRooter
-    {
-      public:
-        inline AutoRooter(ThreadSafeContext *cx, const StackShape *shape_
-                          MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-      private:
-        virtual void trace(JSTracer *trc);
-
-        const StackShape *shape;
-        SkipRoot skip;
-        MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-    };
+    // For RootedGeneric<StackShape*>
+    static inline js::ThingRootKind rootKind() { return js::THING_ROOT_CUSTOM; }
+    void trace(JSTracer *trc);
 };
 
 } /* namespace js */
 
 /* js::Shape pointer tag bit indicating a collision. */
 #define SHAPE_COLLISION                 (uintptr_t(1))
 #define SHAPE_REMOVED                   ((js::Shape *) SHAPE_COLLISION)
 
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -166,18 +166,18 @@ File(JSContext *cx, unsigned argc, Value
     MOZ_ASSERT(initializer);
 
     rv = initializer->Initialize(nullptr, cx, nullptr, args);
     if (NS_FAILED(rv)) {
         XPCThrower::Throw(rv, cx);
         return false;
     }
 
-    nsXPConnect* xpc = nsXPConnect::XPConnect();
-    JSObject* glob = CurrentGlobalOrNull(cx);
+    nsXPConnect *xpc = nsXPConnect::XPConnect();
+    JSObject *glob = CurrentGlobalOrNull(cx);
 
     nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
     rv = xpc->WrapNativeToJSVal(cx, glob, native, nullptr,
                                 &NS_GET_IID(nsISupports),
                                 true, args.rval());
     if (NS_FAILED(rv)) {
         XPCThrower::Throw(rv, cx);
         return false;
@@ -201,18 +201,18 @@ Blob(JSContext *cx, unsigned argc, Value
     MOZ_ASSERT(initializer);
 
     rv = initializer->Initialize(nullptr, cx, nullptr, args);
     if (NS_FAILED(rv)) {
         XPCThrower::Throw(rv, cx);
         return false;
     }
 
-    nsXPConnect* xpc = nsXPConnect::XPConnect();
-    JSObject* glob = CurrentGlobalOrNull(cx);
+    nsXPConnect *xpc = nsXPConnect::XPConnect();
+    JSObject *glob = CurrentGlobalOrNull(cx);
 
     nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
     rv = xpc->WrapNativeToJSVal(cx, glob, native, nullptr,
                                 &NS_GET_IID(nsISupports),
                                 true, args.rval());
     if (NS_FAILED(rv)) {
         XPCThrower::Throw(rv, cx);
         return false;
@@ -1136,19 +1136,19 @@ mozJSComponentLoader::Import(const nsACS
         retval.setObject(*global);
     }
     return rv;
 }
 
 /* [noscript] JSObjectPtr importInto(in AUTF8String registryLocation,
                                      in JSObjectPtr targetObj); */
 NS_IMETHODIMP
-mozJSComponentLoader::ImportInto(const nsACString & aLocation,
+mozJSComponentLoader::ImportInto(const nsACString &aLocation,
                                  JSObject *aTargetObj,
-                                 nsAXPCNativeCallContext * cc,
+                                 nsAXPCNativeCallContext *cc,
                                  JSObject **_retval)
 {
     JSContext *callercx;
     nsresult rv = cc->GetJSContext(&callercx);
     NS_ENSURE_SUCCESS(rv, rv);
 
     RootedObject targetObject(callercx, aTargetObj);
     RootedObject global(callercx);
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -88,35 +88,32 @@ ReportError(JSContext *cx, const char *m
 {
     RootedValue exn(cx, JS::StringValue(JS_NewStringCopyZ(cx, msg)));
     JS_SetPendingException(cx, exn);
     return NS_OK;
 }
 
 nsresult
 mozJSSubScriptLoader::ReadScript(nsIURI *uri, JSContext *cx, JSObject *targetObjArg,
-                                 const nsAString& charset, const char *uriStr,
+                                 const nsAString &charset, const char *uriStr,
                                  nsIIOService *serv, nsIPrincipal *principal,
                                  bool reuseGlobal, JSScript **scriptp,
                                  JSFunction **functionp)
 {
     RootedObject target_obj(cx, targetObjArg);
 
-    nsCOMPtr<nsIChannel>     chan;
-    nsCOMPtr<nsIInputStream> instream;
-    JSErrorReporter  er;
-
     *scriptp = nullptr;
     *functionp = nullptr;
 
-    nsresult rv;
     // Instead of calling NS_OpenURI, we create the channel ourselves and call
     // SetContentType, to avoid expensive MIME type lookups (bug 632490).
-    rv = NS_NewChannel(getter_AddRefs(chan), uri, serv,
-                       nullptr, nullptr, nsIRequest::LOAD_NORMAL);
+    nsCOMPtr<nsIChannel> chan;
+    nsCOMPtr<nsIInputStream> instream;
+    nsresult rv = NS_NewChannel(getter_AddRefs(chan), uri, serv,
+                                nullptr, nullptr, nsIRequest::LOAD_NORMAL);
     if (NS_SUCCEEDED(rv)) {
         chan->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
         rv = chan->Open(getter_AddRefs(instream));
     }
 
     if (NS_FAILED(rv)) {
         return ReportError(cx, LOAD_ERROR_NOSTREAM);
     }
@@ -134,17 +131,17 @@ mozJSSubScriptLoader::ReadScript(nsIURI 
 
     nsCString buf;
     rv = NS_ReadInputStreamToString(instream, buf, len);
     if (NS_FAILED(rv))
         return rv;
 
     /* set our own error reporter so we can report any bad things as catchable
      * exceptions, including the source/line number */
-    er = JS_SetErrorReporter(cx, xpc::SystemErrorReporter);
+    JSErrorReporter er = JS_SetErrorReporter(cx, xpc::SystemErrorReporter);
 
     JS::CompileOptions options(cx);
     options.setPrincipals(nsJSPrincipals::get(principal))
            .setFileAndLine(uriStr, 1);
     if (!charset.IsVoid()) {
         nsString script;
         rv = nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf.get()), len,
                                             charset, nullptr, script);
@@ -178,19 +175,19 @@ mozJSSubScriptLoader::ReadScript(nsIURI 
 
     /* repent for our evil deeds */
     JS_SetErrorReporter(cx, er);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
+mozJSSubScriptLoader::LoadSubScript(const nsAString &url,
                                     HandleValue target,
-                                    const nsAString& charset,
+                                    const nsAString &charset,
                                     JSContext *cx,
                                     MutableHandleValue retval)
 {
     /*
      * Loads a local url and evals it into the current cx
      * Synchronous (an async version would be cool too.)
      *   url: The url to load.  Must be local so that it can be loaded
      *        synchronously.
@@ -203,32 +200,32 @@ mozJSSubScriptLoader::LoadSubScript(cons
     LoadSubScriptOptions options(cx);
     options.charset = charset;
     options.target = target.isObject() ? &target.toObject() : nullptr;
     return DoLoadSubScriptWithOptions(url, options, cx, retval);
 }
 
 
 NS_IMETHODIMP
-mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url,
+mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString &url,
                                                HandleValue optionsVal,
                                                JSContext *cx,
                                                MutableHandleValue retval)
 {
     if (!optionsVal.isObject())
         return NS_ERROR_INVALID_ARG;
     LoadSubScriptOptions options(cx, &optionsVal.toObject());
     if (!options.Parse())
         return NS_ERROR_INVALID_ARG;
     return DoLoadSubScriptWithOptions(url, options, cx, retval);
 }
 
 nsresult
-mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
-                                                 LoadSubScriptOptions& options,
+mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString &url,
+                                                 LoadSubScriptOptions &options,
                                                  JSContext *cx,
                                                  MutableHandleValue retval)
 {
     nsresult rv = NS_OK;
 
     /* set the system principal if it's not here already */
     if (!mSystemPrincipal) {
         nsCOMPtr<nsIScriptSecurityManager> secman =
@@ -237,17 +234,17 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
             return NS_OK;
 
         rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal));
         if (NS_FAILED(rv) || !mSystemPrincipal)
             return rv;
     }
 
     RootedObject targetObj(cx);
-    mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+    mozJSComponentLoader *loader = mozJSComponentLoader::Get();
     rv = loader->FindTargetObject(cx, &targetObj);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // We base reusingGlobal off of what the loader told us, but we may not
     // actually be using that object.
     bool reusingGlobal = !JS_IsGlobalObject(targetObj);
 
     if (options.target)
--- a/js/xpconnect/loader/mozJSSubScriptLoader.h
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.h
@@ -28,20 +28,20 @@ public:
     virtual ~mozJSSubScriptLoader();
 
     // all the interface method declarations...
     NS_DECL_ISUPPORTS
     NS_DECL_MOZIJSSUBSCRIPTLOADER
 
 private:
     nsresult ReadScript(nsIURI *uri, JSContext *cx, JSObject *target_obj,
-                        const nsAString& charset, const char *uriStr,
+                        const nsAString &charset, const char *uriStr,
                         nsIIOService *serv, nsIPrincipal *principal,
                         bool reuseGlobal, JSScript **scriptp,
                         JSFunction **functionp);
 
-    nsresult DoLoadSubScriptWithOptions(const nsAString& url,
-                                        LoadSubScriptOptions& options,
-                                        JSContext* cx,
+    nsresult DoLoadSubScriptWithOptions(const nsAString &url,
+                                        LoadSubScriptOptions  &options,
+                                        JSContext *cx,
                                         JS::MutableHandle<JS::Value> retval);
 
     nsCOMPtr<nsIPrincipal> mSystemPrincipal;
 };
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -533,17 +533,17 @@ xpc::HasInstance(JSContext *cx, HandleOb
         return findResult;
 
     return NS_OK;
 }
 
 /* bool hasInstance (in nsIXPConnectWrappedNative wrapper, in JSContextPtr cx, in JSObjectPtr obj, in jsval val, out bool bp); */
 NS_IMETHODIMP
 nsJSIID::HasInstance(nsIXPConnectWrappedNative *wrapper,
-                     JSContext * cx, JSObject * /* unused */,
+                     JSContext *cx, JSObject * /* unused */,
                      HandleValue val, bool *bp, bool *_retval)
 {
     *bp = false;
 
     if (val.isPrimitive())
         return NS_OK;
 
     // we have a JSObject
@@ -666,17 +666,17 @@ GetWrapperObject(MutableHandleObject obj
 
     nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
     ccxp->GetCalleeWrapper(getter_AddRefs(wrapper));
     obj.set(wrapper->GetJSObject());
 }
 
 /* nsISupports createInstance (); */
 NS_IMETHODIMP
-nsJSCID::CreateInstance(HandleValue iidval, JSContext* cx,
+nsJSCID::CreateInstance(HandleValue iidval, JSContext *cx,
                         uint8_t optionalArgc, MutableHandleValue retval)
 {
     if (!mDetails.IsValid())
         return NS_ERROR_XPC_BAD_CID;
 
     RootedObject obj(cx);
     GetWrapperObject(&obj);
     if (!obj) {
@@ -709,68 +709,65 @@ nsJSCID::CreateInstance(HandleValue iidv
     rv = nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, obj, inst, nullptr, iid, true, retval);
     if (NS_FAILED(rv) || retval.isPrimitive())
         return NS_ERROR_XPC_CANT_CREATE_WN;
     return NS_OK;
 }
 
 /* nsISupports getService (); */
 NS_IMETHODIMP
-nsJSCID::GetService(HandleValue iidval, JSContext* cx,
-                    uint8_t optionalArgc, MutableHandleValue retval)
+nsJSCID::GetService(HandleValue iidval, JSContext *cx, uint8_t optionalArgc,
+                    MutableHandleValue retval)
 {
     if (!mDetails.IsValid())
         return NS_ERROR_XPC_BAD_CID;
 
     RootedObject obj(cx);
     GetWrapperObject(&obj);
     if (!obj) {
         return NS_ERROR_UNEXPECTED;
     }
 
-    nsIXPCSecurityManager* sm;
+    nsIXPCSecurityManager *sm;
     sm = nsXPConnect::XPConnect()->GetDefaultSecurityManager();
     if (sm && NS_FAILED(sm->CanCreateInstance(cx, mDetails.ID()))) {
         MOZ_ASSERT(JS_IsExceptionPending(cx),
                    "security manager vetoed GetService without setting exception");
         return NS_OK;
     }
 
     // If an IID was passed in then use it
-    const nsID* iid = GetIIDArg(optionalArgc, iidval, cx);
+    const nsID *iid = GetIIDArg(optionalArgc, iidval, cx);
     if (!iid)
         return NS_ERROR_XPC_BAD_IID;
 
     nsCOMPtr<nsIServiceManager> svcMgr;
     nsresult rv = NS_GetServiceManager(getter_AddRefs(svcMgr));
     if (NS_FAILED(rv))
         return rv;
 
     nsCOMPtr<nsISupports> srvc;
     rv = svcMgr->GetService(mDetails.ID(), *iid, getter_AddRefs(srvc));
     MOZ_ASSERT(NS_FAILED(rv) || srvc, "service manager returned success, but service is null!");
     if (NS_FAILED(rv) || !srvc)
         return NS_ERROR_XPC_GS_RETURNED_FAILURE;
 
-    RootedObject instJSObj(cx);
     nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
     rv = nsXPConnect::XPConnect()->WrapNative(cx, obj, srvc, *iid, getter_AddRefs(holder));
-    if (NS_FAILED(rv) || !holder ||
-        // Assign, not compare
-        !(instJSObj = holder->GetJSObject()))
+    if (NS_FAILED(rv) || !holder || !holder->GetJSObject())
         return NS_ERROR_XPC_CANT_CREATE_WN;
 
-    retval.setObject(*instJSObj);
+    retval.setObject(*holder->GetJSObject());
     return NS_OK;
 }
 
 /* bool construct (in nsIXPConnectWrappedNative wrapper, in JSContextPtr cx, in JSObjectPtr obj, in uint32_t argc, in JSValPtr argv, in JSValPtr vp); */
 NS_IMETHODIMP
 nsJSCID::Construct(nsIXPConnectWrappedNative *wrapper,
-                   JSContext * cx, JSObject * objArg,
+                   JSContext *cx, JSObject *objArg,
                    const CallArgs &args, bool *_retval)
 {
     RootedObject obj(cx, objArg);
     XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
     if (!rt)
         return NS_ERROR_FAILURE;
 
     // 'push' a call context and call on it
@@ -780,99 +777,100 @@ nsJSCID::Construct(nsIXPConnectWrappedNa
 
     *_retval = XPCWrappedNative::CallMethod(ccx);
     return NS_OK;
 }
 
 /* bool hasInstance (in nsIXPConnectWrappedNative wrapper, in JSContextPtr cx, in JSObjectPtr obj, in jsval val, out bool bp); */
 NS_IMETHODIMP
 nsJSCID::HasInstance(nsIXPConnectWrappedNative *wrapper,
-                     JSContext * cx, JSObject * /* unused */,
+                     JSContext *cx, JSObject * /* unused */,
                      HandleValue val, bool *bp, bool *_retval)
 {
     *bp = false;
     nsresult rv = NS_OK;
 
-    if (!JSVAL_IS_PRIMITIVE(val)) {
+    if (val.isObject()) {
         // we have a JSObject
         RootedObject obj(cx, &val.toObject());
 
         MOZ_ASSERT(obj, "when is an object not an object?");
 
         // is this really a native xpcom object with a wrapper?
-        nsIClassInfo* ci = nullptr;
+        nsIClassInfo *ci = nullptr;
         obj = FindObjectForHasInstance(cx, obj);
         if (!obj || !IS_WN_REFLECTOR(obj))
             return rv;
-        if (XPCWrappedNative* other_wrapper = XPCWrappedNative::Get(obj))
+        if (XPCWrappedNative *other_wrapper = XPCWrappedNative::Get(obj))
             ci = other_wrapper->GetClassInfo();
 
         // We consider CID equality to be the thing that matters here.
         // This is perhaps debatable.
         if (ci) {
             nsID cid;
             if (NS_SUCCEEDED(ci->GetClassIDNoAlloc(&cid)))
                 *bp = cid.Equals(mDetails.ID());
         }
     }
+
     return rv;
 }
 
 /***************************************************************************/
 // additional utilities...
 
 JSObject *
 xpc_NewIDObject(JSContext *cx, HandleObject jsobj, const nsID& aID)
 {
     RootedObject obj(cx);
 
     nsCOMPtr<nsIJSID> iid = nsJSID::NewID(aID);
     if (iid) {
-        nsXPConnect* xpc = nsXPConnect::XPConnect();
+        nsXPConnect *xpc = nsXPConnect::XPConnect();
         if (xpc) {
             nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
             nsresult rv = xpc->WrapNative(cx, jsobj,
                                           static_cast<nsISupports*>(iid),
                                           NS_GET_IID(nsIJSID),
                                           getter_AddRefs(holder));
             if (NS_SUCCEEDED(rv) && holder) {
                 obj = holder->GetJSObject();
             }
         }
     }
     return obj;
 }
 
 // note: returned pointer is only valid while |obj| remains alive!
 const nsID*
-xpc_JSObjectToID(JSContext *cx, JSObject* obj)
+xpc_JSObjectToID(JSContext *cx, JSObject *obj)
 {
     if (!cx || !obj)
         return nullptr;
 
     // NOTE: this call does NOT addref
-    XPCWrappedNative* wrapper = nullptr;
+    XPCWrappedNative *wrapper = nullptr;
     obj = js::CheckedUnwrap(obj);
     if (obj && IS_WN_REFLECTOR(obj))
         wrapper = XPCWrappedNative::Get(obj);
     if (wrapper &&
         (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID))  ||
          wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) ||
          wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)))) {
         return ((nsIJSID*)wrapper->GetIdentityObject())->GetID();
     }
     return nullptr;
 }
 
 bool
-xpc_JSObjectIsID(JSContext *cx, JSObject* obj)
+xpc_JSObjectIsID(JSContext *cx, JSObject *obj)
 {
     MOZ_ASSERT(cx && obj, "bad param");
     // NOTE: this call does NOT addref
-    XPCWrappedNative* wrapper = nullptr;
+    XPCWrappedNative *wrapper = nullptr;
     obj = js::CheckedUnwrap(obj);
     if (obj && IS_WN_REFLECTOR(obj))
         wrapper = XPCWrappedNative::Get(obj);
     return wrapper &&
            (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID))  ||
             wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) ||
             wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)));
 }
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -533,35 +533,37 @@ Parent(JSContext *cx, unsigned argc, jsv
         return false;
     }
 
     *vp = OBJECT_TO_JSVAL(JS_GetParent(JSVAL_TO_OBJECT(v)));
     return true;
 }
 
 static bool
-Atob(JSContext *cx, unsigned argc, jsval *vp)
+Atob(JSContext *cx, unsigned argc, Value *vp)
 {
-    if (!argc)
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length())
         return true;
 
-    return xpc::Base64Decode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
+    return xpc::Base64Decode(cx, args[0], args.rval());
 }
 
 static bool
-Btoa(JSContext *cx, unsigned argc, jsval *vp)
+Btoa(JSContext *cx, unsigned argc, Value *vp)
 {
-  if (!argc)
-      return true;
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length())
+        return true;
 
-  return xpc::Base64Encode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
+  return xpc::Base64Encode(cx, args[0], args.rval());
 }
 
 static bool
-Blob(JSContext *cx, unsigned argc, jsval *vp)
+Blob(JSContext *cx, unsigned argc, Value *vp)
 {
   JS::CallArgs args = CallArgsFromVp(argc, vp);
 
   nsCOMPtr<nsISupports> native =
     do_CreateInstance("@mozilla.org/dom/multipart-blob;1");
   if (!native) {
     JS_ReportError(cx, "Could not create native object!");
     return false;
@@ -577,30 +579,30 @@ Blob(JSContext *cx, unsigned argc, jsval
   }
 
   nsCOMPtr<nsIXPConnect> xpc = do_GetService(kXPConnectServiceContractID, &rv);
   if (NS_FAILED(rv)) {
     JS_ReportError(cx, "Could not get XPConnent service!");
     return false;
   }
 
-  JSObject* global = JS::CurrentGlobalOrNull(cx);
+  JSObject *global = JS::CurrentGlobalOrNull(cx);
   rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr,
                               &NS_GET_IID(nsISupports), true,
                               args.rval());
   if (NS_FAILED(rv)) {
     JS_ReportError(cx, "Could not wrap native object!");
     return false;
   }
 
   return true;
 }
 
 static bool
-File(JSContext *cx, unsigned argc, jsval *vp)
+File(JSContext *cx, unsigned argc, Value *vp)
 {
   JS::CallArgs args = CallArgsFromVp(argc, vp);
 
   nsCOMPtr<nsISupports> native =
     do_CreateInstance("@mozilla.org/dom/multipart-file;1");
   if (!native) {
     JS_ReportError(cx, "Could not create native object!");
     return false;
@@ -616,17 +618,17 @@ File(JSContext *cx, unsigned argc, jsval
   }
 
   nsCOMPtr<nsIXPConnect> xpc = do_GetService(kXPConnectServiceContractID, &rv);
   if (NS_FAILED(rv)) {
     JS_ReportError(cx, "Could not get XPConnent service!");
     return false;
   }
 
-  JSObject* global = JS::CurrentGlobalOrNull(cx);
+  JSObject *global = JS::CurrentGlobalOrNull(cx);
   rv = xpc->WrapNativeToJSVal(cx, global, native, nullptr,
                               &NS_GET_IID(nsISupports), true,
                               args.rval());
   if (NS_FAILED(rv)) {
     JS_ReportError(cx, "Could not wrap native object!");
     return false;
   }
 
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -568,21 +568,21 @@ nsXPConnect::WrapNative(JSContext * aJSC
     RootedObject aScope(aJSContext, aScopeArg);
     RootedValue v(aJSContext);
     return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
                                     false, &v, aHolder);
 }
 
 /* void wrapNativeToJSVal (in JSContextPtr aJSContext, in JSObjectPtr aScope, in nsISupports aCOMObj, in nsIIDPtr aIID, out jsval aVal, out nsIXPConnectJSObjectHolder aHolder); */
 NS_IMETHODIMP
-nsXPConnect::WrapNativeToJSVal(JSContext * aJSContext,
-                               JSObject * aScopeArg,
+nsXPConnect::WrapNativeToJSVal(JSContext *aJSContext,
+                               JSObject *aScopeArg,
                                nsISupports *aCOMObj,
                                nsWrapperCache *aCache,
-                               const nsIID * aIID,
+                               const nsIID *aIID,
                                bool aAllowWrapping,
                                MutableHandleValue aVal)
 {
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aScopeArg, "bad param");
     MOZ_ASSERT(aCOMObj, "bad param");
 
     RootedObject aScope(aJSContext, aScopeArg);
@@ -611,33 +611,33 @@ nsXPConnect::WrapJS(JSContext * aJSConte
                                               &aIID, nullptr, &rv))
         return rv;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::JSValToVariant(JSContext *cx,
                             HandleValue aJSVal,
-                            nsIVariant ** aResult)
+                            nsIVariant **aResult)
 {
     NS_PRECONDITION(aResult, "bad param");
 
     *aResult = XPCVariant::newVariant(cx, aJSVal);
     NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
 
     return NS_OK;
 }
 
 /* void wrapJSAggregatedToNative (in nsISupports aOuter, in JSContextPtr aJSContext, in JSObjectPtr aJSObj, in nsIIDRef aIID, [iid_is (aIID), retval] out nsQIResult result); */
 NS_IMETHODIMP
 nsXPConnect::WrapJSAggregatedToNative(nsISupports *aOuter,
-                                      JSContext * aJSContext,
-                                      JSObject * aJSObjArg,
-                                      const nsIID & aIID,
-                                      void * *result)
+                                      JSContext *aJSContext,
+                                      JSObject *aJSObjArg,
+                                      const nsIID &aIID,
+                                      void **result)
 {
     MOZ_ASSERT(aOuter, "bad param");
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aJSObjArg, "bad param");
     MOZ_ASSERT(result, "bad param");
 
     *result = nullptr;
 
@@ -668,18 +668,18 @@ nsXPConnect::GetWrappedNativeOfJSObject(
 
     // else...
     *_retval = nullptr;
     return NS_ERROR_FAILURE;
 }
 
 /* nsISupports getNativeOfWrapper(in JSContextPtr aJSContext, in JSObjectPtr  aJSObj); */
 NS_IMETHODIMP_(nsISupports*)
-nsXPConnect::GetNativeOfWrapper(JSContext * aJSContext,
-                                JSObject * aJSObj)
+nsXPConnect::GetNativeOfWrapper(JSContext *aJSContext,
+                                JSObject *aJSObj)
 {
     MOZ_ASSERT(aJSContext, "bad param");
     MOZ_ASSERT(aJSObj, "bad param");
 
     aJSObj = js::CheckedUnwrap(aJSObj, /* stopAtOuter = */ false);
     if (!aJSObj) {
         JS_ReportError(aJSContext, "Permission denied to get native of security wrapper");
         return nullptr;
@@ -1326,20 +1326,19 @@ nsXPConnect::HoldObject(JSContext *aJSCo
 
     NS_ADDREF(*aHolder = objHolder);
     return NS_OK;
 }
 
 namespace xpc {
 
 NS_EXPORT_(bool)
-Base64Encode(JSContext *cx, JS::Value val, JS::Value *out)
+Base64Encode(JSContext *cx, HandleValue val, MutableHandleValue out)
 {
     MOZ_ASSERT(cx);
-    MOZ_ASSERT(out);
 
     JS::RootedValue root(cx, val);
     xpc_qsACString encodedString(cx, root, &root, false,
                                  xpc_qsACString::eStringify,
                                  xpc_qsACString::eStringify);
     if (!encodedString.IsValid())
         return false;
 
@@ -1348,25 +1347,24 @@ Base64Encode(JSContext *cx, JS::Value va
         JS_ReportError(cx, "Failed to encode base64 data!");
         return false;
     }
 
     JSString *str = JS_NewStringCopyN(cx, result.get(), result.Length());
     if (!str)
         return false;
 
-    *out = STRING_TO_JSVAL(str);
+    out.setString(str);
     return true;
 }
 
 NS_EXPORT_(bool)
-Base64Decode(JSContext *cx, JS::Value val, JS::Value *out)
+Base64Decode(JSContext *cx, HandleValue val, MutableHandleValue out)
 {
     MOZ_ASSERT(cx);
-    MOZ_ASSERT(out);
 
     JS::RootedValue root(cx, val);
     xpc_qsACString encodedString(cx, root, &root, false,
                                  xpc_qsACString::eStringify,
                                  xpc_qsACString::eStringify);
     if (!encodedString.IsValid())
         return false;
 
@@ -1375,17 +1373,17 @@ Base64Decode(JSContext *cx, JS::Value va
         JS_ReportError(cx, "Failed to decode base64 string!");
         return false;
     }
 
     JSString *str = JS_NewStringCopyN(cx, result.get(), result.Length());
     if (!str)
         return false;
 
-    *out = STRING_TO_JSVAL(str);
+    out.setString(str);
     return true;
 }
 
 void
 SetLocationForGlobal(JSObject *global, const nsACString& location)
 {
     MOZ_ASSERT(global);
     EnsureCompartmentPrivate(global)->SetLocation(location);
@@ -1414,22 +1412,22 @@ nsXPConnect::GetTelemetryValue(JSContext
 {
     RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
     if (!obj)
         return NS_ERROR_OUT_OF_MEMORY;
 
     unsigned attrs = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
 
     size_t i = JS_SetProtoCalled(cx);
-    RootedValue v(cx, DOUBLE_TO_JSVAL(i));
+    RootedValue v(cx, DoubleValue(i));
     if (!JS_DefineProperty(cx, obj, "setProto", v, nullptr, nullptr, attrs))
         return NS_ERROR_OUT_OF_MEMORY;
 
     i = JS_GetCustomIteratorCount(cx);
-    v = DOUBLE_TO_JSVAL(i);
+    v.setDouble(i);
     if (!JS_DefineProperty(cx, obj, "customIter", v, nullptr, nullptr, attrs))
         return NS_ERROR_OUT_OF_MEMORY;
 
     rval.setObject(*obj);
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -1647,31 +1645,33 @@ JS_EXPORT_API(void) DumpCompleteHeap()
     nsJSContext::CycleCollectNow(alltracesListener);
 }
 
 } // extern "C"
 
 namespace xpc {
 
 bool
-Atob(JSContext *cx, unsigned argc, jsval *vp)
+Atob(JSContext *cx, unsigned argc, Value *vp)
 {
-    if (!argc)
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length())
         return true;
 
-    return xpc::Base64Decode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
+    return xpc::Base64Decode(cx, args[0], args.rval());
 }
 
 bool
-Btoa(JSContext *cx, unsigned argc, jsval *vp)
+Btoa(JSContext *cx, unsigned argc, Value *vp)
 {
-    if (!argc)
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.length())
         return true;
 
-    return xpc::Base64Encode(cx, JS_ARGV(cx, vp)[0], &JS_RVAL(cx, vp));
+    return xpc::Base64Encode(cx, args[0], args.rval());
 }
 
 bool
 IsXrayWrapper(JSObject *obj)
 {
     return WrapperFactory::IsXrayWrapper(obj);
 }
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -242,18 +242,18 @@ private:
     static void FinalizeDOMString(const JSStringFinalizer *fin, jschar *chars);
 
     XPCStringConvert();         // not implemented
 };
 
 namespace xpc {
 
 // If these functions return false, then an exception will be set on cx.
-NS_EXPORT_(bool) Base64Encode(JSContext *cx, JS::Value val, JS::Value *out);
-NS_EXPORT_(bool) Base64Decode(JSContext *cx, JS::Value val, JS::Value *out);
+NS_EXPORT_(bool) Base64Encode(JSContext *cx, JS::HandleValue val, JS::MutableHandleValue out);
+NS_EXPORT_(bool) Base64Decode(JSContext *cx, JS::HandleValue val, JS::MutableHandleValue out);
 
 /**
  * Convert an nsString to jsval, returning true on success.
  * Note, the ownership of the string buffer may be moved from str to rval.
  * If that happens, str will point to an empty string after this call.
  */
 bool NonVoidStringToJsval(JSContext *cx, nsAString &str, JS::MutableHandleValue rval);
 inline bool StringToJsval(JSContext *cx, nsAString &str, JS::MutableHandleValue rval)
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -280,17 +280,17 @@ WrapperFactory::PrepareForWrapping(JSCon
     // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
     // so we don't have to.
     RootedValue v(cx);
     nsresult rv =
         nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
                                                     &NS_GET_IID(nsISupports), false, &v);
     NS_ENSURE_SUCCESS(rv, nullptr);
 
-    obj = JSVAL_TO_OBJECT(v);
+    obj.set(&v.toObject());
     MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object");
 
     // Because the underlying native didn't have a PreCreate hook, we had
     // to a new (or possibly pre-existing) XPCWN in our compartment.
     // This could be a problem for chrome code that passes XPCOM objects
     // across compartments, because the effects of QI would disappear across
     // compartments.
     //
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2828,17 +2828,17 @@ ElementRestyler::SendAccessibilityNotifi
                                          childContent->GetNextSibling());
       }
       mVisibleKidsOfHiddenElement.Clear();
     }
   }
 #endif
 }
 
-inline nsIFrame*
+static inline nsIFrame*
 GetNextBlockInInlineSibling(FramePropertyTable* aPropTable, nsIFrame* aFrame)
 {
   NS_ASSERTION(!aFrame->GetPrevContinuation(),
                "must start with the first continuation");
   // Might we have special siblings?
   if (!(aFrame->GetStateBits() & NS_FRAME_IS_SPECIAL)) {
     // nothing more to do here
     return nullptr;
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -18,32 +18,32 @@
 #include "nsCSSKeywords.h"
 #include "nsCSSParser.h"
 #include "nsCSSProps.h"
 #include "nsCSSPseudoClasses.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRendering.h"
 #include "mozilla/dom/Attr.h"
 #include "nsDOMClassInfo.h"
+#include "nsEditorEventListener.h"
 #include "nsEventListenerManager.h"
 #include "nsFrame.h"
 #include "nsGlobalWindow.h"
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "nsLayoutStylesheetCache.h"
 #include "nsNodeInfo.h"
 #include "nsRange.h"
 #include "nsRegion.h"
 #include "nsRepeatService.h"
 #include "nsFloatManager.h"
 #include "nsSprocketLayout.h"
 #include "nsStackLayout.h"
 #include "nsStyleSet.h"
 #include "nsTextControlFrame.h"
-#include "nsXBLWindowKeyHandler.h"
 #include "nsXBLService.h"
 #include "txMozillaXSLTProcessor.h"
 #include "nsTreeSanitizer.h"
 #include "nsCellMap.h"
 #include "nsTextFrame.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsTextFragment.h"
 #include "nsCSSRuleProcessor.h"
@@ -345,17 +345,17 @@ nsLayoutStatics::Shutdown()
   nsContentUtils::Shutdown();
   nsLayoutStylesheetCache::Shutdown();
   NS_NameSpaceManagerShutdown();
 
   ShutdownJSEnvironment();
   nsGlobalWindow::ShutDown();
   nsDOMClassInfo::ShutDown();
   nsListControlFrame::Shutdown();
-  nsXBLWindowKeyHandler::ShutDown();
+  nsEditorEventListener::ShutDown();
   nsXBLService::Shutdown();
   nsAutoCopyListener::Shutdown();
   FrameLayerBuilder::Shutdown();
 
 #ifdef MOZ_MEDIA_PLUGINS
   MediaPluginHost::Shutdown();
 #endif
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -638,17 +638,17 @@ public abstract class GeckoApp
             } else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:PreInstall")) {
                 String name = message.getString("name");
                 String manifestURL = message.getString("manifestURL");
                 String origin = message.getString("origin");
                 // preInstallWebapp will return a File object pointing to the profile directory of the webapp
                 mCurrentResponse = GeckoAppShell.preInstallWebApp(name, manifestURL, origin).toString();
             } else if (event.equals("WebApps:PostInstall")) {
                 if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
-                    GeckoAppShell.postInstallWebApp(message.getString("packageName"), message.getString("origin"));
+                    GeckoAppShell.postInstallWebApp(message.getString("apkPackageName"), message.getString("origin"));
                 } else {
                     String name = message.getString("name");
                     String manifestURL = message.getString("manifestURL");
                     String iconURL = message.getString("iconURL");
                     String originalOrigin = message.getString("originalOrigin");
                     String origin = message.getString("origin");
                     GeckoAppShell.postInstallWebApp(name, manifestURL, origin, iconURL, originalOrigin);
                 }
--- a/mobile/android/base/webapp/InstallHelper.java
+++ b/mobile/android/base/webapp/InstallHelper.java
@@ -76,17 +76,17 @@ public class InstallHelper implements Ge
         if (message == null) {
             message = new JSONObject();
         }
 
         // we can change the profile to be in the app's area here
         GeckoProfile profile = GeckoProfile.get(mContext, profileName);
 
         try {
-            message.put("packageName", mApkResources.getPackageName());
+            message.put("apkPackageName", mApkResources.getPackageName());
             message.put("manifestUrl", mApkResources.getManifestUrl());
             message.put("title", mApkResources.getAppName());
             message.put("manifest", new JSONObject(mApkResources.getManifest(mContext)));
 
             String appType = mApkResources.getWebAppType();
             message.putOpt("type", appType);
             if ("packaged".equals(appType)) {
                 message.putOpt("updateManifest", new JSONObject(mApkResources.getMiniManifest(mContext)));
--- a/mobile/android/base/webapp/UninstallListener.java
+++ b/mobile/android/base/webapp/UninstallListener.java
@@ -42,17 +42,17 @@ public class UninstallListener extends B
         WebAppAllocator allocator = WebAppAllocator.getInstance(context);
         ArrayList<String> installedPackages = allocator.getInstalledPackageNames();
 
         if (installedPackages.contains(packageName)) {
             JSONObject message = new JSONObject();
             JSONArray packageNames = new JSONArray();
             try {
                 packageNames.put(packageName);
-                message.put("packages", packageNames);
+                message.put("apkPackageNames", packageNames);
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:AutoUninstall", message.toString()));
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSON EXCEPTION " + e);
             }
         }
     }
 
     public static void initUninstallPackageScan(Context context) {
@@ -79,16 +79,16 @@ public class UninstallListener extends B
 
         if (uninstalledPackages.size() > 0) {
             JSONObject message = new JSONObject();
             JSONArray packageNames = new JSONArray();
             try {
                 for (String packageName : uninstalledPackages) {
                     packageNames.put(packageName);
                 }
-                message.put("packages", packageNames);
+                message.put("apkPackageNames", packageNames);
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:AutoUninstall", message.toString()));
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSON EXCEPTION " + e);
             }
         }
     }
 }
--- a/mobile/android/base/webapp/WebAppImpl.java
+++ b/mobile/android/base/webapp/WebAppImpl.java
@@ -287,19 +287,17 @@ public class WebAppImpl extends GeckoApp
     @Override
     public void installCompleted(InstallHelper installHelper, String event, JSONObject message) {
         if (event == null) {
             return;
         }
 
         if (event.equals("WebApps:PostInstall")) {
             String origin = message.optString("origin");
-            String manifestUrl = message.optString("manifestURL");
-            String name = message.optString("name", "WebApp");
-            launchWebApp(origin, manifestUrl, name);
+            launchWebApp(origin, mApkResources.getManifestUrl(), mApkResources.getAppName());
         }
     }
 
     @Override
     public void installErrored(InstallHelper installHelper, Exception exception) {
         Log.e(LOGTAG, "Install errored", exception);
     }
 
--- a/mobile/android/chrome/content/WebAppRT.js
+++ b/mobile/android/chrome/content/WebAppRT.js
@@ -59,38 +59,40 @@ let WebAppRT = {
         }
       });
     }
 
     this.findManifestUrlFor(aUrl, aCallback);
   },
 
   getManifestFor: function (aUrl, aCallback) {
-    let request = navigator.mozApps.mgmt.getAll();
-    request.onsuccess = function() {
-      let apps = request.result;
-      for (let i = 0; i < apps.length; i++) {
-        let app = apps[i];
-        let manifest = new ManifestHelper(app.manifest, app.origin);
+    DOMApplicationRegistry.registryReady.then(() => {
+      let request = navigator.mozApps.mgmt.getAll();
+      request.onsuccess = function() {
+        let apps = request.result;
+        for (let i = 0; i < apps.length; i++) {
+          let app = apps[i];
+          let manifest = new ManifestHelper(app.manifest, app.origin);
 
-        // if this is a path to the manifest, or the launch path, then we have a hit.
-        if (app.manifestURL == aUrl || manifest.fullLaunchPath() == aUrl) {
-          aCallback(manifest, app);
-          return;
+          // if this is a path to the manifest, or the launch path, then we have a hit.
+          if (app.manifestURL == aUrl || manifest.fullLaunchPath() == aUrl) {
+            aCallback(manifest, app);
+            return;
+          }
         }
-      }
 
-      // Otherwise, once we loop through all of them, we have a miss.
-      aCallback(undefined);
-    };
+        // Otherwise, once we loop through all of them, we have a miss.
+        aCallback(undefined);
+      };
 
-    request.onerror = function() {
-      // Treat an error like a miss. We can't find the manifest.
-      aCallback(undefined);
-    };
+      request.onerror = function() {
+        // Treat an error like a miss. We can't find the manifest.
+        aCallback(undefined);
+      };
+    });
   },
 
   findManifestUrlFor: function(aUrl, aCallback) {
     this.getManifestFor(aUrl, function(aManifest, aApp) {
       if (!aManifest) {
         // we can't find the manifest, so open it like a web page
         aCallback(aUrl);
         return;
--- a/mobile/android/chrome/content/config.js
+++ b/mobile/android/chrome/content/config.js
@@ -81,22 +81,16 @@ var NewPrefDialog = {
   // As new pref name is initially displayed, re-focused, or modifed during user input
   _updatePositiveButton: function AC_updatePositiveButton(aPrefName) {
     this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.createButton");
     this._positiveButton.setAttribute("disabled", true);
     if (aPrefName == "") {
       return;
     }
 
-    // Avoid "private" preferences
-    if (/^capability\./.test(aPrefName)) {
-      this._positiveButton.textContent = "Private";
-      return;
-    }
-
     // If item already in list, it's being changed, else added
     let item = document.querySelector(".pref-item[name=" + aPrefName.quote() + "]");
     if (item) {
       this._positiveButton.textContent = gStringBundle.GetStringFromName("newPref.changeButton");
     } else {
       this._positiveButton.removeAttribute("disabled");
     }
   },
@@ -202,20 +196,17 @@ var AboutConfig = {
   _list: null,
 
   // Init the main AboutConfig dialog
   init: function AC_init() {
     this.filterInput = document.getElementById("filter-input");
     this._prefsContainer = document.getElementById("prefs-container");
     this._loadingContainer = document.getElementById("loading-container");
 
-    let list = Services.prefs.getChildList("", {}).filter(function(aElement) {
-      // Avoid "private" preferences
-      return !(/^capability\./.test(aElement));
-    });
+    let list = Services.prefs.getChildList("");
     this._list = list.sort().map( function AC_getMapPref(aPref) {
       return new Pref(aPref);
     }, this);
 
     // Display the current prefs list (retains searchFilter value)
     this.bufferFilterInput();
 
     // Setup the prefs observers
@@ -459,17 +450,17 @@ var AboutConfig = {
     pref.value += aInt;
   },
 
   // Observe preference changes
   observe: function AC_observe(aSubject, aTopic, aPrefName) {
     let pref = new Pref(aPrefName);
 
     // Ignore uninteresting changes, and avoid "private" preferences
-    if ((aTopic != "nsPref:changed") || /^capability\./.test(pref.name)) {
+    if (aTopic != "nsPref:changed") {
       return;
     }
 
     // If pref type invalid, refresh display as user reset/removed an item from the list
     if (pref.type == Services.prefs.PREF_INVALID) {
       document.location.reload();
       return;
     }
--- a/mobile/android/modules/WebappManager.jsm
+++ b/mobile/android/modules/WebappManager.jsm
@@ -91,17 +91,17 @@ this.WebappManager = {
     }
 
     DOMApplicationRegistry.confirmInstall(aData, file, (function(aManifest) {
       let localeManifest = new ManifestHelper(aManifest, aData.app.origin);
 
       // aData.app.origin may now point to the app: url that hosts this app.
       sendMessageToJava({
         type: "WebApps:PostInstall",
-        packageName: aData.app.packageName,
+        apkPackageName: aData.app.apkPackageName,
         origin: aData.app.origin,
       });
 
       this.writeDefaultPrefs(file, localeManifest);
     }).bind(this));
   },
 
   launch: function({ manifestURL, origin }) {
@@ -143,17 +143,17 @@ this.WebappManager = {
       message.app.updateManifest = aData.updateManifest;
     }
 
     // The manifest url may be subtly different between the
     // time the APK was built and the APK being installed.
     // Thus, we should take the APK as the source of truth.
     message.app.manifestURL = aData.manifestUrl;
     message.app.manifest = aData.manifest;
-    message.app.packageName = aData.packageName;
+    message.app.apkPackageName = aData.apkPackageName;
     message.profilePath = aData.profilePath;
     message.autoInstall = true;
     message.mm = mm;
 
     switch (aData.type) { // can be hosted or packaged.
       case "hosted":
         DOMApplicationRegistry.doInstall(message, mm);
         break;
@@ -167,22 +167,22 @@ this.WebappManager = {
 
   autoUninstall: function(aData) {
     let mm = {
       sendAsyncMessage: function (aMessageName, aData) {
         // TODO hook this back to Java to report errors.
         dump("autoUninstall sendAsyncMessage " + aMessageName + ": " + JSON.stringify(aData));
       }
     };
-    let installedPackages = {};
-    DOMApplicationRegistry.doGetAll(installedPackages, mm);
+    let installed = {};
+    DOMApplicationRegistry.doGetAll(installed, mm);
 
-    for (let app in installedPackages.apps) {
-      if (aData.packages.indexOf(installedPackages.apps[app].packageName) > -1) {
-        let appToRemove = installedPackages.apps[app];
+    for (let app in installed.apps) {
+      if (aData.apkPackageNames.indexOf(installed.apps[app].apkPackageName) > -1) {
+        let appToRemove = installed.apps[app];
         dump("should remove: " + appToRemove.name);
         DOMApplicationRegistry.uninstall(appToRemove.manifestURL, function() {
           dump(appToRemove.name + " uninstalled");
         }, function() {
           dump(appToRemove.name + " did not uninstall");
         });
       }
     }
--- a/netwerk/base/src/Seer.cpp
+++ b/netwerk/base/src/Seer.cpp
@@ -342,22 +342,25 @@ class SeerNewTransactionEvent : public n
   }
 };
 
 NS_IMETHODIMP
 Seer::Observe(nsISupports *subject, const char *topic,
               const char16_t *data_unicode)
 {
   nsresult rv = NS_OK;
+  MOZ_ASSERT(NS_IsMainThread(), "Seer observing something off main thread!");
 
   if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
-    gSeer->Shutdown();
+    Shutdown();
   } else if (!strcmp(NS_TIMER_CALLBACK_TOPIC, topic)) {
-    nsRefPtr<SeerNewTransactionEvent> event = new SeerNewTransactionEvent();
-    gSeer->mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+    if (mInitialized) { // Can't access io thread if we're not initialized!
+      nsRefPtr<SeerNewTransactionEvent> event = new SeerNewTransactionEvent();
+      mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+    }
   }
 
   return rv;
 }
 
 // Seer::nsISpeculativeConnectionOverrider
 
 NS_IMETHODIMP
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -388,18 +388,25 @@ public:
   {
     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
 
     if (NS_FAILED(aReason)) {
       // Have we seen this failure before?
       FailDelay *knownFailure = mFailures.Lookup(aChannel->mAddress,
                                                  aChannel->mPort);
       if (knownFailure) {
-        // repeated failure to connect: increase delay for next connection
-        knownFailure->FailedAgain();
+        if (aReason == NS_ERROR_NOT_CONNECTED) {
+          // Don't count close() before connection as a network error
+          LOG(("Websocket close() before connection to %s, %d completed"
+               " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
+               aChannel));
+        } else {
+          // repeated failure to connect: increase delay for next connection
+          knownFailure->FailedAgain();
+        }
       } else {
         // new connection failure: record it.
         LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
               aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
         mFailures.Add(aChannel->mAddress, aChannel->mPort);
       }
     }
 
--- a/services/docs/index.rst
+++ b/services/docs/index.rst
@@ -2,17 +2,16 @@
 Firefox Services Module
 =======================
 
 The ``/services`` directory contains code for a variety of application
 features that communicate with external services - hence its name.
 
 It was originally created to hold code for Firefox Sync. Later, it
 became the location for code written by the Mozilla Services Client team
-and thus includes Firefox Health Report. This team no longer exists, but
+and thus includes :ref:`healthreport`. This team no longer exists, but
 the directory remains.
 
 .. toctree::
    :maxdepth: 1
 
    metrics
-   healthreport
    datareporting
new file mode 100644
--- /dev/null
+++ b/services/healthreport/docs/architecture.rst
@@ -0,0 +1,223 @@
+.. _healthreport_architecture:
+
+============
+Architecture
+============
+
+``healthreporter.jsm`` contains the main interface for FHR, the
+``HealthReporter`` type. An instance of this is created by the
+:ref:`data_reporting_service`.
+
+``providers.jsm`` contains numerous ``Metrics.Provider`` and
+``Metrics.Measurement`` used for collecting application metrics. If you
+are looking for the FHR probes, this is where they are.
+
+Storage
+=======
+
+Firefox Health Report stores data in 3 locations:
+
+* Metrics measurements and provider state is stored in a SQLite database
+  (via ``Metrics.Storage``).
+* Service state (such as the IDs of documents uploaded) is stored in a
+  JSON file on disk (via OS.File).
+* Lesser state and run-time options are stored in preferences.
+
+Preferences
+===========
+
+Preferences controlling behavior of Firefox Health Report live in the
+``datareporting.healthreport.*`` branch.
+
+Service and Data Control
+------------------------
+
+The follow preferences control behavior of the service and data upload.
+
+service.enabled
+   Controls whether the entire health report service runs. The overall
+   service performs data collection, storing, and submission.
+
+   This is the primary kill switch for Firefox Health Report
+   outside of the build system variable. i.e. if you are using an
+   official Firefox build and wish to disable FHR, this is what you
+   should set to false to prevent FHR from not only submitting but
+   also collecting data.
+
+uploadEnabled
+   Whether uploading of data is enabled. This is the preference the
+   checkbox in the preferences UI reflects. If this is
+   disabled, FHR still collects data - it just doesn't upload it.
+
+service.loadDelayMsec
+   How long (in milliseconds) after initial application start should FHR
+   wait before initializing.
+
+   FHR may initialize sooner than this if the FHR service is requested.
+   This will happen if e.g. the user goes to ``about:healthreport``.
+
+service.loadDelayFirstRunMsec
+   How long (in milliseconds) FHR should wait to initialize on first
+   application run.
+
+   FHR waits longer than normal to initialize on first application run
+   because first-time initialization can use a lot of I/O to initialize
+   the SQLite database and this I/O should not interfere with the
+   first-run user experience.
+
+documentServerURI
+   The URI of a Bagheera server that FHR should interface with for
+   submitting documents.
+
+   You typically do not need to change this.
+
+documentServerNamespace
+   The namespace on the document server FHR should upload documents to.
+
+   You typically do not need to change this.
+
+infoURL
+   The URL of a page containing more info about FHR, it's privacy
+   policy, etc.
+
+about.reportUrl
+   The URL to load in ``about:healthreport``.
+
+service.providerCategories
+   A comma-delimited list of category manager categories that contain
+   registered ``Metrics.Provider`` records. Read below for how provider
+   registration works.
+
+If the entire service is disabled, you lose data collection. This means
+that **local** data analysis won't be available because there is no data
+to analyze! Keep in mind that Firefox Health Report can be useful even
+if it's not submitting data to remote servers!
+
+Logging
+-------
+
+The following preferences allow you to control the logging behavior of
+Firefox Health Report.
+
+logging.consoleEnabled
+   Whether to write log messages to the web console. This is true by
+   default.
+
+logging.consoleLevel
+   The minimum log level FHR messages must have to be written to the
+   web console. By default, only FHR warnings or errors will be written
+   to the web console. During normal/expected operation, no messages of
+   this type should be produced.
+
+logging.dumpEnabled
+   Whether to write log messages via ``dump()``. If true, FHR will write
+   messages to stdout/stderr.
+
+   This is typically only enabled when developing FHR.
+
+logging.dumpLevel
+   The minimum log level messages must have to be written via
+   ``dump()``.
+
+State
+-----
+
+currentDaySubmissionFailureCount
+   How many submission failures the client has encountered while
+   attempting to upload the most recent document.
+
+lastDataSubmissionFailureTime
+   The time of the last failed document upload.
+
+lastDataSubmissionRequestedTime
+   The time of the last document upload attempt.
+
+lastDataSubmissionSuccessfulTime
+   The time of the last successful document upload.
+
+nextDataSubmissionTime
+   The time the next data submission is scheduled for. FHR will not
+   attempt to upload a new document before this time.
+
+pendingDeleteRemoteData
+   Whether the client currently has a pending request to delete remote
+   data. If true, the client will attempt to delete all remote data
+   before an upload is performed.
+
+FHR stores various state in preferences.
+
+Registering Providers
+=====================
+
+Firefox Health Report providers are registered via the category manager.
+See ``HealthReportComponents.manifest`` for providers defined in this
+directory.
+
+Essentially, the category manager receives the name of a JS type and the
+URI of a JSM to import that exports this symbol. At run-time, the
+providers registered in the category manager are instantiated.
+
+Providers are registered via the category manager to make registration
+simple and less prone to errors. Any XPCOM component can create a
+category manager entry. Therefore, new data providers can be added
+without having to touch core Firefox Health Report code. Additionally,
+category manager registration means providers are more likely to be
+registered on FHR's terms, when it wants. If providers were registered
+in code at application run-time, there would be the risk of other
+components prematurely instantiating FHR (causing a performance hit if
+performed at an inopportune time) or semi-complicated code around
+observers or listeners. Category manager entries are only 1 line per
+provider and leave FHR in control: they are simple and safe.
+
+Document Generation and Lifecycle
+=================================
+
+FHR will attempt to submit a JSON document containing data every 24 wall
+clock hours.
+
+At upload time, FHR will query the database for **all** information from
+the last 180 days and assemble this data into a JSON document. We
+attempt to upload this JSON document with a client-generated UUID to the
+configured server.
+
+Before we attempt upload, the generated UUID is stored in the JSON state
+file on local disk. At this point, the client assumes the document with
+that UUID has been successfully stored on the server.
+
+If the client is aware of other document UUIDs that presumably exist on
+the server, those UUIDs are sent with the upload request so the client
+can request those UUIDs be deleted. This helps ensure that each client
+only has 1 document/UUID on the server at any one time.
+
+Importance of Persisting UUIDs
+------------------------------
+
+The choices of how, where, and when document UUIDs are stored and updated
+are very important. One should not attempt to change things unless she
+has a very detailed understanding of why things are the way they are.
+
+The client is purposefully very conservative about forgetting about
+generated UUIDs. In other words, once a UUID is generated, the client
+deliberately holds on to that UUID until it's very confident that UUID
+is no longer stored on the server. The reason we do this is because
+*orphaned* documents/UUIDs on the server can lead to faulty analysis,
+such as over-reporting the number of Firefox installs that stop being
+used.
+
+When uploading a new UUID, we update the state and save the state file
+to disk *before* an upload attempt because if the upload succeeds but
+the response never makes it back to the client, we want the client to
+know about the uploaded UUID so it can delete it later to prevent an
+orphan.
+
+We maintain a list of UUIDs locally (not simply the last UUID) because
+multiple upload attempts could fail the same way as the previous
+paragraph describes and we have no way of knowing which (if any)
+actually succeeded. The safest approach is to assume every document
+produced managed to get uploaded some how.
+
+We store the UUIDs on a file on disk and not anywhere else because we
+want storage to be robust. We originally stored UUIDs in preferences,
+which only flush to disk periodically. Writes to preferences were
+apparently getting lost. We switched to writing directly to files to
+eliminate this window.
new file mode 100644
--- /dev/null
+++ b/services/healthreport/docs/dataformat.rst
@@ -0,0 +1,1288 @@
+.. _healthreport_dataformat:
+
+==============
+Payload Format
+==============
+
+Currently, the Firefox Health Report is submitted as a compressed JSON
+document. The root JSON element is an object. A *version* field defines
+the version of the payload which in turn defines the expected contents
+the object.
+
+As of 2013-07-03, desktop submits Version 2, and Firefox for Android submits
+Version 3 payloads.
+
+Version 3
+=========
+
+Version 3 is a complete rebuild of the document format. Events are tracked in
+an "environment". Environments are computed from a large swath of local data
+(e.g., add-ons, CPU count, versions), and a new environment comes into being
+when one of its attributes changes.
+
+Client documents, then, will include descriptions of many environments, and
+measurements will be attributed to one particular environment.
+
+A map of environments is present at the top level of the document, with the
+current named "current" in the map. Each environment has a hash identifier and
+a set of attributes. The current environment is completely described, and has
+its hash present in a "hash" attribute. All other environments are represented
+as a tree diff from the current environment, with their hash as the key in the
+"environments" object.
+
+A removed add-on has the value 'null'.
+
+There is no "last" data at present.
+
+Daily data is hierarchical: by day, then by environment, and then by
+measurement, and is present in "data", just as in v2.
+
+Leading by example::
+
+    {
+      "lastPingDate": "2013-06-29",
+      "thisPingDate": "2013-07-03",
+      "version": 3,
+      "environments": {
+        "current": {
+          "org.mozilla.sysinfo.sysinfo": {
+            "memoryMB": 1567,
+            "cpuCount": 4,
+            "architecture": "armeabi-v7a",
+            "_v": 1,
+            "version": "4.1.2",
+            "name": "Android"
+          },
+          "org.mozilla.profile.age": {
+            "_v": 1,
+            "profileCreation": 15827
+          },
+          "org.mozilla.addons.active": {
+            "QuitNow@TWiGSoftware.com": {
+              "appDisabled": false,
+              "userDisabled": false,
+              "scope": 1,
+              "updateDay": 15885,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "blocklistState": 0,
+              "type": "extension",
+              "installDay": 15885,
+              "version": "1.18.02"
+            },
+            "{dbbf9331-b713-6eda-1006-205efead09dc}": {
+              "appDisabled": false,
+              "userDisabled": "askToActivate",
+              "scope": 8,
+              "updateDay": 15779,
+              "foreignInstall": true,
+              "blocklistState": 0,
+              "type": "plugin",
+              "installDay": 15779,
+              "version": "11.1 r115"
+            },
+            "desktopbydefault@bnicholson.mozilla.org": {
+              "appDisabled": false,
+              "userDisabled": true,
+              "scope": 1,
+              "updateDay": 15870,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "blocklistState": 0,
+              "type": "extension",
+              "installDay": 15870,
+              "version": "1.1"
+            },
+            "{6e092a7f-ba58-4abb-88c1-1a4e50b217e4}": {
+              "appDisabled": false,
+              "userDisabled": false,
+              "scope": 1,
+              "updateDay": 15828,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "blocklistState": 0,
+              "type": "extension",
+              "installDay": 15828,
+              "version": "1.1.0"
+            },
+            "{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}": {
+              "appDisabled": false,
+              "userDisabled": true,
+              "scope": 1,
+              "updateDay": 15879,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "blocklistState": 0,
+              "type": "extension",
+              "installDay": 15879,
+              "version": "1.3.2"
+            },
+            "_v": 1
+          },
+          "org.mozilla.appInfo.appinfo": {
+            "_v": 3,
+            "appLocale": "en_us",
+            "osLocale": "en_us",
+            "distribution": "",
+            "acceptLangIsUserSet": 0,
+            "isTelemetryEnabled": 1,
+            "isBlocklistEnabled": 1
+          },
+          "geckoAppInfo": {
+            "updateChannel": "nightly",
+            "id": "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
+            "os": "Android",
+            "platformBuildID": "20130703031323",
+            "platformVersion": "25.0a1",
+            "vendor": "Mozilla",
+            "name": "fennec",
+            "xpcomabi": "arm-eabi-gcc3",
+            "appBuildID": "20130703031323",
+            "_v": 1,
+            "version": "25.0a1"
+          },
+          "hash": "tB4Pnnep9yTxnMDymc3dAB2RRB0=",
+          "org.mozilla.addons.counts": {
+            "extension": 4,
+            "plugin": 1,
+            "_v": 1,
+            "theme": 0
+          }
+        },
+        "k2O3hlreMeS7L1qtxeMsYWxgWWQ=": {
+          "geckoAppInfo": {
+            "platformBuildID": "20130630031138",
+            "appBuildID": "20130630031138",
+            "_v": 1
+          },
+          "org.mozilla.appInfo.appinfo": {
+            "_v": 2,
+          }
+        },
+        "1+KN9TutMpzdl4TJEl+aCxK+xcw=": {
+          "geckoAppInfo": {
+            "platformBuildID": "20130626031100",
+            "appBuildID": "20130626031100",
+            "_v": 1
+          },
+          "org.mozilla.addons.active": {
+            "QuitNow@TWiGSoftware.com": null,
+            "{dbbf9331-b713-6eda-1006-205efead09dc}": null,
+            "desktopbydefault@bnicholson.mozilla.org": null,
+            "{6e092a7f-ba58-4abb-88c1-1a4e50b217e4}": null,
+            "{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}": null,
+            "_v": 1
+          },
+          "org.mozilla.addons.counts": {
+            "extension": 0,
+            "plugin": 0,
+            "_v": 1
+          }
+        }
+      },
+      "data": {
+        "last": {},
+        "days": {
+          "2013-07-03": {
+            "tB4Pnnep9yTxnMDymc3dAB2RRB0=": {
+              "org.mozilla.appSessions": {
+                "normal": [
+                  {
+                    "r": "P",
+                    "d": 2,
+                    "sj": 653
+                  },
+                  {
+                    "r": "P",
+                    "d": 22
+                  },
+                  {
+                    "r": "P",
+                    "d": 5
+                  },
+                  {
+                    "r": "P",
+                    "d": 0
+                  },
+                  {
+                    "r": "P",
+                    "sg": 3560,
+                    "d": 171,
+                    "sj": 518
+                  },
+                  {
+                    "r": "P",
+                    "d": 16
+                  },
+                  {
+                    "r": "P",
+                    "d": 1079
+                  }
+                ],
+                "_v": "4"
+              }
+            },
+            "k2O3hlreMeS7L1qtxeMsYWxgWWQ=": {
+              "org.mozilla.appSessions": {
+                "normal": [
+                  {
+                    "r": "P",
+                    "d": 27
+                  },
+                  {
+                    "r": "P",
+                    "d": 19
+                  },
+                  {
+                    "r": "P",
+                    "d": 55
+                  }
+                ],
+                "_v": "4"
+              },
+              "org.mozilla.searches.counts": {
+                "bartext": {
+                  "google": 1
+                },
+                "_v": "4"
+              }
+            }
+          }
+        }
+      }
+    }
+
+App sessions in Version 3
+-------------------------
+
+Sessions are divided into "normal" and "abnormal". Session objects are stored as discrete JSON::
+
+    "org.mozilla.appSessions": {
+      _v: 4,
+      "normal": [
+        {"r":"P", "d": 123},
+      ],
+      "abnormal": [
+        {"r":"A", "oom": true, "stopped": false}
+      ]
+    }
+
+Keys are:
+
+"r"
+    reason. Values are "P" (activity paused), "A" (abnormal termination).
+"d"
+    duration. Value in seconds.
+"sg"
+    Gecko startup time (msec). Present if this is a clean launch. This
+    corresponds to the telemetry timer *FENNEC_STARTUP_TIME_GECKOREADY*.
+"sj"
+    Java activity init time (msec). Present if this is a clean launch. This
+    corresponds to the telemetry timer *FENNEC_STARTUP_TIME_JAVAUI*,
+    and includes initialization tasks beyond initial
+    *onWindowFocusChanged*.
+
+Abnormal terminations will be missing a duration and will feature these keys:
+
+"oom"
+    was the session killed by an OOM exception?
+"stopped"
+    was the session stopped gently?
+
+Version 3.1
+-----------
+
+As of Firefox 27, *appinfo* is now bumped to v3, including *osLocale*,
+*appLocale* (currently always the same as *osLocale*), *distribution* (a string
+containing the distribution ID and version, separated by a colon), and
+*acceptLangIsUserSet*, an integer-boolean that describes whether the user set
+an *intl.accept_languages* preference.
+
+The search counts measurement is now at version 5, which indicates that
+non-partner searches are recorded. You'll see identifiers like "other-Foo Bar"
+rather than "other".
+
+Other notable differences from Version 2
+----------------------------------------
+
+* There is no default browser indicator on Android.
+* Add-ons include a *blocklistState* attribute, as returned by AddonManager.
+* Searches are now version 4, and are hierarchical: how the search was started
+  (bartext, barkeyword, barsuggest), and then counts per provider.
+
+Version 2
+=========
+
+Version 2 is the same as version 1 with the exception that it has an additional
+top-level field, *geckoAppInfo*, which contains basic application info.
+
+geckoAppInfo
+------------
+
+This field is an object that is a simple map of string keys and values
+describing basic application metadata. It is very similar to the *appinfo*
+measurement in the *last* section. The difference is this field is almost
+certainly guaranteed to exist whereas the one in the data part of the
+payload may be omitted in certain scenarios (such as catastrophic client
+error).
+
+Its keys are as follows:
+
+appBuildID
+    The build ID/date of the application. e.g. "20130314113542".
+
+version
+    The value of nsXREAppData.version. This is the application's version. e.g.
+    "21.0.0".
+
+vendor
+    The value of nsXREAppData.vendor. Can be empty an empty string. For
+    official Mozilla builds, this will be "Mozilla".
+
+name
+    The value of nsXREAppData.name. For official Firefox builds, this
+    will be "Firefox".
+
+id
+    The value of nsXREAppData.ID.
+
+platformVersion
+    The version of the Gecko platform (as opposed to the app version). For
+    Firefox, this is almost certainly equivalent to the *version* field.
+
+platformBuildID
+    The build ID/date of the Gecko platfor (as opposed to the app version).
+    This is commonly equivalent to *appBuildID*.
+
+os
+    The name of the operating system the application is running on.
+
+xpcomabi
+    The binary architecture of the build.
+
+updateChannel
+    The name of the channel used for application updates. Official Mozilla
+    builds have one of the values {release, beta, aurora, nightly}. Local and
+    test builds have *default* as the channel.
+
+Version 1
+=========
+
+Top-level Properties
+--------------------
+
+The main JSON object contains the following properties:
+
+lastPingDate
+    UTC date of the last upload. If this is the first upload from this client,
+    this will not be present.
+
+thisPingDate
+    UTC date when this payload was constructed.
+
+version
+    Integer version of this payload format. Currently only 1 is defined.
+
+data
+    Object holding data constituting health report.
+
+Data Properties
+---------------
+
+The bulk of the health report is contained within the *data* object. This
+object has the following keys:
+
+days
+   Object mapping UTC days to measurements from that day. Keys are in the
+   *YYYY-MM-DD* format. e.g. "2013-03-14"
+
+last
+   Object mapping measurement names to their values.
+
+
+The value of *days* and *last* are objects mapping measurement names to that
+measurement's values. The values are always objects. Each object contains
+a *_v* property. This property defines the version of this measurement.
+Additional non-underscore-prefixed properties are defined by the measurement
+itself (see sections below).
+
+Example
+-------
+
+Here is an example JSON document for version 1::
+
+    {
+      "version": 1,
+      "thisPingDate": "2013-03-11",
+      "lastPingDate": "2013-03-10",
+      "data": {
+        "last": {
+          "org.mozilla.addons.active": {
+            "masspasswordreset@johnathan.nightingale": {
+              "userDisabled": false,
+              "appDisabled": false,
+              "version": "1.05",
+              "type": "extension",
+              "scope": 1,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "installDay": 14973,
+              "updateDay": 15317
+            },
+            "places-maintenance@bonardo.net": {
+              "userDisabled": false,
+              "appDisabled": false,
+              "version": "1.3",
+              "type": "extension",
+              "scope": 1,
+              "foreignInstall": false,
+              "hasBinaryComponents": false,
+              "installDay": 15268,
+              "updateDay": 15379
+            },
+            "_v": 1
+          },
+          "org.mozilla.appInfo.appinfo": {
+            "_v": 1,
+            "appBuildID": "20130309030841",
+            "distributionID": "",
+            "distributionVersion": "",
+            "hotfixVersion": "",
+            "id": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+            "locale": "en-US",
+            "name": "Firefox",
+            "os": "Darwin",
+            "platformBuildID": "20130309030841",
+            "platformVersion": "22.0a1",
+            "updateChannel": "nightly",
+            "vendor": "Mozilla",
+            "version": "22.0a1",
+            "xpcomabi": "x86_64-gcc3"
+          },
+          "org.mozilla.profile.age": {
+            "_v": 1,
+            "profileCreation": 12444
+          },
+          "org.mozilla.appSessions.current": {
+            "_v": 3,
+            "startDay": 15773,
+            "activeTicks": 522,
+            "totalTime": 70858,
+            "main": 1245,
+            "firstPaint": 2695,
+            "sessionRestored": 3436
+          },
+          "org.mozilla.sysinfo.sysinfo": {
+            "_v": 1,
+            "cpuCount": 8,
+            "memoryMB": 16384,
+            "architecture": "x86-64",
+            "name": "Darwin",
+            "version": "12.2.1"
+          }
+        },
+        "days": {
+          "2013-03-11": {
+            "org.mozilla.addons.counts": {
+              "_v": 1,
+              "extension": 15,
+              "plugin": 12,
+              "theme": 1
+            },
+            "org.mozilla.places.places": {
+              "_v": 1,
+              "bookmarks": 757,
+              "pages": 104858
+            },
+            "org.mozilla.appInfo.appinfo": {
+              "_v": 1,
+              "isDefaultBrowser": 1
+            }
+          },
+          "2013-03-10": {
+            "org.mozilla.addons.counts": {
+              "_v": 1,
+              "extension": 15,
+              "plugin": 12,
+              "theme": 1
+            },
+            "org.mozilla.places.places": {
+              "_v": 1,
+              "bookmarks": 757,
+              "pages": 104857
+            },
+            "org.mozilla.searches.counts": {
+              "_v": 1,
+              "google.urlbar": 4
+            },
+            "org.mozilla.appInfo.appinfo": {
+              "_v": 1,
+              "isDefaultBrowser": 1
+            }
+          }
+        }
+      }
+    }
+
+Measurements
+============
+
+The bulk of payloads consists of measurement data. An individual measurement
+is merely a collection of related values e.g. *statistics about the Places
+database* or *system information*.
+
+Each measurement has an integer version number attached. When the fields in
+a measurement or the semantics of data within that measurement change, the
+version number is incremented.
+
+All measurements are defined alphabetically in the sections below.
+
+org.mozilla.addons.active
+-------------------------
+
+This measurement contains information about the currently-installed add-ons.
+
+Version 1
+^^^^^^^^^
+
+The measurement object is a mapping of add-on IDs to objects containing
+add-on metadata.
+
+Each add-on contains the following properties:
+
+* userDisabled
+* appDisabled
+* version
+* type
+* scope
+* foreignInstall
+* hasBinaryComponents
+* installDay
+* updateDay
+
+With the exception of *installDay* and *updateDay*, all these properties
+come direct from the Addon instance. See https://developer.mozilla.org/en-US/docs/Addons/Add-on_Manager/Addon.
+*installDay* and *updateDay* are the number of days since UNIX epoch of
+the add-ons *installDate* and *updateDate* properties, respectively.
+
+Notes
+^^^^^
+
+Add-ons that have opted out of AMO updates via the
+*extensions._id_.getAddons.cache.enabled* preference are, since Bug 868306
+(Firefox 24), included in the list of submitted add-ons.
+
+Example
+^^^^^^^
+::
+
+    "org.mozilla.addons.active": {
+      "_v": 1,
+      "SQLiteManager@mrinalkant.blogspot.com": {
+        "userDisabled": false,
+        "appDisabled": false,
+        "version": "0.7.7",
+        "type": "extension",
+        "scope": 1,
+        "foreignInstall": false,
+        "hasBinaryComponents": false,
+        "installDay": 15196,
+        "updateDay": 15307
+      },
+      "testpilot@labs.mozilla.com": {
+        "userDisabled": false,
+        "appDisabled": false,
+        "version": "1.2.2",
+        "type": "extension",
+        "scope": 1,
+        "foreignInstall": false,
+        "hasBinaryComponents": false,
+        "installDay": 15176,
+        "updateDay": 15595
+      }
+    }
+
+
+org.mozilla.addons.counts
+-------------------------
+
+This measurement contains information about historical add-on counts.
+
+Version 1
+^^^^^^^^^
+
+The measurement object consists of counts of different add-on types. The
+properties are:
+
+extension
+    Integer count of installed extensions.
+plugin
+    Integer count of installed plugins.
+theme
+    Integer count of installed themes.
+lwtheme
+    Integer count of installed lightweigh themes.
+
+Notes
+^^^^^
+
+Add-ons opted out of AMO updates are included in the counts. This differs from
+the behavior of the active add-ons measurement.
+
+If no add-ons of a particular type are installed, the property for that type
+will not be present (as opposed to an explicit property with value of 0).
+
+Example
+^^^^^^^
+
+::
+
+    "2013-03-14": {
+      "org.mozilla.addons.counts": {
+        "_v": 1,
+        "extension": 21,
+        "plugin": 4,
+        "theme": 1
+      }
+    }
+
+
+
+org.mozilla.appInfo.appinfo
+---------------------------
+
+This measurement contains basic XUL application and Gecko platform
+information. It is reported in the *last* section.
+
+Version 2
+^^^^^^^^^
+
+In addition to fields present in version 1, this version has the following
+fields appearing in the *days* section:
+
+isBlocklistEnabled
+    Whether the blocklist ping is enabled. This is an integer, 0 or 1.
+    This does not indicate whether the blocklist ping was sent but merely
+    whether the application will try to send the blocklist ping.
+
+isTelemetryEnabled
+    Whether Telemetry is enabled. This is an integer, 0 or 1.
+
+Version 1
+^^^^^^^^^
+
+The measurement object contains mostly string values describing the
+current application and build. The properties are:
+
+* vendor
+* name
+* id
+* version
+* appBuildID
+* platformVersion
+* platformBuildID
+* os
+* xpcomabi
+* updateChannel
+* distributionID
+* distributionVersion
+* hotfixVersion
+* locale
+* isDefaultBrowser
+
+Notes
+^^^^^
+
+All of the properties appear in the *last* section except for
+*isDefaultBrowser*, which appears under *days*.
+
+Example
+^^^^^^^
+
+This example comes from an official OS X Nightly build::
+
+    "org.mozilla.appInfo.appinfo": {
+      "_v": 1,
+      "appBuildID": "20130311030946",
+      "distributionID": "",
+      "distributionVersion": "",
+      "hotfixVersion": "",
+      "id": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+      "locale": "en-US",
+      "name": "Firefox",
+      "os": "Darwin",
+      "platformBuildID": "20130311030946",
+      "platformVersion": "22.0a1",
+      "updateChannel": "nightly",
+      "vendor": "Mozilla",
+      "version": "22.0a1",
+      "xpcomabi": "x86_64-gcc3"
+    },
+
+org.mozilla.appInfo.update
+--------------------------
+
+This measurement contains information about the application update mechanism
+in the application.
+
+Version 1
+^^^^^^^^^
+
+The following daily values are reported:
+
+enabled
+    Whether automatic application update checking is enabled. 1 for yes,
+    0 for no.
+autoDownload
+    Whether automatic download of available updates is enabled.
+
+Notes
+^^^^^
+
+This measurement was merged to mozilla-central for JS FHR on 2013-07-15.
+
+Example
+^^^^^^^
+
+::
+
+    "2013-07-15": {
+      "org.mozilla.appInfo.update": {
+        "_v": 1,
+        "enabled": 1,
+        "autoDownload": 1,
+      }
+    }
+
+org.mozilla.appInfo.versions
+----------------------------
+
+This measurement contains a history of application version numbers.
+
+Version 2
+^^^^^^^^^
+
+Version 2 reports more fields than version 1 and is not backwards compatible.
+The following fields are present in version 2:
+
+appVersion
+    An array of application version strings.
+appBuildID
+    An array of application build ID strings.
+platformVersion
+    An array of platform version strings.
+platformBuildID
+    An array of platform build ID strings.
+
+When the application is upgraded, the new version and/or build IDs are
+appended to their appropriate fields.
+
+Version 1
+^^^^^^^^^
+
+When the application version (*version* from *org.mozilla.appinfo.appinfo*)
+changes, we record the new version on the day the change was seen. The new
+versions for a day are recorded in an array under the *version* property.
+
+Notes
+^^^^^
+
+If the application isn't upgraded, this measurement will not be present.
+This means this measurement will not be present for most days if a user is
+on the release channel (since updates are typically released every 6 weeks).
+However, users on the Nightly and Aurora channels will likely have a lot
+of these entries since those builds are updated every day.
+
+Values for this measurement are collected when performing the daily
+collection (typically occurs at upload time). As a result, it's possible
+the actual upgrade day may not be attributed to the proper day - the
+reported day may lag behind.
+
+The app and platform versions and build IDs should be identical for most
+clients. If they are different, we are possibly looking at a *Frankenfox*.
+
+Example
+^^^^^^^
+
+::
+
+    "2013-03-27": {
+      "org.mozilla.appInfo.versions": {
+        "_v": 2,
+        "appVersion": [
+           "22.0.0"
+        ],
+        "appBuildID": [
+          "20130325031100"
+        ],
+        "platformVersion": [
+          "22.0.0"
+        ],
+        "platformBuildID": [
+          "20130325031100"
+        ]
+      }
+    }
+
+org.mozilla.appSessions.current
+-------------------------------
+
+This measurement contains information about the currently running XUL
+application's session.
+
+Version 3
+^^^^^^^^^
+
+This measurement has the following properties:
+
+startDay
+    Integer days since UNIX epoch when this session began.
+activeTicks
+    Integer count of *ticks* the session was active for. Gecko periodically
+    sends out a signal when the session is active. Session activity
+    involves keyboard or mouse interaction with the application. Each tick
+    represents a window of 5 seconds where there was interaction.
+totalTime
+    Integer seconds the session has been alive.
+main
+    Integer milliseconds it took for the Gecko process to start up.
+firstPaint
+    Integer milliseconds from process start to first paint.
+sessionRestored
+    Integer milliseconds from process start to session restore.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.appSessions.current": {
+      "_v": 3,
+      "startDay": 15775,
+      "activeTicks": 4282,
+      "totalTime": 249422,
+      "main": 851,
+      "firstPaint": 3271,
+      "sessionRestored": 5998
+    }
+
+org.mozilla.appSessions.previous
+--------------------------------
+
+This measurement contains information about previous XUL application sessions.
+
+Version 3
+^^^^^^^^^
+
+This measurement contains per-day lists of all the sessions started on that
+day. The following properties may be present:
+
+cleanActiveTicks
+    Active ticks of sessions that were properly shut down.
+cleanTotalTime
+    Total number of seconds for sessions that were properly shut down.
+abortedActiveTicks
+    Active ticks of sessions that were not properly shut down.
+abortedTotalTime
+    Total number of seconds for sessions that were not properly shut down.
+main
+    Time in milliseconds from process start to main process initialization.
+firstPaint
+    Time in milliseconds from process start to first paint.
+sessionRestored
+    Time in milliseconds from process start to session restore.
+
+Notes
+^^^^^
+
+Sessions are recorded on the date on which they began.
+
+If a session was aborted/crashed, the total time may be less than the actual
+total time. This is because we don't always update total time during periods
+of inactivity and the abort/crash could occur after a long period of idle,
+before we've updated the total time.
+
+The lengths of the arrays for {cleanActiveTicks, cleanTotalTime},
+{abortedActiveTicks, abortedTotalTime}, and {main, firstPaint, sessionRestored}
+should all be identical.
+
+The length of the clean sessions plus the length of the aborted sessions should
+be equal to the length of the {main, firstPaint, sessionRestored} properties.
+
+It is not possible to distinguish the main, firstPaint, and sessionRestored
+values from a clean vs aborted session: they are all lumped together.
+
+For sessions spanning multiple UTC days, it's not possible to know which
+days the session was active for. It's possible a week long session only
+had activity for 2 days and there's no way for us to tell which days.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.appSessions.previous": {
+      "_v": 3,
+      "cleanActiveTicks": [
+        78,
+        1785
+      ],
+      "cleanTotalTime": [
+        4472,
+        88908
+      ],
+      "main": [
+        32,
+        952
+      ],
+      "firstPaint": [
+        2755,
+        3497
+      ],
+      "sessionRestored": [
+        5149,
+        5520
+      ]
+    }
+
+org.mozilla.crashes.crashes
+---------------------------
+
+This measurement contains a historical record of application crashes.
+
+Version 1
+^^^^^^^^^
+
+This measurement will be reported on each day there was a crash. The
+following properties are reported:
+
+pending
+    The number of crash reports that haven't been submitted.
+submitted
+    The number of crash reports that were submitted.
+
+Notes
+^^^^^
+
+Crashes are typically submitted immediately after they occur (by checking
+a box in the crash reporter, which should appear automatically after a
+crash). If the crash reporter submits the crash successfully, we get a
+submitted crash. Else, we leave it as pending.
+
+A pending crash does not mean it will eventually be submitted.
+
+Pending crash reports can be submitted post-crash by going to
+about:crashes.
+
+If a pending crash is submitted via about:crashes, the submitted count
+increments but the pending count does not decrement. This is because FHR
+does not know which pending crash was just submitted and therefore it does
+not know which day's pending crash to decrement.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.crashes.crashes": {
+      "_v": 1,
+      "pending": 1,
+      "submitted": 2
+    }
+
+org.mozilla.healthreport.submissions
+------------------------------------
+
+This measurement contains a history of FHR's own data submission activity.
+It was added in Firefox 23 in early May 2013.
+
+Version 1
+^^^^^^^^^
+
+Daily counts of upload events are recorded.
+
+firstDocumentUploadAttempt
+    An attempt was made to upload the client's first document to the server.
+    These are uploads where the client is not aware of a previous document ID
+    on the server. Unless the client had disabled upload, there should be at
+    most one of these in the history of the client.
+
+continuationUploadAttempt
+    An attempt was made to upload a document that replaces an existing document
+    on the server. Most upload attempts should be attributed to this as opposed
+    to *firstDocumentUploadAttempt*.
+
+uploadSuccess
+    The upload attempt recorded by *firstDocumentUploadAttempt* or
+    *continuationUploadAttempt* was successful.
+
+uploadTransportFailure
+    An upload attempt failed due to transport failure (network unavailable,
+    etc).
+
+uploadServerFailure
+    An upload attempt failed due to a server-reported failure. Ideally these
+    are failures reported by the FHR server itself. However, intermediate
+    proxies, firewalls, etc may trigger this depending on how things are
+    configured.
+
+uploadClientFailure
+    An upload attempt failued due to an error/exception in the client.
+    This almost certainly points to a bug in the client.
+
+The result for an upload attempt is always attributed to the same day as
+the attempt, even if the result occurred on a different day from the attempt.
+Therefore, the sum of the result counts should equal the result of the attempt
+counts.
+
+org.mozilla.places.places
+-------------------------
+
+This measurement contains information about the Places database (where Firefox
+stores its history and bookmarks).
+
+Version 1
+^^^^^^^^^
+
+Daily counts of items in the database are reported in the following properties:
+
+bookmarks
+    Integer count of bookmarks present.
+pages
+    Integer count of pages in the history database.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.places.places": {
+      "_v": 1,
+      "bookmarks": 388,
+      "pages": 94870
+    }
+
+org.mozilla.profile.age
+-----------------------
+
+This measurement contains information about the current profile's age.
+
+Version 1
+^^^^^^^^^
+
+A single *profileCreation* property is present. It defines the integer
+days since UNIX epoch that the current profile was created.
+
+Notes
+^^^^^
+
+It is somewhat difficult to obtain a reliable *profile born date* due to a
+number of factors.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.profile.age": {
+      "_v": 1,
+      "profileCreation": 15176
+    }
+
+org.mozilla.searches.counts
+---------------------------
+
+This measurement contains information about searches performed in the
+application.
+
+Version 2
+^^^^^^^^^
+
+This behaves like version 1 except we added all search engines that
+Mozilla has a partner agreement with. Like version 1, we concatenate
+a search engine ID with a search origin.
+
+Another difference with version 2 is we should no longer misattribute
+a search to the *other* bucket if the search engine name is localized.
+
+The set of search engine providers is:
+
+* amazon-co-uk
+* amazon-de
+* amazon-en-GB
+* amazon-france
+* amazon-it
+* amazon-jp
+* amazondotcn
+* amazondotcom
+* amazondotcom-de
+* aol-en-GB
+* aol-web-search
+* bing
+* eBay
+* eBay-de
+* eBay-en-GB
+* eBay-es
+* eBay-fi
+* eBay-france
+* eBay-hu
+* eBay-in
+* eBay-it
+* google
+* google-jp
+* google-ku
+* google-maps-zh-TW
+* mailru
+* mercadolibre-ar
+* mercadolibre-cl
+* mercadolibre-mx
+* seznam-cz
+* twitter
+* twitter-de
+* twitter-ja
+* yahoo
+* yahoo-NO
+* yahoo-answer-zh-TW
+* yahoo-ar
+* yahoo-bid-zh-TW
+* yahoo-br
+* yahoo-ch
+* yahoo-cl
+* yahoo-de
+* yahoo-en-GB
+* yahoo-es
+* yahoo-fi
+* yahoo-france
+* yahoo-fy-NL
+* yahoo-id
+* yahoo-in
+* yahoo-it
+* yahoo-jp
+* yahoo-jp-auctions
+* yahoo-mx
+* yahoo-sv-SE
+* yahoo-zh-TW
+* yandex
+* yandex-ru
+* yandex-slovari
+* yandex-tr
+* yandex.by
+* yandex.ru-be
+
+And of course, *other*.
+
+The sources for searches remain:
+
+* abouthome
+* contextmenu
+* searchbar
+* urlbar
+
+The measurement will only be populated with providers and sources that
+occurred that day.
+
+If a user switches locales, searches from default providers on the older
+locale will still be supported. However, if that same search engine is
+added by the user to the new build and is *not* a default search engine
+provider, its searches will be attributed to the *other* bucket.
+
+Version 1
+^^^^^^^^^
+
+We record counts of performed searches grouped by search engine and search
+origin. Only search engines with which Mozilla has a business relationship
+are explicitly counted. All other search engines are grouped into an
+*other* bucket.
+
+The following search engines are explicitly counted:
+
+* Amazon.com
+* Bing
+* Google
+* Yahoo
+* Other
+
+The following search origins are distinguished:
+
+about:home
+    Searches initiated from the search text box on about:home.
+context menu
+    Searches initiated from the context menu (highlight text, right click,
+    and select "search for...")
+search bar
+    Searches initiated from the search bar (the text field next to the
+    Awesomebar)
+url bar
+    Searches initiated from the awesomebar/url bar.
+
+Due to the localization of search engine names, non en-US locales may wrongly
+attribute searches to the *other* bucket. This is fixed in version 2.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.searches.counts": {
+      "_v": 1,
+      "google.searchbar": 3,
+      "google.urlbar": 7
+    },
+
+org.mozilla.sysinfo.sysinfo
+---------------------------
+
+This measurement contains basic information about the system the application
+is running on.
+
+Version 2
+^^^^^^^^^
+
+This version debuted with Firefox 29 on desktop.
+
+A single property was introduced.
+
+isWow64
+   If present, this property indicates whether the machine supports WoW64.
+   This property can be used to identify whether the host machine is 64-bit.
+
+   This property is only present on Windows machines. It is the preferred way
+   to identify 32- vs 64-bit support in that environment.
+
+Version 1
+^^^^^^^^^
+
+The following properties may be available:
+
+cpuCount
+    Integer number of CPUs/cores in the machine.
+memoryMB
+    Integer megabytes of memory in the machine.
+manufacturer
+    The manufacturer of the device.
+device
+    The name of the device (like model number).
+hardware
+    Unknown.
+name
+    OS name.
+version
+    OS version.
+architecture
+    OS architecture that the application is built for. This is not the
+    actual system architecture.
+
+Example
+^^^^^^^
+
+::
+
+    "org.mozilla.sysinfo.sysinfo": {
+      "_v": 1,
+      "cpuCount": 8,
+      "memoryMB": 8192,
+      "architecture": "x86-64",
+      "name": "Darwin",
+      "version": "12.2.0"
+    }
+
rename from services/docs/healthreport.rst
rename to services/healthreport/docs/index.rst
--- a/services/docs/healthreport.rst
+++ b/services/healthreport/docs/index.rst
@@ -1,9 +1,9 @@
-.. _firefox_health_report:
+.. _healthreport:
 
 =====================
 Firefox Health Report
 =====================
 
 ``/services/healthreport`` contains the implementation of the
 ``Firefox Health Report`` (FHR).
 
@@ -14,236 +14,21 @@ actual XPCOM service is implemented in t
 :ref:`data_reporting_service`.
 
 The core types can actually be instantiated multiple times and used to
 power multiple data submission services within a single Gecko
 application. In other words, everything in this directory is effectively
 a reusable library. However, the terminology and some of the features
 are very specific to what the Firefox Health Report feature requires.
 
-Architecture
-============
-
-``healthreporter.jsm`` contains the main interface for FHR, the
-``HealthReporter`` type. An instance of this is created by the
-:ref:`data_reporting_service`.
-
-``providers.jsm`` contains numerous ``Metrics.Provider`` and
-``Metrics.Measurement`` used for collecting application metrics. If you
-are looking for the FHR probes, this is where they are.
-
-Storage
--------
-
-Firefox Health Report stores data in 3 locations:
-
-* Metrics measurements and provider state is stored in a SQLite database
-  (via ``Metrics.Storage``).
-* Service state (such as the IDs of documents uploaded) is stored in a
-  JSON file on disk (via OS.File).
-* Lesser state and run-time options are stored in preferences.
-
-Preferences
------------
-
-Preferences controlling behavior of Firefox Health Report live in the
-``datareporting.healthreport.*`` branch.
-
-Service and Data Control
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-The follow preferences control behavior of the service and data upload.
-
-service.enabled
-   Controls whether the entire health report service runs. The overall
-   service performs data collection, storing, and submission.
-
-   This is the primary kill switch for Firefox Health Report
-   outside of the build system variable. i.e. if you are using an
-   official Firefox build and wish to disable FHR, this is what you
-   should set to false to prevent FHR from not only submitting but
-   also collecting data.
-
-uploadEnabled
-   Whether uploading of data is enabled. This is the preference the
-   checkbox in the preferences UI reflects. If this is
-   disabled, FHR still collects data - it just doesn't upload it.
-
-service.loadDelayMsec
-   How long (in milliseconds) after initial application start should FHR
-   wait before initializing.
-
-   FHR may initialize sooner than this if the FHR service is requested.
-   This will happen if e.g. the user goes to ``about:healthreport``.
-
-service.loadDelayFirstRunMsec
-   How long (in milliseconds) FHR should wait to initialize on first
-   application run.
-
-   FHR waits longer than normal to initialize on first application run
-   because first-time initialization can use a lot of I/O to initialize
-   the SQLite database and this I/O should not interfere with the
-   first-run user experience.
-
-documentServerURI
-   The URI of a Bagheera server that FHR should interface with for
-   submitting documents.
-