Merge m-c to b2g-inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 19 Sep 2014 14:17:27 -0400
changeset 206248 35556216bf86292793015fb8895db4caaaa0e95d
parent 206247 e155b3edd2f3974e8bab20d4dd4ce154b751faf1 (current diff)
parent 206245 a084c4cfd8a1ce35927526d38985ef805427c144 (diff)
child 206249 85ca2e799c130167bdc67fcdefe73aa9ae34dafd
push id27518
push userkwierso@gmail.com
push dateFri, 19 Sep 2014 23:21:15 +0000
treeherdermozilla-central@8be4d99d0890 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound.
--- a/browser/base/content/test/general/browser_parsable_script.js
+++ b/browser/base/content/test/general/browser_parsable_script.js
@@ -2,16 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* This list allows pre-existing or 'unfixable' JS issues to remain, while we
  * detect newly occurring issues in shipping JS. It is a list of regexes
  * matching files which have errors:
  */
 const kWhitelist = new Set([
   /defaults\/profile\/prefs.js$/,
+  /browser\/content\/browser\/places\/controller.js$/,
 ]);
 
 
 let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
 let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
 let {Reflect} = Cu.import("resource://gre/modules/reflect.jsm", {});
 
 /**
--- a/browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
+++ b/browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
@@ -31,30 +31,31 @@ function test() {
                        , visits: [ { transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED
                                    , visitDate:      Date.now() * 1000
                                    } ]
                        }, callback);
 }
 
 function continue_test() {
   function test_autoFill(aTyped, aExpected, aCallback) {
+    info(`Testing with input: ${aTyped}`);
     gURLBar.inputField.value = aTyped.substr(0, aTyped.length - 1);
     gURLBar.focus();
     gURLBar.selectionStart = aTyped.length - 1;
     gURLBar.selectionEnd = aTyped.length - 1;
 
     EventUtils.synthesizeKey(aTyped.substr(-1), {});
     waitForSearchComplete(function () {
       is(gURLBar.value, aExpected, "trim was applied correctly");
       aCallback();
     });
   }
 
   test_autoFill("http://", "http://", function () {
-    test_autoFill("http://a", "http://autofilltrimurl.com/", function () {
+    test_autoFill("http://au", "http://autofilltrimurl.com/", function () {
       test_autoFill("http://www.autofilltrimurl.com", "http://www.autofilltrimurl.com/", function () {
         // Now ensure selecting from the popup correctly trims.
         is(gURLBar.controller.matchCount, 1, "Found the expected number of matches");
         EventUtils.synthesizeKey("VK_DOWN", {});
         is(gURLBar.value, "www.autofilltrimurl.com", "trim was applied correctly");
         gURLBar.closePopup();
         waitForClearHistory(finish);
       });
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -8,49 +8,55 @@ this.EXPORTED_SYMBOLS = ["PlacesUIUtils"
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cr = Components.results;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+// PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
+                                  "resource://gre/modules/PlacesTransactions.jsm");
 
 #ifdef MOZ_SERVICES_CLOUDSYNC
 XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
                                   "resource://gre/modules/CloudSync.jsm");
 #else
 let CloudSync = null;
 #endif
 
 #ifdef MOZ_SERVICES_SYNC
 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
                                   "resource://services-sync/main.js");
 #endif
 
-XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-  return PlacesUtils;
-});
+// copied from utilityOverlay.js
+const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
 
 this.PlacesUIUtils = {
   ORGANIZER_LEFTPANE_VERSION: 7,
   ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
   ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
 
   LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
   DESCRIPTION_ANNO: "bookmarkProperties/description",
 
-  TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
-
   /**
    * Makes a URI from a spec, and do fixup
    * @param   aSpec
    *          The string spec of the URI
    * @returns A URI object for the spec.
    */
   createFixedURI: function PUIU_createFixedURI(aSpec) {
     return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
@@ -323,27 +329,83 @@ this.PlacesUIUtils = {
         }
 
         // Otherwise move the item.
         return new PlacesMoveItemTransaction(data.id, container, index);
         break;
       default:
         if (type == PlacesUtils.TYPE_X_MOZ_URL ||
             type == PlacesUtils.TYPE_UNICODE ||
-            type == this.TYPE_TAB_DROP) {
+            type == TAB_DROP_TYPE) {
           let title = type != PlacesUtils.TYPE_UNICODE ? data.title
                                                        : data.uri;
           return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
                                                      container, index, title);
         }
     }
     return null;
   },
 
   /**
+   * ********* PlacesTransactions version of the function defined above ********
+   *
+   * Constructs a Places Transaction for the drop or paste of a blob of data
+   * into a container.
+   *
+   * @param aData
+   *        The unwrapped data blob of dropped or pasted data.
+   * @param aType
+   *        The content type of the data.
+   * @param aNewParentGuid
+   *        GUID of the container the data was dropped or pasted into.
+   * @param aIndex
+   *        The index within the container the item was dropped or pasted at.
+   * @param aCopy
+   *        The drag action was copy, so don't move folders or links.
+   *
+   * @returns a Places Transaction that can be passed to
+   *          PlacesTranactions.transact for performing the move/insert command.
+   */
+  getTransactionForData: function(aData, aType, aNewParentGuid, aIndex, aCopy) {
+    if (this.SUPPORTED_FLAVORS.indexOf(aData.type) == -1)
+      throw new Error(`Unsupported '${aData.type}' data type`);
+
+    if ("itemGuid" in aData) {
+      if (this.PLACES_FLAVORS.indexOf(aData.type) == -1)
+        throw new Error (`itemGuid unexpectedly set on ${aData.type} data`);
+
+      let info = { GUID: aData.itemGuid
+                 , newParentGUID: aNewParentGuid
+                 , newIndex: aIndex };
+      if (aCopy)
+        return PlacesTransactions.Copy(info);
+      return PlacesTransactions.Move(info);
+    }
+
+    // Since it's cheap and harmless, we allow the paste of separators and
+    // bookmarks from builds that use legacy transactions (i.e. when itemGuid
+    // was not set on PLACES_FLAVORS data). Containers are a different story,
+    // and thus disallowed.
+    if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
+      throw new Error("Can't copy a container from a legacy-transactions build");
+
+    if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+      return PlacesTransactions.NewSeparator({ parentGUID: aNewParentGuid
+                                             , index: aIndex });
+    }
+
+    let title = aData.type != PlacesUtils.TYPE_UNICODE ? aData.title
+                                                       : aData.uri;
+    return PlacesTransactions.NewBookmark({ uri: NetUtil.newURI(aData.uri)
+                                          , title: title
+                                          , parentGUID: aNewParentGuid
+                                          , index: aIndex });
+  },
+
+  /**
    * Shows the bookmark dialog corresponding to the specified info.
    *
    * @param aInfo
    *        Describes the item to be edited/added in the dialog.
    *        See documentation at the top of bookmarkProperties.js
    * @param aWindow
    *        Owner window for the new dialog.
    *
@@ -1019,16 +1081,28 @@ this.PlacesUIUtils = {
     let weaveEnabled = Weave.Service.isLoggedIn &&
                        Weave.Service.engineManager.get("tabs") &&
                        Weave.Service.engineManager.get("tabs").enabled;
     let cloudSyncEnabled = CloudSync && CloudSync.ready && CloudSync().tabsReady && CloudSync().tabs.hasRemoteTabs();
     return weaveEnabled || cloudSyncEnabled;
   },
 };
 
+
+PlacesUIUtils.PLACES_FLAVORS = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
+                                PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
+                                PlacesUtils.TYPE_X_MOZ_PLACE];
+
+PlacesUIUtils.URI_FLAVORS = [PlacesUtils.TYPE_X_MOZ_URL,
+                             TAB_DROP_TYPE,
+                             PlacesUtils.TYPE_UNICODE],
+
+PlacesUIUtils.SUPPORTED_FLAVORS = [...PlacesUIUtils.PLACES_FLAVORS,
+                                   ...PlacesUIUtils.URI_FLAVORS];
+
 XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
                                    "@mozilla.org/rdf/rdf-service;1",
                                    "nsIRDFService");
 
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
   return Services.prefs.getComplexValue("intl.ellipsis",
                                         Ci.nsIPrefLocalizedString).data;
 });
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -164,41 +164,42 @@ PlacesViewBase.prototype = {
         PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
           Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
       return null;
 
     // By default, the insertion point is at the top level, at the end.
     let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
     let container = this._resultNode;
     let orientation = Ci.nsITreeView.DROP_BEFORE;
-    let isTag = false;
+    let tagName = null;
 
     let selectedNode = this.selectedNode;
     if (selectedNode) {
       let popup = document.popupNode;
       if (!popup._placesNode || popup._placesNode == this._resultNode ||
           popup._placesNode.itemId == -1) {
         // If a static menuitem is selected, or if the root node is selected,
         // the insertion point is inside the folder, at the end.
         container = selectedNode;
         orientation = Ci.nsITreeView.DROP_ON;
       }
       else {
         // In all other cases the insertion point is before that node.
         container = selectedNode.parent;
         index = container.getChildIndex(selectedNode);
-        isTag = PlacesUtils.nodeIsTagQuery(container);
+        if (PlacesUtils.nodeIsTagQuery(container))
+          tagName = container.title;
       }
     }
 
     if (PlacesControllerDragHelper.disallowInsertion(container))
       return null;
 
     return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
-                              index, orientation, isTag);
+                              index, orientation, tagName);
   },
 
   buildContextMenu: function PVB_buildContextMenu(aPopup) {
     this._contextMenuShown = aPopup;
     window.updateCommands("places");
     return this.controller.buildContextMenu(aPopup);
   },
 
@@ -1387,20 +1388,22 @@ PlacesToolbar.prototype = {
           dropPoint.ip =
             new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
                                eltIndex, Ci.nsITreeView.DROP_BEFORE);
           dropPoint.beforeIndex = eltIndex;
         }
         else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
                             : (aEvent.clientX < eltRect.right - threshold)) {
           // Drop inside this folder.
+          let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
+                        elt._placesNode.title : null;
           dropPoint.ip =
             new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
                                -1, Ci.nsITreeView.DROP_ON,
-                               PlacesUtils.nodeIsTagQuery(elt._placesNode));
+                               tagName);
           dropPoint.beforeIndex = eltIndex;
           dropPoint.folderElt = elt;
         }
         else {
           // Drop after this folder.
           let beforeIndex =
             (eltIndex == this._rootElt.childNodes.length - 1) ?
             -1 : eltIndex + 1;
@@ -1632,16 +1635,17 @@ PlacesToolbar.prototype = {
   },
 
   _onDrop: function PT__onDrop(aEvent) {
     PlacesControllerDragHelper.currentDropTarget = aEvent.target;
 
     let dropPoint = this._getDropPoint(aEvent);
     if (dropPoint && dropPoint.ip) {
       PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
+                                .then(null, Components.utils.reportError);
       aEvent.preventDefault();
     }
 
     this._cleanupDragDetails();
     aEvent.stopPropagation();
   },
 
   _onDragExit: function PT__onDragExit(aEvent) {
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -32,47 +32,49 @@ const REMOVE_PAGES_CHUNKLEN = 300;
  *          The identifier of the parent container
  * @param   aIndex
  *          The index within the container where we should insert
  * @param   aOrientation
  *          The orientation of the insertion. NOTE: the adjustments to the
  *          insertion point to accommodate the orientation should be done by
  *          the person who constructs the IP, not the user. The orientation
  *          is provided for informational purposes only!
- * @param   [optional] aIsTag
- *          Indicates if parent container is a tag
+ * @param   [optional] aTag
+ *          The tag name if this IP is set to a tag, null otherwise.
  * @param   [optional] aDropNearItemId
  *          When defined we will calculate index based on this itemId
  * @constructor
  */
-function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag,
-                        aDropNearItemId) {
+function InsertionPoint(aItemId, aIndex, aOrientation, aTagName = null,
+                        aDropNearItemId = false) {
   this.itemId = aItemId;
   this._index = aIndex;
   this.orientation = aOrientation;
-  this.isTag = aIsTag;
+  this.tagName = aTagName;
   this.dropNearItemId = aDropNearItemId;
 }
 
 InsertionPoint.prototype = {
   set index(val) {
     return this._index = val;
   },
 
-  promiseGUID: function () PlacesUtils.promiseItemGUID(this.itemId),
+  promiseGuid: function () PlacesUtils.promiseItemGUID(this.itemId),
 
   get index() {
     if (this.dropNearItemId > 0) {
       // If dropNearItemId is set up we must calculate the real index of
       // the item near which we will drop.
       var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
       return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
     }
     return this._index;
-  }
+  },
+
+  get isTag() typeof(this.tagName) == "string"
 };
 
 /**
  * Places Controller
  */
 
 function PlacesController(aView) {
   this._view = aView;
@@ -122,24 +124,18 @@ PlacesController.prototype = {
     // filters out other commands that we do _not_ support (see 329587).
     const CMD_PREFIX = "placesCmd_";
     return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
   },
 
   isCommandEnabled: function PC_isCommandEnabled(aCommand) {
     if (PlacesUIUtils.useAsyncTransactions) {
       switch (aCommand) {
-      case "cmd_cut":
-      case "placesCmd_cut":
-      case "cmd_copy":
-      case "cmd_paste":
       case "cmd_delete":
       case "placesCmd_delete":
-      case "cmd_paste":
-      case "placesCmd_paste":
       case "placesCmd_new:folder":
       case "placesCmd_new:bookmark":
       case "placesCmd_createBookmark":
         return false;
       }
     }
 
     switch (aCommand) {
@@ -238,17 +234,17 @@ PlacesController.prototype = {
       this.cut();
       break;
     case "cmd_copy":
     case "placesCmd_copy":
       this.copy();
       break;
     case "cmd_paste":
     case "placesCmd_paste":
-      this.paste();
+      this.paste().then(null, Components.utils.reportError);
       break;
     case "cmd_delete":
     case "placesCmd_delete":
       this.remove("Remove Selection");
       break;
     case "placesCmd_deleteDataHost":
       var host;
       if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
@@ -373,17 +369,17 @@ PlacesController.prototype = {
    * @return true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
    *                  - clipboard data is of type TEXT_UNICODE and
    *                    is a valid URI.
    */
   _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
     // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
     // pasteable, with no need to unwrap all the nodes.
 
-    var flavors = PlacesControllerDragHelper.placesFlavors;
+    var flavors = PlacesUIUtils.PLACES_FLAVORS;
     var clipboard = this.clipboard;
     var hasPlacesData =
       clipboard.hasDataMatchingFlavors(flavors, flavors.length,
                                        Ci.nsIClipboard.kGlobalClipboard);
     if (hasPlacesData)
       return this._view.insertionPoint != null;
 
     // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
@@ -780,17 +776,17 @@ PlacesController.prototype = {
       PlacesUtils.transactionManager.doTransaction(txn);
       // Select the new item.
       let insertedNodeId = PlacesUtils.bookmarks
                                       .getIdForItemAt(ip.itemId, ip.index);
       this._view.selectItems([insertedNodeId], false);
       return;
     }
 
-    let txn = PlacesTransactions.NewSeparator({ parentGUID: yield ip.promiseGUID()
+    let txn = PlacesTransactions.NewSeparator({ parentGUID: yield ip.promiseGuid()
                                               , index: ip.index });
     let guid = yield PlacesTransactions.transact(txn);
     let itemId = yield PlacesUtils.promiseItemId(guid);
     // Select the new item.
     this._view.selectItems([itemId], false);
   }),
 
   /**
@@ -1248,17 +1244,17 @@ PlacesController.prototype = {
       if (!didSuppressNotifications)
         result.suppressNotifications = false;
     }
   },
 
   /**
    * Paste Bookmarks and Folders from the clipboard
    */
-  paste: function PC_paste() {
+  paste: Task.async(function* () {
     // No reason to proceed if there isn't a valid insertion point.
     let ip = this._view.insertionPoint;
     if (!ip)
       throw Cr.NS_ERROR_NOT_AVAILABLE;
 
     let action = this.clipboardAction;
 
     let xferable = Cc["@mozilla.org/widget/transferable;1"].
@@ -1280,64 +1276,101 @@ PlacesController.prototype = {
       data = data.value.QueryInterface(Ci.nsISupportsString).data;
       type = type.value;
       items = PlacesUtils.unwrapNodes(data, type);
     } catch(ex) {
       // No supported data exists or nodes unwrap failed, just bail out.
       return;
     }
 
-    let transactions = [];
-    let insertionIndex = ip.index;
-    for (let i = 0; i < items.length; ++i) {
+    let itemsToSelect = [];
+    if (PlacesUIUtils.useAsyncTransactions) {
       if (ip.isTag) {
-        // Pasting into a tag container means tagging the item, regardless of
-        // the requested action.
-        let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
-                                                 [ip.itemId]);
-        transactions.push(tagTxn);
-        continue;
+        let uris = [for (item of items) if ("uri" in item)
+                    NetUtil.newURI(item.uri)];
+        yield PlacesTransactions.transact(
+          PlacesTransactions.Tag({ uris: uris, tag: ip.tagName }));
+      }
+      else {
+        yield PlacesTransactions.transact(function* () {
+          let insertionIndex = ip.index;
+          let parent = yield ip.promiseGuid();
+
+          for (let item of items) {
+            let doCopy = action == "copy";
+
+            // If this is not a copy, check for safety that we can move the
+            // source, otherwise report an error and fallback to a copy.
+            if (!doCopy &&
+                !PlacesControllerDragHelper.canMoveUnwrappedNode(item)) {
+              Cu.reportError("Tried to move an unmovable Places node, " +
+                             "reverting to a copy operation.");
+              doCopy = true;
+            }
+            let guid = yield PlacesUIUtils.getTransactionForData(
+              item, type, parent, insertionIndex, doCopy);
+            itemsToSelect.push(yield PlacesUtils.promiseItemId(guid));
+
+            // Adjust index to make sure items are pasted in the correct
+            // position.  If index is DEFAULT_INDEX, items are just appended.
+            if (insertionIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
+              insertionIndex++;
+          }
+        });
+      }
+    }
+    else {
+      let transactions = [];
+      for (let i = 0; i < items.length; ++i) {
+        let insertionIndex = ip.index + i;
+        if (ip.isTag) {
+          // Pasting into a tag container means tagging the item, regardless of
+          // the requested action.
+          let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
+                                                   [ip.itemId]);
+          transactions.push(tagTxn);
+          continue;
+        }
+
+        // Adjust index to make sure items are pasted in the correct position.
+        // If index is DEFAULT_INDEX, items are just appended.
+        if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
+          insertionIndex = ip.index + i;
+
+        // If this is not a copy, check for safety that we can move the source,
+        // otherwise report an error and fallback to a copy.
+        if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
+          Components.utils.reportError("Tried to move an unmovable Places node, " +
+                                       "reverting to a copy operation.");
+          action = "copy";
+        }
+        transactions.push(
+          PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
+                                        insertionIndex, action == "copy")
+        );
       }
 
-      // Adjust index to make sure items are pasted in the correct position.
-      // If index is DEFAULT_INDEX, items are just appended.
-      if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
-        insertionIndex = ip.index + i;
+      let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
+      PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
 
-      // If this is not a copy, check for safety that we can move the source,
-      // otherwise report an error and fallback to a copy.
-      if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
-        Components.utils.reportError("Tried to move an unmovable Places node, " +
-                                     "reverting to a copy operation.");
-        action = "copy";
+      for (let i = 0; i < transactions.length; ++i) {
+        itemsToSelect.push(
+          PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
+        );
       }
-      transactions.push(
-        PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
-                                      insertionIndex, action == "copy")
-      );
     }
- 
-    let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
-    PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
 
     // Cut/past operations are not repeatable, so clear the clipboard.
     if (action == "cut") {
       this._clearClipboard();
     }
 
-    // Select the pasted items, they should be consecutive.
-    let insertedNodeIds = [];
-    for (let i = 0; i < transactions.length; ++i) {
-      insertedNodeIds.push(
-        PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
-      );
-    }
-    if (insertedNodeIds.length > 0)
-      this._view.selectItems(insertedNodeIds, false);
-  },
+    if (itemsToSelect.length > 0)
+      this._view.selectItems(itemsToSelect, false);
+  }),
 
   /**
    * Cache the livemark info for a node.  This allows the controller and the
    * views to treat the given node as a livemark.
    * @param aNode
    *        a places result node.
    * @param aLivemarkInfo
    *        a mozILivemarkInfo object.
@@ -1407,17 +1440,17 @@ let PlacesControllerDragHelper = {
 
   /**
    * Extract the first accepted flavor from a list of flavors.
    * @param aFlavors
    *        The flavors list of type DOMStringList.
    */
   getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
     for (let i = 0; i < aFlavors.length; i++) {
-      if (this.GENERIC_VIEW_DROP_TYPES.indexOf(aFlavors[i]) != -1)
+      if (PlacesUIUtils.SUPPORTED_FLAVORS.indexOf(aFlavors[i]) != -1)
         return aFlavors[i];
     }
 
     // If no supported flavor is found, check if data includes text/plain 
     // contents.  If so, request them as text/unicode, a conversion will happen 
     // automatically.
     if (aFlavors.contains("text/plain")) {
         return PlacesUtils.TYPE_UNICODE;
@@ -1564,22 +1597,25 @@ let PlacesControllerDragHelper = {
     return true;
   },
 
   /**
    * Handles the drop of one or more items onto a view.
    * @param   insertionPoint
    *          The insertion point where the items should be dropped
    */
-  onDrop: function PCDH_onDrop(insertionPoint, dt) {
+  onDrop: Task.async(function* (insertionPoint, dt) {
     let doCopy = ["copy", "link"].indexOf(dt.dropEffect) != -1;
 
     let transactions = [];
     let dropCount = dt.mozItemCount;
     let movedCount = 0;
+    let parentGuid = PlacesUIUtils.useAsyncTransactions ?
+                       (yield insertionPoint.promiseGuid()) : null;
+    let tagName = insertionPoint.tagName;
     for (let i = 0; i < dropCount; ++i) {
       let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
       if (!flavor)
         return;
 
       let data = dt.mozGetDataAt(flavor, i);
       let unwrapped;
       if (flavor != TAB_DROP_TYPE) {
@@ -1608,63 +1644,68 @@ let PlacesControllerDragHelper = {
       if (index != -1 && dragginUp)
         index+= movedCount++;
 
       // If dragging over a tag container we should tag the item.
       if (insertionPoint.isTag &&
           insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
         let uri = NetUtil.newURI(unwrapped.uri);
         let tagItemId = insertionPoint.itemId;
-        let tagTxn = new PlacesTagURITransaction(uri, [tagItemId]);
-        transactions.push(tagTxn);
+        if (PlacesUIUtils.useAsyncTransactions)
+          transactions.push(PlacesTransactions.Tag({ uri: uri, tag: tagName }));
+        else
+          transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
       }
       else {
         // If this is not a copy, check for safety that we can move the source,
         // otherwise report an error and fallback to a copy.
         if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
           Components.utils.reportError("Tried to move an unmovable Places node, " +
                                        "reverting to a copy operation.");
           doCopy = true;
         }
-        transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
-                          flavor, insertionPoint.itemId,
-                          index, doCopy));
+        if (PlacesUIUtils.useAsyncTransactions) {
+          transactions.push(
+            PlacesUIUtils.getTransactionForData(unwrapped,
+                                                flavor,
+                                                parentGuid,
+                                                index,
+                                                doCopy));
+        }
+        else {
+          transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
+                              flavor, insertionPoint.itemId,
+                              index, doCopy));
+        }
       }
     }
 
-    let txn = new PlacesAggregatedTransaction("DropItems", transactions);
-    PlacesUtils.transactionManager.doTransaction(txn);
-  },
+    if (PlacesUIUtils.useAsyncTransactions) {
+      yield PlacesTransactions.transact(transactions);
+    }
+    else {
+      let txn = new PlacesAggregatedTransaction("DropItems", transactions);
+      PlacesUtils.transactionManager.doTransaction(txn);
+    }
+  }),
 
   /**
    * Checks if we can insert into a container.
    * @param   aContainer
    *          The container were we are want to drop
    */
   disallowInsertion: function(aContainer) {
     NS_ASSERT(aContainer, "empty container");
     // Allow dropping into Tag containers.
     if (PlacesUtils.nodeIsTagQuery(aContainer))
       return false;
     // Disallow insertion of items under readonly folders.
     return (!PlacesUtils.nodeIsFolder(aContainer) ||
              PlacesUtils.nodeIsReadOnly(aContainer));
-  },
-
-  placesFlavors: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
-                  PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
-                  PlacesUtils.TYPE_X_MOZ_PLACE],
-
-  // The order matters.
-  GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
-                            PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
-                            PlacesUtils.TYPE_X_MOZ_PLACE,
-                            PlacesUtils.TYPE_X_MOZ_URL,
-                            TAB_DROP_TYPE,
-                            PlacesUtils.TYPE_UNICODE],
+  }
 };
 
 
 XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService",
                                    "@mozilla.org/widget/dragservice;1",
                                    "nsIDragService");
 
 function goUpdatePlacesCommands() {
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -98,59 +98,62 @@
               let isMenu = elt.localName == "menu" ||
                  (elt.localName == "toolbarbutton" &&
                   elt.getAttribute("type") == "menu");
               if (isMenu && elt.lastChild &&
                   elt.lastChild.hasAttribute("placespopup"))
                 dropPoint.folderElt = elt;
               return dropPoint;
             }
-            else if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
-                      PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
-                     !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
+
+            let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
+                            elt._placesNode.title : null;
+            if ((PlacesUtils.nodeIsFolder(elt._placesNode) ||
+                 PlacesUtils.nodeIsTagQuery(elt._placesNode)) &&
+                !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
               // This is a folder or a tag container.
               if (eventY - eltY < eltHeight * 0.20) {
                 // If mouse is in the top part of the element, drop above folder.
                 dropPoint.ip = new InsertionPoint(
                                     PlacesUtils.getConcreteItemId(resultNode),
                                     -1,
                                     Ci.nsITreeView.DROP_BEFORE,
-                                    PlacesUtils.nodeIsTagQuery(elt._placesNode),
+                                    tagName,
                                     elt._placesNode.itemId);
                 return dropPoint;
               }
               else if (eventY - eltY < eltHeight * 0.80) {
                 // If mouse is in the middle of the element, drop inside folder.
                 dropPoint.ip = new InsertionPoint(
                                     PlacesUtils.getConcreteItemId(elt._placesNode),
                                     -1,
                                     Ci.nsITreeView.DROP_ON,
-                                    PlacesUtils.nodeIsTagQuery(elt._placesNode));
+                                    tagName);
                 dropPoint.folderElt = elt;
                 return dropPoint;
               }
             }
             else if (eventY - eltY <= eltHeight / 2) {
               // This is a non-folder node or a readonly folder.
               // If the mouse is above the middle, drop above this item.
               dropPoint.ip = new InsertionPoint(
                                   PlacesUtils.getConcreteItemId(resultNode),
                                   -1,
                                   Ci.nsITreeView.DROP_BEFORE,
-                                  PlacesUtils.nodeIsTagQuery(elt._placesNode),
+                                  tagName,
                                   elt._placesNode.itemId);
               return dropPoint;
             }
 
             // Drop below the item.
             dropPoint.ip = new InsertionPoint(
                                 PlacesUtils.getConcreteItemId(resultNode),
                                 -1,
                                 Ci.nsITreeView.DROP_AFTER,
-                                PlacesUtils.nodeIsTagQuery(elt._placesNode),
+                                tagName,
                                 elt._placesNode.itemId);
             return dropPoint;
         ]]></body>
       </method>
 
       <!-- Sub-menus should be opened when the mouse drags over them, and closed
            when the mouse drags off.  The overFolder object manages opening and
            closing of folders when the mouse hovers. -->
@@ -359,17 +362,18 @@
         event.stopPropagation();
       ]]></handler>
 
       <handler event="drop"><![CDATA[
         PlacesControllerDragHelper.currentDropTarget = event.target;
 
         let dropPoint = this._getDropPoint(event);
         if (dropPoint && dropPoint.ip) {
-          PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer);
+          PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
+                                    .then(null, Components.utils.reportError);
           event.preventDefault();
         }
 
         this._cleanupDragDetails();
         event.stopPropagation();
       ]]></handler>
 
       <handler event="dragover"><![CDATA[
--- a/browser/components/places/content/tree.xml
+++ b/browser/components/places/content/tree.xml
@@ -524,19 +524,21 @@
                 index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
               }
             }
           }
 
           if (PlacesControllerDragHelper.disallowInsertion(container))
             return null;
 
+          let tagName = PlacesUtils.nodeIsTagQuery(container) ?
+                          container.title : null;
           return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
                                     index, orientation,
-                                    PlacesUtils.nodeIsTagQuery(container),
+                                    tagName,
                                     dropNearItemId);
         ]]></body>
       </method>
 
       <!-- nsIPlacesView -->
       <method name="selectAll">
         <body><![CDATA[
           this.view.selection.selectAll();
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1338,29 +1338,32 @@ PlacesTreeView.prototype = {
           index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
         }
       }
     }
 
     if (PlacesControllerDragHelper.disallowInsertion(container))
       return null;
 
+    let tagName = PlacesUtils.nodeIsTagQuery(container) ? container.title : null;
     return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
                               index, orientation,
-                              PlacesUtils.nodeIsTagQuery(container),
+                              tagName,
                               dropNearItemId);
   },
 
   drop: function PTV_drop(aRow, aOrientation, aDataTransfer) {
     // We are responsible for translating the |index| and |orientation|
     // parameters into a container id and index within the container,
     // since this information is specific to the tree view.
     let ip = this._getInsertionPoint(aRow, aOrientation);
-    if (ip)
-      PlacesControllerDragHelper.onDrop(ip, aDataTransfer);
+    if (ip) {
+      PlacesControllerDragHelper.onDrop(ip, aDataTransfer)
+                                .then(null, Components.utils.reportError);
+    }
 
     PlacesControllerDragHelper.currentDropTarget = null;
   },
 
   getParentIndex: function PTV_getParentIndex(aRow) {
     let [parentNode, parentRow] = this._getParentByChildRow(aRow);
     return parentRow;
   },
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -341,16 +341,19 @@ let SessionStoreInternal = {
 
   // Whether session has been initialized
   _sessionInitialized: false,
 
   // Promise that is resolved when we're ready to initialize
   // and restore the session.
   _promiseReadyForInitialization: null,
 
+  // Keep busy state counters per window.
+  _windowBusyStates: new WeakMap(),
+
   /**
    * A promise fulfilled once initialization is complete.
    */
   get promiseInitialized() {
     return this._deferredInitialized.promise;
   },
 
   get canRestoreLastSession() {
@@ -1551,18 +1554,17 @@ let SessionStoreInternal = {
     if (!("__SSi" in window)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
-    this._setWindowStateBusy(window);
-    this.restoreTabs(window, [aTab], [tabState], 0);
+    this.restoreTab(aTab, tabState);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
     if (!aTab.ownerDocument.defaultView.__SSi) {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aWindow.getBrowser) {
       throw Components.Exception("Invalid window object: no getBrowser", Cr.NS_ERROR_INVALID_ARG);
@@ -1574,24 +1576,21 @@ let SessionStoreInternal = {
 
     // Duplicate the tab state
     let tabState = TabState.clone(aTab);
 
     tabState.index += aDelta;
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
-    this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
       aWindow.gBrowser.addTab();
 
-    this.restoreTabs(aWindow, [newTab], [tabState], 0,
-                     true /* Load this tab right away. */);
-
+    this.restoreTab(newTab, tabState, true /* Load this tab right away. */);
     return newTab;
   },
 
   getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
     if ("__SSi" in aWindow) {
       return this._windows[aWindow.__SSi]._closedTabs.length;
     }
 
@@ -1627,23 +1626,22 @@ let SessionStoreInternal = {
     if (!(aIndex in closedTabs)) {
       throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // fetch the data of closed tab, while removing it from the array
     let closedTab = closedTabs.splice(aIndex, 1).shift();
     let closedTabState = closedTab.state;
 
-    this._setWindowStateBusy(aWindow);
     // create a new tab
     let tabbrowser = aWindow.gBrowser;
-    let tab = tabbrowser.addTab();
+    let tab = tabbrowser.selectedTab = tabbrowser.addTab();
 
     // restore tab content
-    this.restoreTabs(aWindow, [tab], [closedTabState], 1);
+    this.restoreTab(tab, closedTabState);
 
     // restore the tab's position
     tabbrowser.moveTabTo(tab, closedTab.pos);
 
     // focus the tab's content area (bug 342432)
     tab.linkedBrowser.focus();
 
     return tab;
@@ -2378,184 +2376,202 @@ let SessionStoreInternal = {
         newClosedTabsData.concat(this._windows[aWindow.__SSi]._closedTabs);
 
       // ... and make sure that we don't exceed the max number of closed tabs
       // we can restore.
       this._windows[aWindow.__SSi]._closedTabs =
         newClosedTabsData.slice(0, this._max_tabs_undo);
     }
 
-    this.restoreTabs(aWindow, tabs, winData.tabs,
-      (overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
+    // Restore tabs, if any.
+    if (winData.tabs.length) {
+      this.restoreTabs(aWindow, tabs, winData.tabs,
+        (overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
+    }
 
     if (aState.scratchpads) {
       ScratchpadManager.restoreSession(aState.scratchpads);
     }
 
     // set smoothScroll back to the original value
     tabstrip.smoothScroll = smoothScroll;
 
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
+    this._setWindowStateReady(aWindow);
     this._sendRestoreCompletedNotifications();
   },
 
   /**
    * Manage history restoration for a window
    * @param aWindow
    *        Window to restore the tabs into
    * @param aTabs
    *        Array of tab references
    * @param aTabData
    *        Array of tab data
    * @param aSelectTab
    *        Index of the tab to select. This is a 1-based index where "1"
    *        indicates the first tab should be selected, and "0" indicates that
    *        the currently selected tab will not be changed.
-   * @param aRestoreImmediately
-   *        Flag to indicate whether the given set of tabs aTabs should be
-   *        restored/loaded immediately even if restore_on_demand = true
    */
-  restoreTabs: function (aWindow, aTabs, aTabData, aSelectTab,
-                         aRestoreImmediately = false)
-  {
-
+  restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
     var tabbrowser = aWindow.gBrowser;
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
       delete this._windows[aWindow.__SSi]._restoring;
     }
 
-    // It's important to set the window state to dirty so that
-    // we collect their data for the first time when saving state.
-    DirtyWindows.add(aWindow);
-
-    // Set the state to restore as the window's current state. Normally, this
-    // will just be overridden the next time we collect state but we need this
-    // as a fallback should Firefox be shutdown early without notifying us
-    // beforehand.
-    this._windows[aWindow.__SSi].tabs = aTabData.slice();
-    this._windows[aWindow.__SSi].selected = aSelectTab;
-
-    if (aTabs.length == 0) {
-      // This is normally done later, but as we're returning early
-      // here we need to take care of it.
-      this._setWindowStateReady(aWindow);
-      return;
+    let numTabsToRestore = aTabs.length;
+    let numTabsInWindow = tabbrowser.tabs.length;
+    let tabsDataArray = this._windows[aWindow.__SSi].tabs;
+
+    // Update the window state in case we shut down without being notified.
+    // Individual tab states will be taken care of by restoreTab() below.
+    if (numTabsInWindow == numTabsToRestore) {
+      // Remove all previous tab data.
+      tabsDataArray.length = 0;
+    } else {
+      // Remove all previous tab data except tabs that should not be overriden.
+      tabsDataArray.splice(numTabsInWindow - numTabsToRestore);
     }
 
+    // Let the tab data array have the right number of slots.
+    tabsDataArray.length = numTabsInWindow;
+
     // If provided, set the selected tab.
     if (aSelectTab > 0 && aSelectTab <= aTabs.length) {
       tabbrowser.selectedTab = aTabs[aSelectTab - 1];
+
+      // Update the window state in case we shut down without being notified.
+      this._windows[aWindow.__SSi].selected = aSelectTab;
     }
 
-    // Prepare the tabs so that they can be properly restored. We'll pin/unpin
-    // and show/hide tabs as necessary. We'll also set the labels, user typed
-    // value, and attach a copy of the tab's data in case we close it before
-    // it's been restored.
+    // Restore all tabs.
     for (let t = 0; t < aTabs.length; t++) {
-      let tab = aTabs[t];
-      let browser = tabbrowser.getBrowserForTab(tab);
-      let tabData = aTabData[t];
-
-      if (tabData.pinned)
-        tabbrowser.pinTab(tab);
-      else
-        tabbrowser.unpinTab(tab);
-
-      if (tabData.hidden)
-        tabbrowser.hideTab(tab);
-      else
-        tabbrowser.showTab(tab);
-
-      if (tabData.lastAccessed) {
-        tab.lastAccessed = tabData.lastAccessed;
-      }
-
-      if ("attributes" in tabData) {
-        // Ensure that we persist tab attributes restored from previous sessions.
-        Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
-      }
-
-      if (!tabData.entries) {
-        tabData.entries = [];
-      }
-      if (tabData.extData) {
-        tab.__SS_extdata = {};
-        for (let key in tabData.extData)
-         tab.__SS_extdata[key] = tabData.extData[key];
-      } else {
-        delete tab.__SS_extdata;
-      }
-      delete tabData.closedAt; // Tab is now open.
-
-      // Flush all data from the content script synchronously. This is done so
-      // that all async messages that are still on their way to chrome will
-      // be ignored and don't override any tab data set when restoring.
-      TabState.flush(tab.linkedBrowser);
-
-      // Ensure the index is in bounds.
-      let activeIndex = (tabData.index || tabData.entries.length) - 1;
-      activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
-      activeIndex = Math.max(activeIndex, 0);
-
-      // Save the index in case we updated it above.
-      tabData.index = activeIndex + 1;
-
-      // In electrolysis, we may need to change the browser's remote
-      // attribute so that it runs in a content process.
-      let activePageData = tabData.entries[activeIndex] || null;
-      let uri = activePageData ? activePageData.url || null : null;
-      tabbrowser.updateBrowserRemotenessByURL(browser, uri);
-
-      // Start a new epoch and include the epoch in the restoreHistory
-      // message. If a message is received that relates to a previous epoch, we
-      // discard it.
-      let epoch = this._nextRestoreEpoch++;
-      this._browserEpochs.set(browser.permanentKey, epoch);
-
-      // keep the data around to prevent dataloss in case
-      // a tab gets closed before it's been properly restored
-      browser.__SS_data = tabData;
-      browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
-      browser.setAttribute("pending", "true");
-      tab.setAttribute("pending", "true");
-
-      // Update the persistent tab state cache with |tabData| information.
-      TabStateCache.update(browser, {
-        history: {entries: tabData.entries, index: tabData.index},
-        scroll: tabData.scroll || null,
-        storage: tabData.storage || null,
-        formdata: tabData.formdata || null,
-        disallow: tabData.disallow || null,
-        pageStyle: tabData.pageStyle || null
-      });
-
-      browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
-                                              {tabData: tabData, epoch: epoch});
-
-      // Restore tab attributes.
-      if ("attributes" in tabData) {
-        TabAttributes.set(tab, tabData.attributes);
-      }
-
-      // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
-      // it ensures each window will have its selected tab loaded.
-      if (aRestoreImmediately || tabbrowser.selectedBrowser == browser) {
-        this.restoreTabContent(tab);
-      } else {
-        TabRestoreQueue.add(tab);
-        this.restoreNextTab();
-      }
+      this.restoreTab(aTabs[t], aTabData[t]);
+    }
+  },
+
+  // Restores the given tab state for a given tab.
+  restoreTab(tab, tabData, restoreImmediately = false) {
+    let browser = tab.linkedBrowser;
+    let window = tab.ownerDocument.defaultView;
+    let tabbrowser = window.gBrowser;
+
+    // Increase the busy state counter before modifying the tab.
+    this._setWindowStateBusy(window);
+
+    // It's important to set the window state to dirty so that
+    // we collect their data for the first time when saving state.
+    DirtyWindows.add(window);
+
+    // Update the tab state in case we shut down without being notified.
+    this._windows[window.__SSi].tabs[tab._tPos] = tabData;
+
+    // Prepare the tab so that it can be properly restored. We'll pin/unpin
+    // and show/hide tabs as necessary. We'll also attach a copy of the tab's
+    // data in case we close it before it's been restored.
+    if (tabData.pinned) {
+      tabbrowser.pinTab(tab);
+    } else {
+      tabbrowser.unpinTab(tab);
+    }
+
+    if (tabData.hidden) {
+      tabbrowser.hideTab(tab);
+    } else {
+      tabbrowser.showTab(tab);
+    }
+
+    if (tabData.lastAccessed) {
+      tab.lastAccessed = tabData.lastAccessed;
+    }
+
+    if ("attributes" in tabData) {
+      // Ensure that we persist tab attributes restored from previous sessions.
+      Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
+    }
+
+    if (!tabData.entries) {
+      tabData.entries = [];
+    }
+    if (tabData.extData) {
+      tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
+    } else {
+      delete tab.__SS_extdata;
     }
 
-    this._setWindowStateReady(aWindow);
+    // Tab is now open.
+    delete tabData.closedAt;
+
+    // Flush all data from the content script synchronously. This is done so
+    // that all async messages that are still on their way to chrome will
+    // be ignored and don't override any tab data set when restoring.
+    TabState.flush(browser);
+
+    // Ensure the index is in bounds.
+    let activeIndex = (tabData.index || tabData.entries.length) - 1;
+    activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
+    activeIndex = Math.max(activeIndex, 0);
+
+    // Save the index in case we updated it above.
+    tabData.index = activeIndex + 1;
+
+    // In electrolysis, we may need to change the browser's remote
+    // attribute so that it runs in a content process.
+    let activePageData = tabData.entries[activeIndex] || null;
+    let uri = activePageData ? activePageData.url || null : null;
+    tabbrowser.updateBrowserRemotenessByURL(browser, uri);
+
+    // Start a new epoch and include the epoch in the restoreHistory
+    // message. If a message is received that relates to a previous epoch, we
+    // discard it.
+    let epoch = this._nextRestoreEpoch++;
+    this._browserEpochs.set(browser.permanentKey, epoch);
+
+    // keep the data around to prevent dataloss in case
+    // a tab gets closed before it's been properly restored
+    browser.__SS_data = tabData;
+    browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
+    browser.setAttribute("pending", "true");
+    tab.setAttribute("pending", "true");
+
+    // Update the persistent tab state cache with |tabData| information.
+    TabStateCache.update(browser, {
+      history: {entries: tabData.entries, index: tabData.index},
+      scroll: tabData.scroll || null,
+      storage: tabData.storage || null,
+      formdata: tabData.formdata || null,
+      disallow: tabData.disallow || null,
+      pageStyle: tabData.pageStyle || null
+    });
+
+    browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
+                                            {tabData: tabData, epoch: epoch});
+
+    // Restore tab attributes.
+    if ("attributes" in tabData) {
+      TabAttributes.set(tab, tabData.attributes);
+    }
+
+    // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
+    // it ensures each window will have its selected tab loaded.
+    if (restoreImmediately || tabbrowser.selectedBrowser == browser) {
+      this.restoreTabContent(tab);
+    } else {
+      TabRestoreQueue.add(tab);
+      this.restoreNextTab();
+    }
+
+    // Decrease the busy state counter after we're done.
+    this._setWindowStateReady(window);
   },
 
   /**
    * Restores the specified tab. If the tab can't be restored (eg, no history or
    * calling gotoIndex fails), then state changes will be rolled back.
    * This method will check if gTabsProgressListener is attached to the tab's
    * window, ensuring that we don't get caught without one.
    * This method removes the session history listener right before starting to
@@ -3235,27 +3251,40 @@ let SessionStoreInternal = {
     }
   },
 
   /**
    * Set the given window's state to 'not busy'.
    * @param aWindow the window
    */
   _setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
-    this._setWindowStateBusyValue(aWindow, false);
-    this._sendWindowStateEvent(aWindow, "Ready");
+    let newCount = (this._windowBusyStates.get(aWindow) || 0) - 1;
+    if (newCount < 0) {
+      throw new Error("Invalid window busy state (less than zero).");
+    }
+    this._windowBusyStates.set(aWindow, newCount);
+
+    if (newCount == 0) {
+      this._setWindowStateBusyValue(aWindow, false);
+      this._sendWindowStateEvent(aWindow, "Ready");
+    }
   },
 
   /**
    * Set the given window's state to 'busy'.
    * @param aWindow the window
    */
   _setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
-    this._setWindowStateBusyValue(aWindow, true);
-    this._sendWindowStateEvent(aWindow, "Busy");
+    let newCount = (this._windowBusyStates.get(aWindow) || 0) + 1;
+    this._windowBusyStates.set(aWindow, newCount);
+
+    if (newCount == 1) {
+      this._setWindowStateBusyValue(aWindow, true);
+      this._sendWindowStateEvent(aWindow, "Busy");
+    }
   },
 
   /**
    * Dispatch an SSWindowState_____ event for the given window.
    * @param aWindow the window
    * @param aType the type of event, SSWindowState will be prepended to this string
    */
   _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
--- a/browser/devtools/profiler/profiler.xul
+++ b/browser/devtools/profiler/profiler.xul
@@ -108,19 +108,19 @@
                  type="duration"
                  crop="end"
                  value="&profilerUI.table.duration;"/>
           <label class="plain call-tree-header"
                  type="percentage"
                  crop="end"
                  value="&profilerUI.table.percentage;"/>
           <label class="plain call-tree-header"
-                 type="invocations"
+                 type="samples"
                  crop="end"
-                 value="&profilerUI.table.invocations;"/>
+                 value="&profilerUI.table.samples;"/>
           <label class="plain call-tree-header"
                  type="function"
                  crop="end"
                  value="&profilerUI.table.function;"/>
         </hbox>
         <vbox class="call-tree-cells-container" flex="1"/>
       </vbox>
     </tabpanel>
--- a/browser/devtools/profiler/test/browser_profiler_data-massaging-01.js
+++ b/browser/devtools/profiler/test/browser_profiler_data-massaging-01.js
@@ -1,9 +1,9 @@
-s/* Any copyright is dedicated to the Public Domain.
+/* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the retrieved profiler data samples are correctly filtered and
  * normalized before passed to consumers.
  */
 
 const WAIT_TIME = 1000; // ms
--- a/browser/devtools/profiler/test/browser_profiler_tree-model-01.js
+++ b/browser/devtools/profiler/test/browser_profiler_tree-model-01.js
@@ -125,51 +125,51 @@ function test() {
 
   is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.calls).length, 0,
     "The correct number of child calls were calculated for the '.A.B.D.E.F.G' node.");
   is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
     "The correct number of child calls were calculated for the '.A.B.D' node.");
   is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
     "The correct number of child calls were calculated for the '.A.E.F' node.");
 
-  // Check the location, sample times, duration and invocations of the root.
+  // Check the location, sample times, duration and samples of the root.
 
   is(root.calls.A.location, "A",
     "The '.A' node has the correct location.");
   is(root.calls.A.sampleTimes.toSource(),
     "[{start:5, end:10}, {start:11, end:17}, {start:18, end:25}, {start:20, end:22}]",
     "The '.A' node has the correct sample times.");
   is(root.calls.A.duration, 20,
     "The '.A' node has the correct duration in milliseconds.");
-  is(root.calls.A.invocations, 4,
-    "The '.A' node has the correct number of invocations.");
+  is(root.calls.A.samples, 4,
+    "The '.A' node has the correct number of samples.");
 
   // ...and the rightmost leaf.
 
   is(root.calls.A.calls.E.calls.F.location, "F",
     "The '.A.E.F' node has the correct location.");
   is(root.calls.A.calls.E.calls.F.sampleTimes.toSource(),
     "[{start:18, end:25}]",
     "The '.A.E.F' node has the correct sample times.");
   is(root.calls.A.calls.E.calls.F.duration, 7,
     "The '.A.E.F' node has the correct duration in milliseconds.");
-  is(root.calls.A.calls.E.calls.F.invocations, 1,
-    "The '.A.E.F' node has the correct number of invocations.");
+  is(root.calls.A.calls.E.calls.F.samples, 1,
+    "The '.A.E.F' node has the correct number of samples.");
 
   // ...and the leftmost leaf.
 
   is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.location, "G",
     "The '.A.B.C.D.E.F.G' node has the correct location.");
   is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.sampleTimes.toSource(),
     "[{start:20, end:22}]",
     "The '.A.B.C.D.E.F.G' node has the correct sample times.");
   is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.duration, 2,
     "The '.A.B.C.D.E.F.G' node has the correct duration in milliseconds.");
-  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.invocations, 1,
-    "The '.A.B.C.D.E.F.G' node has the correct number of invocations.");
+  is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.samples, 1,
+    "The '.A.B.C.D.E.F.G' node has the correct number of samples.");
 
   finish();
 }
 
 let gSamples = [{
   time: 5,
   frames: [
     { location: "(root)" },
--- a/browser/devtools/profiler/test/browser_profiler_tree-view-01.js
+++ b/browser/devtools/profiler/test/browser_profiler_tree-view-01.js
@@ -32,20 +32,20 @@ function test() {
   is(container.childNodes[0].childNodes[0].getAttribute("value"), "18",
     "The root node in the tree has the correct duration cell value.");
 
   is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
     "The root node in the tree has a percentage cell.");
   is(container.childNodes[0].childNodes[1].getAttribute("value"), "100%",
     "The root node in the tree has the correct percentage cell value.");
 
-  is(container.childNodes[0].childNodes[2].getAttribute("type"), "invocations",
-    "The root node in the tree has an invocations cell.");
-  is(container.childNodes[0].childNodes[2].getAttribute("value"), "",
-    "The root node in the tree has the correct invocations cell value.");
+  is(container.childNodes[0].childNodes[2].getAttribute("type"), "samples",
+    "The root node in the tree has an samples cell.");
+  is(container.childNodes[0].childNodes[2].getAttribute("value"), "3",
+    "The root node in the tree has the correct samples cell value.");
 
   is(container.childNodes[0].childNodes[3].getAttribute("type"), "function",
     "The root node in the tree has a function cell.");
   is(container.childNodes[0].childNodes[3].style.MozMarginStart, "0px",
     "The root node in the tree has the correct indentation.");
 
   finish();
 }
--- a/browser/devtools/profiler/test/browser_profiler_tree-view-02.js
+++ b/browser/devtools/profiler/test/browser_profiler_tree-view-02.js
@@ -15,29 +15,29 @@ function test() {
 
   let container = document.createElement("vbox");
   treeRoot.autoExpandDepth = 0;
   treeRoot.attachTo(container);
 
   let $$fun = node => container.querySelectorAll(".call-tree-cell[type=function] > " + node);
   let $$dur = i => container.querySelectorAll(".call-tree-cell[type=duration]")[i];
   let $$perc = i => container.querySelectorAll(".call-tree-cell[type=percentage]")[i];
-  let $$invoc = i => container.querySelectorAll(".call-tree-cell[type=invocations]")[i];
+  let $$sampl = i => container.querySelectorAll(".call-tree-cell[type=samples]")[i];
 
   is(container.childNodes.length, 1,
     "The container node should have one child available.");
   is(container.childNodes[0].className, "call-tree-item",
     "The root node in the tree has the correct class name.");
 
   is($$dur(0).getAttribute("value"), "18",
     "The root's duration cell displays the correct value.");
   is($$perc(0).getAttribute("value"), "100%",
     "The root's percentage cell displays the correct value.");
-  is($$invoc(0).getAttribute("value"), "",
-    "The root's invocations cell displays the correct value.");
+  is($$sampl(0).getAttribute("value"), "3",
+    "The root's samples cell displays the correct value.");
   is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
     "The root's function cell displays the correct name.");
   is($$fun(".call-tree-url")[0].getAttribute("value"), "",
     "The root's function cell displays the correct url.");
   is($$fun(".call-tree-line")[0].getAttribute("value"), "",
     "The root's function cell displays the correct line.");
   is($$fun(".call-tree-host")[0].getAttribute("value"), "",
     "The root's function cell displays the correct host.");
@@ -52,18 +52,18 @@ function test() {
     "The root node in the tree has the correct class name.");
   is(container.childNodes[1].className, "call-tree-item",
     "The .A node in the tree has the correct class name.");
 
   is($$dur(1).getAttribute("value"), "18",
     "The .A node's duration cell displays the correct value.");
   is($$perc(1).getAttribute("value"), "100%",
     "The .A node's percentage cell displays the correct value.");
-  is($$invoc(1).getAttribute("value"), "3",
-    "The .A node's invocations cell displays the correct value.");
+  is($$sampl(1).getAttribute("value"), "3",
+    "The .A node's samples cell displays the correct value.");
   is($$fun(".call-tree-name")[1].getAttribute("value"), "A",
     "The .A node's function cell displays the correct name.");
   is($$fun(".call-tree-url")[1].getAttribute("value"), "baz",
     "The .A node's function cell displays the correct url.");
   ok($$fun(".call-tree-url")[1].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
     "The .A node's function cell displays the correct url tooltiptext.");
   is($$fun(".call-tree-line")[1].getAttribute("value"), ":12",
     "The .A node's function cell displays the correct line.");
@@ -79,39 +79,39 @@ function test() {
     "The container node should have four children available.");
   is(container.childNodes[2].className, "call-tree-item",
     "The .B node in the tree has the correct class name.");
   is(container.childNodes[3].className, "call-tree-item",
     "The .E node in the tree has the correct class name.");
 
   is($$dur(2).getAttribute("value"), "11",
     "The .A.B node's duration cell displays the correct value.");
-  is($$perc(2).getAttribute("value"), "61.11%",
+  is($$perc(2).getAttribute("value"), "66.66%",
     "The .A.B node's percentage cell displays the correct value.");
-  is($$invoc(2).getAttribute("value"), "2",
-    "The .A.B node's invocations cell displays the correct value.");
+  is($$sampl(2).getAttribute("value"), "2",
+    "The .A.B node's samples cell displays the correct value.");
   is($$fun(".call-tree-name")[2].getAttribute("value"), "B",
     "The .A.B node's function cell displays the correct name.");
   is($$fun(".call-tree-url")[2].getAttribute("value"), "baz",
     "The .A.B node's function cell displays the correct url.");
   ok($$fun(".call-tree-url")[2].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
     "The .A.B node's function cell displays the correct url tooltiptext.");
   is($$fun(".call-tree-line")[2].getAttribute("value"), ":34",
     "The .A.B node's function cell displays the correct line.");
   is($$fun(".call-tree-host")[2].getAttribute("value"), "foo",
     "The .A.B node's function cell displays the correct host.");
   is($$fun(".call-tree-category")[2].getAttribute("value"), "Styles",
     "The .A.B node's function cell displays the correct category.");
 
   is($$dur(3).getAttribute("value"), "7",
     "The .A.E node's duration cell displays the correct value.");
-  is($$perc(3).getAttribute("value"), "38.88%",
+  is($$perc(3).getAttribute("value"), "33.33%",
     "The .A.E node's percentage cell displays the correct value.");
-  is($$invoc(3).getAttribute("value"), "1",
-    "The .A.E node's invocations cell displays the correct value.");
+  is($$sampl(3).getAttribute("value"), "1",
+    "The .A.E node's samples cell displays the correct value.");
   is($$fun(".call-tree-name")[3].getAttribute("value"), "E",
     "The .A.E node's function cell displays the correct name.");
   is($$fun(".call-tree-url")[3].getAttribute("value"), "baz",
     "The .A.E node's function cell displays the correct url.");
   ok($$fun(".call-tree-url")[3].getAttribute("tooltiptext").contains("http://foo/bar/baz"),
     "The .A.E node's function cell displays the correct url tooltiptext.");
   is($$fun(".call-tree-line")[3].getAttribute("value"), ":90",
     "The .A.E node's function cell displays the correct line.");
--- a/browser/devtools/profiler/test/browser_profiler_tree-view-04.js
+++ b/browser/devtools/profiler/test/browser_profiler_tree-view-04.js
@@ -43,17 +43,17 @@ function test() {
     "The .A.B.C node's category label cell should not be hidden.");
 
   is(C.target.childNodes.length, 4,
     "The number of columns displayed for tree items is correct.");
   is(C.target.childNodes[0].getAttribute("type"), "duration",
     "The first column displayed for tree items is correct.");
   is(C.target.childNodes[1].getAttribute("type"), "percentage",
     "The second column displayed for tree items is correct.");
-  is(C.target.childNodes[2].getAttribute("type"), "invocations",
+  is(C.target.childNodes[2].getAttribute("type"), "samples",
     "The third column displayed for tree items is correct.");
   is(C.target.childNodes[3].getAttribute("type"), "function",
     "The fourth column displayed for tree items is correct.");
 
   let functionCell = C.target.childNodes[3];
 
   is(functionCell.childNodes.length, 8,
     "The number of columns displayed for function cells is correct.");
--- a/browser/devtools/profiler/utils/tree-model.js
+++ b/browser/devtools/profiler/utils/tree-model.js
@@ -18,27 +18,27 @@ const CONTENT_SCHEMES = ["http://", "htt
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
 exports._isContent = isContent; // used in tests
 
 /**
  * A call tree for a thread. This is essentially a linkage between all frames
  * of all samples into a single tree structure, with additional information
- * on each node, like the time spent (in milliseconds) and invocations count.
+ * on each node, like the time spent (in milliseconds) and samples count.
  *
  * Example:
  * {
  *   duration: number,
  *   calls: {
  *     "FunctionName (url:line)": {
  *       line: number,
  *       category: number,
+ *       samples: number,
  *       duration: number,
- *       invocations: number,
  *       calls: {
  *         ...
  *       }
  *     }, // FrameNode
  *     ...
  *   }
  * } // ThreadNode
  *
@@ -47,16 +47,17 @@ exports._isContent = isContent; // used 
  * @param boolean contentOnly [optional]
  *        @see ThreadNode.prototype.insert
  * @param number beginAt [optional]
  *        @see ThreadNode.prototype.insert
  * @param number endAt [optional]
  *        @see ThreadNode.prototype.insert
  */
 function ThreadNode(threadSamples, contentOnly, beginAt, endAt) {
+  this.samples = 0;
   this.duration = 0;
   this.calls = {};
   this._previousSampleTime = 0;
 
   for (let sample of threadSamples) {
     this.insert(sample, contentOnly, beginAt, endAt);
   }
 }
@@ -92,16 +93,17 @@ ThreadNode.prototype = {
       rootIndex = 0;
     }
     if (!sampleFrames.length) {
       return;
     }
 
     let sampleDuration = sampleTime - this._previousSampleTime;
     this._previousSampleTime = sampleTime;
+    this.samples++;
     this.duration += sampleDuration;
 
     FrameNode.prototype.insert(
       sampleFrames, rootIndex, sampleTime, sampleDuration, this.calls);
   },
 
   /**
    * Gets additional details about this node.
@@ -127,18 +129,18 @@ ThreadNode.prototype = {
  * @param number category
  *        The category type of this function call ("js", "graphics" etc.).
  */
 function FrameNode({ location, line, category }) {
   this.location = location;
   this.line = line;
   this.category = category;
   this.sampleTimes = [];
+  this.samples = 0;
   this.duration = 0;
-  this.invocations = 0;
   this.calls = {};
 }
 
 FrameNode.prototype = {
   /**
    * Adds function calls in the tree from a sample's frames. For example, given
    * the the frames below (which would account for three calls to `insert` on
    * the root frame), the following tree structure is created:
@@ -160,18 +162,18 @@ FrameNode.prototype = {
   insert: function(frames, index, time, duration, _store = this.calls) {
     let frame = frames[index];
     if (!frame) {
       return;
     }
     let location = frame.location;
     let child = _store[location] || (_store[location] = new FrameNode(frame));
     child.sampleTimes.push({ start: time, end: time + duration });
+    child.samples++;
     child.duration += duration;
-    child.invocations++;
     child.insert(frames, ++index, time, duration);
   },
 
   /**
    * Parses the raw location of this function call to retrieve the actual
    * function name and source url.
    *
    * @return object
--- a/browser/devtools/profiler/utils/tree-view.js
+++ b/browser/devtools/profiler/utils/tree-view.js
@@ -33,17 +33,17 @@ exports.CallView = CallView;
  *
  * Every instance of a `CallView` represents a row in the call tree. The same
  * parent node is used for all rows.
  *
  * @param CallView caller
  *        The CallView considered the "caller" frame. This instance will be
  *        represent the "callee". Should be null for root nodes.
  * @param ThreadNode | FrameNode frame
- *        Details about this function, like { duration, invocation, calls } etc.
+ *        Details about this function, like { samples, duration, calls } etc.
  * @param number level
  *        The indentation level in the call tree. The root node is at level 0.
  */
 function CallView({ caller, frame, level }) {
   AbstractTreeItem.call(this, { parent: caller, level: level });
 
   this.autoExpandDepth = caller ? caller.autoExpandDepth : CALL_TREE_AUTO_EXPAND;
   this.frame = frame;
@@ -58,38 +58,38 @@ CallView.prototype = Heritage.extend(Abs
    * @param nsIDOMNode document
    * @param nsIDOMNode arrowNode
    * @return nsIDOMNode
    */
   _displaySelf: function(document, arrowNode) {
     this.document = document;
 
     let frameInfo = this.frame.getInfo();
-    let framePercentage = this.frame.duration / this.root.frame.duration * 100;
+    let framePercentage = this.frame.samples / this.root.frame.samples * 100;
 
     let durationCell = this._createTimeCell(this.frame.duration);
     let percentageCell = this._createExecutionCell(framePercentage);
-    let invocationsCell = this._createInvocationsCell(this.frame.invocations);
+    let samplesCell = this._createSamplesCell(this.frame.samples);
     let functionCell = this._createFunctionCell(arrowNode, frameInfo, this.level);
 
     let targetNode = document.createElement("hbox");
     targetNode.className = "call-tree-item";
     targetNode.setAttribute("origin", frameInfo.isContent ? "content" : "chrome");
     targetNode.setAttribute("category", frameInfo.categoryData.abbrev || "");
     targetNode.setAttribute("tooltiptext", this.frame.location || "");
 
     let isRoot = frameInfo.nodeType == "Thread";
     if (isRoot) {
       functionCell.querySelector(".call-tree-zoom").hidden = true;
       functionCell.querySelector(".call-tree-category").hidden = true;
     }
 
     targetNode.appendChild(durationCell);
     targetNode.appendChild(percentageCell);
-    targetNode.appendChild(invocationsCell);
+    targetNode.appendChild(samplesCell);
     targetNode.appendChild(functionCell);
 
     return targetNode;
   },
 
   /**
    * Populates this node in the call tree with the corresponding "callees".
    * These are defined in the `frame` data source for this call view.
@@ -125,20 +125,20 @@ CallView.prototype = Heritage.extend(Abs
   _createExecutionCell: function(percentage) {
     let cell = this.document.createElement("label");
     cell.className = "plain call-tree-cell";
     cell.setAttribute("type", "percentage");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + "%");
     return cell;
   },
-  _createInvocationsCell: function(count) {
+  _createSamplesCell: function(count) {
     let cell = this.document.createElement("label");
     cell.className = "plain call-tree-cell";
-    cell.setAttribute("type", "invocations");
+    cell.setAttribute("type", "samples");
     cell.setAttribute("crop", "end");
     cell.setAttribute("value", count || "");
     return cell;
   },
   _createFunctionCell: function(arrowNode, frameInfo, frameLevel) {
     let cell = this.document.createElement("hbox");
     cell.className = "call-tree-cell";
     cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.dtd
@@ -36,14 +36,14 @@
 <!-- LOCALIZATION NOTE (profilerUI.clearButton): This string is displayed
   -  on a button that remvoes all the recordings. -->
 <!ENTITY profilerUI.clearButton "Clear">
 
 <!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
   -  in the call tree headers for a recording. -->
 <!ENTITY profilerUI.table.duration    "Time (ms)">
 <!ENTITY profilerUI.table.percentage  "Cost">
-<!ENTITY profilerUI.table.invocations "Calls">
+<!ENTITY profilerUI.table.samples     "Samples">
 <!ENTITY profilerUI.table.function    "Function">
 
 <!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown
   -  on the "+" (new tab) button for a profile when a selection is available. -->
 <!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1464,21 +1464,26 @@ notification[value="translation"] menuli
   list-style-image: url("chrome://browser/skin/places/star-icons.png");
   -moz-image-region: rect(0px 32px 16px 16px);
   width: 16px;
   height: 16px;
 }
 
 .ac-result-type-keyword,
 .autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
-  list-style-image: url(moz-icon://stock/gtk-find?size=menu);
+  list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
   width: 16px;
   height: 16px;
 }
 
+.ac-result-type-keyword[selected="true"],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+  list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+}
+
 .ac-result-type-tag,
 .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
   width: 16px;
   height: 16px;
 }
 
 .ac-comment {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2170,20 +2170,24 @@ toolbarbutton[sdk-button="true"][cui-are
 .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/star-icons.png");
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
 .ac-result-type-keyword,
 .autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
-  list-style-image: url(chrome://global/skin/icons/search-textbox.png);
-  margin: 2px;
-  width: 12px;
-  height: 12px;
+  list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+  width: 16px;
+  height: 16px;
+}
+
+.ac-result-type-keyword[selected="true"],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+  list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
 }
 
 richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/star-icons.png");
   -moz-image-region: rect(0, 64px, 16px, 48px);
 }
 
--- a/browser/themes/shared/devtools/profiler.inc.css
+++ b/browser/themes/shared/devtools/profiler.inc.css
@@ -255,18 +255,18 @@
   width: 7em;
 }
 
 .call-tree-header[type="percentage"],
 .call-tree-cell[type="percentage"] {
   width: 5em;
 }
 
-.call-tree-header[type="invocations"],
-.call-tree-cell[type="invocations"] {
+.call-tree-header[type="samples"],
+.call-tree-cell[type="samples"] {
   width: 5em;
 }
 
 .call-tree-header[type="function"],
 .call-tree-cell[type="function"] {
   -moz-box-flex: 1;
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1390,22 +1390,32 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
 
 richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-bookmark,
 .autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
   -moz-image-region: rect(0px 48px 16px 32px);
 }
 
 .ac-result-type-keyword,
 .autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
-  list-style-image: url(chrome://global/skin/icons/Search-glass.png);
-  -moz-image-region: rect(0px 32px 16px 16px);
+  list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
   width: 16px;
   height: 16px;
 }
 
+%ifdef WINDOWS_AERO
+@media not all and (-moz-windows-default-theme) {
+%endif
+  .ac-result-type-keyword[selected="true"],
+  .autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+    list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+  }
+%ifdef WINDOWS_AERO
+}
+%endif
+
 .ac-result-type-tag,
 .autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
   width: 16px;
   height: 16px;
 }
 
 .ac-comment {
--- a/content/base/src/DirectionalityUtils.cpp
+++ b/content/base/src/DirectionalityUtils.cpp
@@ -315,22 +315,25 @@ GetDirectionFromText(const char16_t* aTe
 
     if (NS_IS_HIGH_SURROGATE(ch) &&
         start < end &&
         NS_IS_LOW_SURROGATE(*start)) {
       ch = SURROGATE_TO_UCS4(ch, *start++);
       current++;
     }
 
-    Directionality dir = GetDirectionFromChar(ch);
-    if (dir != eDir_NotSet) {
-      if (aFirstStrong) {
-        *aFirstStrong = current;
+    // Just ignore lone surrogates
+    if (!IS_SURROGATE(ch)) {
+      Directionality dir = GetDirectionFromChar(ch);
+      if (dir != eDir_NotSet) {
+        if (aFirstStrong) {
+          *aFirstStrong = current;
+        }
+        return dir;
       }
-      return dir;
     }
   }
 
   if (aFirstStrong) {
     *aFirstStrong = UINT32_MAX;
   }
   return eDir_NotSet;
 }
--- a/content/media/MediaRecorder.cpp
+++ b/content/media/MediaRecorder.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaRecorder.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "DOMMediaStream.h"
 #include "EncodedBufferCache.h"
 #include "MediaEncoder.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/AudioStreamTrack.h"
 #include "mozilla/dom/BlobEvent.h"
 #include "mozilla/dom/RecordErrorEvent.h"
 #include "mozilla/dom/VideoStreamTrack.h"
 #include "nsError.h"
 #include "nsIDocument.h"
@@ -30,16 +31,83 @@ PRLogModuleInfo* gMediaRecorderLog;
 #else
 #define LOG(type, msg)
 #endif
 
 namespace mozilla {
 
 namespace dom {
 
+/**
++ * MediaRecorderReporter measures memory being used by the Media Recorder.
++ *
++ * It is a singleton reporter and the single class object lives as long as at
++ * least one Recorder is registered. In MediaRecorder, the reporter is unregistered
++ * when it is destroyed.
++ */
+class MediaRecorderReporter MOZ_FINAL : public nsIMemoryReporter
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  MediaRecorderReporter() {};
+  static MediaRecorderReporter* UniqueInstance();
+  void InitMemoryReporter();
+
+  static void AddMediaRecorder(MediaRecorder *aRecorder)
+  {
+    GetRecorders().AppendElement(aRecorder);
+  }
+
+  static void RemoveMediaRecorder(MediaRecorder *aRecorder)
+  {
+    RecordersArray& recorders = GetRecorders();
+    recorders.RemoveElement(aRecorder);
+    if (recorders.IsEmpty()) {
+      sUniqueInstance = nullptr;
+    }
+  }
+
+  NS_METHOD
+  CollectReports(nsIHandleReportCallback* aHandleReport,
+                 nsISupports* aData, bool aAnonymize)
+  {
+    int64_t amount = 0;
+    RecordersArray& recorders = GetRecorders();
+    for (size_t i = 0; i < recorders.Length(); ++i) {
+      amount += recorders[i]->SizeOfExcludingThis(MallocSizeOf);
+    }
+
+  #define MEMREPORT(_path, _amount, _desc)                                    \
+    do {                                                                      \
+      nsresult rv;                                                            \
+      rv = aHandleReport->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \
+                                   KIND_HEAP, UNITS_BYTES, _amount,           \
+                                   NS_LITERAL_CSTRING(_desc), aData);         \
+      NS_ENSURE_SUCCESS(rv, rv);                                              \
+    } while (0)
+
+    MEMREPORT("explicit/media/recorder", amount,
+              "Memory used by media recorder.");
+
+    return NS_OK;
+  }
+
+private:
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+  virtual ~MediaRecorderReporter();
+  static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
+  typedef nsTArray<MediaRecorder*> RecordersArray;
+  static RecordersArray& GetRecorders()
+  {
+    return UniqueInstance()->mRecorders;
+  }
+  RecordersArray mRecorders;
+};
+NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper,
                                    mDOMStream, mAudioNode)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
   NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
@@ -327,16 +395,24 @@ public:
   bool IsEncoderError()
   {
     if (mEncoder && mEncoder->HasError()) {
       return true;
     }
     return false;
   }
 
+  size_t
+  SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = mEncoder->SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+
 private:
   // Only DestroyRunnable is allowed to delete Session object.
   virtual ~Session()
   {
     LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
     CleanupStreams();
   }
   // Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
@@ -676,28 +752,29 @@ MediaRecorder::Start(const Optional<int3
   if (aTimeSlice.WasPassed()) {
     if (aTimeSlice.Value() < 0) {
       aResult.Throw(NS_ERROR_INVALID_ARG);
       return;
     }
 
     timeSlice = aTimeSlice.Value();
   }
-
+  MediaRecorderReporter::AddMediaRecorder(this);
   mState = RecordingState::Recording;
   // Start a session.
   mSessions.AppendElement();
   mSessions.LastElement() = new Session(this, timeSlice);
   mSessions.LastElement()->Start();
 }
 
 void
 MediaRecorder::Stop(ErrorResult& aResult)
 {
   LOG(PR_LOG_DEBUG, ("MediaRecorder.Stop %p", this));
+  MediaRecorderReporter::RemoveMediaRecorder(this);
   if (mState == RecordingState::Inactive) {
     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   mState = RecordingState::Inactive;
   MOZ_ASSERT(mSessions.Length() > 0);
   mSessions.LastElement()->Stop();
 }
@@ -983,10 +1060,41 @@ MediaRecorder::GetSourcePrincipal()
   if (mDOMStream != nullptr) {
     return mDOMStream->GetPrincipal();
   }
   MOZ_ASSERT(mAudioNode != nullptr);
   nsIDocument* doc = mAudioNode->GetOwner()->GetExtantDoc();
   return doc ? doc->NodePrincipal() : nullptr;
 }
 
+size_t
+MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 42;
+  for (size_t i = 0; i < mSessions.Length(); ++i) {
+    amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
+  }
+  return amount;
+}
+
+StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
+
+MediaRecorderReporter* MediaRecorderReporter::UniqueInstance()
+{
+  if (!sUniqueInstance) {
+    sUniqueInstance = new MediaRecorderReporter();
+    sUniqueInstance->InitMemoryReporter();
+  }
+  return sUniqueInstance;
+ }
+
+void MediaRecorderReporter::InitMemoryReporter()
+{
+  RegisterWeakMemoryReporter(this);
+}
+
+MediaRecorderReporter::~MediaRecorderReporter()
+{
+  UnregisterWeakMemoryReporter(this);
+}
+
 }
 }
--- a/content/media/MediaRecorder.h
+++ b/content/media/MediaRecorder.h
@@ -4,16 +4,17 @@
  * 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 MediaRecorder_h
 #define MediaRecorder_h
 
 #include "mozilla/dom/MediaRecorderBinding.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MemoryReporting.h"
 #include "nsIDocumentActivity.h"
 
 // Max size for allowing queue encoded data in memory
 #define MAX_ALLOW_MEMORY_BUFFER 1024000
 namespace mozilla {
 
 class AudioNodeStream;
 class DOMMediaStream;
@@ -85,16 +86,21 @@ public:
   // Construct a recorder with a Web Audio destination node as its source.
   static already_AddRefed<MediaRecorder>
   Constructor(const GlobalObject& aGlobal,
               AudioNode& aSrcAudioNode,
               uint32_t aSrcOutput,
               const MediaRecorderOptions& aInitDict,
               ErrorResult& aRv);
 
+  /*
+   * Measure the size of the buffer, and memory occupied by mAudioEncoder
+   * and mVideoEncoder
+   */
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   // EventHandler
   IMPL_EVENT_HANDLER(dataavailable)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(start)
   IMPL_EVENT_HANDLER(stop)
   IMPL_EVENT_HANDLER(warning)
 
   NS_DECL_NSIDOCUMENTACTIVITY
--- a/content/media/encoder/MediaEncoder.cpp
+++ b/content/media/encoder/MediaEncoder.cpp
@@ -3,16 +3,17 @@
  * 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 "MediaEncoder.h"
 #include "MediaDecoder.h"
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "prlog.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
 
 #include "OggWriter.h"
 #ifdef MOZ_OPUS
 #include "OpusTrackEncoder.h"
 
 #endif
 
 #ifdef MOZ_VORBIS
@@ -199,16 +200,19 @@ MediaEncoder::GetEncodedData(nsTArray<ns
       rv = CopyMetadataToMuxer(mVideoEncoder.get());
       if (NS_FAILED(rv)) {
         LOG(PR_LOG_ERROR, ("Error! Fail to Set Video Metadata"));
         break;
       }
 
       rv = mWriter->GetContainerData(aOutputBufs,
                                      ContainerWriter::GET_HEADER);
+      if (aOutputBufs != nullptr) {
+        mSizeOfBuffer = aOutputBufs->SizeOfExcludingThis(MallocSizeOf);
+      }
       if (NS_FAILED(rv)) {
        LOG(PR_LOG_ERROR,("Error! writer fail to generate header!"));
        mState = ENCODE_ERROR;
        break;
       }
       LOG(PR_LOG_DEBUG, ("Finish ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp()));
       mState = ENCODE_TRACK;
       break;
@@ -231,30 +235,34 @@ MediaEncoder::GetEncodedData(nsTArray<ns
       }
       LOG(PR_LOG_DEBUG, ("Video encoded TimeStamp = %f", GetEncodeTimeStamp()));
       // In audio only or video only case, let unavailable track's flag to be true.
       bool isAudioCompleted = (mAudioEncoder && mAudioEncoder->IsEncodingComplete()) || !mAudioEncoder;
       bool isVideoCompleted = (mVideoEncoder && mVideoEncoder->IsEncodingComplete()) || !mVideoEncoder;
       rv = mWriter->GetContainerData(aOutputBufs,
                                      isAudioCompleted && isVideoCompleted ?
                                      ContainerWriter::FLUSH_NEEDED : 0);
+      if (aOutputBufs != nullptr) {
+        mSizeOfBuffer = aOutputBufs->SizeOfExcludingThis(MallocSizeOf);
+      }
       if (NS_SUCCEEDED(rv)) {
         // Successfully get the copy of final container data from writer.
         reloop = false;
       }
       mState = (mWriter->IsWritingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
       LOG(PR_LOG_DEBUG, ("END ENCODE_TRACK TimeStamp = %f "
           "mState = %d aComplete %d vComplete %d",
           GetEncodeTimeStamp(), mState, isAudioCompleted, isVideoCompleted));
       break;
     }
 
     case ENCODE_DONE:
     case ENCODE_ERROR:
       LOG(PR_LOG_DEBUG, ("MediaEncoder has been shutdown."));
+      mSizeOfBuffer = 0;
       mShutdown = true;
       reloop = false;
       break;
     default:
       MOZ_CRASH("Invalid encode state");
     }
   }
 }
@@ -318,9 +326,26 @@ MediaEncoder::IsWebMEncoderEnabled()
 #ifdef MOZ_OMX_ENCODER
 bool
 MediaEncoder::IsOMXEncoderEnabled()
 {
   return Preferences::GetBool("media.encoder.omx.enabled");
 }
 #endif
 
+/*
+ * SizeOfExcludingThis measures memory being used by the Media Encoder.
+ * Currently it measures the size of the Encoder buffer and memory occupied
+ * by mAudioEncoder and mVideoEncoder.
+ */
+size_t
+MediaEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+  if (mState == ENCODE_TRACK) {
+    amount = mSizeOfBuffer +
+             (mAudioEncoder != nullptr ? mAudioEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0) +
+             (mVideoEncoder != nullptr ? mVideoEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0);
+  }
+  return amount;
 }
+
+}
--- a/content/media/encoder/MediaEncoder.h
+++ b/content/media/encoder/MediaEncoder.h
@@ -5,16 +5,18 @@
 
 #ifndef MediaEncoder_h_
 #define MediaEncoder_h_
 
 #include "mozilla/DebugOnly.h"
 #include "TrackEncoder.h"
 #include "ContainerWriter.h"
 #include "MediaStreamGraph.h"
+#include "nsIMemoryReporter.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 /**
  * MediaEncoder is the framework of encoding module, it controls and manages
  * procedures between ContainerWriter and TrackEncoder. ContainerWriter packs
  * the encoded track data with a specific container (e.g. ogg, mp4).
  * AudioTrackEncoder and VideoTrackEncoder are subclasses of TrackEncoder, and
@@ -61,16 +63,17 @@ public :
                AudioTrackEncoder* aAudioEncoder,
                VideoTrackEncoder* aVideoEncoder,
                const nsAString& aMIMEType)
     : mWriter(aWriter)
     , mAudioEncoder(aAudioEncoder)
     , mVideoEncoder(aVideoEncoder)
     , mStartTime(TimeStamp::Now())
     , mMIMEType(aMIMEType)
+    , mSizeOfBuffer(0)
     , mState(MediaEncoder::ENCODE_METADDATA)
     , mShutdown(false)
   {}
 
   ~MediaEncoder() {};
 
   /**
    * Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw
@@ -135,26 +138,34 @@ public :
 #ifdef MOZ_WEBM_ENCODER
   static bool IsWebMEncoderEnabled();
 #endif
 
 #ifdef MOZ_OMX_ENCODER
   static bool IsOMXEncoderEnabled();
 #endif
 
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+  /*
+   * Measure the size of the buffer, and memory occupied by mAudioEncoder
+   * and mVideoEncoder
+   */
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
   // Get encoded data from trackEncoder and write to muxer
   nsresult WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder);
   // Get metadata from trackEncoder and copy to muxer
   nsresult CopyMetadataToMuxer(TrackEncoder* aTrackEncoder);
   nsAutoPtr<ContainerWriter> mWriter;
   nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
   nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
   TimeStamp mStartTime;
   nsString mMIMEType;
+  int64_t mSizeOfBuffer;
   int mState;
   bool mShutdown;
   // Get duration from create encoder, for logging purpose
   double GetEncodeTimeStamp()
   {
     TimeDuration decodeTime;
     decodeTime = TimeStamp::Now() - mStartTime;
     return decodeTime.ToMilliseconds();
--- a/content/media/encoder/TrackEncoder.cpp
+++ b/content/media/encoder/TrackEncoder.cpp
@@ -168,16 +168,22 @@ AudioTrackEncoder::DeInterleaveTrackData
 {
   for (int32_t i = 0; i < aChannels; ++i) {
     for(int32_t j = 0; j < aDuration; ++j) {
       aOutput[i * aDuration + j] = aInput[i + j * aChannels];
     }
   }
 }
 
+size_t
+AudioTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
                                             TrackID aID,
                                             TrackRate aTrackRate,
                                             TrackTicks aTrackOffset,
                                             uint32_t aTrackEvents,
                                             const MediaSegment& aQueuedMedia)
 {
@@ -258,9 +264,15 @@ VideoTrackEncoder::NotifyEndOfStream()
          DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_TRACK_RATE);
   }
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mEndOfStream = true;
   mReentrantMonitor.NotifyAll();
 }
 
+size_t
+VideoTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
 }
+
+}
--- a/content/media/encoder/TrackEncoder.h
+++ b/content/media/encoder/TrackEncoder.h
@@ -163,16 +163,20 @@ public:
                                   AudioDataValue* aOutput);
 
   /**
    * De-interleaves the aInput data and stores the result into aOutput.
    * No up-mix or down-mix operations inside.
    */
   static void DeInterleaveTrackData(AudioDataValue* aInput, int32_t aDuration,
                                     int32_t aChannels, AudioDataValue* aOutput);
+  /**
+  * Measure size of mRawSegment
+  */
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 protected:
   /**
    * Number of samples per channel in a pcm buffer. This is also the value of
    * frame size required by audio encoder, and mReentrantMonitor will be
    * notified when at least this much data has been added to mRawSegment.
    */
   virtual int GetPacketDuration() { return 0; }
@@ -236,16 +240,20 @@ public:
    * Notified by the same callbcak of MediaEncoder when it has received a track
    * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
   void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                 TrackRate aTrackRate,
                                 TrackTicks aTrackOffset,
                                 uint32_t aTrackEvents,
                                 const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
+  /**
+  * Measure size of mRawSegment
+  */
+  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 protected:
   /**
    * Initialized the video encoder. In order to collect the value of width and
    * height of source frames, this initialization is delayed until we have
    * received the first valid video frame from MediaStreamGraph;
    * mReentrantMonitor will be notified after it has successfully initialized,
    * and this method is called on the MediaStramGraph thread.
copy from content/media/mediasource/SourceBuffer.cpp
copy to content/media/mediasource/ContainerParser.cpp
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/ContainerParser.cpp
@@ -1,113 +1,81 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "SourceBuffer.h"
+#include "ContainerParser.h"
 
-#include "AsyncEventRunner.h"
-#include "MediaSourceUtils.h"
-#include "TrackBuffer.h"
 #include "WebMBufferedParser.h"
 #include "mozilla/Endian.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/FloatingPoint.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/dom/MediaSourceBinding.h"
-#include "mozilla/dom/TimeRanges.h"
 #include "mp4_demuxer/BufferStream.h"
 #include "mp4_demuxer/MoofParser.h"
-#include "nsError.h"
-#include "nsIEventTarget.h"
-#include "nsIRunnable.h"
-#include "nsThreadUtils.h"
 #include "prlog.h"
 
-struct JSContext;
-class JSObject;
-
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
 #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
 namespace mozilla {
 
-class ContainerParser {
-public:
-  virtual ~ContainerParser() {}
-
-  // Return true if aData starts with an initialization segment.
-  // The base implementation exists only for debug logging and is expected
-  // to be called first from the overriding implementation.
-  virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
-              this, aLength,
-              aLength > 0 ? aData[0] : 0,
-              aLength > 1 ? aData[1] : 0,
-              aLength > 2 ? aData[2] : 0,
-              aLength > 3 ? aData[3] : 0);
-    return false;
-  }
+bool
+ContainerParser::IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
+{
+MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
+            this, aLength,
+            aLength > 0 ? aData[0] : 0,
+            aLength > 1 ? aData[1] : 0,
+            aLength > 2 ? aData[2] : 0,
+            aLength > 3 ? aData[3] : 0);
+return false;
+}
 
-  // Return true if aData starts with a media segment.
-  // The base implementation exists only for debug logging and is expected
-  // to be called first from the overriding implementation.
-  virtual bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    MSE_DEBUG("ContainerParser(%p)::IsMediaSegmentPresent aLength=%u [%x%x%x%x]",
-              this, aLength,
-              aLength > 0 ? aData[0] : 0,
-              aLength > 1 ? aData[1] : 0,
-              aLength > 2 ? aData[2] : 0,
-              aLength > 3 ? aData[3] : 0);
-    return false;
-  }
+bool
+ContainerParser::IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
+{
+  MSE_DEBUG("ContainerParser(%p)::IsMediaSegmentPresent aLength=%u [%x%x%x%x]",
+            this, aLength,
+            aLength > 0 ? aData[0] : 0,
+            aLength > 1 ? aData[1] : 0,
+            aLength > 2 ? aData[2] : 0,
+            aLength > 3 ? aData[3] : 0);
+  return false;
+}
 
-  // Parse aData to extract the start and end frame times from the media
-  // segment.  aData may not start on a parser sync boundary.  Return true
-  // if aStart and aEnd have been updated.
-  virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
-                                          int64_t& aStart, int64_t& aEnd)
-  {
-    return false;
-  }
+bool
+ContainerParser::ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
+                                            int64_t& aStart, int64_t& aEnd)
+{
+  return false;
+}
 
-  // Compare aLhs and rHs, considering any error that may exist in the
-  // timestamps from the format's base representation.  Return true if aLhs
-  // == aRhs within the error epsilon.
-  virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
-  {
-    NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
-    return aLhs == aRhs;
-  }
+bool
+ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
+{
+  NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
+  return aLhs == aRhs;
+}
 
-  virtual const nsTArray<uint8_t>& InitData()
-  {
-    MOZ_ASSERT(mInitData.Length() > 0);
-    return mInitData;
-  }
-
-  static ContainerParser* CreateForMIMEType(const nsACString& aType);
-
-protected:
-  nsTArray<uint8_t> mInitData;
-};
+const nsTArray<uint8_t>&
+ContainerParser::InitData()
+{
+  MOZ_ASSERT(mHasInitData);
+  return mInitData;
+}
 
 class WebMContainerParser : public ContainerParser {
 public:
   WebMContainerParser()
     : mParser(0), mOffset(0)
   {}
 
   static const unsigned NS_PER_USEC = 1000;
@@ -178,16 +146,17 @@ public:
       if (!mapping.IsEmpty()) {
         length = mapping[0].mSyncOffset;
         MOZ_ASSERT(length <= aLength);
       }
       MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
                 this, length);
 
       mInitData.ReplaceElementsAt(0, mInitData.Length(), aData, length);
+      mHasInitData = true;
     }
     mOffset += aLength;
 
     if (mapping.IsEmpty()) {
       return false;
     }
 
     // Exclude frames that we don't enough data to cover the end of.
@@ -268,16 +237,17 @@ public:
     if (initSegment) {
       const MediaByteRange& range = mParser->mInitRange;
       MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
                 this, range.mEnd - range.mStart);
 
       mInitData.ReplaceElementsAt(0, mInitData.Length(),
                                   aData + range.mStart,
                                   range.mEnd - range.mStart);
+      mHasInitData = true;
     }
 
     mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
       mParser->GetCompositionRange(byteRanges);
 
     mStream->DiscardBefore(mParser->mOffset);
 
     if (compositionRange.IsNull()) {
@@ -290,451 +260,22 @@ public:
     return true;
   }
 
 private:
   nsRefPtr<mp4_demuxer::BufferStream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
 };
 
-
 /*static*/ ContainerParser*
 ContainerParser::CreateForMIMEType(const nsACString& aType)
 {
   if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
     return new WebMContainerParser();
   }
 
   if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
     return new MP4ContainerParser();
   }
   return new ContainerParser();
 }
 
-namespace dom {
-
-void
-SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::SetMode(aMode=%d)", this, aMode);
-  if (!IsAttached() || mUpdating) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
-  if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
-    mMediaSource->SetReadyState(MediaSourceReadyState::Open);
-  }
-  // TODO: Test append state.
-  // TODO: If aMode is "sequence", set sequence start time.
-  mAppendMode = aMode;
-}
-
-void
-SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::SetTimestampOffset(aTimestampOffset=%f)", this, aTimestampOffset);
-  if (!IsAttached() || mUpdating) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
-  if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
-    mMediaSource->SetReadyState(MediaSourceReadyState::Open);
-  }
-  // TODO: Test append state.
-  // TODO: If aMode is "sequence", set sequence start time.
-  mTimestampOffset = aTimestampOffset;
-}
-
-already_AddRefed<TimeRanges>
-SourceBuffer::GetBuffered(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!IsAttached()) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-  nsRefPtr<TimeRanges> ranges = new TimeRanges();
-  double highestEndTime = mTrackBuffer->Buffered(ranges);
-  if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
-    // Set the end time on the last range to highestEndTime by adding a
-    // new range spanning the current end time to highestEndTime, which
-    // Normalize() will then merge with the old last range.
-    ranges->Add(ranges->GetEndTime(), highestEndTime);
-    ranges->Normalize();
-  }
-  MSE_DEBUGV("SourceBuffer(%p)::GetBuffered ranges=%s", this, DumpTimeRanges(ranges).get());
-  return ranges.forget();
-}
-
-void
-SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::SetAppendWindowStart(aAppendWindowStart=%f)", this, aAppendWindowStart);
-  if (!IsAttached() || mUpdating) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  if (aAppendWindowStart < 0 || aAppendWindowStart >= mAppendWindowEnd) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
-    return;
-  }
-  mAppendWindowStart = aAppendWindowStart;
-}
-
-void
-SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::SetAppendWindowEnd(aAppendWindowEnd=%f)", this, aAppendWindowEnd);
-  if (!IsAttached() || mUpdating) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  if (IsNaN(aAppendWindowEnd) ||
-      aAppendWindowEnd <= mAppendWindowStart) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
-    return;
-  }
-  mAppendWindowEnd = aAppendWindowEnd;
-}
-
-void
-SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::AppendBuffer(ArrayBuffer)", this);
-  aData.ComputeLengthAndData();
-  AppendData(aData.Data(), aData.Length(), aRv);
-}
-
-void
-SourceBuffer::AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::AppendBuffer(ArrayBufferView)", this);
-  aData.ComputeLengthAndData();
-  AppendData(aData.Data(), aData.Length(), aRv);
-}
-
-void
-SourceBuffer::Abort(ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::Abort()", this);
-  if (!IsAttached()) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  if (mUpdating) {
-    // TODO: Abort segment parser loop, buffer append, and stream append loop algorithms.
-    AbortUpdating();
-  }
-  // TODO: Run reset parser algorithm.
-  mAppendWindowStart = 0;
-  mAppendWindowEnd = PositiveInfinity<double>();
-
-  MSE_DEBUG("SourceBuffer(%p)::Abort() Discarding decoder", this);
-  mTrackBuffer->DiscardDecoder();
-}
-
-void
-SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p)::Remove(aStart=%f, aEnd=%f)", this, aStart, aEnd);
-  if (!IsAttached()) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  if (IsNaN(mMediaSource->Duration()) ||
-      aStart < 0 || aStart > mMediaSource->Duration() ||
-      aEnd <= aStart || IsNaN(aEnd)) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
-    return;
-  }
-  if (mUpdating || mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  StartUpdating();
-  /// TODO: Run coded frame removal algorithm.
-
-  // Run the final step of the coded frame removal algorithm asynchronously
-  // to ensure the SourceBuffer's updating flag transition behaves as
-  // required by the spec.
-  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &SourceBuffer::StopUpdating);
-  NS_DispatchToMainThread(event);
-}
-
-void
-SourceBuffer::Detach()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("SourceBuffer(%p)::Detach", this);
-  if (mTrackBuffer) {
-    mTrackBuffer->Detach();
-  }
-  mTrackBuffer = nullptr;
-  mMediaSource = nullptr;
-}
-
-void
-SourceBuffer::Ended()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(IsAttached());
-  MSE_DEBUG("SourceBuffer(%p)::Ended", this);
-  mTrackBuffer->DiscardDecoder();
-}
-
-SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
-  : DOMEventTargetHelper(aMediaSource->GetParentObject())
-  , mMediaSource(aMediaSource)
-  , mType(aType)
-  , mAppendWindowStart(0)
-  , mAppendWindowEnd(PositiveInfinity<double>())
-  , mTimestampOffset(0)
-  , mAppendMode(SourceBufferAppendMode::Segments)
-  , mUpdating(false)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aMediaSource);
-  mEvictionThreshold = Preferences::GetUint("media.mediasource.eviction_threshold",
-                                            75 * (1 << 20));
-  mParser = ContainerParser::CreateForMIMEType(aType);
-  mTrackBuffer = new TrackBuffer(aMediaSource->GetDecoder(), aType);
-  MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Create mParser=%p mTrackBuffer=%p",
-            this, mParser.get(), mTrackBuffer.get());
-}
-
-SourceBuffer::~SourceBuffer()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mMediaSource);
-  MSE_DEBUG("SourceBuffer(%p)::~SourceBuffer", this);
-}
-
-MediaSource*
-SourceBuffer::GetParentObject() const
-{
-  return mMediaSource;
-}
-
-JSObject*
-SourceBuffer::WrapObject(JSContext* aCx)
-{
-  return SourceBufferBinding::Wrap(aCx, this);
-}
-
-void
-SourceBuffer::DispatchSimpleEvent(const char* aName)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_API("SourceBuffer(%p) Dispatch event '%s'", this, aName);
-  DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
-}
-
-void
-SourceBuffer::QueueAsyncSimpleEvent(const char* aName)
-{
-  MSE_DEBUG("SourceBuffer(%p) Queuing event '%s'", this, aName);
-  nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName);
-  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-}
-
-void
-SourceBuffer::StartUpdating()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mUpdating);
-  mUpdating = true;
-  QueueAsyncSimpleEvent("updatestart");
-}
-
-void
-SourceBuffer::StopUpdating()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mUpdating);
-  mUpdating = false;
-  QueueAsyncSimpleEvent("update");
-  QueueAsyncSimpleEvent("updateend");
-}
-
-void
-SourceBuffer::AbortUpdating()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mUpdating);
-  mUpdating = false;
-  QueueAsyncSimpleEvent("abort");
-  QueueAsyncSimpleEvent("updateend");
-}
-
-void
-SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
-{
-  MSE_DEBUG("SourceBuffer(%p)::AppendData(aLength=%u)", this, aLength);
-  if (!PrepareAppend(aRv)) {
-    return;
-  }
-  StartUpdating();
-  // TODO: Run more of the buffer append algorithm asynchronously.
-  if (mParser->IsInitSegmentPresent(aData, aLength)) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: New initialization segment.", this);
-    mMediaSource->QueueInitializationEvent();
-    mTrackBuffer->DiscardDecoder();
-    if (!mTrackBuffer->NewDecoder()) {
-      aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
-      return;
-    }
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
-  } else if (!mTrackBuffer->HasInitSegment()) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
-    Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
-    ErrorResult dummy;
-    mMediaSource->EndOfStream(decodeError, dummy);
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-  int64_t start, end;
-  if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
-    int64_t lastStart, lastEnd;
-    mTrackBuffer->LastTimestamp(lastStart, lastEnd);
-    if (mParser->IsMediaSegmentPresent(aData, aLength) &&
-        !mParser->TimestampsFuzzyEqual(start, lastEnd)) {
-      MSE_DEBUG("SourceBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
-                this, lastStart, lastEnd, start, end);
-
-      // This data is earlier in the timeline than data we have already
-      // processed, so we must create a new decoder to handle the decoding.
-      mTrackBuffer->DiscardDecoder();
-
-      // If we've got a decoder here, it's not initialized, so we can use it
-      // rather than creating a new one.
-      if (!mTrackBuffer->NewDecoder()) {
-        aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
-        return;
-      }
-      MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
-      const nsTArray<uint8_t>& initData = mParser->InitData();
-      mTrackBuffer->AppendData(initData.Elements(), initData.Length());
-      mTrackBuffer->SetLastStartTimestamp(start);
-    }
-    mTrackBuffer->SetLastEndTimestamp(end);
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
-              this, lastStart, lastEnd, start, end);
-  }
-  if (!mTrackBuffer->AppendData(aData, aLength)) {
-    Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
-    ErrorResult dummy;
-    mMediaSource->EndOfStream(decodeError, dummy);
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  // Schedule the state machine thread to ensure playback starts
-  // if required when data is appended.
-  mMediaSource->GetDecoder()->ScheduleStateMachineThread();
-
-  // Run the final step of the buffer append algorithm asynchronously to
-  // ensure the SourceBuffer's updating flag transition behaves as required
-  // by the spec.
-  nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &SourceBuffer::StopUpdating);
-  NS_DispatchToMainThread(event);
-}
-
-bool
-SourceBuffer::PrepareAppend(ErrorResult& aRv)
-{
-  if (!IsAttached() || mUpdating) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return false;
-  }
-  if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
-    mMediaSource->SetReadyState(MediaSourceReadyState::Open);
-  }
-
-  // Eviction uses a byte threshold. If the buffer is greater than the
-  // number of bytes then data is evicted. The time range for this
-  // eviction is reported back to the media source. It will then
-  // evict data before that range across all SourceBuffers it knows
-  // about.
-  // TODO: Make the eviction threshold smaller for audio-only streams.
-  // TODO: Drive evictions off memory pressure notifications.
-  // TODO: Consider a global eviction threshold  rather than per TrackBuffer.
-  bool evicted = mTrackBuffer->EvictData(mEvictionThreshold);
-  if (evicted) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendData Evict; current buffered start=%f",
-              this, GetBufferedStart());
-
-    // We notify that we've evicted from the time range 0 through to
-    // the current start point.
-    mMediaSource->NotifyEvicted(0.0, GetBufferedStart());
-  }
-
-  // TODO: Test buffer full flag.
-  return true;
-}
-
-double
-SourceBuffer::GetBufferedStart()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  ErrorResult dummy;
-  nsRefPtr<TimeRanges> ranges = GetBuffered(dummy);
-  return ranges->Length() > 0 ? ranges->GetStartTime() : 0;
-}
-
-double
-SourceBuffer::GetBufferedEnd()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  ErrorResult dummy;
-  nsRefPtr<TimeRanges> ranges = GetBuffered(dummy);
-  return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
-}
-
-void
-SourceBuffer::Evict(double aStart, double aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("SourceBuffer(%p)::Evict(aStart=%f, aEnd=%f)", this, aStart, aEnd);
-  double currentTime = mMediaSource->GetDecoder()->GetCurrentTime();
-  double evictTime = aEnd;
-  const double safety_threshold = 5;
-  if (currentTime + safety_threshold >= evictTime) {
-    evictTime -= safety_threshold;
-  }
-  mTrackBuffer->EvictBefore(evictTime);
-}
-
-#if defined(DEBUG)
-void
-SourceBuffer::Dump(const char* aPath)
-{
-  if (mTrackBuffer) {
-    mTrackBuffer->Dump(aPath);
-  }
-}
-#endif
-
-NS_IMPL_CYCLE_COLLECTION_INHERITED(SourceBuffer, DOMEventTargetHelper,
-                                   mMediaSource)
-
-NS_IMPL_ADDREF_INHERITED(SourceBuffer, DOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(SourceBuffer, DOMEventTargetHelper)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(SourceBuffer)
-NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
-
-} // namespace dom
-
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/mediasource/ContainerParser.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_CONTAINERPARSER_H_
+#define MOZILLA_CONTAINERPARSER_H_
+
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class ContainerParser {
+public:
+  ContainerParser() : mHasInitData(false) {}
+  virtual ~ContainerParser() {}
+
+  // Return true if aData starts with an initialization segment.
+  // The base implementation exists only for debug logging and is expected
+  // to be called first from the overriding implementation.
+  virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength);
+
+  // Return true if aData starts with a media segment.
+  // The base implementation exists only for debug logging and is expected
+  // to be called first from the overriding implementation.
+  virtual bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength);
+
+  // Parse aData to extract the start and end frame times from the media
+  // segment.  aData may not start on a parser sync boundary.  Return true
+  // if aStart and aEnd have been updated.
+  virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
+                                          int64_t& aStart, int64_t& aEnd);
+
+  // Compare aLhs and rHs, considering any error that may exist in the
+  // timestamps from the format's base representation.  Return true if aLhs
+  // == aRhs within the error epsilon.
+  virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
+
+  const nsTArray<uint8_t>& InitData();
+
+  bool HasInitData()
+  {
+    return mHasInitData;
+  }
+
+  static ContainerParser* CreateForMIMEType(const nsACString& aType);
+
+protected:
+  nsTArray<uint8_t> mInitData;
+  bool mHasInitData;
+};
+
+} // namespace mozilla
+#endif /* MOZILLA_CONTAINERPARSER_H_ */
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -4,25 +4,21 @@
  * 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 "SourceBuffer.h"
 
 #include "AsyncEventRunner.h"
 #include "MediaSourceUtils.h"
 #include "TrackBuffer.h"
-#include "WebMBufferedParser.h"
-#include "mozilla/Endian.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/MediaSourceBinding.h"
 #include "mozilla/dom/TimeRanges.h"
-#include "mp4_demuxer/BufferStream.h"
-#include "mp4_demuxer/MoofParser.h"
 #include "nsError.h"
 #include "nsIEventTarget.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
 struct JSContext;
 class JSObject;
@@ -37,283 +33,16 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
 namespace mozilla {
 
-class ContainerParser {
-public:
-  virtual ~ContainerParser() {}
-
-  // Return true if aData starts with an initialization segment.
-  // The base implementation exists only for debug logging and is expected
-  // to be called first from the overriding implementation.
-  virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
-              this, aLength,
-              aLength > 0 ? aData[0] : 0,
-              aLength > 1 ? aData[1] : 0,
-              aLength > 2 ? aData[2] : 0,
-              aLength > 3 ? aData[3] : 0);
-    return false;
-  }
-
-  // Return true if aData starts with a media segment.
-  // The base implementation exists only for debug logging and is expected
-  // to be called first from the overriding implementation.
-  virtual bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    MSE_DEBUG("ContainerParser(%p)::IsMediaSegmentPresent aLength=%u [%x%x%x%x]",
-              this, aLength,
-              aLength > 0 ? aData[0] : 0,
-              aLength > 1 ? aData[1] : 0,
-              aLength > 2 ? aData[2] : 0,
-              aLength > 3 ? aData[3] : 0);
-    return false;
-  }
-
-  // Parse aData to extract the start and end frame times from the media
-  // segment.  aData may not start on a parser sync boundary.  Return true
-  // if aStart and aEnd have been updated.
-  virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
-                                          int64_t& aStart, int64_t& aEnd)
-  {
-    return false;
-  }
-
-  // Compare aLhs and rHs, considering any error that may exist in the
-  // timestamps from the format's base representation.  Return true if aLhs
-  // == aRhs within the error epsilon.
-  virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
-  {
-    NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
-    return aLhs == aRhs;
-  }
-
-  virtual const nsTArray<uint8_t>& InitData()
-  {
-    MOZ_ASSERT(mInitData.Length() > 0);
-    return mInitData;
-  }
-
-  static ContainerParser* CreateForMIMEType(const nsACString& aType);
-
-protected:
-  nsTArray<uint8_t> mInitData;
-};
-
-class WebMContainerParser : public ContainerParser {
-public:
-  WebMContainerParser()
-    : mParser(0), mOffset(0)
-  {}
-
-  static const unsigned NS_PER_USEC = 1000;
-
-  bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    ContainerParser::IsInitSegmentPresent(aData, aLength);
-    // XXX: This is overly primitive, needs to collect data as it's appended
-    // to the SB and handle, rather than assuming everything is present in a
-    // single aData segment.
-    // 0x1a45dfa3 // EBML
-    // ...
-    // DocType == "webm"
-    // ...
-    // 0x18538067 // Segment (must be "unknown" size)
-    // 0x1549a966 // -> Segment Info
-    // 0x1654ae6b // -> One or more Tracks
-    if (aLength >= 4 &&
-        aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) {
-      return true;
-    }
-    return false;
-  }
-
-  bool IsMediaSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    ContainerParser::IsMediaSegmentPresent(aData, aLength);
-    // XXX: This is overly primitive, needs to collect data as it's appended
-    // to the SB and handle, rather than assuming everything is present in a
-    // single aData segment.
-    // 0x1a45dfa3 // EBML
-    // ...
-    // DocType == "webm"
-    // ...
-    // 0x18538067 // Segment (must be "unknown" size)
-    // 0x1549a966 // -> Segment Info
-    // 0x1654ae6b // -> One or more Tracks
-    if (aLength >= 4 &&
-        aData[0] == 0x1f && aData[1] == 0x43 && aData[2] == 0xb6 && aData[3] == 0x75) {
-      return true;
-    }
-    return false;
-  }
-
-  bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
-                                  int64_t& aStart, int64_t& aEnd)
-  {
-    bool initSegment = IsInitSegmentPresent(aData, aLength);
-    if (initSegment) {
-      mOffset = 0;
-      mParser = WebMBufferedParser(0);
-      mOverlappedMapping.Clear();
-    }
-
-    // XXX if it only adds new mappings, overlapped but not available
-    // (e.g. overlap < 0) frames are "lost" from the reported mappings here.
-    nsTArray<WebMTimeDataOffset> mapping;
-    mapping.AppendElements(mOverlappedMapping);
-    mOverlappedMapping.Clear();
-    ReentrantMonitor dummy("dummy");
-    mParser.Append(aData, aLength, mapping, dummy);
-
-    // XXX This is a bit of a hack.  Assume if there are no timecodes
-    // present and it's an init segment that it's _just_ an init segment.
-    // We should be more precise.
-    if (initSegment) {
-      uint32_t length = aLength;
-      if (!mapping.IsEmpty()) {
-        length = mapping[0].mSyncOffset;
-        MOZ_ASSERT(length <= aLength);
-      }
-      MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
-                this, length);
-
-      mInitData.ReplaceElementsAt(0, mInitData.Length(), aData, length);
-    }
-    mOffset += aLength;
-
-    if (mapping.IsEmpty()) {
-      return false;
-    }
-
-    // Exclude frames that we don't enough data to cover the end of.
-    uint32_t endIdx = mapping.Length() - 1;
-    while (mOffset < mapping[endIdx].mEndOffset && endIdx > 0) {
-      endIdx -= 1;
-    }
-
-    if (endIdx == 0) {
-      return false;
-    }
-
-    uint64_t frameDuration = mapping[endIdx].mTimecode - mapping[endIdx - 1].mTimecode;
-    aStart = mapping[0].mTimecode / NS_PER_USEC;
-    aEnd = (mapping[endIdx].mTimecode + frameDuration) / NS_PER_USEC;
-
-    MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld] [fso=%lld, leo=%lld, l=%u endIdx=%u]",
-              this, aStart, aEnd, mapping[0].mSyncOffset, mapping[endIdx].mEndOffset, mapping.Length(), endIdx);
-
-    mapping.RemoveElementsAt(0, endIdx + 1);
-    mOverlappedMapping.AppendElements(mapping);
-
-    return true;
-  }
-
-  bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
-  {
-    int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
-    return llabs(aLhs - aRhs) <= error * 2;
-  }
-
-private:
-  WebMBufferedParser mParser;
-  nsTArray<WebMTimeDataOffset> mOverlappedMapping;
-  int64_t mOffset;
-};
-
-class MP4ContainerParser : public ContainerParser {
-public:
-  MP4ContainerParser() {}
-
-  bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
-  {
-    ContainerParser::IsInitSegmentPresent(aData, aLength);
-    // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4
-    // file is the 'ftyp' atom followed by a file type. We just check for a
-    // vaguely valid 'ftyp' atom.
-
-    if (aLength < 8) {
-      return false;
-    }
-
-    uint32_t chunk_size = BigEndian::readUint32(aData);
-    if (chunk_size < 8) {
-      return false;
-    }
-
-    return aData[4] == 'f' && aData[5] == 't' && aData[6] == 'y' &&
-           aData[7] == 'p';
-  }
-
-  bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
-                                  int64_t& aStart, int64_t& aEnd)
-  {
-    bool initSegment = IsInitSegmentPresent(aData, aLength);
-    if (initSegment) {
-      mStream = new mp4_demuxer::BufferStream();
-      mParser = new mp4_demuxer::MoofParser(mStream, 0);
-    } else if (!mStream || !mParser) {
-      return false;
-    }
-
-    mStream->AppendBytes(aData, aLength);
-    nsTArray<MediaByteRange> byteRanges;
-    byteRanges.AppendElement(mStream->GetByteRange());
-    mParser->RebuildFragmentedIndex(byteRanges);
-
-    if (initSegment) {
-      const MediaByteRange& range = mParser->mInitRange;
-      MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
-                this, range.mEnd - range.mStart);
-
-      mInitData.ReplaceElementsAt(0, mInitData.Length(),
-                                  aData + range.mStart,
-                                  range.mEnd - range.mStart);
-    }
-
-    mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
-      mParser->GetCompositionRange(byteRanges);
-
-    mStream->DiscardBefore(mParser->mOffset);
-
-    if (compositionRange.IsNull()) {
-      return false;
-    }
-    aStart = compositionRange.start;
-    aEnd = compositionRange.end;
-    MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld]",
-              this, aStart, aEnd);
-    return true;
-  }
-
-private:
-  nsRefPtr<mp4_demuxer::BufferStream> mStream;
-  nsAutoPtr<mp4_demuxer::MoofParser> mParser;
-};
-
-
-/*static*/ ContainerParser*
-ContainerParser::CreateForMIMEType(const nsACString& aType)
-{
-  if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
-    return new WebMContainerParser();
-  }
-
-  if (aType.LowerCaseEqualsLiteral("video/mp4") || aType.LowerCaseEqualsLiteral("audio/mp4")) {
-    return new MP4ContainerParser();
-  }
-  return new ContainerParser();
-}
-
 namespace dom {
 
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SourceBuffer(%p)::SetMode(aMode=%d)", this, aMode);
   if (!IsAttached() || mUpdating) {
@@ -492,31 +221,29 @@ SourceBuffer::Ended()
   MOZ_ASSERT(IsAttached());
   MSE_DEBUG("SourceBuffer(%p)::Ended", this);
   mTrackBuffer->DiscardDecoder();
 }
 
 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
-  , mType(aType)
   , mAppendWindowStart(0)
   , mAppendWindowEnd(PositiveInfinity<double>())
   , mTimestampOffset(0)
   , mAppendMode(SourceBufferAppendMode::Segments)
   , mUpdating(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aMediaSource);
   mEvictionThreshold = Preferences::GetUint("media.mediasource.eviction_threshold",
                                             75 * (1 << 20));
-  mParser = ContainerParser::CreateForMIMEType(aType);
   mTrackBuffer = new TrackBuffer(aMediaSource->GetDecoder(), aType);
-  MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Create mParser=%p mTrackBuffer=%p",
-            this, mParser.get(), mTrackBuffer.get());
+  MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Create mTrackBuffer=%p",
+            this, mTrackBuffer.get());
 }
 
 SourceBuffer::~SourceBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mMediaSource);
   MSE_DEBUG("SourceBuffer(%p)::~SourceBuffer", this);
 }
@@ -581,73 +308,28 @@ SourceBuffer::AbortUpdating()
 void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   MSE_DEBUG("SourceBuffer(%p)::AppendData(aLength=%u)", this, aLength);
   if (!PrepareAppend(aRv)) {
     return;
   }
   StartUpdating();
-  // TODO: Run more of the buffer append algorithm asynchronously.
-  if (mParser->IsInitSegmentPresent(aData, aLength)) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: New initialization segment.", this);
-    mMediaSource->QueueInitializationEvent();
-    mTrackBuffer->DiscardDecoder();
-    if (!mTrackBuffer->NewDecoder()) {
-      aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
-      return;
-    }
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
-  } else if (!mTrackBuffer->HasInitSegment()) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
-    Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
-    ErrorResult dummy;
-    mMediaSource->EndOfStream(decodeError, dummy);
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-  int64_t start, end;
-  if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
-    int64_t lastStart, lastEnd;
-    mTrackBuffer->LastTimestamp(lastStart, lastEnd);
-    if (mParser->IsMediaSegmentPresent(aData, aLength) &&
-        !mParser->TimestampsFuzzyEqual(start, lastEnd)) {
-      MSE_DEBUG("SourceBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
-                this, lastStart, lastEnd, start, end);
 
-      // This data is earlier in the timeline than data we have already
-      // processed, so we must create a new decoder to handle the decoding.
-      mTrackBuffer->DiscardDecoder();
-
-      // If we've got a decoder here, it's not initialized, so we can use it
-      // rather than creating a new one.
-      if (!mTrackBuffer->NewDecoder()) {
-        aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
-        return;
-      }
-      MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized.", this);
-      const nsTArray<uint8_t>& initData = mParser->InitData();
-      mTrackBuffer->AppendData(initData.Elements(), initData.Length());
-      mTrackBuffer->SetLastStartTimestamp(start);
-    }
-    mTrackBuffer->SetLastEndTimestamp(end);
-    MSE_DEBUG("SourceBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
-              this, lastStart, lastEnd, start, end);
-  }
   if (!mTrackBuffer->AppendData(aData, aLength)) {
     Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
     ErrorResult dummy;
     mMediaSource->EndOfStream(decodeError, dummy);
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  // Schedule the state machine thread to ensure playback starts
-  // if required when data is appended.
-  mMediaSource->GetDecoder()->ScheduleStateMachineThread();
+  if (mTrackBuffer->HasInitSegment()) {
+    mMediaSource->QueueInitializationEvent();
+  }
 
   // Run the final step of the buffer append algorithm asynchronously to
   // ensure the SourceBuffer's updating flag transition behaves as required
   // by the spec.
   nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this, &SourceBuffer::StopUpdating);
   NS_DispatchToMainThread(event);
 }
 
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -23,17 +23,16 @@
 #include "nsString.h"
 #include "nscore.h"
 
 class JSObject;
 struct JSContext;
 
 namespace mozilla {
 
-class ContainerParser;
 class ErrorResult;
 class TrackBuffer;
 template <typename T> class AsyncEventRunner;
 
 namespace dom {
 
 class TimeRanges;
 
@@ -115,43 +114,32 @@ public:
 
 private:
   ~SourceBuffer();
 
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
-  // Create a new decoder for mType, and store the result in mDecoder.
-  // Returns true if mDecoder was set.
-  bool InitNewDecoder();
-
-  // Set mDecoder to null and reset mDecoderInitialized.
-  void DiscardDecoder();
-
   // Update mUpdating and fire the appropriate events.
   void StartUpdating();
   void StopUpdating();
   void AbortUpdating();
 
   // Shared implementation of AppendBuffer overloads.
   void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
 
   // Implements the "Prepare Append Algorithm".  Returns true if the append
   // may continue, or false (with aRv set) on error.
   bool PrepareAppend(ErrorResult& aRv);
 
   nsRefPtr<MediaSource> mMediaSource;
 
-  const nsCString mType;
-
   uint32_t mEvictionThreshold;
 
-  nsAutoPtr<ContainerParser> mParser;
-
   nsRefPtr<TrackBuffer> mTrackBuffer;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;
 
   double mTimestampOffset;
 
   SourceBufferAppendMode mAppendMode;
--- a/content/media/mediasource/TrackBuffer.cpp
+++ b/content/media/mediasource/TrackBuffer.cpp
@@ -1,37 +1,29 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TrackBuffer.h"
 
+#include "ContainerParser.h"
 #include "MediaSourceDecoder.h"
 #include "SharedThreadPool.h"
 #include "MediaTaskQueue.h"
 #include "SourceBufferDecoder.h"
 #include "SourceBufferResource.h"
 #include "VideoUtils.h"
-#include "mozilla/FloatingPoint.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "nsError.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
-#if defined(DEBUG)
-#include <sys/stat.h>
-#include <sys/types.h>
-#endif
-
-struct JSContext;
-class JSObject;
-
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
 #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
@@ -42,19 +34,19 @@ extern PRLogModuleInfo* GetMediaSourceAP
 
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
   , mLastEndTimestamp(0)
-  , mHasInit(false)
 {
   MOZ_COUNT_CTOR(TrackBuffer);
+  mParser = ContainerParser::CreateForMIMEType(aType);
   mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
   aParentDecoder->AddTrackBuffer(this);
 }
 
 TrackBuffer::~TrackBuffer()
 {
   MOZ_COUNT_DTOR(TrackBuffer);
 }
@@ -99,16 +91,63 @@ TrackBuffer::Shutdown()
   MOZ_ASSERT(mDecoders.IsEmpty());
   mParentDecoder = nullptr;
 }
 
 bool
 TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  // TODO: Run more of the buffer append algorithm asynchronously.
+  if (mParser->IsInitSegmentPresent(aData, aLength)) {
+    MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this);
+    if (!NewDecoder()) {
+      return false;
+    }
+  } else if (!mParser->HasInitData()) {
+    MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
+    return false;
+  }
+
+  int64_t start, end;
+  if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
+    if (mParser->IsMediaSegmentPresent(aData, aLength) &&
+        !mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp)) {
+      MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
+                this, mLastStartTimestamp, mLastEndTimestamp, start, end);
+
+      // This data is earlier in the timeline than data we have already
+      // processed, so we must create a new decoder to handle the decoding.
+      if (!NewDecoder()) {
+        return false;
+      }
+      MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
+      const nsTArray<uint8_t>& initData = mParser->InitData();
+      AppendDataToCurrentResource(initData.Elements(), initData.Length());
+      mLastStartTimestamp = start;
+    }
+    mLastEndTimestamp = end;
+    MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
+              this, mLastStartTimestamp, mLastEndTimestamp, start, end);
+  }
+
+  if (!AppendDataToCurrentResource(aData, aLength)) {
+    return false;
+  }
+
+  // Schedule the state machine thread to ensure playback starts if required
+  // when data is appended.
+  mParentDecoder->ScheduleStateMachineThread();
+  return true;
+}
+
+bool
+TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength)
+{
+  MOZ_ASSERT(NS_IsMainThread());
   if (!mCurrentDecoder) {
     return false;
   }
 
   SourceBufferResource* resource = mCurrentDecoder->GetResource();
   int64_t appendOffset = resource->GetLength();
   resource->AppendData(aData, aLength);
   // XXX: For future reference: NDA call must run on the main thread.
@@ -174,29 +213,30 @@ TrackBuffer::Buffered(dom::TimeRanges* a
 
   return highestEndTime;
 }
 
 bool
 TrackBuffer::NewDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mCurrentDecoder && mParentDecoder);
+  MOZ_ASSERT(mParentDecoder);
+
+  DiscardDecoder();
 
   nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType);
   if (!decoder) {
     return false;
   }
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   mCurrentDecoder = decoder;
   mDecoders.AppendElement(decoder);
 
   mLastStartTimestamp = 0;
   mLastEndTimestamp = 0;
-  mHasInit = true;
 
   return QueueInitializeDecoder(decoder);
 }
 
 bool
 TrackBuffer::QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
 {
   RefPtr<nsIRunnable> task =
@@ -311,47 +351,25 @@ TrackBuffer::Detach()
     DiscardDecoder();
   }
 }
 
 bool
 TrackBuffer::HasInitSegment()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  return mHasInit;
+  return mParser->HasInitData();
 }
 
 bool
 TrackBuffer::IsReady()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty());
-  return HasInitSegment() && (mInfo.HasAudio() || mInfo.HasVideo());
-}
-
-void
-TrackBuffer::LastTimestamp(int64_t& aStart, int64_t& aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  aStart = mLastStartTimestamp;
-  aEnd = mLastEndTimestamp;
-}
-
-void
-TrackBuffer::SetLastStartTimestamp(int64_t aStart)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mLastStartTimestamp = aStart;
-}
-
-void
-TrackBuffer::SetLastEndTimestamp(int64_t aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mLastEndTimestamp = aEnd;
+  return mParser->HasInitData() && (mInfo.HasAudio() || mInfo.HasVideo());
 }
 
 bool
 TrackBuffer::ContainsTime(int64_t aTime)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
--- a/content/media/mediasource/TrackBuffer.h
+++ b/content/media/mediasource/TrackBuffer.h
@@ -12,16 +12,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/mozalloc.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nscore.h"
 
 namespace mozilla {
 
+class ContainerParser;
 class MediaSourceDecoder;
 
 namespace dom {
 
 class TimeRanges;
 
 } // namespace dom
 
@@ -40,39 +41,29 @@ public:
   bool EvictData(uint32_t aThreshold);
   void EvictBefore(double aTime);
 
   // Returns the highest end time of all of the buffered ranges in the
   // decoders managed by this TrackBuffer, and returns the union of the
   // decoders buffered ranges in aRanges.
   double Buffered(dom::TimeRanges* aRanges);
 
-  // Create a new decoder, set mCurrentDecoder to the new decoder, and queue
-  // the decoder for initialization.  The decoder is not considered
-  // initialized until it is added to mDecoders.
-  bool NewDecoder();
-
   // Mark the current decoder's resource as ended, clear mCurrentDecoder and
   // reset mLast{Start,End}Timestamp.
   void DiscardDecoder();
 
   void Detach();
 
   // Returns true if an init segment has been appended.
   bool HasInitSegment();
 
-  // Returns true iff HasInitSegment() and the decoder using that init
+  // Returns true iff mParser->HasInitData() and the decoder using that init
   // segment has successfully initialized by setting mHas{Audio,Video}..
   bool IsReady();
 
-  // Query and update mLast{Start,End}Timestamp.
-  void LastTimestamp(int64_t& aStart, int64_t& aEnd);
-  void SetLastStartTimestamp(int64_t aStart);
-  void SetLastEndTimestamp(int64_t aEnd);
-
   // Returns true if any of the decoders managed by this track buffer
   // contain aTime in their buffered ranges.
   bool ContainsTime(int64_t aTime);
 
   void BreakCycles();
 
   // Call ResetDecode() on each decoder in mDecoders.
   void ResetDecode();
@@ -84,16 +75,25 @@ public:
 
 #if defined(DEBUG)
   void Dump(const char* aPath);
 #endif
 
 private:
   ~TrackBuffer();
 
+  // Create a new decoder, set mCurrentDecoder to the new decoder, and queue
+  // the decoder for initialization.  The decoder is not considered
+  // initialized until it is added to mDecoders.
+  bool NewDecoder();
+
+  // Helper for AppendData, ensures NotifyDataArrived is called whenever
+  // data is appended to the current decoder's SourceBufferResource.
+  bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength);
+
   // Queue execution of InitializeDecoder on mTaskQueue.
   bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
   // Runs decoder initialization including calling ReadMetadata.  Runs as an
   // event on the decode thread pool.
   void InitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
   // Adds a successfully initialized decoder to mDecoders and (if it's the
@@ -107,16 +107,18 @@ private:
   bool ValidateTrackFormats(const MediaInfo& aInfo);
 
   // Remove aDecoder from mDecoders and dispatch an event to the main thread
   // to clean up the decoder.  If aDecoder was added to
   // mInitializedDecoders, it must have been removed before calling this
   // function.
   void RemoveDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
 
+  nsAutoPtr<ContainerParser> mParser;
+
   // A task queue using the shared media thread pool.  Used exclusively to
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   // All of the decoders managed by this TrackBuffer.  Access protected by
   // mParentDecoder's monitor.
   nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
@@ -131,19 +133,15 @@ private:
   nsRefPtr<MediaSourceDecoder> mParentDecoder;
   const nsCString mType;
 
   // The last start and end timestamps added to the TrackBuffer via
   // AppendData.  Accessed on the main thread only.
   int64_t mLastStartTimestamp;
   int64_t mLastEndTimestamp;
 
-  // Set when the initialization segment is first seen and cached (implied
-  // by new decoder creation).  Protected by mParentDecoder's monitor.
-  bool mHasInit;
-
   // Set when the first decoder used by this TrackBuffer is initialized.
   // Protected by mParentDecoder's monitor.
   MediaInfo mInfo;
 };
 
 } // namespace mozilla
 #endif /* MOZILLA_TRACKBUFFER_H_ */
--- a/content/media/mediasource/moz.build
+++ b/content/media/mediasource/moz.build
@@ -12,16 +12,17 @@ EXPORTS += [
 
 EXPORTS.mozilla.dom += [
     'MediaSource.h',
     'SourceBuffer.h',
     'SourceBufferList.h',
 ]
 
 UNIFIED_SOURCES += [
+    'ContainerParser.cpp',
     'MediaSource.cpp',
     'MediaSourceDecoder.cpp',
     'MediaSourceReader.cpp',
     'MediaSourceUtils.cpp',
     'SourceBuffer.cpp',
     'SourceBufferDecoder.cpp',
     'SourceBufferList.cpp',
     'SourceBufferResource.cpp',
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -664,20 +664,22 @@ public:
                    bool strict,
                    JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
   virtual bool keys(JSContext *cx, JS::Handle<JSObject*> proxy,
                     JS::AutoIdVector &props) const MOZ_OVERRIDE;
   virtual bool iterate(JSContext *cx, JS::Handle<JSObject*> proxy,
                        unsigned flags,
                        JS::MutableHandle<JS::Value> vp) const MOZ_OVERRIDE;
 
+  static void ObjectMoved(JSObject *obj, const JSObject *old);
+
   static const nsOuterWindowProxy singleton;
 
 protected:
-  nsGlobalWindow* GetWindow(JSObject *proxy) const
+  static nsGlobalWindow* GetWindow(JSObject *proxy)
   {
     return nsGlobalWindow::FromSupports(
       static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate()));
   }
 
   // False return value means we threw an exception.  True return value
   // but false "found" means we didn't have a subframe at that index.
   bool GetSubframeWindow(JSContext *cx, JS::Handle<JSObject*> proxy,
@@ -699,17 +701,18 @@ const js::Class OuterWindowProxyClass =
     PROXY_CLASS_WITH_EXT(
         "Proxy",
         0, /* additional slots */
         0, /* additional class flags */
         PROXY_MAKE_EXT(
             nullptr, /* outerObject */
             js::proxy_innerObject,
             nullptr, /* iteratorObject */
-            false   /* isWrappedNative */
+            false,   /* isWrappedNative */
+            nsOuterWindowProxy::ObjectMoved
         ));
 
 bool
 nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy,
                                  bool *extensible) const
 {
   // If [[Extensible]] could be false, then navigating a window could navigate
   // to a window that's [[Extensible]] after being at one that wasn't: an
@@ -1019,16 +1022,25 @@ nsOuterWindowProxy::watch(JSContext *cx,
 
 bool
 nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy,
                             JS::Handle<jsid> id) const
 {
   return js::UnwatchGuts(cx, proxy, id);
 }
 
+void
+nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old)
+{
+  nsGlobalWindow* global = GetWindow(obj);
+  if (global) {
+    global->UpdateWrapper(obj, old);
+  }
+}
+
 const nsOuterWindowProxy
 nsOuterWindowProxy::singleton;
 
 class nsChromeOuterWindowProxy : public nsOuterWindowProxy
 {
 public:
   MOZ_CONSTEXPR nsChromeOuterWindowProxy() : nsOuterWindowProxy() { }
 
--- a/dom/base/nsWrapperCache.h
+++ b/dom/base/nsWrapperCache.h
@@ -3,16 +3,17 @@
  * 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 nsWrapperCache_h___
 #define nsWrapperCache_h___
 
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Assertions.h"
+#include "js/Class.h"
 #include "js/Id.h"          // must come before js/RootingAPI.h
 #include "js/Value.h"       // must come before js/RootingAPI.h
 #include "js/RootingAPI.h"
 #include "js/TracingAPI.h"
 
 class XPCWrappedNativeScope;
 
 #define NS_WRAPPERCACHE_IID \
@@ -39,16 +40,24 @@ class XPCWrappedNativeScope;
  *  If WRAPPER_IS_DOM_BINDING is not set (IsDOMBinding() returns false):
  *    - a slim wrapper or the JSObject of an XPCWrappedNative wrapper
  *
  *  If WRAPPER_IS_DOM_BINDING is set (IsDOMBinding() returns true):
  *    - a DOM binding object (regular JS object or proxy)
  *
  * The finalizer for the wrapper clears the cache.
  *
+ * A compacting GC can move the wrapper object. Pointers to moved objects are
+ * usually found and updated by tracing the heap, however non-preserved wrappers
+ * are weak references and are not traced, so another approach is
+ * necessary. Instead a class hook (objectMovedOp) is provided that is called
+ * when an object is moved and is responsible for ensuring pointers are
+ * updated. It does this by calling UpdateWrapper() on the wrapper
+ * cache. SetWrapper() asserts that the hook is implemented for any wrapper set.
+ *
  * A number of the methods are implemented in nsWrapperCacheInlines.h because we
  * have to include some JS headers that don't play nicely with the rest of the
  * codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
  */
 class nsWrapperCache
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_WRAPPERCACHE_IID)
@@ -84,31 +93,45 @@ public:
   {
     return GetWrapperJSObject();
   }
 
   void SetWrapper(JSObject* aWrapper)
   {
     MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
     MOZ_ASSERT(aWrapper, "Use ClearWrapper!");
+    MOZ_ASSERT(js::HasObjectMovedOp(aWrapper),
+               "Object has not provided the hook to update the wrapper if it is moved");
 
     SetWrapperJSObject(aWrapper);
   }
 
   /**
    * Clear the wrapper. This should be called from the finalizer for the
    * wrapper.
    */
   void ClearWrapper()
   {
     MOZ_ASSERT(!PreservingWrapper(), "Clearing a preserved wrapper!");
 
     SetWrapperJSObject(nullptr);
   }
 
+  /**
+   * Update the wrapper if the object it contains is moved.
+   *
+   * This method must be called from the objectMovedOp class extension hook for
+   * any wrapper cached object.
+   */
+  void UpdateWrapper(JSObject* aNewObject, const JSObject* aOldObject)
+  {
+    MOZ_ASSERT(mWrapper == aOldObject);
+    mWrapper = aNewObject;
+  }
+
   bool PreservingWrapper()
   {
     return HasWrapperFlag(WRAPPER_BIT_PRESERVED);
   }
 
   void SetIsDOMBinding()
   {
     MOZ_ASSERT(!mWrapper && !(GetWrapperFlags() & ~WRAPPER_IS_DOM_BINDING),
@@ -157,17 +180,17 @@ public:
 
   void TraceWrapper(const TraceCallbacks& aCallbacks, void* aClosure)
   {
     if (PreservingWrapper() && mWrapper) {
       aCallbacks.Trace(&mWrapper, "Preserved wrapper", aClosure);
     }
   }
 
-  /* 
+  /*
    * The following methods for getting and manipulating flags allow the unused
    * bits of mFlags to be used by derived classes.
    */
 
   typedef uint32_t FlagsType;
 
   FlagsType GetFlags() const
   {
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1208,16 +1208,34 @@ template<class T>
 inline void
 ClearWrapper(T* p, void*)
 {
   nsWrapperCache* cache;
   CallQueryInterface(p, &cache);
   ClearWrapper(p, cache);
 }
 
+template<class T>
+inline void
+UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj, const JSObject* old)
+{
+  JS::AutoAssertGCCallback inCallback(obj);
+  cache->UpdateWrapper(obj, old);
+}
+
+template<class T>
+inline void
+UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old)
+{
+  JS::AutoAssertGCCallback inCallback(obj);
+  nsWrapperCache* cache;
+  CallQueryInterface(p, &cache);
+  UpdateWrapper(p, cache, obj, old);
+}
+
 // Attempt to preserve the wrapper, if any, for a Paris DOM bindings object.
 // Return true if we successfully preserved the wrapper, or there is no wrapper
 // to preserve. In the latter case we don't need to preserve the wrapper, because
 // the object can only be obtained by JS once, or they cannot be meaningfully
 // owned from the native side.
 //
 // This operation will return false only for non-nsISupports cycle-collected
 // objects, because we cannot determine if they are wrappercached or not.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -12,16 +12,17 @@ import textwrap
 
 from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLUndefinedValue, IDLEmptySequenceValue
 from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, Descriptor
 
 AUTOGENERATED_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
 ADDPROPERTY_HOOK_NAME = '_addProperty'
 FINALIZE_HOOK_NAME = '_finalize'
+OBJECT_MOVED_HOOK_NAME = '_objectMoved'
 CONSTRUCT_HOOK_NAME = '_constructor'
 LEGACYCALLER_HOOK_NAME = '_legacycaller'
 HASINSTANCE_HOOK_NAME = '_hasInstance'
 NEWRESOLVE_HOOK_NAME = '_newResolve'
 ENUMERATE_HOOK_NAME = '_enumerate'
 ENUM_ENTRY_VARIABLE_NAME = 'strings'
 INSTANCE_RESERVED_SLOTS = 1
 
@@ -359,58 +360,71 @@ class CGDOMJSClass(CGThing):
         self.descriptor = descriptor
 
     def declare(self):
         return ""
 
     def define(self):
         traceHook = 'nullptr'
         callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr'
+        objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
         slotCount = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots
         classFlags = "JSCLASS_IS_DOMJSCLASS | "
-        classExtensionAndObjectOps = """\
-JS_NULL_CLASS_EXT,
-JS_NULL_OBJECT_OPS
-"""
+        classExtensionAndObjectOps = fill(
+            """
+            {
+              nullptr, /* outerObject */
+              nullptr, /* innerObject */
+              nullptr, /* iteratorObject */
+              false,   /* isWrappedNative */
+              nullptr, /* weakmapKeyDelegateOp */
+              ${objectMoved} /* objectMovedOp */
+            },
+            JS_NULL_OBJECT_OPS
+            """,
+            objectMoved=objectMovedHook)
         if self.descriptor.isGlobal():
             classFlags += "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS) | JSCLASS_IMPLEMENTS_BARRIERS"
             traceHook = "JS_GlobalObjectTraceHook"
             reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
             if not self.descriptor.workers:
-                classExtensionAndObjectOps = """\
-{
-  nsGlobalWindow::OuterObject, /* outerObject */
-  nullptr, /* innerObject */
-  nullptr, /* iteratorObject */
-  false, /* isWrappedNative */
-  nullptr /* weakmapKeyDelegateOp */
-},
-{
-  nullptr, /* lookupGeneric */
-  nullptr, /* lookupProperty */
-  nullptr, /* lookupElement */
-  nullptr, /* defineGeneric */
-  nullptr, /* defineProperty */
-  nullptr, /* defineElement */
-  nullptr, /* getGeneric  */
-  nullptr, /* getProperty */
-  nullptr, /* getElement */
-  nullptr, /* setGeneric */
-  nullptr, /* setProperty */
-  nullptr, /* setElement */
-  nullptr, /* getGenericAttributes */
-  nullptr, /* setGenericAttributes */
-  nullptr, /* deleteGeneric */
-  nullptr, /* watch */
-  nullptr, /* unwatch */
-  nullptr, /* slice */
-  nullptr, /* enumerate */
-  JS_ObjectToOuterObject /* thisObject */
-}
-"""
+                classExtensionAndObjectOps = fill(
+                    """
+                    {
+                      nsGlobalWindow::OuterObject, /* outerObject */
+                      nullptr, /* innerObject */
+                      nullptr, /* iteratorObject */
+                      false,   /* isWrappedNative */
+                      nullptr, /* weakmapKeyDelegateOp */
+                      ${objectMoved} /* objectMovedOp */
+                    },
+                    {
+                      nullptr, /* lookupGeneric */
+                      nullptr, /* lookupProperty */
+                      nullptr, /* lookupElement */
+                      nullptr, /* defineGeneric */
+                      nullptr, /* defineProperty */
+                      nullptr, /* defineElement */
+                      nullptr, /* getGeneric  */
+                      nullptr, /* getProperty */
+                      nullptr, /* getElement */
+                      nullptr, /* setGeneric */
+                      nullptr, /* setProperty */
+                      nullptr, /* setElement */
+                      nullptr, /* getGenericAttributes */
+                      nullptr, /* setGenericAttributes */
+                      nullptr, /* deleteGeneric */
+                      nullptr, /* watch */
+                      nullptr, /* unwatch */
+                      nullptr, /* slice */
+                      nullptr, /* enumerate */
+                      JS_ObjectToOuterObject /* thisObject */
+                    }
+                    """,
+                    objectMoved=objectMovedHook)
         else:
             classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
             reservedSlots = slotCount
         if self.descriptor.interface.getExtendedAttribute("NeedNewResolve"):
             newResolveHook = "(JSResolveOp)" + NEWRESOLVE_HOOK_NAME
             classFlags += " | JSCLASS_NEW_RESOLVE"
             enumerateHook = ENUMERATE_HOOK_NAME
         elif self.descriptor.isGlobal():
@@ -476,27 +490,34 @@ class CGDOMProxyJSClass(CGThing):
 
     def define(self):
         flags = ["JSCLASS_IS_DOMJSCLASS"]
         # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because
         # we don't want people ever adding that to any interface other than
         # HTMLAllCollection.  So just hardcode it here.
         if self.descriptor.interface.identifier.name == "HTMLAllCollection":
             flags.append("JSCLASS_EMULATES_UNDEFINED")
+        objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
         return fill(
             """
             static const DOMJSClass Class = {
-              PROXY_CLASS_DEF("${name}",
-                              0, /* extra slots */
-                              ${flags}),
+              PROXY_CLASS_WITH_EXT("${name}",
+                                   0, /* extra slots */
+                                   ${flags},
+                                   PROXY_MAKE_EXT(nullptr, /* outerObject */
+                                                  nullptr, /* innerObject */
+                                                  nullptr, /* iteratorObject */
+                                                  false,   /* isWrappedNative */
+                                                  ${objectMoved})),
               $*{descriptor}
             };
             """,
             name=self.descriptor.interface.identifier.name,
             flags=" | ".join(flags),
+            objectMoved=objectMovedHook,
             descriptor=DOMClass(self.descriptor))
 
 
 def PrototypeIDAndDepth(descriptor):
     prototypeID = "prototypes::id::"
     if descriptor.interface.hasInterfacePrototypeObject():
         prototypeID += descriptor.interface.identifier.name
         if descriptor.workers:
@@ -1544,21 +1565,38 @@ class CGClassFinalizeHook(CGAbstractClas
         args = [Argument('js::FreeOp*', 'fop'), Argument('JSObject*', 'obj')]
         CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
                                      'void', args)
 
     def generate_code(self):
         return finalizeHook(self.descriptor, self.name, self.args[0].name).define()
 
 
+class CGClassObjectMovedHook(CGAbstractClassHook):
+    """
+    A hook for objectMovedOp, used to update the wrapper cache when an object it
+    is holding moves.
+    """
+    def __init__(self, descriptor):
+        args = [Argument('JSObject*', 'obj'), Argument('const JSObject*', 'old')]
+        CGAbstractClassHook.__init__(self, descriptor, OBJECT_MOVED_HOOK_NAME,
+                                     'void', args)
+
+    def generate_code(self):
+        assert self.descriptor.wrapperCache
+        return CGIfWrapper(CGGeneric("UpdateWrapper(self, self, obj, old);\n"),
+                           "self").define()
+
+
 def JSNativeArguments():
     return [Argument('JSContext*', 'cx'),
             Argument('unsigned', 'argc'),
             Argument('JS::Value*', 'vp')]
 
+
 class CGClassConstructor(CGAbstractStaticMethod):
     """
     JS-visible constructor for our objects
     """
     def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME):
         CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool',
                                         JSNativeArguments())
         self._ctor = ctor
@@ -10914,16 +10952,19 @@ class CGDescriptor(CGThing):
         if descriptor.concrete and not descriptor.proxy:
             if wantsAddProperty(descriptor):
                 cgThings.append(CGAddPropertyHook(descriptor))
 
             # Always have a finalize hook, regardless of whether the class
             # wants a custom hook.
             cgThings.append(CGClassFinalizeHook(descriptor))
 
+        if descriptor.concrete and descriptor.wrapperCache:
+            cgThings.append(CGClassObjectMovedHook(descriptor))
+
         if len(descriptor.permissions):
             for (k, v) in sorted(descriptor.permissions.items()):
                 perms = CGList((CGGeneric('"%s",' % p) for p in k), joiner="\n")
                 perms.append(CGGeneric("nullptr"))
                 cgThings.append(CGWrapper(CGIndenter(perms),
                                           pre="static const char* const permissions_%i[] = {\n" % v,
                                           post="\n};\n",
                                           defineOnly=True))
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -336,16 +336,17 @@ struct nsIConsoleService::COMTypeInfo<ns
   static const nsIID kIID;
 };
 const nsIID nsIConsoleService::COMTypeInfo<nsConsoleService, void>::kIID = NS_ICONSOLESERVICE_IID;
 
 namespace mozilla {
 namespace dom {
 
 #ifdef MOZ_NUWA_PROCESS
+int32_t ContentParent::sNuwaPid = 0;
 bool ContentParent::sNuwaReady = false;
 #endif
 
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 
 class MemoryReportRequestParent : public PMemoryReportRequestParent
 {
 public:
@@ -581,16 +582,17 @@ ContentParent::RunNuwaProcess()
         new ContentParent(/* aApp = */ nullptr,
                           /* aOpener = */ nullptr,
                           /* aIsForBrowser = */ false,
                           /* aIsForPreallocated = */ true,
                           PROCESS_PRIORITY_BACKGROUND,
                           /* aIsNuwaProcess = */ true);
     nuwaProcess->Init();
 #ifdef MOZ_NUWA_PROCESS
+    sNuwaPid = nuwaProcess->Pid();
     sNuwaReady = false;
 #endif
     return nuwaProcess.forget();
 }
 
 // PreallocateAppProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within
 // GetNewOrPreallocatedAppProcess.
@@ -1982,16 +1984,17 @@ ContentParent::~ContentParent()
         // that sAppContentParents->Get(mAppManifestURL) != this.
         MOZ_ASSERT(!sAppContentParents ||
                    sAppContentParents->Get(mAppManifestURL) != this);
     }
 
 #ifdef MOZ_NUWA_PROCESS
     if (IsNuwaProcess()) {
         sNuwaReady = false;
+        sNuwaPid = 0;
     }
 #endif
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority,
                             bool aSetupOffMainThreadCompositing,
                             bool aSendRegisteredChrome)
@@ -3673,16 +3676,22 @@ ContentParent::DoSendAsyncMessage(JSCont
     ClonedMessageData data;
     if (!BuildClonedMessageDataForParent(this, aData, data)) {
         return false;
     }
     InfallibleTArray<CpowEntry> cpows;
     if (aCpows && !GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) {
         return false;
     }
+#ifdef MOZ_NUWA_PROCESS
+    if (IsNuwaProcess() && IsNuwaReady()) {
+        // Nuwa won't receive frame messages after it is frozen.
+        return true;
+    }
+#endif
     return SendAsyncMessage(nsString(aMessage), data, cpows, Principal(aPrincipal));
 }
 
 bool
 ContentParent::CheckPermission(const nsAString& aPermission)
 {
     return AssertAppProcessPermission(this, NS_ConvertUTF16toUTF8(aPermission).get());
 }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -74,16 +74,20 @@ class ContentParent MOZ_FINAL : public P
     typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
     typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
     typedef mozilla::ipc::TestShellParent TestShellParent;
     typedef mozilla::ipc::URIParams URIParams;
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
 public:
 #ifdef MOZ_NUWA_PROCESS
+    static int32_t NuwaPid() {
+        return sNuwaPid;
+    }
+
     static bool IsNuwaReady() {
         return sNuwaReady;
     }
 #endif
     virtual bool IsContentParent() MOZ_OVERRIDE { return true; }
     /**
      * Start up the content-process machinery.  This might include
      * scheduling pre-launch tasks.
@@ -707,16 +711,17 @@ private:
 
 #ifdef MOZ_X11
     // Dup of child's X socket, used to scope its resources to this
     // object instead of the child process's lifetime.
     ScopedClose mChildXSocketFdDup;
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
+    static int32_t sNuwaPid;
     static bool sNuwaReady;
 #endif
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver {
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1893,18 +1893,19 @@ TabChild::RecvHandleDoubleTap(const CSSP
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Gesture:DoubleTap"), data);
 
     return true;
 }
 
 bool
 TabChild::RecvHandleSingleTap(const CSSPoint& aPoint, const ScrollableLayerGuid& aGuid)
 {
-  TABC_LOG("Handling single tap at %s with %p %p %d\n",
-    Stringify(aPoint).c_str(), mGlobal.get(), mTabChildGlobal.get(), mTouchEndCancelled);
+  TABC_LOG("Handling single tap at %s on %s with %p %p %d\n",
+    Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mGlobal.get(),
+    mTabChildGlobal.get(), mTouchEndCancelled);
 
   if (!mGlobal || !mTabChildGlobal) {
     return true;
   }
 
   if (mTouchEndCancelled) {
     return true;
   }
--- a/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_offerRequiresReceiveVideo.html
@@ -15,15 +15,15 @@
     bug: "850275",
     title: "Simple offer media constraint test with video"
   });
 
   runNetworkTest(function() {
     var test = new PeerConnectionTest();
     // TODO: Stop using old constraint-like RTCOptions soon (Bug 1064223).
     // Watch out for case-difference when fixing: { offerToReceiveVideo: true }
-    test.setOfferOptions({ optional: [{ OfferToReceiveAudio: true }] });
+    test.setOfferOptions({ optional: [{ OfferToReceiveVideo: true }] });
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/storage/DOMStorageIPC.cpp
+++ b/dom/storage/DOMStorageIPC.cpp
@@ -584,18 +584,25 @@ DOMStorageDBParent::RecvAsyncFlush()
 
 // DOMStorageObserverSink
 
 nsresult
 DOMStorageDBParent::Observe(const char* aTopic,
                             const nsACString& aScopePrefix)
 {
   if (mIPCOpen) {
-    mozilla::unused << SendObserve(nsDependentCString(aTopic),
-                                   nsCString(aScopePrefix));
+#ifdef MOZ_NUWA_PROCESS
+    if (!(static_cast<ContentParent*>(Manager())->IsNuwaProcess() &&
+          ContentParent::IsNuwaReady())) {
+#endif
+      mozilla::unused << SendObserve(nsDependentCString(aTopic),
+                                     nsCString(aScopePrefix));
+#ifdef MOZ_NUWA_PROCESS
+    }
+#endif
   }
 
   return NS_OK;
 }
 
 namespace { // anon
 
 // Results must be sent back on the main thread
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -148,16 +148,25 @@ AppendToString(std::stringstream& aStrea
     AppendToString(aStream, m.GetScrollParentId(), " p=");
     aStream << nsPrintfCString(" i=(%ld %lld) }",
             m.GetPresShellId(), m.GetScrollId()).get();
   }
   aStream << sfx;
 }
 
 void
+AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
+               const char* pfx, const char* sfx)
+{
+  aStream << pfx
+          << nsPrintfCString("{ l=%llu, p=%u, v=%llu }", s.mLayersId, s.mPresShellId, s.mScrollId).get()
+          << sfx;
+}
+
+void
 AppendToString(std::stringstream& aStream, const Matrix4x4& m,
                const char* pfx, const char* sfx)
 {
   aStream << pfx;
   if (m.Is2D()) {
     Matrix matrix = m.As2D();
     if (matrix.IsIdentity()) {
       aStream << "[ I ]";
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -92,16 +92,20 @@ AppendToString(std::stringstream& aStrea
 void
 AppendToString(std::stringstream& aStream, const nsIntSize& sz,
                const char* pfx="", const char* sfx="");
 
 void
 AppendToString(std::stringstream& aStream, const FrameMetrics& m,
                const char* pfx="", const char* sfx="", bool detailed = false);
 
+void
+AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s,
+               const char* pfx="", const char* sfx="");
+
 template<class T>
 void
 AppendToString(std::stringstream& aStream, const mozilla::gfx::MarginTyped<T>& m,
                const char* pfx="", const char* sfx="")
 {
   aStream << pfx;
   aStream << nsPrintfCString(
     "(l=%f, t=%f, r=%f, b=%f)",
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2745,17 +2745,17 @@ void AsyncPanZoomController::NotifyLayer
 
   if (scrollOffsetUpdated) {
     // Once layout issues a scroll offset update, it becomes impervious to
     // scroll offset updates from APZ until we acknowledge the update it sent.
     // This prevents APZ updates from clobbering scroll updates from other
     // more "legitimate" sources like content scripts.
     nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
     if (controller) {
-      APZC_LOG("%p sending scroll update acknowledgement with gen %lu\n", this, aLayerMetrics.GetScrollGeneration());
+      APZC_LOG("%p sending scroll update acknowledgement with gen %u\n", this, aLayerMetrics.GetScrollGeneration());
       controller->AcknowledgeScrollUpdate(aLayerMetrics.GetScrollId(),
                                           aLayerMetrics.GetScrollGeneration());
     }
   }
 
   if (needContentRepaint) {
     RequestContentRepaint();
   }
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -287,16 +287,25 @@ public:
     NS_IMETHOD Run() {
         MOZ_ASSERT(NS_IsMainThread());
 
         nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
         if (sf) {
             sf->ResetScrollInfoIfGeneration(mScrollGeneration);
         }
 
+        // Since the APZ and content are in sync, we need to clear any callback transform
+        // that might have been set on the last repaint request (which might have failed
+        // due to the inflight scroll update that this message is acknowledging).
+        nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(mScrollId);
+        if (content) {
+            content->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(),
+                                 nsINode::DeleteProperty<CSSPoint>);
+        }
+
         return NS_OK;
     }
 
 protected:
     ViewID mScrollId;
     uint32_t mScrollGeneration;
 };
 
--- a/gfx/skia/trunk/include/config/SkUserConfig.h
+++ b/gfx/skia/trunk/include/config/SkUserConfig.h
@@ -200,14 +200,13 @@
 #define SK_A32_SHIFT 24
 #define SK_R32_SHIFT 16
 #define SK_G32_SHIFT 8
 #define SK_B32_SHIFT 0
 
 #define SK_ALLOW_STATIC_GLOBAL_INITIALIZERS 0
 
 #define SK_SUPPORT_LEGACY_GETDEVICE
-#define SK_SUPPORT_LEGACY_GETTOPDEVICE
 #define SK_IGNORE_ETC1_SUPPORT
 
 #define SK_RASTERIZE_EVEN_ROUNDING
 
 #endif
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -1101,20 +1101,19 @@ void imgLoader::GlobalInit()
   if (NS_SUCCEEDED(rv))
     sCacheTimeWeight = timeweight / 1000.0;
   else
     sCacheTimeWeight = 0.5;
 
   int32_t cachesize;
   rv = Preferences::GetInt("image.cache.size", &cachesize);
   if (NS_SUCCEEDED(rv))
-    sCacheMaxSize = cachesize;
+    sCacheMaxSize = cachesize > 0 ? cachesize : 0;
   else
     sCacheMaxSize = 5 * 1024 * 1024;
-  sCacheMaxSize = sCacheMaxSize > 0 ? sCacheMaxSize : 0;
 
   sMemReporter = new imgMemoryReporter();
   RegisterStrongMemoryReporter(sMemReporter);
   RegisterImagesContentUsedUncompressedDistinguishedAmount(imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
 }
 
 nsresult imgLoader::InitCache()
 {
--- a/ipc/glue/FileDescriptor.cpp
+++ b/ipc/glue/FileDescriptor.cpp
@@ -37,17 +37,17 @@ FileDescriptor::FileDescriptor(PlatformH
   mHandleCreatedByOtherProcessWasUsed(false)
 {
   DuplicateInCurrentProcess(aHandle);
 }
 
 void
 FileDescriptor::DuplicateInCurrentProcess(PlatformHandleType aHandle)
 {
-  MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
+  MOZ_ASSERT_IF(mHandleCreatedByOtherProcess && IsValid(),
                 mHandleCreatedByOtherProcessWasUsed);
 
   if (IsValid(aHandle)) {
     PlatformHandleType newHandle;
 #ifdef XP_WIN
     if (DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(),
                         &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
 #else // XP_WIN
@@ -60,17 +60,17 @@ FileDescriptor::DuplicateInCurrentProces
   }
 
   mHandle = INVALID_HANDLE;
 }
 
 void
 FileDescriptor::CloseCurrentProcessHandle()
 {
-  MOZ_ASSERT_IF(mHandleCreatedByOtherProcess,
+  MOZ_ASSERT_IF(mHandleCreatedByOtherProcess && IsValid(),
                 mHandleCreatedByOtherProcessWasUsed);
 
   // Don't actually close handles created by another process.
   if (mHandleCreatedByOtherProcess) {
     return;
   }
 
   if (IsValid()) {
--- a/ipc/glue/MessageLink.cpp
+++ b/ipc/glue/MessageLink.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/ipc/MessageLink.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/dom/ContentParent.h"
 #endif
 
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsXULAppAPI.h"
 
@@ -65,16 +66,19 @@ MessageLink::~MessageLink()
 #endif
 }
 
 ProcessLink::ProcessLink(MessageChannel *aChan)
   : MessageLink(aChan)
   , mTransport(nullptr)
   , mIOLoop(nullptr)
   , mExistingListener(nullptr)
+#ifdef MOZ_NUWA_PROCESS
+  , mIsToNuwaProcess(false)
+#endif
 {
 }
 
 ProcessLink::~ProcessLink()
 {
 #ifdef DEBUG
     mTransport = nullptr;
     mIOLoop = nullptr;
@@ -163,16 +167,30 @@ ProcessLink::EchoMessage(Message *msg)
 }
 
 void
 ProcessLink::SendMessage(Message *msg)
 {
     mChan->AssertWorkerThread();
     mChan->mMonitor->AssertCurrentThreadOwns();
 
+#ifdef MOZ_NUWA_PROCESS
+    if (mIsToNuwaProcess && mozilla::dom::ContentParent::IsNuwaReady()) {
+        switch (msg->type()) {
+        case mozilla::dom::PContent::Msg_NuwaFork__ID:
+        case mozilla::dom::PContent::Reply_AddNewProcess__ID:
+        case mozilla::dom::PContent::Msg_NotifyPhoneStateChange__ID:
+        case GOODBYE_MESSAGE_TYPE:
+            break;
+        default:
+            MOZ_CRASH();
+        }
+    }
+#endif
+
     mIOLoop->PostTask(
         FROM_HERE,
         NewRunnableMethod(mTransport, &Transport::Send, msg));
 }
 
 void
 ProcessLink::SendClose()
 {
@@ -355,16 +373,20 @@ ProcessLink::OnChannelConnected(int32_t 
           mChan->mMonitor->Notify();
           notifyChannel = true;
         }
     }
 
     if (mExistingListener)
         mExistingListener->OnChannelConnected(peer_pid);
 
+#ifdef MOZ_NUWA_PROCESS
+    mIsToNuwaProcess = (peer_pid == mozilla::dom::ContentParent::NuwaPid());
+#endif
+
     if (notifyChannel) {
       mChan->OnChannelConnected(peer_pid);
     }
 }
 
 void
 ProcessLink::OnChannelError()
 {
--- a/ipc/glue/MessageLink.h
+++ b/ipc/glue/MessageLink.h
@@ -165,16 +165,19 @@ class ProcessLink
 
     virtual bool Unsound_IsClosed() const MOZ_OVERRIDE;
     virtual uint32_t Unsound_NumQueuedMessages() const MOZ_OVERRIDE;
 
   protected:
     Transport* mTransport;
     MessageLoop* mIOLoop;       // thread where IO happens
     Transport::Listener* mExistingListener; // channel's previous listener
+#ifdef MOZ_NUWA_PROCESS
+    bool mIsToNuwaProcess;
+#endif
 };
 
 class ThreadLink : public MessageLink
 {
   public:
     ThreadLink(MessageChannel *aChan, MessageChannel *aTargetChan);
     virtual ~ThreadLink();
 
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -388,34 +388,70 @@ class JS_PUBLIC_API(AutoAssertOnGC)
     explicit AutoAssertOnGC(JSRuntime *rt) {}
     ~AutoAssertOnGC() {}
 
     static void VerifyIsSafeToGC(JSRuntime *rt) {}
 #endif
 };
 
 /*
- * Disable the static rooting hazard analysis in the live region, but assert if
- * any GC occurs while this guard object is live. This is most useful to help
- * the exact rooting hazard analysis in complex regions, since it cannot
- * understand dataflow.
+ * Assert if an allocation of a GC thing occurs while this class is live. This
+ * class does not disable the static rooting hazard analysis.
+ */
+class JS_PUBLIC_API(AutoAssertNoAlloc)
+{
+#ifdef JS_DEBUG
+    js::gc::GCRuntime *gc;
+
+  public:
+    AutoAssertNoAlloc() : gc(nullptr) {}
+    explicit AutoAssertNoAlloc(JSRuntime *rt);
+    void disallowAlloc(JSRuntime *rt);
+    ~AutoAssertNoAlloc();
+#else
+  public:
+    AutoAssertNoAlloc() {}
+    explicit AutoAssertNoAlloc(JSRuntime *rt) {}
+    void disallowAlloc(JSRuntime *rt) {}
+#endif
+};
+
+/*
+ * Disable the static rooting hazard analysis in the live region and assert if
+ * any allocation that could potentially trigger a GC occurs while this guard
+ * object is live. This is most useful to help the exact rooting hazard analysis
+ * in complex regions, since it cannot understand dataflow.
  *
  * Note: GC behavior is unpredictable even when deterministice and is generally
  *       non-deterministic in practice. The fact that this guard has not
  *       asserted is not a guarantee that a GC cannot happen in the guarded
  *       region. As a rule, anyone performing a GC unsafe action should
  *       understand the GC properties of all code in that region and ensure
  *       that the hazard analysis is correct for that code, rather than relying
  *       on this class.
  */
-class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertOnGC
+class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc
 {
   public:
-    AutoSuppressGCAnalysis() : AutoAssertOnGC() {}
-    explicit AutoSuppressGCAnalysis(JSRuntime *rt) : AutoAssertOnGC(rt) {}
+    AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {}
+    explicit AutoSuppressGCAnalysis(JSRuntime *rt) : AutoAssertNoAlloc(rt) {}
+};
+
+/*
+ * Assert that code is only ever called from a GC callback, disable the static
+ * rooting hazard analysis and assert if any allocation that could potentially
+ * trigger a GC occurs while this guard object is live.
+ *
+ * This is useful to make the static analysis ignore code that runs in GC
+ * callbacks.
+ */
+class JS_PUBLIC_API(AutoAssertGCCallback) : public AutoSuppressGCAnalysis
+{
+  public:
+    explicit AutoAssertGCCallback(JSObject *obj);
 };
 
 /*
  * Place AutoCheckCannotGC in scopes that you believe can never GC. These
  * annotations will be verified both dynamically via AutoAssertOnGC, and
  * statically with the rooting hazard analysis (implemented by making the
  * analysis consider AutoCheckCannotGC to be a GC pointer, and therefore
  * complain if it is live across a GC call.) It is useful when dealing with
--- a/js/src/asmjs/AsmJSLink.cpp
+++ b/js/src/asmjs/AsmJSLink.cpp
@@ -355,16 +355,20 @@ ValidateSimdOperation(JSContext *cx, Asm
           case AsmJSSimdOperation_lessThan: native = simd_int32x4_lessThan; break;
           case AsmJSSimdOperation_greaterThan: native = simd_int32x4_greaterThan; break;
           case AsmJSSimdOperation_equal: native = simd_int32x4_equal; break;
           case AsmJSSimdOperation_and: native = simd_int32x4_and; break;
           case AsmJSSimdOperation_or: native = simd_int32x4_or; break;
           case AsmJSSimdOperation_xor: native = simd_int32x4_xor; break;
           case AsmJSSimdOperation_select: native = simd_int32x4_select; break;
           case AsmJSSimdOperation_splat: native = simd_int32x4_splat; break;
+          case AsmJSSimdOperation_withX: native = simd_int32x4_withX; break;
+          case AsmJSSimdOperation_withY: native = simd_int32x4_withY; break;
+          case AsmJSSimdOperation_withZ: native = simd_int32x4_withZ; break;
+          case AsmJSSimdOperation_withW: native = simd_int32x4_withW; break;
           case AsmJSSimdOperation_lessThanOrEqual:
           case AsmJSSimdOperation_greaterThanOrEqual:
           case AsmJSSimdOperation_notEqual:
           case AsmJSSimdOperation_mul:
           case AsmJSSimdOperation_div:
           case AsmJSSimdOperation_max:
           case AsmJSSimdOperation_min:
             MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("shouldn't have been validated in the first "
@@ -385,16 +389,20 @@ ValidateSimdOperation(JSContext *cx, Asm
           case AsmJSSimdOperation_notEqual: native = simd_float32x4_notEqual ; break;
           case AsmJSSimdOperation_greaterThan: native = simd_float32x4_greaterThan; break;
           case AsmJSSimdOperation_greaterThanOrEqual: native = simd_float32x4_greaterThanOrEqual ; break;
           case AsmJSSimdOperation_and: native = simd_float32x4_and; break;
           case AsmJSSimdOperation_or: native = simd_float32x4_or; break;
           case AsmJSSimdOperation_xor: native = simd_float32x4_xor; break;
           case AsmJSSimdOperation_select: native = simd_float32x4_select; break;
           case AsmJSSimdOperation_splat: native = simd_float32x4_splat; break;
+          case AsmJSSimdOperation_withX: native = simd_float32x4_withX; break;
+          case AsmJSSimdOperation_withY: native = simd_float32x4_withY; break;
+          case AsmJSSimdOperation_withZ: native = simd_float32x4_withZ; break;
+          case AsmJSSimdOperation_withW: native = simd_float32x4_withW; break;
         }
         break;
     }
     if (!native || !IsNativeFunction(v, native))
         return LinkFail(cx, "bad SIMD.type.* operation");
     return true;
 }
 
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -87,17 +87,21 @@ enum AsmJSSimdOperation
     AsmJSSimdOperation_equal,
     AsmJSSimdOperation_notEqual,
     AsmJSSimdOperation_greaterThan,
     AsmJSSimdOperation_greaterThanOrEqual,
     AsmJSSimdOperation_and,
     AsmJSSimdOperation_or,
     AsmJSSimdOperation_xor,
     AsmJSSimdOperation_select,
-    AsmJSSimdOperation_splat
+    AsmJSSimdOperation_splat,
+    AsmJSSimdOperation_withX,
+    AsmJSSimdOperation_withY,
+    AsmJSSimdOperation_withZ,
+    AsmJSSimdOperation_withW
 };
 
 // These labels describe positions in the prologue/epilogue of functions while
 // compiling an AsmJSModule.
 struct AsmJSFunctionLabels
 {
     AsmJSFunctionLabels(jit::Label &entry, jit::Label &overflowExit)
       : entry(entry), overflowExit(overflowExit) {}
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -1418,17 +1418,21 @@ class MOZ_STACK_CLASS ModuleCompiler
             !addStandardLibrarySimdOpName("greaterThan", AsmJSSimdOperation_greaterThan) ||
             !addStandardLibrarySimdOpName("greaterThanOrEqual", AsmJSSimdOperation_greaterThanOrEqual) ||
             !addStandardLibrarySimdOpName("and", AsmJSSimdOperation_and) ||
             !addStandardLibrarySimdOpName("or", AsmJSSimdOperation_or) ||
             !addStandardLibrarySimdOpName("xor", AsmJSSimdOperation_xor) ||
             !addStandardLibrarySimdOpName("select", AsmJSSimdOperation_select) ||
             !addStandardLibrarySimdOpName("splat", AsmJSSimdOperation_splat) ||
             !addStandardLibrarySimdOpName("max", AsmJSSimdOperation_max) ||
-            !addStandardLibrarySimdOpName("min", AsmJSSimdOperation_min))
+            !addStandardLibrarySimdOpName("min", AsmJSSimdOperation_min) ||
+            !addStandardLibrarySimdOpName("withX", AsmJSSimdOperation_withX) ||
+            !addStandardLibrarySimdOpName("withY", AsmJSSimdOperation_withY) ||
+            !addStandardLibrarySimdOpName("withZ", AsmJSSimdOperation_withZ) ||
+            !addStandardLibrarySimdOpName("withW", AsmJSSimdOperation_withW))
         {
             return false;
         }
 
         uint32_t srcStart = parser_.pc->maybeFunction->pn_body->pn_pos.begin;
         uint32_t srcBodyStart = tokenStream().currentToken().pos.end;
 
         // "use strict" should be added to the source if we are in an implicit
@@ -2473,16 +2477,28 @@ class FunctionCompiler
             return nullptr;
 
         JS_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type());
         MSimdBinaryComp *ins = MSimdBinaryComp::NewAsmJS(alloc(), lhs, rhs, op);
         curBlock_->add(ins);
         return ins;
     }
 
+    MDefinition *insertElementSimd(MDefinition *vec, MDefinition *val, SimdLane lane, MIRType type)
+    {
+        if (inDeadCode())
+            return nullptr;
+
+        MOZ_ASSERT(IsSimdType(vec->type()) && vec->type() == type);
+        MOZ_ASSERT(!IsSimdType(val->type()));
+        MSimdInsertElement *ins = MSimdInsertElement::NewAsmJS(alloc(), vec, val, type, lane);
+        curBlock_->add(ins);
+        return ins;
+    }
+
     MDefinition *ternarySimd(MDefinition *mask, MDefinition *lhs, MDefinition *rhs,
                              MSimdTernaryBitwise::Operation op, MIRType type)
     {
         if (inDeadCode())
             return nullptr;
 
         MOZ_ASSERT(IsSimdType(mask->type()));
         MOZ_ASSERT(mask->type() == MIRType_Int32x4);
@@ -3562,16 +3578,20 @@ IsSimdValidOperationType(AsmJSSimdType t
       case AsmJSSimdOperation_lessThan:
       case AsmJSSimdOperation_equal:
       case AsmJSSimdOperation_greaterThan:
       case AsmJSSimdOperation_and:
       case AsmJSSimdOperation_or:
       case AsmJSSimdOperation_xor:
       case AsmJSSimdOperation_select:
       case AsmJSSimdOperation_splat:
+      case AsmJSSimdOperation_withX:
+      case AsmJSSimdOperation_withY:
+      case AsmJSSimdOperation_withZ:
+      case AsmJSSimdOperation_withW:
         return true;
       case AsmJSSimdOperation_mul:
       case AsmJSSimdOperation_div:
       case AsmJSSimdOperation_max:
       case AsmJSSimdOperation_min:
       case AsmJSSimdOperation_lessThanOrEqual:
       case AsmJSSimdOperation_notEqual:
       case AsmJSSimdOperation_greaterThanOrEqual:
@@ -4348,40 +4368,45 @@ CheckMathMinMax(FunctionCompiler &f, Par
         return f.fail(callNode, "Math.min/max must be passed at least 2 arguments");
 
     ParseNode *firstArg = CallArgList(callNode);
     MDefinition *firstDef;
     Type firstType;
     if (!CheckExpr(f, firstArg, &firstDef, &firstType))
         return false;
 
-    bool opIsDouble = firstType.isMaybeDouble();
-    bool opIsInteger = firstType.isInt();
+    if (firstType.isMaybeDouble()) {
+        *type = MathRetType::Double;
+        firstType = Type::MaybeDouble;
+    } else if (firstType.isMaybeFloat()) {
+        *type = MathRetType::Float;
+        firstType = Type::MaybeFloat;
+    } else if (firstType.isInt()) {
+        *type = MathRetType::Signed;
+        firstType = Type::Int;
+    } else {
+        return f.failf(firstArg, "%s is not a subtype of double?, float? or int",
+                       firstType.toChars());
+    }
+
     MIRType opType = firstType.toMIRType();
-
-    if (!opIsDouble && !opIsInteger)
-        return f.failf(firstArg, "%s is not a subtype of double? or int", firstType.toChars());
-
     MDefinition *lastDef = firstDef;
     ParseNode *nextArg = NextNode(firstArg);
     for (unsigned i = 1; i < CallArgListLength(callNode); i++, nextArg = NextNode(nextArg)) {
         MDefinition *nextDef;
         Type nextType;
         if (!CheckExpr(f, nextArg, &nextDef, &nextType))
             return false;
 
-        if (opIsDouble && !nextType.isMaybeDouble())
-            return f.failf(nextArg, "%s is not a subtype of double?", nextType.toChars());
-        if (opIsInteger && !nextType.isInt())
-            return f.failf(nextArg, "%s is not a subtype of int", nextType.toChars());
+        if (!(nextType <= firstType))
+            return f.failf(nextArg, "%s is not a subtype of %s", nextType.toChars(), firstType.toChars());
 
         lastDef = f.minMax(lastDef, nextDef, opType, isMax);
     }
 
-    *type = MathRetType(opIsDouble ? MathRetType::Double : MathRetType::Signed);
     *def = lastDef;
     return true;
 }
 
 typedef bool (*CheckArgType)(FunctionCompiler &f, ParseNode *argNode, Type type);
 
 static bool
 CheckCallArgs(FunctionCompiler &f, ParseNode *callNode, CheckArgType checkArg,
@@ -4813,16 +4838,45 @@ class CheckSimdSelectArgs
         if (!(actualType <= formalType_)) {
             return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
                            formalType_.toChars());
         }
         return true;
     }
 };
 
+class CheckSimdVectorScalarArgs
+{
+    Type formalType_;
+
+  public:
+    explicit CheckSimdVectorScalarArgs(Type t) : formalType_(t) {}
+
+    bool operator()(FunctionCompiler &f, ParseNode *arg, unsigned argIndex, Type actualType) const
+    {
+        MOZ_ASSERT(argIndex < 2);
+        if (argIndex == 0) {
+            // First argument is the vector
+            if (!(actualType <= formalType_)) {
+                return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
+                               formalType_.toChars());
+            }
+            return true;
+        }
+
+        // Second argument is the scalar
+        Type coercedFormalType = formalType_.simdToCoercedScalarType();
+        if (!(actualType <= coercedFormalType)) {
+            return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(),
+                           coercedFormalType.toChars());
+        }
+        return true;
+    }
+};
+
 } // anonymous namespace
 
 template<class OpEnum>
 static inline bool
 CheckSimdBinary(FunctionCompiler &f, ParseNode *call, Type retType, OpEnum op, MDefinition **def,
                 Type *type)
 {
     DefinitionVector argDefs;
@@ -4843,16 +4897,28 @@ CheckSimdBinary<MSimdBinaryComp::Operati
     if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(retType), &argDefs))
         return false;
     *def = f.binarySimd(argDefs[0], argDefs[1], op);
     *type = Type::Int32x4;
     return true;
 }
 
 static bool
+CheckSimdWith(FunctionCompiler &f, ParseNode *call, Type retType, SimdLane lane, MDefinition **def,
+              Type *type)
+{
+    DefinitionVector defs;
+    if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(retType), &defs))
+        return false;
+    *def = f.insertElementSimd(defs[0], defs[1], lane, retType.toMIRType());
+    *type = retType;
+    return true;
+}
+
+static bool
 CheckSimdOperationCall(FunctionCompiler &f, ParseNode *call, const ModuleCompiler::Global *global,
                        MDefinition **def, Type *type)
 {
     MOZ_ASSERT(global->isSimdOperation());
 
     Type retType = global->simdOperationType();
 
     switch (global->simdOperation()) {
@@ -4884,16 +4950,25 @@ CheckSimdOperationCall(FunctionCompiler 
 
       case AsmJSSimdOperation_and:
         return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::and_, def, type);
       case AsmJSSimdOperation_or:
         return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::or_, def, type);
       case AsmJSSimdOperation_xor:
         return CheckSimdBinary(f, call, retType, MSimdBinaryBitwise::xor_, def, type);
 
+      case AsmJSSimdOperation_withX:
+        return CheckSimdWith(f, call, retType, SimdLane::LaneX, def, type);
+      case AsmJSSimdOperation_withY:
+        return CheckSimdWith(f, call, retType, SimdLane::LaneY, def, type);
+      case AsmJSSimdOperation_withZ:
+        return CheckSimdWith(f, call, retType, SimdLane::LaneZ, def, type);
+      case AsmJSSimdOperation_withW:
+        return CheckSimdWith(f, call, retType, SimdLane::LaneW, def, type);
+
       case AsmJSSimdOperation_splat: {
         DefinitionVector defs;
         Type formalType = retType.simdToCoercedScalarType();
         if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(formalType), &defs))
             return false;
         *def = f.splatSimd(defs[0], retType.toMIRType());
         *type = retType;
         return true;
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -217,36 +217,49 @@ GC(JSContext *cx, unsigned argc, jsval *
 
     /*
      * If the first argument is 'compartment', we collect any compartments
      * previously scheduled for GC via schedulegc. If the first argument is an
      * object, we collect the object's compartment (and any other compartments
      * scheduled for GC). Otherwise, we collect all compartments.
      */
     bool compartment = false;
-    if (args.length() == 1) {
+    if (args.length() >= 1) {
         Value arg = args[0];
         if (arg.isString()) {
             if (!JS_StringEqualsAscii(cx, arg.toString(), "compartment", &compartment))
                 return false;
         } else if (arg.isObject()) {
             PrepareZoneForGC(UncheckedUnwrap(&arg.toObject())->zone());
             compartment = true;
         }
     }
 
+    bool shrinking = false;
+    if (args.length() >= 2) {
+        Value arg = args[1];
+        if (arg.isString()) {
+            if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
+                return false;
+        }
+    }
+
 #ifndef JS_MORE_DETERMINISTIC
     size_t preBytes = cx->runtime()->gc.usage.gcBytes();
 #endif
 
     if (compartment)
         PrepareForDebugGC(cx->runtime());
     else
         PrepareForFullGC(cx->runtime());
-    GCForReason(cx->runtime(), gcreason::API);
+
+    if (shrinking)
+        ShrinkingGC(cx->runtime(), gcreason::API);
+    else
+        GCForReason(cx->runtime(), gcreason::API);
 
     char buf[256] = { '\0' };
 #ifndef JS_MORE_DETERMINISTIC
     JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n",
                 (unsigned long)preBytes, (unsigned long)cx->runtime()->gc.usage.gcBytes());
 #endif
     JSString *str = JS_NewStringCopyZ(cx, buf);
     if (!str)
@@ -1996,20 +2009,22 @@ IsSimdAvailable(JSContext *cx, unsigned 
     bool available = cx->jitSupportsSimd();
 #endif
     args.rval().set(BooleanValue(available));
     return true;
 }
 
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
-"gc([obj] | 'compartment')",
+"gc([obj] | 'compartment' [, 'shrinking'])",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
-"  GC via schedulegc."),
+"  GC via schedulegc.\n"
+"  If 'shrinking' is passes as the optional second argument, perform a\n"
+"  shrinking GC rather than a normal GC."),
 
     JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
 "minorgc([aboutToOverflow])",
 "  Run a minor collector on the Nursery. When aboutToOverflow is true, marks\n"
 "  the store buffer as about-to-overflow before collecting."),
 
     JS_FN_HELP("gcparam", GCParameter, 2, 0,
 "gcparam(name [, value])",
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2885,48 +2885,16 @@ js::ClampToUint8(ThreadSafeContext *, un
     args.rval().setNumber(ClampDoubleToUint8(args[0].toNumber()));
     return true;
 }
 
 JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ClampToUint8JitInfo, ClampToUint8JitInfo,
                                       js::ClampToUint8);
 
 bool
-js::Memcpy(ThreadSafeContext *, unsigned argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    JS_ASSERT(args.length() == 5);
-    JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
-    JS_ASSERT(args[1].isInt32());
-    JS_ASSERT(args[2].isObject() && args[2].toObject().is<TypedObject>());
-    JS_ASSERT(args[3].isInt32());
-    JS_ASSERT(args[4].isInt32());
-
-    TypedObject &targetTypedObj = args[0].toObject().as<TypedObject>();
-    int32_t targetOffset = args[1].toInt32();
-    TypedObject &sourceTypedObj = args[2].toObject().as<TypedObject>();
-    int32_t sourceOffset = args[3].toInt32();
-    int32_t size = args[4].toInt32();
-
-    JS_ASSERT(targetOffset >= 0);
-    JS_ASSERT(sourceOffset >= 0);
-    JS_ASSERT(size >= 0);
-    JS_ASSERT(size + targetOffset <= targetTypedObj.size());
-    JS_ASSERT(size + sourceOffset <= sourceTypedObj.size());
-
-    uint8_t *target = targetTypedObj.typedMem(targetOffset);
-    uint8_t *source = sourceTypedObj.typedMem(sourceOffset);
-    memcpy(target, source, size);
-    args.rval().setUndefined();
-    return true;
-}
-
-JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::MemcpyJitInfo, MemcpyJitInfo, js::Memcpy);
-
-bool
 js::GetTypedObjectModule(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Rooted<GlobalObject*> global(cx, cx->global());
     JS_ASSERT(global);
     args.rval().setObject(global->getTypedObjectModule());
     return true;
 }
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -847,30 +847,16 @@ extern const JSJitInfo TypedObjectIsAtta
  * Usage: ClampToUint8(v)
  *
  * Same as the C function ClampDoubleToUint8. `v` must be a number.
  */
 bool ClampToUint8(ThreadSafeContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo ClampToUint8JitInfo;
 
 /*
- * Usage: Memcpy(targetDatum, targetOffset,
- *               sourceDatum, sourceOffset,
- *               size)
- *
- * Intrinsic function. Copies size bytes from the data for
- * `sourceDatum` at `sourceOffset` into the data for
- * `targetDatum` at `targetOffset`.
- *
- * Both `sourceDatum` and `targetDatum` must be attached.
- */
-bool Memcpy(ThreadSafeContext *cx, unsigned argc, Value *vp);
-extern const JSJitInfo MemcpyJitInfo;
-
-/*
  * Usage: GetTypedObjectModule()
  *
  * Returns the global "typed object" module, which provides access
  * to the various builtin type descriptors. These are currently
  * exported as immutable properties so it is safe for self-hosted code
  * to access them; eventually this should be linked into the module
  * system.
  */
--- a/js/src/builtin/TypedObject.js
+++ b/js/src/builtin/TypedObject.js
@@ -190,30 +190,16 @@ function TypedObjectGetSimd(descr, typed
 
 // Writes `fromValue` into the `typedObj` at offset `offset`, adapting
 // it to `descr` as needed. This is the most general entry point
 // and works for any type.
 function TypedObjectSet(descr, typedObj, offset, fromValue) {
   if (!TypedObjectIsAttached(typedObj))
     ThrowError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
 
-  // Fast path: `fromValue` is a typed object with same type
-  // representation as the destination. In that case, we can just do a
-  // memcpy.
-  if (IsObject(fromValue) && ObjectIsTypedObject(fromValue)) {
-    if (!descr.variable && DescrsEquiv(descr, TypedObjectTypeDescr(fromValue))) {
-      if (!TypedObjectIsAttached(fromValue))
-        ThrowError(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
-
-      var size = DESCR_SIZE(descr);
-      Memcpy(typedObj, offset, fromValue, 0, size);
-      return;
-    }
-  }
-
   switch (DESCR_KIND(descr)) {
   case JS_TYPEREPR_SCALAR_KIND:
     TypedObjectSetScalar(descr, typedObj, offset, fromValue);
     return;
 
   case JS_TYPEREPR_REFERENCE_KIND:
     TypedObjectSetReference(descr, typedObj, offset, fromValue);
     return;
@@ -338,23 +324,43 @@ function TypedObjectSetReference(descr, 
   }
 
   assert(false, "Unhandled scalar type: " + type);
   return undefined;
 }
 
 // Sets `fromValue` to `this` assuming that `this` is a scalar type.
 function TypedObjectSetSimd(descr, typedObj, offset, fromValue) {
-  // It is only permitted to set a float32x4/int32x4 value from another
-  // float32x4/int32x4; in that case, the "fast path" that uses memcopy will
-  // have already matched. So if we get to this point, we're supposed
-  // to "adapt" fromValue, but there are no legal adaptions.
-  ThrowError(JSMSG_CANT_CONVERT_TO,
-             typeof(fromValue),
-             DESCR_STRING_REPR(descr));
+  if (!IsObject(fromValue) || !ObjectIsTypedObject(fromValue))
+    ThrowError(JSMSG_CANT_CONVERT_TO,
+               typeof(fromValue),
+               DESCR_STRING_REPR(descr));
+
+  if (!DescrsEquiv(descr, TypedObjectTypeDescr(fromValue)))
+    ThrowError(JSMSG_CANT_CONVERT_TO,
+               typeof(fromValue),
+               DESCR_STRING_REPR(descr));
+
+  var type = DESCR_TYPE(descr);
+  switch (type) {
+    case JS_SIMDTYPEREPR_FLOAT32:
+      Store_float32(typedObj, offset + 0, Load_float32(fromValue, 0));
+      Store_float32(typedObj, offset + 4, Load_float32(fromValue, 4));
+      Store_float32(typedObj, offset + 8, Load_float32(fromValue, 8));
+      Store_float32(typedObj, offset + 12, Load_float32(fromValue, 12));
+      break;
+    case JS_SIMDTYPEREPR_INT32:
+      Store_int32(typedObj, offset + 0, Load_int32(fromValue, 0));
+      Store_int32(typedObj, offset + 4, Load_int32(fromValue, 4));
+      Store_int32(typedObj, offset + 8, Load_int32(fromValue, 8));
+      Store_int32(typedObj, offset + 12, Load_int32(fromValue, 12));
+      break;
+    default:
+      assert(false, "Unhandled Simd type: " + type);
+  }
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // C++ Wrappers
 //
 // These helpers are invoked by C++ code or used as method bodies.
 
 // Wrapper for use from C++ code.
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -217,16 +217,17 @@ function isRootedPointerTypeName(name)
         return /\(js::AllowGC\)1u>::RootType/.test(name);
 
     return name.startsWith('Rooted') || name.startsWith('PersistentRooted');
 }
 
 function isSuppressConstructor(name)
 {
     return name.indexOf("::AutoSuppressGC") != -1
+        || name.indexOf("::AutoAssertGCCallback") != -1
         || name.indexOf("::AutoEnterAnalysis") != -1
         || name.indexOf("::AutoSuppressGCAnalysis") != -1
         || name.indexOf("::AutoIgnoreRootingHazards") != -1;
 }
 
 // nsISupports subclasses' methods may be scriptable (or overridden
 // via binary XPCOM), and so may GC. But some fields just aren't going
 // to get overridden with something that can GC.
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -133,17 +133,17 @@ struct AutoStopVerifyingBarriers
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 void
 CheckHashTablesAfterMovingGC(JSRuntime *rt);
 #endif
 
 #ifdef JSGC_COMPACTING
 struct MovingTracer : JSTracer {
-    MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapValues) {}
+    MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapKeysValues) {}
 
     static void Visit(JSTracer *jstrc, void **thingp, JSGCTraceKind kind);
     static void Sweep(JSTracer *jstrc);
     static bool IsMovingTracer(JSTracer *trc) {
         return trc->callback == Visit;
     }
 };
 #endif
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -619,16 +619,18 @@ struct ArenaHeader : public JS::shadow::
 
     inline ArenaHeader *getNextDelayedMarking() const;
     inline void setNextDelayedMarking(ArenaHeader *aheader);
     inline void unsetDelayedMarking();
 
     inline ArenaHeader *getNextAllocDuringSweep() const;
     inline void setNextAllocDuringSweep(ArenaHeader *aheader);
     inline void unsetAllocDuringSweep();
+
+    void unmarkAll();
 };
 
 struct Arena
 {
     /*
      * Layout of an arena:
      * An arena is 4K in size and 4K-aligned. It starts with the ArenaHeader
      * descriptor followed by some pad bytes. The remainder of the arena is
--- a/js/src/jit-test/tests/asm.js/testMathLib.js
+++ b/js/src/jit-test/tests/asm.js/testMathLib.js
@@ -61,62 +61,68 @@ var f = asmLink(asmCompile('glob', USE_A
 for (n of [-Math.pow(2,31)-1, -Math.pow(2,31), -Math.pow(2,31)+1, -1, 0, 1, Math.pow(2,31)-2, Math.pow(2,31)-1, Math.pow(2,31)])
     assertEq(f(n), Math.abs(n|0)|0);
 
 var f = asmLink(asmCompile('glob', USE_ASM + 'var clz32=glob.Math.clz32; function f(i) { i=i|0; return clz32(i)|0 } return f'), this);
 for (n of [0, 1, 2, 15, 16, Math.pow(2,31)-1, Math.pow(2,31), Math.pow(2,31)+1, Math.pow(2,32)-1, Math.pow(2,32), Math.pow(2,32)+1])
     assertEq(f(n), Math.clz32(n|0));
 
 var doubleNumbers = [NaN, Infinity, -Infinity, -10000, -3.4, -0, 0, 3.4, 10000];
+var floatNumbers = [];
+for (var x of doubleNumbers) floatNumbers.push(Math.fround(x));
 var intNumbers = [-10000, -3, -1, 0, 3, 10000];
+
 function testBinary(f, g, numbers) {
     for (n of numbers)
         for (o of numbers)
             assertEq(f(n,o), g(n,o));
 }
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var po=glob.Math.pow; function f(d,e) { d=+d;e=+e; return +po(d,e) } return f'), {Math:{pow:Math.sin}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var po=glob.Math.pow; function f(d,e) { d=+d;e=+e; return +po(d,e) } return f'), {Math:{pow:null}});
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var po=glob.Math.pow; function f(d,e) { d=+d;e=+e; return +po(d,e) } return f'), {Math:{pow:Math.pow}}), Math.pow, doubleNumbers);
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var at=glob.Math.atan2; function f(d,e) { d=+d;e=+e; return +at(d,e) } return f'), {Math:{atan2:Math.sin}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var at=glob.Math.atan2; function f(d,e) { d=+d;e=+e; return +at(d,e) } return f'), {Math:{atan2:null}});
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var at=glob.Math.atan2; function f(d,e) { d=+d;e=+e; return +at(d,e) } return f'), {Math:{atan2:Math.atan2}}), Math.atan2, doubleNumbers);
 
 assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; return +min(d) } return f');
-assertAsmTypeFail('glob', USE_ASM + 'var f32=glob.Math.fround; var min=glob.Math.min; function f(d) { d=f32(d); return +min(d, f32(5)) } return f');
 assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + 'var i32=new glob.Int32Array(heap); var min=glob.Math.min; function f() { return min(i32[0], 5)|0 } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(x) { x=x|0; return min(3 + x, 5)|0 } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(x) { x=x|0; return min(5, 3 + x)|0 } return f');
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:Math.sin}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:null}});
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=+d;e=+e; return +min(d,e) } return f'), {Math:{min:Math.min}}), Math.min, doubleNumbers);
+testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; var f32=glob.Math.fround; function f(d,e) { d=f32(d);e=f32(e); return f32(min(d,e)) } return f'), this), Math.min, floatNumbers);
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e) { d=d|0;e=e|0; return min(d,e)|0} return f'), {Math:{min:Math.min}}), Math.min, intNumbers);
 
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:Math.sin}});
 assertAsmLinkFail(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:null}});
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=+d;e=+e; return +max(d,e) } return f'), {Math:{max:Math.max}}), Math.max, doubleNumbers);
+testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; var f32=glob.Math.fround; function f(d,e) { d=f32(d);e=f32(e); return f32(max(d,e)) } return f'), this), Math.max, floatNumbers);
 testBinary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e) { d=d|0;e=e|0; return max(d,e)|0} return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
 
 function testTernary(f, g, numbers) {
     for (n of numbers)
         for (o of numbers)
             for (p of numbers)
                 assertEq(f(n,o,p), g(n,o,p));
 }
 
 assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=+d;e=+e;g=g|0; return +min(d,e,g) } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=d|0;e=e|0;g=+g; return max(d,e,g)|0 } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=+d;e=+e;g=+g; return min(d,e,g)|0 } return f');
 testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return +max(d,e,g) } return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
 testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return max(d,e,g)|0 } return f'), {Math:{max:Math.max}}), Math.max, intNumbers);
 testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d,e,g) { d=+d;e=+e;g=+g; return +max(d,e,g) } return f'), {Math:{max:Math.max}}), Math.max, doubleNumbers);
+testTernary(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; var _=glob.Math.fround; function f(d,e,g) { d=_(d);e=_(e);g=_(g); return _(max(d,e,g)) } return f'), this), Math.max, floatNumbers);
 testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=d|0;e=e|0;g=g|0; return min(d,e,g)|0 } return f'), {Math:{min:Math.min}}), Math.min, intNumbers);
 testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d,e,g) { d=+d;e=+e;g=+g; return +min(d,e,g) } return f'), {Math:{min:Math.min}}), Math.min, doubleNumbers);
+testTernary(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; var _=glob.Math.fround; function f(d,e,g) { d=_(d);e=_(e);g=_(g); return _(min(d,e,g)) } return f'), this), Math.min, floatNumbers);
 
 // Implicit return coercions of math functions
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var im=glob.Math.imul; function f(i) { i=i|0; i = im(i,i); return i|0 } return f'), this)(3), 9);
 assertAsmTypeFail('glob', USE_ASM + 'var im=glob.Math.imul; function f(d) { d=+d; d = im(d, d) } return f');
 assertAsmTypeFail('glob', USE_ASM + FROUND + 'var im=glob.Math.imul; function f(d) { d=fround(d); d = im(d, d) } return f');
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(d) { d=d|0; d = abs(d|0); return +(d>>>0) } return f'), this)(-1), 1);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(d) { d=d|0; var m = 0; m = (-1)>>>0; return (abs(d|0) < (m>>>0))|0 } return f'), this)(42), 1);
@@ -129,17 +135,17 @@ assertEq(asmLink(asmCompile('glob', USE_
 assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var sqrt=glob.Math.sqrt; function f(d) { d=fround(d); d = fround(sqrt(d)); return +d } return f'), this)(13.37), Math.fround(Math.sqrt(Math.fround(13.37))));
 assertAsmTypeFail('glob', USE_ASM + FROUND + 'var sqrt=glob.Math.sqrt; function f(d) { d=fround(d); d = sqrt(d); return fround(d) } return f');
 assertAsmTypeFail('glob', USE_ASM + FROUND + 'var sqrt=glob.Math.sqrt; function f(d) { d=fround(d); d = sqrt(d); return d } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var sqrt=glob.Math.sqrt; function f(n) { n=n|0; var d=0.; d = sqrt(n|0) } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var sqrt=glob.Math.sqrt; function f(n) { n=n|0; var d=3.; n = sqrt(d)|0 } return f');
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; d = min(d, 13.); return +d } return f'), this)(12), 12);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=d|0; d = min(d, 11); return d|0 } return f'), this)(12), 11);
-assertAsmTypeFail('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(d) { d=fround(d); d = max(d) } return f');
+assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(d) { d=fround(d); d = min(d, fround(13.37)); return fround(d) } return f'), this)(14), Math.fround(13.37));
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var sin=glob.Math.sin; function f(d) { d=+d; d = sin(d); return +d } return f'), this)(Math.PI), Math.sin(Math.PI));
 assertAsmTypeFail('glob', USE_ASM + FROUND + 'var sin=glob.Math.sin; function f(d) { d=fround(d); d = sin(d) } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var sin=glob.Math.sin; function f(d) { d=d|0; d = sin(d) } return f');
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var pow=glob.Math.pow; function f(d) { d=+d; d = pow(d,d); return +d } return f'), this)(3), 27);
 assertAsmTypeFail('glob', USE_ASM + FROUND + 'var pow=glob.Math.pow; function f(d) { d=fround(d); d = pow(d, d) } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var pow=glob.Math.pow; function f(d) { d=d|0; d = pow(d, d) } return f');
@@ -148,19 +154,23 @@ assertAsmTypeFail('glob', USE_ASM + 'var
 assertAsmTypeFail('glob', USE_ASM + 'var pow=glob.Math.pow; function f(d) { d=+d; var i=0; i = pow(d,d)|0; } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var atan2=glob.Math.atan2; function f(d) { d=+d; var i=0; i = atan2(d,d)|0; } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var sqrt=glob.Math.sqrt; function f(d) { d=+d; sqrt(d)|0; } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var abs=glob.Math.abs; function f(d) { d=+d; abs(d)|0; } return f');
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var im=glob.Math.imul; function f(i) { i=i|0; var d=0.0; d = +im(i,i); return +d } return f'), this)(42), Math.imul(42, 42));
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(i) { i=i|0; var d=0.0; d = +abs(i|0); return +d } return f'), this)(-42), 42);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(i) { i=i|0; var d=0.0; d = +min(i, 0); return +d } return f'), this)(-42), -42);
+assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(i) { i=i|0; var d=fround(0); d = fround(min(i, 0)); return +d } return f'), this)(-42), -42);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(i) { i=i|0; var d=0.0; d = +max(i, 0); return +d } return f'), this)(-42), 0);
+assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(i) { i=i|0; var d=fround(0); d = fround(max(i, 0)); return +d } return f'), this)(-42), 0);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var min=glob.Math.min; function f(d) { d=+d; var i=0; i = ~~min(d, 0.)|0; return i|0 } return f'), this)(-42), -42);
+assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var min=glob.Math.min; function f(d) { d=fround(d); var i=0; i = ~~min(d, fround(0))|0; return i|0 } return f'), this)(-42), -42);
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var max=glob.Math.max; function f(d) { d=+d; var i=0; i = ~~max(d, 0.)|0; return i|0 } return f'), this)(-42), 0);
+assertEq(asmLink(asmCompile('glob', USE_ASM + FROUND + 'var max=glob.Math.max; function f(d) { d=fround(d); var i=0; i = ~~max(d, fround(0))|0; return i|0 } return f'), this)(-42), 0);
 
 assertEq(asmLink(asmCompile('glob', USE_ASM + 'var abs=glob.Math.abs; function f(i) { i=i|0; var d=0.0; return +d; +abs(i|0); return 3.0;} return f'), this)(-42), 0);
 
 assertAsmTypeFail('glob', USE_ASM + 'var tau=glob.Math.TAU; function f() {} return f');
 assertAsmTypeFail('glob', USE_ASM + 'var pi=glob.Math.PI; function f() { return pi | 0 } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var pi=glob.Math.PI; function f() { return +pi() } return f');
 assertAsmTypeFail('glob', USE_ASM + 'var pi=glob.Math.PI; function f() { pi = +3; } return f');
 assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + 'var pi=glob.Math.PI; function f() {} return f'), {});
--- a/js/src/jit-test/tests/asm.js/testSIMD.js
+++ b/js/src/jit-test/tests/asm.js/testSIMD.js
@@ -472,16 +472,45 @@ CheckF4(F32D, 'var x=f4(1,2,3,4); x=f4d(
 CheckF4(F32D, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4d(x,y)', [1/4,2/3,3/5,2]);
 CheckF4(F32D, 'var x=f4(13.37,1,1,4); var y=f4(4,0,-0.,2); x=f4d(x,y)', [Math.fround(13.37) / 4,+Infinity,-Infinity,2]);
 
 // Test NaN
 var f32x4 = SIMD.float32x4(0, 0, -0, NaN);
 var another = SIMD.float32x4(0, -0, 0, 0);
 assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + "function f(x,y) {x=f4(x); y=f4(y); x=f4d(x,y); return f4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]);
 
+// With
+const WXF = 'var w = f4.withX;';
+const WYF = 'var w = f4.withY;';
+const WZF = 'var w = f4.withZ;';
+const WWF = 'var w = f4.withW;';
+
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1);} return f");
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, 1.0);} return f");
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + "function f() {var x = f4(1,2,3,4); x = w(x, x);} return f");
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1, f32(1));} return f");
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(1., f32(1));} return f");
+assertAsmTypeFail('glob', USE_ASM + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); x = w(f32(1), f32(1));} return f");
+assertAsmTypeFail('glob', USE_ASM + I32 + F32 + WXF + FROUND + "function f() {var x = f4(1,2,3,4); var y = i4(1,2,3,4); x = w(y, f32(1));} return f");
+
+CheckF4(WXF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [Math.fround(13.37), 2, 3, 4]);
+CheckF4(WYF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, Math.fround(13.37), 3, 4]);
+CheckF4(WZF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, Math.fround(13.37), 4]);
+CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37));', [1, 2, 3, Math.fround(13.37)]);
+CheckF4(WWF + FROUND, 'var x = f4(1,2,3,4); x = w(x, f32(13.37) + f32(6.63));', [1, 2, 3, Math.fround(Math.fround(13.37) + Math.fround(6.63))]);
+
+const WXI = 'var w = i4.withX;';
+const WYI = 'var w = i4.withY;';
+const WZI = 'var w = i4.withZ;';
+const WWI = 'var w = i4.withW;';
+CheckI4(WXI, 'var x = i4(1,2,3,4); x = w(x, 42);', [42, 2, 3, 4]);
+CheckI4(WYI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 42, 3, 4]);
+CheckI4(WZI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 42, 4]);
+CheckI4(WWI, 'var x = i4(1,2,3,4); x = w(x, 42);', [1, 2, 3, 42]);
+
 // Comparisons
 // True yields all bits set to 1 (i.e as an int32, 0xFFFFFFFF === -1), false
 // yields all bits set to 0 (i.e 0).
 const T = -1;
 const F = 0;
 assertAsmTypeFail('glob', USE_ASM + I32 + "var lt=i4.lessThanOrEqual; function f() {} return f");
 assertAsmTypeFail('glob', USE_ASM + I32 + "var ge=i4.greaterThanOrEqual; function f() {} return f");
 assertAsmTypeFail('glob', USE_ASM + I32 + "var ne=i4.notEqual; function f() {} return f");
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -456,16 +456,24 @@ function rpowhalf_object(i) {
 var uceFault_min_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_min_number'));
 function rmin_number(i) {
     var x = Math.min(i, i-1, i-2.1);
     if (uceFault_min_number(i) || uceFault_min_number(i))
         assertEq(x, i-2.1);
     return i;
 }
 
+var uceFault_min_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_min_float'));
+function rmin_float(i) {
+    var x = Math.fround(Math.min(Math.fround(20), Math.fround(13.37)));
+    if (uceFault_min_number(i) || uceFault_min_number(i))
+        assertEq(x, Math.fround(13.37));
+    return i;
+}
+
 var uceFault_min_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_min_object'));
 function rmin_object(i) {
     var t = i;
     var o = { valueOf: function () { return t; } };
     var x = Math.min(o, o-1, o-2.1)
     t = 1000;
     if (uceFault_min_object(i) || uceFault_min_object(i))
         assertEq(x, i-2.1);
@@ -475,16 +483,24 @@ function rmin_object(i) {
 var uceFault_max_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_max_number'));
 function rmax_number(i) {
     var x = Math.max(i, i-1, i-2.1);
     if (uceFault_max_number(i) || uceFault_max_number(i))
         assertEq(x, i);
     return i;
 }
 
+var uceFault_max_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_max_float'));
+function rmax_float(i) {
+    var x = Math.fround(Math.max(Math.fround(2), Math.fround(13.37)));
+    if (uceFault_max_number(i) || uceFault_max_number(i))
+        assertEq(x, Math.fround(13.37));
+    return i;
+}
+
 var uceFault_max_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_max_object'));
 function rmax_object(i) {
     var t = i;
     var o = { valueOf: function () { return t; } };
     var x = Math.max(o, o-1, o-2.1)
     t = 1000;
     if (uceFault_max_object(i) || uceFault_max_object(i))
         assertEq(x, i);
@@ -970,18 +986,20 @@ for (i = 0; i < 100; i++) {
     rcharCodeAt(i);
     rfrom_char_code(i);
     rfrom_char_code_non_ascii(i);
     rpow_number(i);
     rpow_object(i);
     rpowhalf_number(i);
     rpowhalf_object(i);
     rmin_number(i);
+    rmin_float(i);
     rmin_object(i);
     rmax_number(i);
+    rmax_float(i);
     rmax_object(i);
     rabs_number(i);
     rabs_object(i);
     rsqrt_number(i);
     rsqrt_float(i);
     rsqrt_object(i);
     ratan2_number(i);
     ratan2_object(i);
--- a/js/src/jit-test/tests/ion/testFloat32-correctness.js
+++ b/js/src/jit-test/tests/ion/testFloat32-correctness.js
@@ -84,16 +84,48 @@ function sqrt() {
     for(var i = 0; i < 7; ++i) {
         var sqrtf = Math.fround(Math.sqrt(f32[i]));
         var sqrtd = 1 + Math.sqrt(f32[i]) - 1; // force no float32 by chaining arith ops
         assertEq( sqrtf, Math.fround(sqrtd) );
     }
 }
 test(setupSqrt, sqrt);
 
+// MMinMax
+function setupMinMax() {
+    f32[0] = -0;
+    f32[1] = 0;
+    f32[2] = 1;
+    f32[3] = 4;
+    f32[4] = -1;
+    f32[5] = Infinity;
+    f32[6] = NaN;
+    f32[7] = 13.37;
+    f32[8] = -Infinity;
+    f32[9] = Math.pow(2,31) - 1;
+}
+function minMax() {
+    for(var i = 0; i < 9; ++i) {
+        for(var j = 0; j < 9; j++) {
+            var minf = Math.fround(Math.min(f32[i], f32[j]));
+            var mind = 1 / (1 / Math.min(f32[i], f32[j])); // force no float32 by chaining arith ops
+            assertFloat32(minf, true);
+            assertFloat32(mind, false);
+            assertEq( minf, Math.fround(mind) );
+
+            var maxf = Math.fround(Math.max(f32[i], f32[j]));
+            var maxd = 1 / (1 / Math.max(f32[i], f32[j])); // force no float32 by chaining arith ops
+            assertFloat32(maxf, true);
+            assertFloat32(maxd, false);
+            assertEq( maxf, Math.fround(maxd) );
+        }
+    }
+}
+test(setupMinMax, minMax);
+
 // MTruncateToInt32
 // The only way to get a MTruncateToInt32 with a Float32 input is to use Math.imul
 function setupTruncateToInt32() {
     f32[0] = -1;
     f32[1] = 4;
     f32[2] = 5.13;
 }
 function truncateToInt32() {
--- a/js/src/jit-test/tests/ion/testFloat32.js
+++ b/js/src/jit-test/tests/ion/testFloat32.js
@@ -56,18 +56,18 @@
 setJitCompilerOption("ion.warmup.trigger", 50);
 
 function test(f) {
     f32[0] = .5;
     for(var n = 110; n; n--)
         f();
 }
 
-var f32 = new Float32Array(2);
-var f64 = new Float64Array(2);
+var f32 = new Float32Array(4);
+var f64 = new Float64Array(4);
 
 function acceptAdd() {
     var use = f32[0] + 1;
     assertFloat32(use, true);
     f32[0] = use;
 }
 test(acceptAdd);
 
@@ -146,16 +146,89 @@ test(acceptSqrt);
 
 function refuseSqrt() {
     var res = Math.sqrt(f32[0]);
     assertFloat32(res, false);
     f32[0] = res + 1;
 }
 test(refuseSqrt);
 
+function acceptMin() {
+    var res = Math.min(f32[0], f32[1]);
+    assertFloat32(res, true);
+    f64[0] = res;
+}
+test(acceptMin);
+
+// In theory, we could do it, as Math.min/max actually behave as a Phi (it's a
+// float32 producer iff its inputs are producers, it's a consumer iff its uses
+// are consumers). In practice, this would involve some overhead for big chains
+// of min/max.
+function refuseMinAdd() {
+    var res = Math.min(f32[0], f32[1]) + f32[2];
+    assertFloat32(res, false);
+    f32[3] = res;
+}
+test(refuseMinAdd);
+
+function acceptSeveralMinMax() {
+    var x = Math.min(f32[0], f32[1]);
+    var y = Math.max(f32[2], f32[3]);
+    var res = Math.min(x, y);
+    assertFloat32(res, true);
+    f64[0] = res;
+}
+test(acceptSeveralMinMax);
+
+function acceptSeveralMinMax2() {
+    var res = Math.min(f32[0], f32[1], f32[2], f32[3]);
+    assertFloat32(res, true);
+    f64[0] = res;
+}
+test(acceptSeveralMinMax2);
+
+function partialMinMax() {
+    var x = Math.min(f32[0], f32[1]);
+    var y = Math.min(f64[0], f32[1]);
+    var res  = Math.min(x, y);
+    assertFloat32(x, true);
+    assertFloat32(y, false);
+    assertFloat32(res, false);
+    f64[0] = res;
+}
+test(partialMinMax);
+
+function refuseSeveralMinMax() {
+    var res = Math.min(f32[0], f32[1] + f32[2], f32[2], f32[3]);
+    assertFloat32(res, false);
+    f64[0] = res;
+}
+test(refuseSeveralMinMax);
+
+function refuseMin() {
+    var res = Math.min(f32[0], 42.13 + f32[1]);
+    assertFloat32(res, false);
+    f64[0] = res;
+}
+test(refuseMin);
+
+function acceptMax() {
+    var res = Math.max(f32[0], f32[1]);
+    assertFloat32(res, true);
+    f64[0] = res;
+}
+test(acceptMax);
+
+function refuseMax() {
+    var res = Math.max(f32[0], 42.13 + f32[1]);
+    assertFloat32(res, false);
+    f64[0] = res;
+}
+test(refuseMax);
+
 function acceptAbs() {
     var res = Math.abs(f32[0]);
     assertFloat32(res, true);
     f32[0] = res;
 }
 test(acceptAbs);
 
 function refuseAbs() {
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -1,118 +1,148 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jit/JitOptions.h"
+#include "mozilla/TypeTraits.h"
 
+#include <cstdlib>
 #include "jsfun.h"
-
 using namespace js;
 using namespace js::jit;
 
 namespace js {
 namespace jit {
 
 JitOptions js_JitOptions;
 
+template<typename T> struct IsBool : mozilla::FalseType {};
+template<> struct IsBool<bool> : mozilla::TrueType {};
+
+template<typename T>
+T overrideDefault(const char *param, T dflt) {
+    char *str = getenv(param);
+    if (!str)
+        return dflt;
+    if (IsBool<T>::value) {
+        if (strcmp(str, "true") == 0 ||
+            strcmp(str, "yes")) {
+            return true;
+        }
+        if (strcmp(str, "false") == 0 ||
+            strcmp(str, "no")) {
+            return false;
+        }
+        fprintf(stderr, "Warning: I didn't understand %s=\"%s\"", param, str);
+    } else {
+        char *endp;
+        int retval = strtol(str, &endp, 0);
+        if (*endp == '\0')
+            return retval;
+
+        fprintf(stderr, "Warning: I didn't understand %s=\"%s\"", param, str);
+    }
+    return dflt;
+}
+#define SET_DEFAULT(var, dflt) var = overrideDefault("JIT_OPTION_" #var, dflt)
 JitOptions::JitOptions()
 {
     // Whether to perform expensive graph-consistency DEBUG-only assertions.
     // It can be useful to disable this to reduce DEBUG-compile time of large
     // asm.js programs.
-    checkGraphConsistency = true;
+    SET_DEFAULT(checkGraphConsistency, true);
 
 #ifdef CHECK_OSIPOINT_REGISTERS
     // Emit extra code to verify live regs at the start of a VM call
     // are not modified before its OsiPoint.
-    checkOsiPointRegisters = false;
+    SET_DEFAULT(checkOsiPointRegisters, false);
 #endif
 
     // Whether to enable extra code to perform dynamic validation of
     // RangeAnalysis results.
-    checkRangeAnalysis = false;
+    SET_DEFAULT(checkRangeAnalysis, false);
 
     // Whether Ion should compile try-catch statements.
-    compileTryCatch = true;
+    SET_DEFAULT(compileTryCatch, true);
 
     // Toggle whether eager scalar replacement is globally disabled.
-    disableScalarReplacement = true; // experimental
+    SET_DEFAULT(disableScalarReplacement, true); // experimental
 
     // Toggle whether global value numbering is globally disabled.
-    disableGvn = false;
+    SET_DEFAULT(disableGvn, false);
 
     // Toggles whether loop invariant code motion is globally disabled.
-    disableLicm = false;
+    SET_DEFAULT(disableLicm, false);
 
     // Toggles whether inlining is globally disabled.
-    disableInlining = false;
+    SET_DEFAULT(disableInlining, false);
 
     // Toggles whether Edge Case Analysis is gobally disabled.
-    disableEdgeCaseAnalysis = false;
+    SET_DEFAULT(disableEdgeCaseAnalysis, false);
 
     // Toggles whether Range Analysis is globally disabled.
-    disableRangeAnalysis = false;
+    SET_DEFAULT(disableRangeAnalysis, false);
 
     // Toggles whether Loop Unrolling is globally disabled.
-    disableLoopUnrolling = true;
+    SET_DEFAULT(disableLoopUnrolling, true);
 
     // Toggles whether Effective Address Analysis is globally disabled.
-    disableEaa = false;
+    SET_DEFAULT(disableEaa, false);
 
     // Whether functions are compiled immediately.
-    eagerCompilation = false;
+    SET_DEFAULT(eagerCompilation, false);
 
     // Force how many invocation or loop iterations are needed before compiling
     // a function with the highest ionmonkey optimization level.
     // (i.e. OptimizationLevel_Normal)
-    forceDefaultIonWarmUpThreshold = false;
-    forcedDefaultIonWarmUpThreshold = 1000;
+    SET_DEFAULT(forceDefaultIonWarmUpThreshold, false);
+    SET_DEFAULT(forcedDefaultIonWarmUpThreshold, 1000);
 
     // Force the used register allocator instead of letting the
     // optimization pass decide.
     forceRegisterAllocator = false;
     forcedRegisterAllocator = RegisterAllocator_LSRA;
 
     // Toggles whether large scripts are rejected.
-    limitScriptSize = true;
+    SET_DEFAULT(limitScriptSize, true);
 
     // Toggles whether functions may be entered at loop headers.
-    osr = true;
+    SET_DEFAULT(osr, true);
 
     // How many invocations or loop iterations are needed before functions
     // are compiled with the baseline compiler.
-    baselineWarmUpThreshold = 10;
+    SET_DEFAULT(baselineWarmUpThreshold, 10);
 
     // Number of exception bailouts (resuming into catch/finally block) before
     // we invalidate and forbid Ion compilation.
-    exceptionBailoutThreshold = 10;
+    SET_DEFAULT(exceptionBailoutThreshold, 10);
 
     // Number of bailouts without invalidation before we set
     // JSScript::hadFrequentBailouts and invalidate.
-    frequentBailoutThreshold = 10;
+    SET_DEFAULT(frequentBailoutThreshold, 10);
 
     // How many actual arguments are accepted on the C stack.
-    maxStackArgs = 4096;
+    SET_DEFAULT(maxStackArgs, 4096);
 
     // How many times we will try to enter a script via OSR before
     // invalidating the script.
-    osrPcMismatchesBeforeRecompile = 6000;
+    SET_DEFAULT(osrPcMismatchesBeforeRecompile, 6000);
 
     // The bytecode length limit for small function.
     //
     // The default for this was arrived at empirically via benchmarking.
     // We may want to tune it further after other optimizations have gone
     // in.
-    smallFunctionMaxBytecodeLength_ = 100;
+    SET_DEFAULT(smallFunctionMaxBytecodeLength_, 100);
 
     // How many uses of a parallel kernel before we attempt compilation.
-    compilerWarmUpThresholdPar = 1;
+    SET_DEFAULT(compilerWarmUpThresholdPar, 1);
 }
 
 bool
 JitOptions::isSmallFunction(JSScript *script) const
 {
     return script->length() <= smallFunctionMaxBytecodeLength_;
 }
 
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -138,52 +138,90 @@ class LSimdSplatX4 : public LInstruction
         setOperand(0, v);
     }
 
     MSimdSplatX4 *mir() const {
         return mir_->toSimdSplatX4();
     }
 };
 
-// Extracts an element from a given SIMD int32x4 lane.
-class LSimdExtractElementI : public LInstructionHelper<1, 1, 0>
-{
-    SimdLane lane_;
-
-  public:
-    LIR_HEADER(SimdExtractElementI);
-
-    LSimdExtractElementI(const LAllocation &base, SimdLane lane) : lane_(lane) {
+class LSimdExtractElementBase : public LInstructionHelper<1, 1, 0>
+{
+  protected:
+    LSimdExtractElementBase(const LAllocation &base) {
         setOperand(0, base);
     }
+
+  public:
     const LAllocation *getBase() {
         return getOperand(0);
     }
     SimdLane lane() const {
-        return lane_;
-    }
-};
-
+        return mir_->toSimdExtractElement()->lane();
+    }
+};
+
+// Extracts an element from a given SIMD int32x4 lane.
+class LSimdExtractElementI : public LSimdExtractElementBase
+{
+  public:
+    LIR_HEADER(SimdExtractElementI);
+    LSimdExtractElementI(const LAllocation &base)
+      : LSimdExtractElementBase(base)
+    {}
+};
 // Extracts an element from a given SIMD float32x4 lane.
-class LSimdExtractElementF : public LInstructionHelper<1, 1, 0>
-{
-    SimdLane lane_;
-
+class LSimdExtractElementF : public LSimdExtractElementBase
+{
   public:
     LIR_HEADER(SimdExtractElementF);
-
-    LSimdExtractElementF(const LAllocation &base, SimdLane lane) : lane_(lane) {
-        setOperand(0, base);
-    }
-    const LAllocation *getBase() {
+    LSimdExtractElementF(const LAllocation &base)
+      : LSimdExtractElementBase(base)
+    {}
+};
+
+class LSimdInsertElementBase : public LInstructionHelper<1, 2, 0>
+{
+  protected:
+    LSimdInsertElementBase(const LAllocation &vec, const LAllocation &val)
+    {
+        setOperand(0, vec);
+        setOperand(1, val);
+    }
+
+  public:
+    const LAllocation *vector() {
         return getOperand(0);
     }
-    SimdLane lane() const {
-        return lane_;
-    }
+    const LAllocation *value() {
+        return getOperand(1);
+    }
+    const SimdLane lane() const {
+        return mir_->toSimdInsertElement()->lane();
+    }
+};
+
+// Replace an element from a given SIMD int32x4 lane with a given value.
+class LSimdInsertElementI : public LSimdInsertElementBase
+{
+  public:
+    LIR_HEADER(SimdInsertElementI);
+    LSimdInsertElementI(const LAllocation &vec, const LAllocation &val)
+      : LSimdInsertElementBase(vec, val)
+    {}
+};
+
+// Replace an element from a given SIMD float32x4 lane with a given value.
+class LSimdInsertElementF : public LSimdInsertElementBase
+{
+  public:
+    LIR_HEADER(SimdInsertElementF);
+    LSimdInsertElementF(const LAllocation &vec, const LAllocation &val)
+      : LSimdInsertElementBase(vec, val)
+    {}
 };
 
 class LSimdSignMaskX4 : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(SimdSignMaskX4);
 
     explicit LSimdSignMaskX4(const LAllocation &input) {
@@ -2651,26 +2689,26 @@ class LReturn : public LInstructionHelpe
 class LThrow : public LCallInstructionHelper<0, BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(Throw)
 
     static const size_t Value = 0;
 };
 
-class LMinMaxI : public LInstructionHelper<1, 2, 0>
-{
-  public:
-    LIR_HEADER(MinMaxI)
-    LMinMaxI(const LAllocation &first, const LAllocation &second)
+class LMinMaxBase : public LInstructionHelper<1, 2, 0>
+{
+  protected:
+    LMinMaxBase(const LAllocation &first, const LAllocation &second)
     {
         setOperand(0, first);
         setOperand(1, second);
     }
 
+  public:
     const LAllocation *first() {
         return this->getOperand(0);
     }
     const LAllocation *second() {
         return this->getOperand(1);
     }
     const LDefinition *output() {
         return this->getDef(0);
@@ -2678,41 +2716,38 @@ class LMinMaxI : public LInstructionHelp
     MMinMax *mir() const {
         return mir_->toMinMax();
     }
     const char *extraName() const {
         return mir()->isMax() ? "Max" : "Min";
     }
 };
 
-class LMinMaxD : public LInstructionHelper<1, 2, 0>
+class LMinMaxI : public LMinMaxBase
+{
+  public:
+    LIR_HEADER(MinMaxI)
+    LMinMaxI(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
+    {}
+};
+
+class LMinMaxD : public LMinMaxBase
 {
   public:
     LIR_HEADER(MinMaxD)
-    LMinMaxD(const LAllocation &first, const LAllocation &second)
-    {
-        setOperand(0, first);
-        setOperand(1, second);
-    }
-
-    const LAllocation *first() {
-        return this->getOperand(0);
-    }
-    const LAllocation *second() {
-        return this->getOperand(1);
-    }
-    const LDefinition *output() {
-        return this->getDef(0);
-    }
-    MMinMax *mir() const {
-        return mir_->toMinMax();
-    }
-    const char *extraName() const {
-        return mir()->isMax() ? "Max" : "Min";
-    }
+    LMinMaxD(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
+    {}
+};
+
+class LMinMaxF : public LMinMaxBase
+{
+  public:
+    LIR_HEADER(MinMaxF)
+    LMinMaxF(const LAllocation &first, const LAllocation &second) : LMinMaxBase(first, second)
+    {}
 };
 
 // Negative of an integer
 class LNegI : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(NegI);
     explicit LNegI(const LAllocation &num) {
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -16,16 +16,18 @@
     _(Pointer)                      \
     _(Double)                       \
     _(Float32)                      \
     _(SimdSplatX4)                  \
     _(Int32x4)                      \
     _(Float32x4)                    \
     _(SimdExtractElementI)          \
     _(SimdExtractElementF)          \
+    _(SimdInsertElementI)           \
+    _(SimdInsertElementF)           \
     _(SimdSignMaskX4)               \
     _(SimdBinaryCompIx4)            \
     _(SimdBinaryCompFx4)            \
     _(SimdBinaryArithIx4)           \
     _(SimdBinaryArithFx4)           \
     _(SimdBinaryBitwiseX4)          \
     _(SimdSelect)                   \
     _(Value)                        \
@@ -110,16 +112,17 @@
     _(CompareVM)                    \
     _(BitAndAndBranch)              \
     _(IsNullOrLikeUndefined)        \
     _(IsNullOrLikeUndefinedAndBranch)\
     _(EmulatesUndefined)            \
     _(EmulatesUndefinedAndBranch)   \
     _(MinMaxI)                      \
     _(MinMaxD)                      \
+    _(MinMaxF)                      \
     _(NegI)                         \
     _(NegD)                         \
     _(NegF)                         \
     _(AbsI)                         \
     _(AbsD)                         \
     _(AbsF)                         \
     _(ClzI)                         \
     _(SqrtD)                        \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1283,16 +1283,22 @@ LIRGenerator::visitMinMax(MMinMax *ins)
 
     ReorderCommutative(&first, &second, ins);
 
     if (ins->specialization() == MIRType_Int32) {
         LMinMaxI *lir = new(alloc()) LMinMaxI(useRegisterAtStart(first), useRegisterOrConstant(second));
         return defineReuseInput(lir, ins, 0);
     }
 
+    if (ins->specialization() == MIRType_Float32) {
+        LMinMaxF *lir = new(alloc()) LMinMaxF(useRegisterAtStart(first), useRegister(second));
+        return defineReuseInput(lir, ins, 0);
+    }
+
+    MOZ_ASSERT(ins->specialization() == MIRType_Double);
     LMinMaxD *lir = new(alloc()) LMinMaxD(useRegisterAtStart(first), useRegister(second));
     return defineReuseInput(lir, ins, 0);
 }
 
 bool
 LIRGenerator::visitAbs(MAbs *ins)
 {
     MDefinition *num = ins->input();
@@ -3716,28 +3722,43 @@ LIRGenerator::visitSimdExtractElement(MS
 {
     JS_ASSERT(IsSimdType(ins->input()->type()));
     JS_ASSERT(!IsSimdType(ins->type()));
 
     if (ins->input()->type() == MIRType_Int32x4) {
         // Note: there could be int16x8 in the future, which doesn't use the
         // same instruction. We either need to pass the arity or create new LIns.
         LUse use = useRegisterAtStart(ins->input());
-        return define(new(alloc()) LSimdExtractElementI(use, ins->lane()), ins);
+        return define(new(alloc()) LSimdExtractElementI(use), ins);
     }
 
     if (ins->input()->type() == MIRType_Float32x4) {
         LUse use = useRegisterAtStart(ins->input());
-        return define(new(alloc()) LSimdExtractElementF(use, ins->lane()), ins);
+        return define(new(alloc()) LSimdExtractElementF(use), ins);
     }
 
     MOZ_CRASH("Unknown SIMD kind when extracting element");
 }
 
 bool
+LIRGenerator::visitSimdInsertElement(MSimdInsertElement *ins)
+{
+    JS_ASSERT(IsSimdType(ins->type()));
+
+    LUse vec = useRegisterAtStart(ins->vector());
+    LUse val = useRegister(ins->value());
+    if (ins->type() == MIRType_Int32x4)
+        return defineReuseInput(new(alloc()) LSimdInsertElementI(vec, val), ins, 0);
+    if (ins->type() == MIRType_Float32x4)
+        return defineReuseInput(new(alloc()) LSimdInsertElementF(vec, val), ins, 0);
+
+    MOZ_CRASH("Unknown SIMD kind when generating constant");
+}
+
+bool
 LIRGenerator::visitSimdSignMask(MSimdSignMask *ins)
 {
     MDefinition *input = ins->input();
     MOZ_ASSERT(IsSimdType(input->type()));
     MOZ_ASSERT(ins->type() == MIRType_Int32);
 
     LUse use = useRegisterAtStart(input);
 
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -264,16 +264,17 @@ class LIRGenerator : public LIRGenerator
     bool visitAsmJSVoidReturn(MAsmJSVoidReturn *ins);
     bool visitAsmJSPassStackArg(MAsmJSPassStackArg *ins);
     bool visitAsmJSCall(MAsmJSCall *ins);
     bool visitSetDOMProperty(MSetDOMProperty *ins);
     bool visitGetDOMProperty(MGetDOMProperty *ins);
     bool visitGetDOMMember(MGetDOMMember *ins);
     bool visitRecompileCheck(MRecompileCheck *ins);
     bool visitSimdExtractElement(MSimdExtractElement *ins);
+    bool visitSimdInsertElement(MSimdInsertElement *ins);
     bool visitSimdSignMask(MSimdSignMask *ins);
     bool visitSimdBinaryComp(MSimdBinaryComp *ins);
     bool visitSimdBinaryArith(MSimdBinaryArith *ins);
     bool visitSimdBinaryBitwise(MSimdBinaryBitwise *ins);
     bool visitSimdConstant(MSimdConstant *ins);
     bool visitPhi(MPhi *ins);
     bool visitBeta(MBeta *ins);
     bool visitObjectState(MObjectState *ins);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1693,18 +1693,41 @@ MBinaryArithInstruction::trySpecializeFl
 {
     // Do not use Float32 if we can use int32.
     if (specialization_ == MIRType_Int32)
         return;
 
     MDefinition *left = lhs();
     MDefinition *right = rhs();
 
-    if (!left->canProduceFloat32() || !right->canProduceFloat32()
-        || !CheckUsesAreFloat32Consumers(this))
+    if (!left->canProduceFloat32() || !right->canProduceFloat32() ||
+        !CheckUsesAreFloat32Consumers(this))
+    {
+        if (left->type() == MIRType_Float32)
+            ConvertDefinitionToDouble<0>(alloc, left, this);
+        if (right->type() == MIRType_Float32)
+            ConvertDefinitionToDouble<1>(alloc, right, this);
+        return;
+    }
+
+    specialization_ = MIRType_Float32;
+    setResultType(MIRType_Float32);
+}
+
+void
+MMinMax::trySpecializeFloat32(TempAllocator &alloc)
+{
+    if (specialization_ == MIRType_Int32)
+        return;
+
+    MDefinition *left = lhs();
+    MDefinition *right = rhs();
+
+    if (!(left->canProduceFloat32() || (left->isMinMax() && left->type() == MIRType_Float32)) ||
+        !(right->canProduceFloat32() || (right->isMinMax() && right->type() == MIRType_Float32)))
     {
         if (left->type() == MIRType_Float32)
             ConvertDefinitionToDouble<0>(alloc, left, this);
         if (right->type() == MIRType_Float32)
             ConvertDefinitionToDouble<1>(alloc, right, this);
         return;
     }
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1397,16 +1397,60 @@ class MSimdExtractElement : public MUnar
             return false;
         const MSimdExtractElement *other = ins->toSimdExtractElement();
         if (other->lane_ != lane_)
             return false;
         return congruentIfOperandsEqual(other);
     }
 };
 
+// Replaces the datum in the given lane by a scalar value of the same type.
+class MSimdInsertElement : public MBinaryInstruction
+{
+  private:
+    SimdLane lane_;
+
+    MSimdInsertElement(MDefinition *vec, MDefinition *val, MIRType type, SimdLane lane)
+      : MBinaryInstruction(vec, val), lane_(lane)
+    {
+        MOZ_ASSERT(IsSimdType(type) && vec->type() == type);
+        MOZ_ASSERT(SimdTypeToScalarType(type) == val->type());
+
+        setMovable();
+        setResultType(type);
+    }
+
+  public:
+    INSTRUCTION_HEADER(SimdInsertElement)
+
+    static MSimdInsertElement *NewAsmJS(TempAllocator &alloc, MDefinition *vec, MDefinition *val,
+                                         MIRType type, SimdLane lane)
+    {
+        return new(alloc) MSimdInsertElement(vec, val, type, lane);
+    }
+
+    MDefinition *vector() {
+        return getOperand(0);
+    }
+    MDefinition *value() {
+        return getOperand(1);
+    }
+    SimdLane lane() const {
+        return lane_;
+    }
+
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+
+    bool congruentTo(const MDefinition *ins) const {
+        return binaryCongruentTo(ins) && lane_ == ins->toSimdInsertElement()->lane();
+    }
+};
+
 // Extracts the sign bits from a given vector, returning an MIRType_Int32.
 class MSimdSignMask : public MUnaryInstruction
 {
   protected:
     explicit MSimdSignMask(MDefinition *obj)
       : MUnaryInstruction(obj)
     {
         MOZ_ASSERT(IsSimdType(obj->type()));
@@ -4534,17 +4578,17 @@ class MMinMax
     public ArithPolicy::Data
 {
     bool isMax_;
 
     MMinMax(MDefinition *left, MDefinition *right, MIRType type, bool isMax)
       : MBinaryInstruction(left, right),
         isMax_(isMax)
     {
-        JS_ASSERT(type == MIRType_Double || type == MIRType_Int32);
+        JS_ASSERT(IsNumberType(type));
         setResultType(type);
         setMovable();
         specialization_ = type;
     }
 
   public:
     INSTRUCTION_HEADER(MinMax)
     static MMinMax *New(TempAllocator &alloc, MDefinition *left, MDefinition *right, MIRType type,
@@ -4569,16 +4613,19 @@ class MMinMax
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
     bool writeRecoverData(CompactBufferWriter &writer) const;
     bool canRecoverOnBailout() const {
         return true;
     }
 
+    bool isFloat32Commutative() const { return true; }
+    void trySpecializeFloat32(TempAllocator &alloc);
+
     ALLOW_CLONE(MMinMax)
 };
 
 class MAbs
   : public MUnaryInstruction,
     public ArithPolicy::Data
 {
     bool implicitTruncate_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -11,16 +11,17 @@ namespace js {
 namespace jit {
 
 #define MIR_OPCODE_LIST(_)                                                  \
     _(Constant)                                                             \
     _(SimdValueX4)                                                          \
     _(SimdSplatX4)                                                          \
     _(SimdConstant)                                                         \
     _(SimdExtractElement)                                                   \
+    _(SimdInsertElement)                                                    \
     _(SimdSignMask)                                                         \
     _(SimdBinaryComp)                                                       \
     _(SimdBinaryArith)                                                      \
     _(SimdBinaryBitwise)                                                    \
     _(SimdTernaryBitwise)                                                   \
     _(CloneLiteral)                                                         \
     _(Parameter)                                                            \
     _(Callee)                                                               \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -110,16 +110,17 @@ class ParallelSafetyVisitor : public MDe
     // I am taking the policy of blacklisting everything that's not
     // obviously safe for now.  We can loosen as we need.
 
     SAFE_OP(Constant)
     SAFE_OP(SimdValueX4)
     SAFE_OP(SimdSplatX4)
     SAFE_OP(SimdConstant)
     SAFE_OP(SimdExtractElement)
+    SAFE_OP(SimdInsertElement)
     SAFE_OP(SimdSignMask)
     SAFE_OP(SimdBinaryComp)
     SAFE_OP(SimdBinaryArith)
     SAFE_OP(SimdBinaryBitwise)
     SAFE_OP(SimdTernaryBitwise)
     UNSAFE_OP(CloneLiteral)
     SAFE_OP(Parameter)
     SAFE_OP(Callee)
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -286,16 +286,65 @@ CodeGeneratorARM::visitMinMaxD(LMinMaxD 
     masm.bind(&returnSecond);
     masm.ma_vmov(second, output);
 
     masm.bind(&done);
     return true;
 }
 
 bool
+CodeGeneratorARM::visitMinMaxF(LMinMaxF *ins)
+{
+    FloatRegister first = ToFloatRegister(ins->first());
+    FloatRegister second = ToFloatRegister(ins->second());
+    FloatRegister output = ToFloatRegister(ins->output());
+
+    JS_ASSERT(first == output);
+
+    Assembler::Condition cond = ins->mir()->isMax()
+        ? Assembler::VFP_LessThanOrEqual
+        : Assembler::VFP_GreaterThanOrEqual;
+    Label nan, equal, returnSecond, done;
+
+    masm.compareFloat(first, second);
+    // First or second is NaN, result is NaN.
+    masm.ma_b(&nan, Assembler::VFP_Unordered);
+    // Make sure we handle -0 and 0 right.
+    masm.ma_b(&equal, Assembler::VFP_Equal);
+    masm.ma_b(&returnSecond, cond);
+    masm.ma_b(&done);
+
+    // Check for zero.
+    masm.bind(&equal);
+    masm.compareFloat(first, NoVFPRegister);
+    // First wasn't 0 or -0, so just return it.
+    masm.ma_b(&done, Assembler::VFP_NotEqualOrUnordered);
+    // So now both operands are either -0 or 0.
+    if (ins->mir()->isMax()) {
+        // -0 + -0 = -0 and -0 + 0 = 0.
+        masm.ma_vadd_f32(second, first, first);
+    } else {
+        masm.ma_vneg_f32(first, first);
+        masm.ma_vsub_f32(first, second, first);
+        masm.ma_vneg_f32(first, first);
+    }
+    masm.ma_b(&done);
+
+    masm.bind(&nan);
+    masm.loadConstantFloat32(GenericNaN(), output);
+    masm.ma_b(&done);
+
+    masm.bind(&returnSecond);
+    masm.ma_vmov_f32(second, output);
+
+    masm.bind(&done);
+    return true;
+}
+
+bool
 CodeGeneratorARM::visitAbsD(LAbsD *ins)
 {
     FloatRegister input = ToFloatRegister(ins->input());
     JS_ASSERT(input == ToFloatRegister(ins->output()));
     masm.ma_vabs(input, input);
     return true;
 }
 
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -97,16 +97,17 @@ class CodeGeneratorARM : public CodeGene
         emitBranch(cond, ifTrue, ifFalse);
     }
 
     bool emitTableSwitchDispatch(MTableSwitch *mir, Register index, Register base);
 
   public:
     // Instruction visitors.
     virtual bool visitMinMaxD(LMinMaxD *ins);
+    virtual bool visitMinMaxF(LMinMaxF *ins);
     virtual bool visitAbsD(LAbsD *ins);
     virtual bool visitAbsF(LAbsF *ins);
     virtual bool visitSqrtD(LSqrtD *ins);
     virtual bool visitSqrtF(LSqrtF *ins);
     virtual bool visitAddI(LAddI *ins);
     virtual bool visitSubI(LSubI *ins);
     virtual bool visitBitNotI(LBitNotI *ins);
     virtual bool visitBitOpI(LBitOpI *ins);
--- a/js/src/jit/shared/Assembler-x86-shared.h
+++ b/js/src/jit/shared/Assembler-x86-shared.h
@@ -520,43 +520,50 @@ class AssemblerX86Shared : public Assemb
           case Operand::MEM_SCALE:
             masm.movups_rm(src.code(), dest.disp(), dest.base(), dest.index(), dest.scale());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
 
-    // movsd and movss are only provided in load/store form since the
+    // movsd is only provided in load/store form since the
     // register-to-register form has different semantics (it doesn't clobber
     // the whole output register) and isn't needed currently.
     void movsd(const Address &src, FloatRegister dest) {
         masm.movsd_mr(src.offset, src.base.code(), dest.code());
     }
     void movsd(const BaseIndex &src, FloatRegister dest) {
         masm.movsd_mr(src.offset, src.base.code(), src.index.code(), src.scale, dest.code());
     }
     void movsd(FloatRegister src, const Address &dest) {
         masm.movsd_rm(src.code(), dest.offset, dest.base.code());
     }
     void movsd(FloatRegister src, const BaseIndex &dest) {
         masm.movsd_rm(src.code(), dest.offset, dest.base.code(), dest.index.code(), dest.scale);
     }
+    // Although movss is not only provided in load/store form (for the same
+    // reasons as movsd above), the register to register form should be only
+    // used in contexts where we care about not clearing the higher lanes of
+    // the FloatRegister.
     void movss(const Address &src, FloatRegister dest) {
         masm.movss_mr(src.offset, src.base.code(), dest.code());
     }
     void movss(const BaseIndex &src, FloatRegister dest) {
         masm.movss_mr(src.offset, src.base.code(), src.index.code(), src.scale, dest.code());
     }
     void movss(FloatRegister src, const Address &dest) {
         masm.movss_rm(src.code(), dest.offset, dest.base.code());
     }
     void movss(FloatRegister src, const BaseIndex &dest) {
         masm.movss_rm(src.code(), dest.offset, dest.base.code(), dest.index.code(), dest.scale);
     }
+    void movss(FloatRegister src, const FloatRegister &dest) {
+        masm.movss_rr(src.code(), dest.code());
+    }
     void movdqu(const Operand &src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::MEM_REG_DISP:
             masm.movdqu_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::MEM_SCALE:
             masm.movdqu_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
@@ -1407,21 +1414,21 @@ class AssemblerX86Shared : public Assemb
         masm.divl_r(divisor.code());
     }
 
     void unpcklps(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.unpcklps_rr(src.code(), dest.code());
     }
     void pinsrd(unsigned lane, Register src, FloatRegister dest) {
-        JS_ASSERT(HasSSE2());
+        JS_ASSERT(HasSSE41());
         masm.pinsrd_irr(lane, src.code(), dest.code());
     }
     void pinsrd(unsigned lane, const Operand &src, FloatRegister dest) {
-        JS_ASSERT(HasSSE2());
+        JS_ASSERT(HasSSE41());
         switch (src.kind()) {
           case Operand::REG:
             masm.pinsrd_irr(lane, src.reg(), dest.code());
             break;
           case Operand::MEM_REG_DISP:
             masm.pinsrd_imr(lane, src.disp(), src.base(), dest.code());
             break;
           default:
@@ -1926,44 +1933,59 @@ class AssemblerX86Shared : public Assemb
     void xorps(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.xorps_rr(src.code(), dest.code());
     }
     void orpd(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.orpd_rr(src.code(), dest.code());
     }
+    void orps(FloatRegister src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
+        masm.orps_rr(src.code(), dest.code());
+    }
     void andpd(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.andpd_rr(src.code(), dest.code());
     }
     void andps(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.andps_rr(src.code(), dest.code());
     }
     void sqrtsd(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.sqrtsd_rr(src.code(), dest.code());
     }
     void sqrtss(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.sqrtss_rr(src.code(), dest.code());
     }
-    void roundsd(FloatRegister src, FloatRegister dest,
-                 X86Assembler::RoundingMode mode)
-    {
+    void roundsd(FloatRegister src, FloatRegister dest, X86Assembler::RoundingMode mode) {
         JS_ASSERT(HasSSE41());
         masm.roundsd_rr(src.code(), dest.code(), mode);
     }
-    void roundss(FloatRegister src, FloatRegister dest,
-                 X86Assembler::RoundingMode mode)
-    {
+    void roundss(FloatRegister src, FloatRegister dest, X86Assembler::RoundingMode mode) {
         JS_ASSERT(HasSSE41());
         masm.roundss_rr(src.code(), dest.code(), mode);
     }
+    unsigned insertpsMask(SimdLane sourceLane, SimdLane destLane, unsigned zeroMask = 0)
+    {
+        // Note that the sourceLane bits are ignored in the case of a source
+        // memory operand, and the source is the given 32-bits memory location.
+        MOZ_ASSERT(zeroMask < 16);
+        unsigned ret = zeroMask ;
+        ret |= unsigned(destLane) << 4;
+        ret |= unsigned(sourceLane) << 6;
+        MOZ_ASSERT(ret < 256);
+        return ret;
+    }
+    void insertps(FloatRegister src, FloatRegister dest, unsigned mask) {
+        JS_ASSERT(HasSSE41());
+        masm.insertps_irr(mask, src.code(), dest.code());
+    }
     void minsd(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.minsd_rr(src.code(), dest.code());
     }
     void minsd(const Operand &src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
@@ -1971,16 +1993,20 @@ class AssemblerX86Shared : public Assemb
             break;
           case Operand::MEM_REG_DISP:
             masm.minsd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
+    void minss(FloatRegister src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
+        masm.minss_rr(src.code(), dest.code());
+    }
     void maxsd(FloatRegister src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         masm.maxsd_rr(src.code(), dest.code());
     }
     void maxsd(const Operand &src, FloatRegister dest) {
         JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
@@ -1988,16 +2014,20 @@ class AssemblerX86Shared : public Assemb
             break;
           case Operand::MEM_REG_DISP:
             masm.maxsd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
         }
     }
+    void maxss(FloatRegister src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
+        masm.maxss_rr(src.code(), dest.code());
+    }
     void fisttp(const Operand &dest) {
         JS_ASSERT(HasSSE3());
         switch (dest.kind()) {
           case Operand::MEM_REG_DISP:
             masm.fisttp_m(dest.disp(), dest.base());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
--- a/js/src/jit/shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/shared/BaseAssembler-x86-shared.h
@@ -303,20 +303,22 @@ private:
         OP2_ADDPS_VpsWps    = 0x58,
         OP2_MULSD_VsdWsd    = 0x59,
         OP2_MULPS_VpsWps    = 0x59,
         OP2_CVTSS2SD_VsdEd  = 0x5A,
         OP2_CVTSD2SS_VsdEd  = 0x5A,
         OP2_SUBSD_VsdWsd    = 0x5C,
         OP2_SUBPS_VpsWps    = 0x5C,
         OP2_MINSD_VsdWsd    = 0x5D,
+        OP2_MINSS_VssWss    = 0x5D,
         OP2_MINPS_VpsWps    = 0x5D,
         OP2_DIVSD_VsdWsd    = 0x5E,
         OP2_DIVPS_VpsWps    = 0x5E,
         OP2_MAXSD_VsdWsd    = 0x5F,
+        OP2_MAXSS_VssWss    = 0x5F,
         OP2_MAXPS_VpsWps    = 0x5F,
         OP2_SQRTSD_VsdWsd   = 0x51,
         OP2_SQRTSS_VssWss   = 0x51,
         OP2_ANDPD_VpdWpd    = 0x54,
         OP2_ORPD_VpdWpd     = 0x56,
         OP2_XORPD_VpdWpd    = 0x57,
         OP2_PCMPGTD_VdqWdq  = 0x66,
         OP2_MOVD_VdEd       = 0x6E,
@@ -345,23 +347,25 @@ private:
         OP2_PSUBD_VdqWdq    = 0xFA,
         OP2_PADDD_VdqWdq    = 0xFE
     } TwoByteOpcodeID;
 
     typedef enum {
         OP3_ROUNDSS_VsdWsd  = 0x0A,
         OP3_ROUNDSD_VsdWsd  = 0x0B,
         OP3_PTEST_VdVd      = 0x17,
+        OP3_INSERTPS_VpsUps = 0x21,
         OP3_PINSRD_VdqEdIb  = 0x22
     } ThreeByteOpcodeID;
 
     typedef enum {
         ESCAPE_PTEST        = 0x38,
         ESCAPE_PINSRD       = 0x3A,
-        ESCAPE_ROUNDSD      = 0x3A
+        ESCAPE_ROUNDSD      = 0x3A,
+        ESCAPE_INSERTPS     = 0x3A
     } ThreeByteEscape;
 
     TwoByteOpcodeID jccRel32(Condition cond)
     {
         return (TwoByteOpcodeID)(OP2_JCC_rel32 + cond);
     }
 
     TwoByteOpcodeID setccOpcode(Condition cond)
@@ -3597,16 +3601,26 @@ public:
     {
         spew("roundss    %s, %s, %d",
              nameFPReg(src), nameFPReg(dst), (int)mode);
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.threeByteOp(OP3_ROUNDSS_VsdWsd, ESCAPE_ROUNDSD, (RegisterID)dst, (RegisterID)src);
         m_formatter.immediate8(mode); // modes are the same for roundsd and roundss
     }
 
+    void insertps_irr(unsigned mask, XMMRegisterID src, XMMRegisterID dst)
+    {
+        MOZ_ASSERT(mask < 256);
+        spew("insertps     $%u, %s, %s",
+             mask, nameFPReg(src), nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_66);
+        m_formatter.threeByteOp(OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, (RegisterID)dst, (RegisterID)src);
+        m_formatter.immediate8(uint8_t(mask));
+    }
+
     void pinsrd_irr(unsigned lane, RegisterID src, XMMRegisterID dst)
     {
         MOZ_ASSERT(lane < 4);
         spew("pinsrd     $%u, %s, %s",
              lane, nameIReg(src), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_66);
         m_formatter.threeByteOp(OP3_PINSRD_VdqEdIb, ESCAPE_PINSRD, (RegisterID)dst, (RegisterID)src);
         m_formatter.immediate8(uint8_t(lane));
@@ -3634,32 +3648,48 @@ public:
     void minsd_mr(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("minsd      %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_MINSD_VsdWsd, (RegisterID)dst, base, offset);
     }
 
+    void minss_rr(XMMRegisterID src, XMMRegisterID dst)
+    {
+        spew("minss      %s, %s",
+             nameFPReg(src), nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MINSS_VssWss, (RegisterID)dst, (RegisterID)src);
+    }
+
     void maxsd_rr(XMMRegisterID src, XMMRegisterID dst)
     {
         spew("maxsd      %s, %s",
              nameFPReg(src), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_MAXSD_VsdWsd, (RegisterID)dst, (RegisterID)src);
     }
 
     void maxsd_mr(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("maxsd      %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_MAXSD_VsdWsd, (RegisterID)dst, base, offset);
     }
 
+    void maxss_rr(XMMRegisterID src, XMMRegisterID dst)
+    {
+        spew("maxss      %s, %s",
+             nameFPReg(src), nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MAXSS_VssWss, (RegisterID)dst, (RegisterID)src);
+    }
+
     // Misc instructions:
 
     void int3()
     {
         spew("int3");
         m_formatter.oneByteOp(OP_INT3);
     }
 
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp
@@ -532,16 +532,68 @@ CodeGeneratorX86Shared::visitMinMaxD(LMi
     else
         masm.minsd(second, first);
 
     masm.bind(&done);
     return true;
 }
 
 bool
+CodeGeneratorX86Shared::visitMinMaxF(LMinMaxF *ins)
+{
+    FloatRegister first = ToFloatRegister(ins->first());
+    FloatRegister second = ToFloatRegister(ins->second());
+#ifdef DEBUG
+    FloatRegister output = ToFloatRegister(ins->output());
+    JS_ASSERT(first == output);
+#endif
+
+    Label done, nan, minMaxInst;
+
+    // Do a ucomiss to catch equality and NaNs, which both require special
+    // handling. If the operands are ordered and inequal, we branch straight to
+    // the min/max instruction. If we wanted, we could also branch for less-than
+    // or greater-than here instead of using min/max, however these conditions
+    // will sometimes be hard on the branch predictor.
+    masm.ucomiss(first, second);
+    masm.j(Assembler::NotEqual, &minMaxInst);
+    if (!ins->mir()->range() || ins->mir()->range()->canBeNaN())
+        masm.j(Assembler::Parity, &nan);
+
+    // Ordered and equal. The operands are bit-identical unless they are zero
+    // and negative zero. These instructions merge the sign bits in that
+    // case, and are no-ops otherwise.
+    if (ins->mir()->isMax())
+        masm.andps(second, first);
+    else
+        masm.orps(second, first);
+    masm.jump(&done);
+
+    // x86's min/max are not symmetric; if either operand is a NaN, they return
+    // the read-only operand. We need to return a NaN if either operand is a
+    // NaN, so we explicitly check for a NaN in the read-write operand.
+    if (!ins->mir()->range() || ins->mir()->range()->canBeNaN()) {
+        masm.bind(&nan);
+        masm.ucomiss(first, first);
+        masm.j(Assembler::Parity, &done);
+    }
+
+    // When the values are inequal, or second is NaN, x86's min and max will
+    // return the value we need.
+    masm.bind(&minMaxInst);
+    if (ins->mir()->isMax())
+        masm.maxss(second, first);
+    else
+        masm.minss(second, first);
+
+    masm.bind(&done);
+    return true;
+}
+
+bool
 CodeGeneratorX86Shared::visitAbsD(LAbsD *ins)
 {
     FloatRegister input = ToFloatRegister(ins->input());
     JS_ASSERT(input == ToFloatRegister(ins->output()));
     // Load a value which is all ones except for the sign bit.
     masm.loadConstantDouble(SpecificNaN<double>(0, FloatingPoint<double>::kSignificandBits),
                             ScratchDoubleReg);
     masm.andpd(ScratchDoubleReg, input);
@@ -2228,16 +2280,73 @@ CodeGeneratorX86Shared::visitSimdExtract
         uint32_t mask = MacroAssembler::ComputeShuffleMask(lane);
         masm.shuffleFloat32(mask, input, output);
     }
     masm.canonicalizeFloat(output);
     return true;
 }
 
 bool
+CodeGeneratorX86Shared::visitSimdInsertElementI(LSimdInsertElementI *ins)
+{
+    FloatRegister vector = ToFloatRegister(ins->vector());
+    Register value = ToRegister(ins->value());
+    FloatRegister output = ToFloatRegister(ins->output());
+    MOZ_ASSERT(vector == output); // defineReuseInput(0)
+
+    unsigned component = unsigned(ins->lane());
+
+    // Note that, contrarily to float32x4, we cannot use movd if the inserted
+    // value goes into the first component, as movd clears out the higher lanes
+    // of the output.
+    if (AssemblerX86Shared::HasSSE41()) {
+        masm.pinsrd(component, value, output);
+        return true;
+    }
+
+    masm.reserveStack(Simd128DataSize);
+    masm.storeAlignedInt32x4(vector, Address(StackPointer, 0));
+    masm.store32(value, Address(StackPointer, component * sizeof(int32_t)));
+    masm.loadAlignedInt32x4(Address(StackPointer, 0), output);
+    masm.freeStack(Simd128DataSize);
+    return true;
+}
+
+bool
+CodeGeneratorX86Shared::visitSimdInsertElementF(LSimdInsertElementF *ins)
+{
+    FloatRegister vector = ToFloatRegister(ins->vector());
+    FloatRegister value = ToFloatRegister(ins->value());
+    FloatRegister output = ToFloatRegister(ins->output());
+    MOZ_ASSERT(vector == output); // defineReuseInput(0)
+
+    if (ins->lane() == SimdLane::LaneX) {
+        // As both operands are registers, movss doesn't modify the upper bits
+        // of the destination operand.
+        if (value != output)
+            masm.movss(value, output);
+        return true;
+    }
+
+    if (AssemblerX86Shared::HasSSE41()) {
+        // The input value is in the low float32 of the 'value' FloatRegister.
+        masm.insertps(value, output, masm.insertpsMask(SimdLane::LaneX, ins->lane()));
+        return true;
+    }
+
+    unsigned component = unsigned(ins->lane());
+    masm.reserveStack(Simd128DataSize);
+    masm.storeAlignedFloat32x4(vector, Address(StackPointer, 0));
+    masm.storeFloat32(value, Address(StackPointer, component * sizeof(int32_t)));
+    masm.loadAlignedFloat32x4(Address(StackPointer, 0), output);
+    masm.freeStack(Simd128DataSize);
+    return true;
+}
+
+bool
 CodeGeneratorX86Shared::visitSimdSignMaskX4(LSimdSignMaskX4 *ins)
 {
     FloatRegister input = ToFloatRegister(ins->input());
     Register output = ToRegister(ins->output());
 
     // For Float32x4 and Int32x4.
     masm.movmskps(input, output);
     return true;
--- a/js/src/jit/shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.h
@@ -146,16 +146,17 @@ class CodeGeneratorX86Shared : public Co
   public:
     CodeGeneratorX86Shared(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm);
 
   public:
     // Instruction visitors.
     virtual bool visitDouble(LDouble *ins);
     virtual bool visitFloat32(LFloat32 *ins);
     virtual bool visitMinMaxD(LMinMaxD *ins);
+    virtual bool visitMinMaxF(LMinMaxF *ins);
     virtual bool visitAbsD(LAbsD *ins);
     virtual bool visitAbsF(LAbsF *ins);
     virtual bool visitClzI(LClzI *ins);
     virtual bool visitSqrtD(LSqrtD *ins);
     virtual bool visitSqrtF(LSqrtF *ins);
     virtual bool visitPowHalfD(LPowHalfD *ins);
     virtual bool visitAddI(LAddI *ins);
     virtual bool visitSubI(LSubI *ins);
@@ -208,16 +209,18 @@ class CodeGeneratorX86Shared : public Co
     // SIMD operators
     bool visitSimdValueInt32x4(LSimdValueInt32x4 *lir);
     bool visitSimdValueFloat32x4(LSimdValueFloat32x4 *lir);
     bool visitSimdSplatX4(LSimdSplatX4 *lir);
     bool visitInt32x4(LInt32x4 *ins);
     bool visitFloat32x4(LFloat32x4 *ins);
     bool visitSimdExtractElementI(LSimdExtractElementI *lir);
     bool visitSimdExtractElementF(LSimdExtractElementF *lir);
+    bool visitSimdInsertElementI(LSimdInsertElementI *lir);
+    bool visitSimdInsertElementF(LSimdInsertElementF *lir);
     bool visitSimdSignMaskX4(LSimdSignMaskX4 *ins);
     bool visitSimdBinaryCompIx4(LSimdBinaryCompIx4 *lir);
     bool visitSimdBinaryCompFx4(LSimdBinaryCompFx4 *lir);
     bool visitSimdBinaryArithIx4(LSimdBinaryArithIx4 *lir);
     bool visitSimdBinaryArithFx4(LSimdBinaryArithFx4 *lir);
     bool visitSimdBinaryBitwiseX4(LSimdBinaryBitwiseX4 *lir);
     bool visitSimdSelect(LSimdSelect *ins);
 
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -18,17 +18,18 @@ const js::Class OuterWrapperClass =
     PROXY_CLASS_WITH_EXT(
         "Proxy",
         0, /* additional slots */
         0, /* additional class flags */
         PROXY_MAKE_EXT(
             nullptr, /* outerObject */
             js::proxy_innerObject,
             nullptr, /* iteratorObject */
-            false   /* isWrappedNative */
+            false,   /* isWrappedNative */
+            nullptr  /* objectMoved */
         ));
 
 static JSObject *
 wrap(JSContext *cx, JS::HandleObject toWrap, JS::HandleObject target)
 {
     JSAutoCompartment ac(cx, target);
     JS::RootedObject wrapper(cx, toWrap);
     if (!JS_WrapObject(cx, &wrapper))
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -247,24 +247,24 @@ namespace js {
 
 /*
  * Helper Macros for creating JSClasses that function as proxies.
  *
  * NB: The macro invocation must be surrounded by braces, so as to
  *     allow for potention JSClass extensions.
  */
 #define PROXY_MAKE_EXT(outerObject, innerObject, iteratorObject,        \
-                       isWrappedNative)                                 \
+                       isWrappedNative, objectMoved)                    \
     {                                                                   \
         outerObject,                                                    \
         innerObject,                                                    \
         iteratorObject,                                                 \
         isWrappedNative,                                                \
         js::proxy_WeakmapKeyDelegate,                                   \
-        js::proxy_ObjectMoved                                           \
+        objectMoved                                                     \
     }
 
 #define PROXY_CLASS_WITH_EXT(name, extraSlots, flags, ext)                              \
     {                                                                                   \
         name,                                                                           \
         js::Class::NON_NATIVE |                                                         \
             JSCLASS_IS_PROXY |                                                          \
             JSCLASS_IMPLEMENTS_BARRIERS |                                               \
@@ -308,17 +308,18 @@ namespace js {
     }
 
 #define PROXY_CLASS_DEF(name, extraSlots, flags)                        \
   PROXY_CLASS_WITH_EXT(name, extraSlots, flags,                         \
                        PROXY_MAKE_EXT(                                  \
                          nullptr, /* outerObject */                     \
                          nullptr, /* innerObject */                     \
                          nullptr, /* iteratorObject */                  \
-                         false    /* isWrappedNative */                 \
+                         false,   /* isWrappedNative */                 \
+                         js::proxy_ObjectMoved                          \
                        ))
 
 /*
  * Proxy stubs, similar to JS_*Stub, for embedder proxy class definitions.
  *
  * NB: Should not be called directly.
  */
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -450,16 +450,23 @@ ArenaHeader::checkSynchronizedWithFreeLi
     /*
      * Here this arena has free things, FreeList::lists[thingKind] is not
      * empty and also points to this arena. Thus they must be the same.
      */
     JS_ASSERT(freeList->isSameNonEmptySpan(firstSpan));
 }
 #endif
 
+void
+ArenaHeader::unmarkAll()
+{
+    uintptr_t *word = chunk()->bitmap.arenaBits(this);
+    memset(word, 0, ArenaBitmapWords * sizeof(uintptr_t));
+}
+
 /* static */ void
 Arena::staticAsserts()
 {
     static_assert(JS_ARRAY_LENGTH(ThingSizes) == FINALIZE_LIMIT, "We have defined all thing sizes.");
     static_assert(JS_ARRAY_LENGTH(FirstThingOffsets) == FINALIZE_LIMIT, "We have defined all offsets.");
 }
 
 void
@@ -2487,16 +2494,19 @@ GCRuntime::releaseRelocatedArenas(ArenaH
     }
 #endif
 
     unsigned count = 0;
     while (relocatedList) {
         ArenaHeader *aheader = relocatedList;
         relocatedList = relocatedList->next;
 
+        // Clear the mark bits
+        aheader->unmarkAll();
+
         // Mark arena as empty
         AllocKind thingKind = aheader->getAllocKind();
         size_t thingSize = aheader->getThingSize();
         Arena *arena = aheader->getArena();
         FreeSpan fullSpan;
         fullSpan.initFinal(arena->thingsStart(thingKind), arena->thingsEnd() - thingSize, thingSize);
         aheader->setFirstFreeSpan(&fullSpan);
 
@@ -5637,22 +5647,22 @@ GCRuntime::scanZonesBeforeGC()
     return zoneStats;
 }
 
 void
 GCRuntime::collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
                    JS::gcreason::Reason reason)
 {
     /* GC shouldn't be running in parallel execution mode */
-    MOZ_ASSERT(!InParallelSection());
+    MOZ_ALWAYS_TRUE(!InParallelSection());
 
     JS_AbortIfWrongThread(rt);
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
-    MOZ_ASSERT(!rt->isHeapBusy());
+    MOZ_ALWAYS_TRUE(!rt->isHeapBusy());
 
     /* The engine never locks across anything that could GC. */
     MOZ_ASSERT(!rt->currentThreadHasExclusiveAccess());
 
     if (rt->mainThread.suppressGC)
         return;
 
     TraceLogger *logger = TraceLoggerForMainThread(rt);
@@ -6354,18 +6364,43 @@ JS::AutoAssertOnGC::~AutoAssertOnGC()
 }
 
 /* static */ void
 JS::AutoAssertOnGC::VerifyIsSafeToGC(JSRuntime *rt)
 {
     if (rt->gc.isInsideUnsafeRegion())
         MOZ_CRASH("[AutoAssertOnGC] possible GC in GC-unsafe region");
 }
+
+JS::AutoAssertNoAlloc::AutoAssertNoAlloc(JSRuntime *rt)
+  : gc(nullptr)
+{
+    disallowAlloc(rt);
+}
+
+void JS::AutoAssertNoAlloc::disallowAlloc(JSRuntime *rt)
+{
+    JS_ASSERT(!gc);
+    gc = &rt->gc;
+    gc->disallowAlloc();
+}
+
+JS::AutoAssertNoAlloc::~AutoAssertNoAlloc()
+{
+    if (gc)
+        gc->allowAlloc();
+}
 #endif
 
+JS::AutoAssertGCCallback::AutoAssertGCCallback(JSObject *obj)
+  : AutoSuppressGCAnalysis()
+{
+    MOZ_ASSERT(obj->runtimeFromMainThread()->isHeapMajorCollecting());
+}
+
 #ifdef JSGC_HASH_TABLE_CHECKS
 void
 js::gc::CheckHashTablesAfterMovingGC(JSRuntime *rt)
 {
     /*
      * Check that internal hash tables no longer have any pointers to things
      * that have been moved.
      */
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -690,20 +690,18 @@ class ArenaLists
         return true;
     }
 
     void unmarkAll() {
         for (size_t i = 0; i != FINALIZE_LIMIT; ++i) {
             /* The background finalization must have stopped at this point. */
             JS_ASSERT(backgroundFinalizeState[i] == BFS_DONE ||
                       backgroundFinalizeState[i] == BFS_JUST_FINISHED);
-            for (ArenaHeader *aheader = arenaLists[i].head(); aheader; aheader = aheader->next) {
-                uintptr_t *word = aheader->chunk()->bitmap.arenaBits(aheader);
-                memset(word, 0, ArenaBitmapWords * sizeof(uintptr_t));
-            }
+            for (ArenaHeader *aheader = arenaLists[i].head(); aheader; aheader = aheader->next)
+                aheader->unmarkAll();
         }
     }
 
     bool doneBackgroundFinalize(AllocKind kind) const {
         return backgroundFinalizeState[kind] == BFS_DONE ||
                backgroundFinalizeState[kind] == BFS_JUST_FINISHED;
     }
 
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -320,47 +320,19 @@ class ZoneCellIterUnderGC : public ZoneC
 #ifdef JSGC_GENERATIONAL
         JS_ASSERT(zone->runtimeFromAnyThread()->gc.nursery.isEmpty());
 #endif
         JS_ASSERT(zone->runtimeFromAnyThread()->isHeapBusy());
         init(zone, kind);
     }
 };
 
-/* In debug builds, assert that no allocation occurs. */
-class AutoAssertNoAlloc
-{
-#ifdef JS_DEBUG
-    GCRuntime *gc;
-
-  public:
-    AutoAssertNoAlloc() : gc(nullptr) {}
-    explicit AutoAssertNoAlloc(JSRuntime *rt) : gc(nullptr) {
-        disallowAlloc(rt);
-    }
-    void disallowAlloc(JSRuntime *rt) {
-        JS_ASSERT(!gc);
-        gc = &rt->gc;
-        gc->disallowAlloc();
-    }
-    ~AutoAssertNoAlloc() {
-        if (gc)
-            gc->allowAlloc();
-    }
-#else
-  public:
-    AutoAssertNoAlloc() {}
-    explicit AutoAssertNoAlloc(JSRuntime *) {}
-    void disallowAlloc(JSRuntime *rt) {}
-#endif
-};
-
 class ZoneCellIter : public ZoneCellIterImpl
 {
-    AutoAssertNoAlloc noAlloc;
+    JS::AutoAssertNoAlloc noAlloc;
     ArenaLists *lists;
     AllocKind kind;
 
   public:
     ZoneCellIter(JS::Zone *zone, AllocKind kind)
       : lists(&zone->allocator.arenas),
         kind(kind)
     {
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -658,16 +658,22 @@ JSObject::finish(js::FreeOp *fop)
                 // so that other objects can access these elements while they
                 // are themselves finalized.
                 fop->freeLater(elements);
             }
         } else {
             fop->free_(elements);
         }
     }
+
+    // It's possible that unreachable shapes may be marked whose listp points
+    // into this object. In case this happens, null out the shape's pointer here
+    // so that a moving GC will not try to access the dead object.
+    if (shape_->listp == &shape_)
+        shape_->listp = nullptr;
 }
 
 /* static */ inline bool
 JSObject::hasProperty(JSContext *cx, js::HandleObject obj,
                       js::HandleId id, bool *foundp)
 {
     JS::RootedObject pobj(cx);
     js::RootedShape prop(cx);
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -66,18 +66,22 @@ WeakMapBase::unmarkCompartment(JSCompart
 {
     for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
         m->marked = false;
 }
 
 void
 WeakMapBase::markAll(JSCompartment *c, JSTracer *tracer)
 {
-    for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
-        m->markIteratively(tracer);
+    JS_ASSERT(tracer->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps);
+    for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
+        m->trace(tracer);
+        if (m->memberOf)
+            gc::MarkObject(tracer, &m->memberOf, "memberOf");
+    }
 }
 
 bool
 WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer)
 {
     bool markedAny = false;
     for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
         if (m->marked && m->markIteratively(tracer))
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -86,17 +86,17 @@ class WeakMapBase {
     virtual void nonMarkingTraceValues(JSTracer *tracer) = 0;
     virtual bool markIteratively(JSTracer *tracer) = 0;
     virtual bool findZoneEdges() = 0;
     virtual void sweep() = 0;
     virtual void traceMappings(WeakMapTracer *tracer) = 0;
     virtual void finish() = 0;
 
     // Object that this weak map is part of, if any.
-    JSObject *memberOf;
+    HeapPtrObject memberOf;
 
     // Compartment that this weak map is part of.
     JSCompartment *compartment;
 
     // Link in a list of all WeakMaps in a compartment, headed by
     // JSCompartment::gcWeakMapList. The last element of the list has nullptr as
     // its next. Maps not in the list have WeakMapNotInList as their next.
     WeakMapBase *next;
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -767,20 +767,23 @@ RegExpCompartment::sweep(JSRuntime *rt)
         // stack is traced but is not in a zone being collected.
         //
         // Because of this we only treat the marked_ bit as a hint, and destroy
         // the RegExpShared if it was accidentally marked earlier but wasn't
         // marked by the current trace.
         bool keep = shared->marked() && !IsStringAboutToBeFinalized(shared->source.unsafeGet());
         for (size_t i = 0; i < ArrayLength(shared->compilationArray); i++) {
             RegExpShared::RegExpCompilation &compilation = shared->compilationArray[i];
-            if (keep && compilation.jitCode)
-                keep = !IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet());
+            if (compilation.jitCode &&
+                IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet()))
+            {
+                keep = false;
+            }
         }
-        if (keep) {
+        if (keep || rt->isHeapCompacting()) {
             shared->clearMarked();
         } else {
             js_delete(shared);
             e.removeFront();
         }
     }
 
     if (matchResultTemplateObject_ &&
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -194,17 +194,17 @@ class RegExpShared
     bool isCompiled() const {
         return isCompiled(Normal, true) || isCompiled(Normal, false)
             || isCompiled(MatchOnly, true) || isCompiled(MatchOnly, false);
     }
 
     void trace(JSTracer *trc);
 
     bool marked() const { return marked_; }
-    void clearMarked() { JS_ASSERT(marked_); marked_ = false; }
+    void clearMarked() { marked_ = false; }
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 };
 
 /*
  * Extend the lifetime of a given RegExpShared to at least the lifetime of
  * the guard object. See Regular Expression comment at the top.
  */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -892,19 +892,16 @@ static const JSFunctionSpec intrinsic_fu
               intrinsic_TypeDescrIsSizedArrayType,
               &js::TypeDescrIsSizedArrayTypeJitInfo, 1, 0),
     JS_FNINFO("TypeDescrIsSimpleType",
               intrinsic_TypeDescrIsSimpleType,
               &js::TypeDescrIsSimpleTypeJitInfo, 1, 0),
     JS_FNINFO("ClampToUint8",
               JSNativeThreadSafeWrapper<js::ClampToUint8>,
               &js::ClampToUint8JitInfo, 1, 0),
-    JS_FNINFO("Memcpy",
-              JSNativeThreadSafeWrapper<js::Memcpy>,
-              &js::MemcpyJitInfo, 5, 0),
     JS_FN("GetTypedObjectModule", js::GetTypedObjectModule, 0, 0),
     JS_FN("GetFloat32x4TypeDescr", js::GetFloat32x4TypeDescr, 0, 0),
     JS_FN("GetInt32x4TypeDescr", js::GetInt32x4TypeDescr, 0, 0),
 
 #define LOAD_AND_STORE_SCALAR_FN_DECLS(_constant, _type, _name)               \
     JS_FNINFO("Store_" #_name,                                                \
               JSNativeThreadSafeWrapper<js::StoreScalar##_type::Func>,        \
               &js::StoreScalar##_type::JitInfo, 3, 0),                        \
--- a/js/xpconnect/public/SandboxPrivate.h
+++ b/js/xpconnect/public/SandboxPrivate.h
@@ -44,15 +44,20 @@ public:
         return GetWrapper();
     }
 
     void ForgetGlobalObject()
     {
         ClearWrapper();
     }
 
+    void ObjectMoved(JSObject *obj, const JSObject *old)
+    {
+        UpdateWrapper(obj, old);
+    }
+
 private:
     virtual ~SandboxPrivate() { }
 
     nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
 #endif // __SANDBOXPRIVATE_H__
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -305,30 +305,39 @@ sandbox_enumerate(JSContext *cx, HandleO
 static bool
 sandbox_resolve(JSContext *cx, HandleObject obj, HandleId id)
 {
     bool resolved;
     return JS_ResolveStandardClass(cx, obj, id, &resolved);
 }
 
 static void
-sandbox_finalize(JSFreeOp *fop, JSObject *obj)
+sandbox_finalize(js::FreeOp *fop, JSObject *obj)
 {
     nsIScriptObjectPrincipal *sop =
         static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(obj));
     if (!sop) {
         // sop can be null if CreateSandboxObject fails in the middle.
         return;
     }
 
     static_cast<SandboxPrivate *>(sop)->ForgetGlobalObject();
     NS_RELEASE(sop);
     DestroyProtoAndIfaceCache(obj);
 }
 
+static void
+sandbox_moved(JSObject *obj, const JSObject *old)
+{
+    nsIScriptObjectPrincipal *sop =
+        static_cast<nsIScriptObjectPrincipal *>(xpc_GetJSPrivate(obj));
+    MOZ_ASSERT(sop);
+    static_cast<SandboxPrivate *>(sop)->ObjectMoved(obj, old);
+}
+
 static bool
 sandbox_convert(JSContext *cx, HandleObject obj, JSType type, MutableHandleValue vp)
 {
     if (type == JSTYPE_OBJECT) {
         vp.set(OBJECT_TO_JSVAL(obj));
         return true;
     }
 
@@ -438,45 +447,65 @@ sandbox_addProperty(JSContext *cx, Handl
                                writeToProto_getProperty, writeToProto_setProperty))
         return false;
 
     return true;
 }
 
 #define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET)
 
-static const JSClass SandboxClass = {
+static const js::Class SandboxClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     JS_PropertyStub,   JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     sandbox_enumerate, sandbox_resolve, sandbox_convert,  sandbox_finalize,
-    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
+    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
+    JS_NULL_CLASS_SPEC,
+    {
+      nullptr,      /* outerObject */
+      nullptr,      /* innerObject */
+      nullptr,      /* iteratorObject */
+      false,        /* isWrappedNative */
+      nullptr,      /* weakmapKeyDelegateOp */
+      sandbox_moved /* objectMovedOp */
+    },
+    JS_NULL_OBJECT_OPS
 };
 
 // Note to whomever comes here to remove addProperty hooks: billm has promised
 // to do the work for this class.
-static const JSClass SandboxWriteToProtoClass = {
+static const js::Class SandboxWriteToProtoClass = {
     "Sandbox",
     XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1),
     sandbox_addProperty,   JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     sandbox_enumerate, sandbox_resolve, sandbox_convert,  sandbox_finalize,
-    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook
+    nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook,
+    JS_NULL_CLASS_SPEC,
+    {
+      nullptr,      /* outerObject */
+      nullptr,      /* innerObject */
+      nullptr,      /* iteratorObject */
+      false,        /* isWrappedNative */
+      nullptr,      /* weakmapKeyDelegateOp */
+      sandbox_moved /* objectMovedOp */
+    },
+    JS_NULL_OBJECT_OPS
 };
 
 static const JSFunctionSpec SandboxFunctions[] = {
     JS_FS("dump",    SandboxDump,    1,0),
     JS_FS("debug",   SandboxDebug,   1,0),
     JS_FS("importFunction", SandboxImport, 1,0),
     JS_FS_END
 };
 
 bool
 xpc::IsSandbox(JSObject *obj)
 {
-    const JSClass *clasp = GetObjectJSClass(obj);
+    const Class *clasp = GetObjectClass(obj);
     return clasp == &SandboxClass || clasp == &SandboxWriteToProtoClass;
 }
 
 /***************************************************************************/
 nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox()
 {
 }
 
@@ -856,21 +885,21 @@ xpc::CreateSandboxObject(JSContext *cx, 
         NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE);
     } else if (JSObject *obj = JS::CurrentGlobalOrNull(cx)) {
         if (JSAddonId *id = JS::AddonIdOfObject(obj))
             addonId = id;
     }
 
     compartmentOptions.setAddonId(addonId);
 
-    const JSClass *clasp = options.writeToGlobalPrototype
-                         ? &SandboxWriteToProtoClass
-                         : &SandboxClass;
+    const Class *clasp = options.writeToGlobalPrototype
+                       ? &SandboxWriteToProtoClass
+                       : &SandboxClass;
 
-    RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, clasp,
+    RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, js::Jsvalify(clasp),
                                                      principal, compartmentOptions));
     if (!sandbox)
         return NS_ERROR_FAILURE;
 
     CompartmentPrivate::Get(sandbox)->writeToGlobalPrototype =
       options.writeToGlobalPrototype;
 
     // Set up the wantXrays flag, which indicates whether xrays are desired even
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -987,16 +987,30 @@ XPCWrappedNative::FlatJSObjectFinalized(
 
     // Note that it's not safe to touch mNativeWrapper here since it's
     // likely that it has already been finalized.
 
     Release();
 }
 
 void
+XPCWrappedNative::FlatJSObjectMoved(JSObject *obj, const JSObject *old)
+{
+    JS::AutoAssertGCCallback inCallback(obj);
+    MOZ_ASSERT(mFlatJSObject == old);
+
+    nsWrapperCache *cache = nullptr;
+    CallQueryInterface(mIdentity, &cache);
+    if (cache)
+        cache->UpdateWrapper(obj, old);
+
+    mFlatJSObject = obj;
+}
+
+void
 XPCWrappedNative::SystemIsBeingShutDown()
 {
     if (!IsValid())
         return;
 
     // The long standing strategy is to leak some objects still held at shutdown.
     // The general problem is that propagating release out of xpconnect at
     // shutdown time causes a world of problems.
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -568,16 +568,27 @@ WrappedNativeFinalize(js::FreeOp *fop, J
 
     XPCWrappedNative* wrapper = static_cast<XPCWrappedNative*>(p);
     if (helperType == WN_HELPER)
         wrapper->GetScriptableCallback()->Finalize(wrapper, js::CastToJSFreeOp(fop), obj);
     wrapper->FlatJSObjectFinalized();
 }
 
 static void
+WrappedNativeObjectMoved(JSObject *obj, const JSObject *old)
+{
+    nsISupports* p = static_cast<nsISupports*>(xpc_GetJSPrivate(obj));
+    if (!p)
+        return;
+
+    XPCWrappedNative* wrapper = static_cast<XPCWrappedNative*>(p);
+    wrapper->FlatJSObjectMoved(obj, old);
+}
+
+static void
 XPC_WN_NoHelper_Finalize(js::FreeOp *fop, JSObject *obj)
 {
     WrappedNativeFinalize(fop, obj, WN_NOHELPER);
 }
 
 /*
  * General comment about XPConnect tracing: Given a C++ object |wrapper| and its
  * corresponding JS object |obj|, calling |wrapper->TraceSelf| will ask the JS
@@ -653,17 +664,19 @@ const XPCWrappedNativeJSClass XPC_WN_NoH
     XPCWrappedNative::Trace,         // trace
     JS_NULL_CLASS_SPEC,
 
     // ClassExtension
     {
         nullptr, // outerObject
         nullptr, // innerObject
         nullptr, // iteratorObject
-        true,   // isWrappedNative
+        true,    // isWrappedNative
+        nullptr, // weakmapKeyDelegateOp
+        WrappedNativeObjectMoved
     },
 
     // ObjectOps
     {
         nullptr, // lookupGeneric
         nullptr, // lookupProperty
         nullptr, // lookupElement
         nullptr, // defineGeneric
@@ -1160,16 +1173,17 @@ XPCNativeScriptableShared::PopulateJSCla
         mJSClass.base.hasInstance = XPC_WN_Helper_HasInstance;
 
     if (mFlags.IsGlobalObject())
         mJSClass.base.trace = JS_GlobalObjectTraceHook;
     else
         mJSClass.base.trace = XPCWrappedNative::Trace;
 
     mJSClass.base.ext.isWrappedNative = true;
+    mJSClass.base.ext.objectMovedOp = WrappedNativeObjectMoved;
 }
 
 /***************************************************************************/
 /***************************************************************************/
 
 // Compatibility hack.
 //
 // XPConnect used to do all sorts of funny tricks to find the "correct"
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2136,16 +2136,17 @@ public:
     ReparentWrapperIfFound(XPCWrappedNativeScope* aOldScope,
                            XPCWrappedNativeScope* aNewScope,
                            JS::HandleObject aNewParent,
                            nsISupports* aCOMObj);
 
     nsresult RescueOrphans();
 
     void FlatJSObjectFinalized();
+    void FlatJSObjectMoved(JSObject *obj, const JSObject *old);
 
     void SystemIsBeingShutDown();
 
     enum CallMode {CALL_METHOD, CALL_GETTER, CALL_SETTER};
 
     static bool CallMethod(XPCCallContext& ccx,
                            CallMode mode = CALL_METHOD);
 
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -1397,17 +1397,17 @@ nsBidiPresUtils::RepositionFrame(nsIFram
   // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
   // have been reflowed, which is required for GetUsedMargin/Border/Padding
   LogicalMargin margin(aLineWM, aFrame->GetUsedMargin());
   if (isFirst) {
     aStart += margin.IStart(aLineWM);
   }
 
   nscoord start = aStart;
-  nscoord frameWidth = aFrame->GetSize().width;
+  nscoord frameISize = aFrame->ISize(aLineWM);
 
   if (!IsBidiLeaf(aFrame))
   {
     nscoord iCoord = 0;
     LogicalMargin borderPadding(frameWM, aFrame->GetUsedBorderAndPadding());
     if (isFirst) {
       iCoord += borderPadding.IStart(frameWM);
     }
@@ -1431,29 +1431,29 @@ nsBidiPresUtils::RepositionFrame(nsIFram
     // Reposition the child frames
     int32_t index = 0;
     while (frame) {
       RepositionFrame(frame,
                       aIsEvenLevel,
                       iCoord,
                       aContinuationStates,
                       frameWM,
-                      frameWidth);
+                      frameISize);
       index++;
       frame = reverseOrder ?
                 childList[childList.Length() - index - 1] :
                 frame->GetNextSibling();
     }
 
     if (isLast) {
       iCoord += borderPadding.IEnd(frameWM);
     }
     aStart += iCoord;
   } else {
-    aStart += frameWidth;
+    aStart += frameISize;
   }
 
   LogicalRect logicalRect(aLineWM, aFrame->GetRect(), aLineWidth);
   logicalRect.IStart(aLineWM) = start;
   logicalRect.ISize(aLineWM) = aStart - start;
   aFrame->SetRect(aLineWM, logicalRect, aLineWidth);
 
   if (isLast) {
--- a/layout/generic/test/test_backspace_delete.xul
+++ b/layout/generic/test/test_backspace_delete.xul
@@ -28,33 +28,50 @@ function execTests() {
     var n = editor.firstChild;
     if (node) {
       n = node();
     }
     sel.collapse(n, firstChildOffsetForCaret);
   }
 
   var eatSpace;
+  var deleteImmediately;
 
-  function getPrefs() {
+  function getPrefs(branch) {
     const prefSvcContractID = "@mozilla.org/preferences-service;1";
     const prefSvcIID = Components.interfaces.nsIPrefService;
     return Components.classes[prefSvcContractID].getService(prefSvcIID)
-                                                 .getBranch("layout.word_select.");
+                                                 .getBranch(branch);
+  }
+
+  function setPref(branch, pref, newValue) {
+    getPrefs(branch).setBoolPref(pref, newValue);
+    return newValue;
+  }
+
+  function restorePref(branch, pref, newValue) {
+    try {
+      getPrefs(branch).clearUserPref(pref);
+    } catch(ex) {}
   }
 
   function setEatSpace(newValue) {
-    getPrefs().setBoolPref("eat_space_to_next_word", newValue);
-    eatSpace = newValue;
+    eatSpace = setPref("layout.word_select.", "eat_space_to_next_word", newValue);
   }
 
   function restoreEatSpace() {
-    try {
-      getPrefs().clearUserPref("eat_space_to_next_word");
-    } catch(ex) {}
+    restorePref("layout.word_select.", "eat_space_to_next_word");
+  }
+
+  function setDeleteImmediately(newValue) {
+    deleteImmediately = setPref("bidi.edit.", "delete_immediately", newValue);
+  }
+
+  function restoreDeleteImmediately() {
+    restorePref("bidi.edit.", "delete_immediately");
   }
 
   function doCommand(cmd) {
     var controller = document.commandDispatcher.getControllerForCommand(cmd);
     if (controller) {
       try {
         controller.doCommand(cmd);
         ok(true, 'doCommand(' + cmd + ') succeeded');
@@ -81,30 +98,30 @@ function execTests() {
     is(selRange.startContainer, startNode, selErrString("Word right"));
     is(selRange.startOffset, startOffset, selErrString("Word right"));
     is(selRange.endContainer, endNode, selErrString("Word right"));
     is(selRange.endOffset, endOffset, selErrString("Word right"));
   }
 
   function testDelete(node, offset, text, richtext) {
     doCommand("cmd_deleteCharForward");
-    var msg = "Delete broken in \"" + editor.innerHTML + "\", offset " + offset;
+    var msg = "Delete broken in \"" + editor.innerHTML + "\", offset " + offset + " with deleteImmediately=" + deleteImmediately;
     if(typeof node == 'function'){
       node = node();
     }
     is(sel.anchorNode, node, msg);
 
     is(sel.anchorOffset, offset, msg);
     let text_result = richtext ? editor.innerHTML : editor.textContent;
     is(text_result, text, msg);
   }
 
   function testBackspace(node, offset, text) {
     doCommand("cmd_deleteCharBackward");
-    var msg = "Backspace broken in \"" + editor.innerHTML + "\", offset " + offset;
+    var msg = "Backspace broken in \"" + editor.innerHTML + "\", offset " + offset + " with deleteImmediately=" + deleteImmediately;
     is(sel.anchorNode, node, msg);
 
     is(sel.anchorOffset, offset, msg);
     is(editor.textContent, text, msg);
   }
 
   function testDeletePrevWord(node, offset, text) {
     doCommand("cmd_deleteWordBackward");
@@ -190,20 +207,48 @@ function execTests() {
 
   // Tests for Bug 419217
 
   setupTest("foo<div>bar</div>", 3);
   testDelete(function(){return editor.firstChild;}, 3, "foobar", true);
 
   // Tests for Bug 419406
   var s = "helloשלום";
+
+  setDeleteImmediately(true);
+
+  setupTest(s, 4);
+  testRight(editor.firstChild, 5);
+  testDelete(editor.firstChild, 5, "helloלום");
+
+  setDeleteImmediately(false);
+
   setupTest(s, 4);
   testRight(editor.firstChild, 5);
   testDelete(editor.firstChild, 5, "helloשלום");
 
+  // Tests for bug 1034337
+  s = "اهلاhello";
+
+  setDeleteImmediately(true);
+
+  setupTest(s, 4);
+  // first delete an ltr character to make sure that the caret is ltr
+  testDelete(editor.firstChild, 4, "اهلاello");
+  testBackspace(editor.firstChild, 3, "اهلello");
+
+  setDeleteImmediately(false);
+
+  setupTest(s, 4);
+  // first delete an ltr character to make sure that the caret is ltr
+  testDelete(editor.firstChild, 4, "اهلاello");
+  testBackspace(editor.firstChild, 4, "اهلاello");
+
+  restoreDeleteImmediately();
+
   // Tests for Bug 462188
   setupTest("You should not see this text.", 29);
   testDeletePrevWord(editor.firstChild, 24, "You should not see this ");
   testDeletePrevWord(editor.firstChild, 19, "You should not see ");
   testDeletePrevWord(editor.firstChild, 15, "You should not ");
   testDeletePrevWord(editor.firstChild, 11, "You should ");
   testDeletePrevWord(editor.firstChild,  4, "You ");
   testDeletePrevWord(editor,  0, "");
--- a/mfbt/tests/TestJSONWriter.cpp
+++ b/mfbt/tests/TestJSONWriter.cpp
@@ -289,26 +289,28 @@ void TestBasicElements()
   w.EndArray();
   w.End();
 
   Check(w.WriteFunc(), expected);
 }
 
 void TestStringEscaping()
 {
+  // This test uses hexadecimal character escapes because UTF8 literals cause
+  // problems for some compilers (see bug 1069726).
   const char* expected = "\
 {\n\
- \"ascii\": \"~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\\\"! \\u001f\\u001e\\u001d\\u001c\\u001b\\u001a\\u0019\\u0018\\u0017\\u0016\\u0015\\u0014\\u0013\\u0012\\u0011\\u0010\\u000f\\u000e\\r\\f\\u000b\\n\\t\\b\\u0007\\u0006\\u0005\\u0004\\u0003\\u0002\\u0001\",\n\
- \"مرحبا هناك\": true,\n\
- \"բարեւ չկա\": -123,\n\
- \"你好\": 1.234,\n\
- \"γεια εκεί\": \"سلام\",\n\
- \"halló þarna\": \"0x1234\",\n\
- \"こんにちは\": {\n\
-  \"привет\": [\n\
+ \"ascii\": \"\x7F~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\\\"! \\u001f\\u001e\\u001d\\u001c\\u001b\\u001a\\u0019\\u0018\\u0017\\u0016\\u0015\\u0014\\u0013\\u0012\\u0011\\u0010\\u000f\\u000e\\r\\f\\u000b\\n\\t\\b\\u0007\\u0006\\u0005\\u0004\\u0003\\u0002\\u0001\",\n\
+ \"\xD9\x85\xD8\xB1\xD8\xAD\xD8\xA8\xD8\xA7 \xD9\x87\xD9\x86\xD8\xA7\xD9\x83\": true,\n\
+ \"\xD5\xA2\xD5\xA1\xD6\x80\xD5\xA5\xD6\x82 \xD5\xB9\xD5\xAF\xD5\xA1\": -123,\n\
+ \"\xE4\xBD\xA0\xE5\xA5\xBD\": 1.234,\n\
+ \"\xCE\xB3\xCE\xB5\xCE\xB9\xCE\xB1 \xCE\xB5\xCE\xBA\xCE\xB5\xCE\xAF\": \"\xD8\xB3\xD9\x84\xD8\xA7\xD9\x85\",\n\
+ \"hall\xC3\xB3 \xC3\xBE" "arna\": \"0x1234\",\n\
+ \"\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF\": {\n\
+  \"\xD0\xBF\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82\": [\n\
   ]\n\
  }\n\
 }\n\
 ";
 
   JSONWriter w(MakeUnique<StringWriteFunc>());
 
   // Test the string escaping behaviour.
@@ -318,24 +320,24 @@ void TestStringEscaping()
     // at the end serves as the null char.
     char buf[128];
     for (int i = 0; i < 128; i++) {
       buf[i] = 127 - i;
     }
     w.StringProperty("ascii", buf);
 
     // Test lots of unicode stuff. Note that this file is encoded as UTF-8.
-    w.BoolProperty("مرحبا هناك", true);
-    w.IntProperty("բարեւ չկա", -123);
-    w.DoubleProperty("你好", 1.234);
-    w.StringProperty("γεια εκεί", "سلام");
-    w.PointerProperty("halló þarna", (void*)0x1234);
-    w.StartObjectProperty("こんにちは");
+    w.BoolProperty("\xD9\x85\xD8\xB1\xD8\xAD\xD8\xA8\xD8\xA7 \xD9\x87\xD9\x86\xD8\xA7\xD9\x83", true);
+    w.IntProperty("\xD5\xA2\xD5\xA1\xD6\x80\xD5\xA5\xD6\x82 \xD5\xB9\xD5\xAF\xD5\xA1", -123);
+    w.DoubleProperty("\xE4\xBD\xA0\xE5\xA5\xBD", 1.234);
+    w.StringProperty("\xCE\xB3\xCE\xB5\xCE\xB9\xCE\xB1 \xCE\xB5\xCE\xBA\xCE\xB5\xCE\xAF", "\xD8\xB3\xD9\x84\xD8\xA7\xD9\x85");
+    w.PointerProperty("hall\xC3\xB3 \xC3\xBE" "arna", (void*)0x1234);
+    w.StartObjectProperty("\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF");
     {
-      w.StartArrayProperty("привет");
+      w.StartArrayProperty("\xD0\xBF\xD1\x80\xD0\xB8\xD0\xB2\xD0\xB5\xD1\x82");
       w.EndArray();
     }
     w.EndObject();
   }
   w.End();
 
   Check(w.WriteFunc(), expected);
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -493,16 +493,22 @@ just addresses the organization to follo
 <!ENTITY updater_downloading_title2 "Downloading &brandShortName;">
 <!ENTITY updater_downloading_title_failed2 "Download failed">
 <!ENTITY updater_downloading_select2 "Touch to apply update once downloaded">
 <!ENTITY updater_downloading_retry2 "Touch to retry">
 
 <!ENTITY updater_apply_title2 "Update available for &brandShortName;">
 <!ENTITY updater_apply_select2 "Touch to update">
 
+<!-- New tablet UI -->
+
+<!-- LOCALIZATION NOTE (new_tablet_restart): Notification displayed after the user toggles the new tablet UI in the settings screen.-->
+<!ENTITY new_tablet_restart "Restart the browser for the changes to take effect">
+<!ENTITY new_tablet_pref "Enable new tablet UI">
+
 <!-- Guest mode -->
 <!ENTITY new_guest_session "New Guest Session">
 <!ENTITY exit_guest_session "Exit Guest Session">
 <!ENTITY guest_session_dialog_continue "Continue">
 <!ENTITY guest_session_dialog_cancel "Cancel">
 <!ENTITY new_guest_session_title "&brandShortName; will now restart">
 <!ENTITY new_guest_session_text "The person using it will not be able to see any of your personal browsing data (like saved passwords, history or bookmarks).\n\nWhen your guest is done, their browsing data will be deleted and your session will be restored.">
 <!ENTITY guest_browsing_notification_title "Guest browsing is enabled">
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -1006,17 +1006,17 @@ OnSharedPreferenceChangeListener
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
         if (PREFS_BROWSER_LOCALE.equals(key)) {
             onLocaleSelected(BrowserLocaleManager.getLanguageTag(lastLocale),
                              sharedPreferences.getString(key, null));
         } else if (PREFS_SUGGESTED_SITES.equals(key)) {
             refreshSuggestedSites();
         } else if (PREFS_NEW_TABLET_UI.equals(key)) {
-            Toast.makeText(this, "Restart the browser for the changes to take effect", Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, R.string.new_tablet_restart, Toast.LENGTH_SHORT).show();
         }
     }
 
     public interface PrefHandler {
         public void setupPref(Context context, Preference pref);
         public void onChange(Context context, Preference pref, Object newValue);
     }
 
--- a/mobile/android/base/resources/xml/preferences_display.xml
+++ b/mobile/android/base/resources/xml/preferences_display.xml
@@ -20,20 +20,18 @@
                     android:entries="@array/pref_titlebar_mode_entries"
                     android:entryValues="@array/pref_titlebar_mode_values"
                     android:persistent="false" />
 
     <CheckBoxPreference android:key="browser.chrome.dynamictoolbar"
                         android:title="@string/pref_scroll_title_bar2"
                         android:summary="@string/pref_scroll_title_bar_summary" />
 
-    <!-- Title string is hardcoded here because it's not meant to be translated
-         because this is just a temporary development toggle. -->
     <CheckBoxPreference android:key="android.not_a_preference.new_tablet_ui"
-                        android:title="Enable new tablet UI"
+                        android:title="@string/new_tablet_pref"
                         android:defaultValue="false" />
 
     <PreferenceCategory android:title="@string/pref_category_advanced">
 
         <CheckBoxPreference
                         android:key="browser.zoom.reflowOnZoom"
                         android:title="@string/pref_reflow_on_zoom"
                         android:defaultValue="false"
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -428,16 +428,20 @@
   <string name="updater_downloading_title">&updater_downloading_title2;</string>
   <string name="updater_downloading_title_failed">&updater_downloading_title_failed2;</string>
   <string name="updater_downloading_select">&updater_downloading_select2;</string>
   <string name="updater_downloading_retry">&updater_downloading_retry2;</string>
 
   <string name="updater_apply_title">&updater_apply_title2;</string>
   <string name="updater_apply_select">&updater_apply_select2;</string>
 
+  <!-- New tablet UI -->
+  <string name="new_tablet_restart">&new_tablet_restart;</string>
+  <string name="new_tablet_pref">&new_tablet_pref;</string>
+
   <!-- Search suggestions opt-in -->
   <string name="suggestions_prompt">&suggestions_prompt3;</string>
 
   <string name="suggestion_for_engine">&suggestion_for_engine;</string>
 
   <!-- Set Image Notifications -->
   <string name="set_image_fail">&set_image_fail;</string>
   <string name="set_image_path_fail">&set_image_path_fail;</string>
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -314,17 +314,17 @@ nsJARChannel::CreateJarInput(nsIZipReade
     // Make GetContentLength meaningful
     mContentLength = input->GetContentLength();
 
     input.forget(resultInput);
     return NS_OK;
 }
 
 nsresult
-nsJARChannel::LookupFile()
+nsJARChannel::LookupFile(bool aAllowAsync)
 {
     LOG(("nsJARChannel::LookupFile [this=%x %s]\n", this, mSpec.get()));
 
     if (mJarFile)
         return NS_OK;
 
     nsresult rv;
     nsCOMPtr<nsIURI> uri;
@@ -382,16 +382,21 @@ nsJARChannel::LookupFile()
                     jarCache->GetFd(mJarFile, &fd);
                     if (fd) {
                         return SetRemoteNSPRFileDesc(fd);
                     }
                     #endif
                 }
             }
 
+            if (!aAllowAsync) {
+                mJarFile = nullptr;
+                return NS_OK;
+            }
+
             mOpeningRemote = true;
 
             #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
             #else
             if (mEnsureChildFd && jarCache) {
                 jarCache->SetMustCacheFd(remoteFile, true);
             }
             #endif
@@ -793,17 +798,17 @@ nsJARChannel::Open(nsIInputStream **stre
     LOG(("nsJARChannel::Open [this=%x]\n", this));
 
     NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
     mJarFile = nullptr;
     mIsUnsafe = true;
 
-    nsresult rv = LookupFile();
+    nsresult rv = LookupFile(false);
     if (NS_FAILED(rv))
         return rv;
 
     // If mJarInput was not set by LookupFile, the JAR is a remote jar.
     if (!mJarFile) {
         NS_NOTREACHED("need sync downloader");
         return NS_ERROR_NOT_IMPLEMENTED;
     }
@@ -830,17 +835,17 @@ nsJARChannel::AsyncOpen(nsIStreamListene
     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
 
     mJarFile = nullptr;
     mIsUnsafe = true;
 
     // Initialize mProgressSink
     NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
 
-    nsresult rv = LookupFile();
+    nsresult rv = LookupFile(true);
     if (NS_FAILED(rv))
         return rv;
 
     // These variables must only be set if we're going to trigger an
     // OnStartRequest, either from AsyncRead or OnDownloadComplete.
     // 
     // That means: Do not add early return statements beyond this point!
     mListener = listener;
@@ -905,17 +910,17 @@ nsJARChannel::GetJarFile(nsIFile **aFile
 {
     NS_IF_ADDREF(*aFile = mJarFile);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::GetZipEntry(nsIZipEntry **aZipEntry)
 {
-    nsresult rv = LookupFile();
+    nsresult rv = LookupFile(false);
     if (NS_FAILED(rv))
         return rv;
 
     if (!mJarFile)
         return NS_ERROR_NOT_AVAILABLE;
 
     nsCOMPtr<nsIZipReader> reader;
     rv = gJarHandler->JarCache()->GetZip(mJarFile, getter_AddRefs(reader));
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -53,17 +53,17 @@ public:
     nsJARChannel();
 
     nsresult Init(nsIURI *uri);
 
 private:
     virtual ~nsJARChannel();
 
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
-    nsresult LookupFile();
+    nsresult LookupFile(bool aAllowAsync);
     nsresult OpenLocalFile();
     void NotifyError(nsresult aError);
     void FireOnProgress(uint64_t aProgress);
     nsresult SetRemoteNSPRFileDesc(PRFileDesc *fd);
 
 #if defined(PR_LOGGING)
     nsCString                       mSpec;
 #endif
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1919,17 +1919,17 @@ pref("bidi.numeral", 0);
 // ------------------
 // 1 = mozillaBidisupport *
 // 2 = OsBidisupport
 // 3 = disableBidisupport
 pref("bidi.support", 1);
 // Whether delete and backspace should immediately delete characters not
 // visually adjacent to the caret, or adjust the visual position of the caret
 // on the first keypress and delete the character on a second keypress
-pref("bidi.edit.delete_immediately", false);
+pref("bidi.edit.delete_immediately", true);
 
 // Bidi caret movement style:
 // 0 = logical
 // 1 = visual
 // 2 = visual, but logical during selection
 pref("bidi.edit.caret_movement_style", 2);
 
 // Setting this pref to |true| forces Bidi UI menu items and keyboard shortcuts
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -1555,8 +1555,15 @@ class Marionette(object):
         An error will be returned if the requested window size would result
         in the window being in the maximised state.
 
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
 
         """
         self._send_message("setWindowSize", "ok", width=width, height=height)
+
+    def maximize_window(self):
+        """ Resize the browser window currently receiving commands. The action
+        should be equivalent to the user pressing the the maximize button
+        """
+
+        return self._send_message("maximizeWindow", "ok")
--- a/testing/marionette/client/marionette/tests/unit/test_set_window_size.py
+++ b/testing/marionette/client/marionette/tests/unit/test_set_window_size.py
@@ -48,8 +48,28 @@ class TestSetWindowSize(MarionetteTestCa
         height = self.max_height - 100
         self.marionette.set_window_size(width, height)
         # invalid size (cannot maximize)
         with self.assertRaisesRegexp(MarionetteException, "Invalid requested size"):
             self.marionette.set_window_size(self.max_width, self.max_height)
         size = self.marionette.window_size
         self.assertEqual(size['width'], width, "Window width should not have changed")
         self.assertEqual(size['height'], height, "Window height should not have changed")
+
+    def test_that_we_can_maximise_the_window(self):
+        # valid size
+        width = self.max_width - 100
+        height = self.max_height - 100
+        self.marionette.set_window_size(width, height)
+
+        # event handler
+        self.marionette.execute_script("""
+        window.wrappedJSObject.rcvd_event = false;
+        window.onresize = function() {
+            window.wrappedJSObject.rcvd_event = true;
+        };
+        """)
+        self.marionette.maximize_window()
+        self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;"))
+
+        size = self.marionette.window_size
+        self.assertEqual(size['width'], self.max_width, "Window width does not use availWidth")
+        self.assertEqual(size['height'], self.max_height, "Window height does not use availHeight")
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -2440,16 +2440,35 @@ MarionetteServerConnection.prototype = {
       return;
     }
 
     curWindow.resizeTo(width, height);
     this.sendOk(this.command_id);
   },
 
   /**
+   * Maximizes the Browser Window as if the user pressed the maximise button
+   *
+   * Not Supported on B2G or Fennec
+   */
+  maximizeWindow: function MDA_maximizeWindow (aRequest) {
+    this.command_id = this.getCommandId();
+
+    if (appName !== "Firefox") {
+      this.sendError("Not supported for mobile", 405, null, this.command_id);
+      return;
+    }
+
+    let curWindow = this.getCurrentWindow();
+    curWindow.moveTo(0,0);
+    curWindow.resizeTo(curWindow.screen.availWidth, curWindow.screen.availHeight);
+    this.sendOk(this.command_id);
+  },
+
+  /**
    * Helper function to convert an outerWindowID into a UID that Marionette
    * tracks.
    */
   generateFrameId: function MDA_generateFrameId(id) {
     let uid = id + (appName == "B2G" ? "-b2g" : "");
     return uid;
   },
 
@@ -2648,17 +2667,18 @@ MarionetteServerConnection.prototype.req
   "getCookies": MarionetteServerConnection.prototype.getCookies,
   "getAllCookies": MarionetteServerConnection.prototype.getCookies,  // deprecated
   "deleteAllCookies": MarionetteServerConnection.prototype.deleteAllCookies,
   "deleteCookie": MarionetteServerConnection.prototype.deleteCookie,
   "getActiveElement": MarionetteServerConnection.prototype.getActiveElement,
   "getScreenOrientation": MarionetteServerConnection.prototype.getScreenOrientation,
   "setScreenOrientation": MarionetteServerConnection.prototype.setScreenOrientation,
   "getWindowSize": MarionetteServerConnection.prototype.getWindowSize,
-  "setWindowSize": MarionetteServerConnection.prototype.setWindowSize
+  "setWindowSize": MarionetteServerConnection.prototype.setWindowSize,
+  "maximizeWindow": MarionetteServerConnection.prototype.maximizeWindow
 };
 
 /**
  * Creates a BrowserObj. BrowserObjs handle interactions with the
  * browser, according to the current environment (desktop, b2g, etc.)
  *
  * @param nsIDOMWindow win
  *        The window whose browser needs to be accessed
--- a/toolkit/components/places/PlacesTransactions.jsm
+++ b/toolkit/components/places/PlacesTransactions.jsm
@@ -317,45 +317,61 @@ executedTransactions.add = k => executed
 
 let PlacesTransactions = {
   /**
    * Asynchronously transact either a single transaction, or a sequence of
    * transactions that would be treated as a single entry in the transactions
    * history.
    *
    * @param aToTransact
-   *        Either a transaction object or a generator function (ES6-style only)
-   *        that yields transaction objects.
+   *        Either a transaction object or an array of transaction objects, or a
+   *        generator function (ES6-style only) that yields transaction objects.
    *
    *        Generator mode how-to: when a transaction is yielded, it's executed.
    *        Then, if it was executed successfully, the resolution of |execute|
    *        is sent to the generator.  If |execute| threw or rejected, the
    *        exception is propagated to the generator.
    *        Any other value yielded by a generator function is handled the
    *        same way as in a Task (see Task.jsm).
    *
    * @return {Promise}
-   * @resolves either to the resolution of |execute|, in single-transaction mode,
-   * or to the return value of the generator, in generator-mode.
+   * @resolves either to the resolution of |execute|, in single-transaction
+   * mode, or to the return value of the generator, in generator-mode. For an
+   * array of transactions, there's no resolution value.
+   *
    * @rejects either if |execute| threw, in single-transaction mode, or if
    * the generator function threw (or didn't handle) an exception, in generator
    * mode.
    * @throws if aTransactionOrGeneratorFunction is neither a transaction object
-   * created by this module or a generator function.
+   * created by this module, nor an array of such object, nor a generator
+   * function.
    * @note If no transaction was executed successfully, the transactions history
    * is not affected.
    *
    * @note All PlacesTransactions operations are serialized. This means that the
    * transactions history state may change by the time the transaction/generator
    * is processed.  It's guaranteed, however, that a generator function "blocks"
    * the queue: that is, it is assured that no other operations are performed
    * by or on PlacesTransactions until the generator returns.  Keep in mind you
    * are not protected from consumers who use the raw places APIs directly.
    */
   transact: function (aToTransact) {
+    if (Array.isArray(aToTransact)) {
+      if (aToTransact.some(
+           o => !TransactionsHistory.isProxifiedTransactionObject(o))) {
+        throw new Error("aToTransact contains non-transaction element");
+      }
+      // Proceed as if a generator yielding the transactions was passed in.
+      return this.transact(function* () {
+        for (let t of aToTransact) {
+          yield t;
+        }
+      });
+    }
+
     let isGeneratorObj =
       o => Object.prototype.toString.call(o) ==  "[object Generator]";
 
     let generator = null;
     if (typeof(aToTransact) == "function") {
       generator = aToTransact();
       if (!isGeneratorObj(generator))
         throw new Error("aToTransact is not a generator function");
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1110,17 +1110,19 @@ this.PlacesUtils = {
    *          An nsIOutputStream. NOTE: it only uses the write(str, len)
    *          method of nsIOutputStream. The caller is responsible for
    *          closing the stream.
    */
   _serializeNodeAsJSONToOutputStream: function (aNode, aStream) {
     function addGenericProperties(aPlacesNode, aJSNode) {
       aJSNode.title = aPlacesNode.title;
       aJSNode.id = aPlacesNode.itemId;
-      if (aJSNode.id != -1) {
+      let guid = aPlacesNode.bookmarkGuid;
+      if (guid) {
+        aJSNode.itemGuid = guid;
         var parent = aPlacesNode.parent;
         if (parent) {
           aJSNode.parent = parent.itemId;
           aJSNode.parentReadOnly = PlacesUtils.nodeIsReadOnly(parent);
         }
         var dateAdded = aPlacesNode.dateAdded;
         if (dateAdded)
           aJSNode.dateAdded = dateAdded;
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -661,57 +661,67 @@ Search.prototype = {
     TelemetryStopwatch.start(TELEMETRY_1ST_RESULT);
     if (this._searchString)
       TelemetryStopwatch.start(TELEMETRY_6_FIRST_RESULTS);
 
     // Since we call the synchronous parseSubmissionURL function later, we must
     // wait for the initialization of PlacesSearchAutocompleteProvider first.
     yield PlacesSearchAutocompleteProvider.ensureInitialized();
 
-    // For any given search, we run many queries:
-    // 1) search engine domains
-    // 2) inline completion
-    // 3) keywords (this._keywordQuery)
-    // 4) adaptive learning (this._adaptiveQuery)
-    // 5) open pages not supported by history (this._switchToTabQuery)
-    // 6) query based on match behavior
+    // For any given search, we run many queries/heuristics:
+    // 1) by alias (as defined in SearchService)
+    // 2) inline completion from search engine resultDomains
+    // 3) inline completion for hosts (this._hostQuery) or urls (this._urlQuery)
+    // 4) directly typed in url (ie, can be navigated to as-is)
+    // 5) submission for the current search engine
+    // 6) keywords (this._keywordQuery)
+    // 7) adaptive learning (this._adaptiveQuery)
+    // 8) open pages not supported by history (this._switchToTabQuery)
+    // 9) query based on match behavior
     //
-    // (3) only gets ran if we get any filtered tokens, since if there are no
-    // tokens, there is nothing to match.
+    // (6) only gets ran if we get any filtered tokens, since if there are no
+    // tokens, there is nothing to match. This is the *first* query we check if
+    // we want to run, but it gets queued to be run later.
+    //
+    // (1), (4), (5) only get run if actions are enabled. When actions are
+    // enabled, the first result is always a special result (resulting from one
+    // of the queries between (1) and (6) inclusive). As such, the UI is
+    // expected to auto-select the first result when actions are enabled. If the
+    // first result is an inline completion result, that will also be the
+    // default result and therefore be autofilled (this also happens if actions
+    // are not enabled).
 
     // Get the final query, based on the tokens found in the search string.
     let queries = [ this._adaptiveQuery,
                     this._switchToTabQuery,
                     this._searchQuery ];
 
-    let hasKeyword = false;
+    // When actions are enabled, we run a series of heuristics to determine what
+    // the first result should be - which is always a special result.
+    // |hasFirstResult| is used to keep track of whether we've obtained such a
+    // result yet, so we can skip further heuristics and not add any additional
+    // special results.
+    let hasFirstResult = false;
+
     if (this._searchTokens.length > 0 &&
         PlacesUtils.bookmarks.getURIForKeyword(this._searchTokens[0])) {
+      // This may be a keyword of a bookmark.
       queries.unshift(this._keywordQuery);
-      hasKeyword = true;
+      hasFirstResult = true;
     }
 
-    if (this._shouldAutofill) {
-      if (this._searchTokens.length == 1 && !hasKeyword)
-        yield this._matchSearchEngineUrl();
+    let shouldAutofill = this._shouldAutofill;
+    if (this.pending && !hasFirstResult && shouldAutofill) {
+      // Or it may look like a URL we know about from search engines.
+      hasFirstResult = yield this._matchSearchEngineUrl();
+    }
 
-      // Hosts have no "/" in them.
-      let lastSlashIndex = this._searchString.lastIndexOf("/");
-      // Search only URLs if there's a slash in the search string...
-      if (lastSlashIndex != -1) {
-        // ...but not if it's exactly at the end of the search string.
-        if (lastSlashIndex < this._searchString.length - 1) {
-          queries.unshift(this._urlQuery);
-        }
-      } else if (this.pending) {
-        // The host query is executed immediately, while any other is delayed
-        // to avoid overloading the connection.
-        let [ query, params ] = this._hostQuery;
-        yield conn.executeCached(query, params, this._onResultRow.bind(this));
-      }
+    if (this.pending && !hasFirstResult && shouldAutofill) {
+      // It may also look like a URL we know from the database.
+      hasFirstResult = yield this._matchKnownUrl(conn, queries);
     }
 
     yield this._sleep(Prefs.delay);
     if (!this.pending)
       return;
 
     for (let [query, params] of queries) {
       yield conn.executeCached(query, params, this._onResultRow.bind(this));
@@ -735,64 +745,100 @@ Search.prototype = {
 
     // If we didn't find enough matches and we have some frecency-driven
     // matches, add them.
     if (this._frecencyMatches) {
       this._frecencyMatches.forEach(this._addMatch, this);
     }
   }),
 
+  _matchKnownUrl: function* (conn, queries) {
+    // Hosts have no "/" in them.
+    let lastSlashIndex = this._searchString.lastIndexOf("/");
+    // Search only URLs if there's a slash in the search string...
+    if (lastSlashIndex != -1) {
+      // ...but not if it's exactly at the end of the search string.
+      if (lastSlashIndex < this._searchString.length - 1) {
+        // We don't want to execute this query right away because it needs to
+        // search the entire DB without an index, but we need to know if we have
+        // a result as it will influence other heuristics. So we guess by
+        // assuming that if we get a result from a *host* query and it *looks*
+        // like a URL, then we'll probably have a result.
+        let gotResult = false;
+        let [ query, params ] = this._urlPredictQuery;
+        yield conn.executeCached(query, params, row => {
+          gotResult = true;
+          queries.unshift(this._urlQuery);
+        });
+        return gotResult;
+      }
+
+      return false;
+    }
+
+    let gotResult = false;
+    let [ query, params ] = this._hostQuery;
+    yield conn.executeCached(query, params, row => {
+      gotResult = true;
+      this._onResultRow(row);
+    });
+
+    return gotResult;
+  },
+
   _matchSearchEngineUrl: function* () {
     if (!Prefs.autofillSearchEngines)
-      return;
+      return false;
 
     let match = yield PlacesSearchAutocompleteProvider.findMatchByToken(
                                                            this._searchString);
-    if (match) {
-      // The match doesn't contain a 'scheme://www.' prefix, but since we have
-      // stripped it from the search string, here we could still be matching
-      // 'https://www.g' to 'google.com'.
-      // There are a couple cases where we don't want to match though:
-      //
-      //  * If the protocol differs we should not match. For example if the user
-      //    searched https we should not return http.
-      try {
-        let prefixURI = NetUtil.newURI(this._strippedPrefix);
-        let finalURI = NetUtil.newURI(match.url);
-        if (prefixURI.scheme != finalURI.scheme)
-          return;
-      } catch (e) {}
+    if (!match)
+      return false;
 
-      //  * If the user typed "www." but the final url doesn't have it, we
-      //    should not match as well, the two urls may point to different pages.
-      if (this._strippedPrefix.endsWith("www.") &&
-          !stripHttpAndTrim(match.url).startsWith("www."))
-        return;
+    // The match doesn't contain a 'scheme://www.' prefix, but since we have
+    // stripped it from the search string, here we could still be matching
+    // 'https://www.g' to 'google.com'.
+    // There are a couple cases where we don't want to match though:
+    //
+    //  * If the protocol differs we should not match. For example if the user
+    //    searched https we should not return http.
+    try {
+      let prefixURI = NetUtil.newURI(this._strippedPrefix);
+      let finalURI = NetUtil.newURI(match.url);
+      if (prefixURI.scheme != finalURI.scheme)
+        return false;
+    } catch (e) {}
 
-      let value = this._strippedPrefix + match.token;
+    //  * If the user typed "www." but the final url doesn't have it, we
+    //    should not match as well, the two urls may point to different pages.
+    if (this._strippedPrefix.endsWith("www.") &&
+        !stripHttpAndTrim(match.url).startsWith("www."))
+      return false;
 
-      // In any case, we should never arrive here with a value that doesn't
-      // match the search string.  If this happens there is some case we
-      // are not handling properly yet.
-      if (!value.startsWith(this._originalSearchString)) {
-        Components.utils.reportError(`Trying to inline complete in-the-middle
-                                      ${this._originalSearchString} to ${value}`);
-        return;
-      }
+    let value = this._strippedPrefix + match.token;
 
-      this._result.setDefaultIndex(0);
-      this._addFrecencyMatch({
-        value: value,
-        comment: match.engineName,
-        icon: match.iconUrl,
-        style: "priority-search",
-        finalCompleteValue: match.url,
-        frecency: FRECENCY_SEARCHENGINES_DEFAULT
-      });
+    // In any case, we should never arrive here with a value that doesn't
+    // match the search string.  If this happens there is some case we
+    // are not handling properly yet.
+    if (!value.startsWith(this._originalSearchString)) {
+      Components.utils.reportError(`Trying to inline complete in-the-middle
+                                    ${this._originalSearchString} to ${value}`);
+      return false;
     }
+
+    this._result.setDefaultIndex(0);
+    this._addFrecencyMatch({
+      value: value,
+      comment: match.engineName,
+      icon: match.iconUrl,
+      style: "priority-search",
+      finalCompleteValue: match.url,
+      frecency: FRECENCY_SEARCHENGINES_DEFAULT
+    });
+    return true;
   },
 
   _onResultRow: function (row) {
     TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT);
     let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
     let match;
     switch (queryType) {
       case QUERYTYPE_AUTOFILL_HOST:
@@ -912,28 +958,41 @@ Search.prototype = {
     }
   },
 
   _processHostRow: function (row) {
     let match = {};
     let trimmedHost = row.getResultByIndex(QUERYINDEX_URL);
     let untrimmedHost = row.getResultByIndex(QUERYINDEX_TITLE);
     let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
+
     // If the untrimmed value doesn't preserve the user's input just
     // ignore it and complete to the found host.
     if (untrimmedHost &&
         !untrimmedHost.toLowerCase().contains(this._trimmedOriginalSearchString.toLowerCase())) {
       untrimmedHost = null;
     }
 
     match.value = this._strippedPrefix + trimmedHost;
     // Remove the trailing slash.
     match.comment = stripHttpAndTrim(trimmedHost);
     match.finalCompleteValue = untrimmedHost;
+
+    try {
+      let iconURI = NetUtil.newURI(untrimmedHost);
+      iconURI.path = "/favicon.ico";
+      match.icon = PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
+    } catch (e) {
+      // This can fail, which is ok.
+    }
+
+    // Although this has a frecency, this query is executed before any other
+    // queries that would result in frecency matches.
     match.frecency = frecency;
+    match.style = "autofill";
     return match;
   },
 
   _processUrlRow: function (row) {
     let match = {};
     let value = row.getResultByIndex(QUERYINDEX_URL);
     let url = fixupSearchText(value);
     let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
@@ -957,17 +1016,20 @@ Search.prototype = {
     if (untrimmedURL &&
         !untrimmedURL.toLowerCase().contains(this._trimmedOriginalSearchString.toLowerCase())) {
       untrimmedURL = null;
      }
 
     match.value = this._strippedPrefix + url;
     match.comment = url;
     match.finalCompleteValue = untrimmedURL;
+    // Although this has a frecency, this query is executed before any other
+    // queries that would result in frecency matches.
     match.frecency = frecency;
+    match.style = "autofill";
     return match;
   },
 
   _processRow: function (row) {
     let match = {};
     match.placeId = row.getResultByIndex(QUERYINDEX_PLACEID);
     let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
     let escapedURL = row.getResultByIndex(QUERYINDEX_URL);
@@ -1157,16 +1219,19 @@ Search.prototype = {
   /**
    * Whether we should try to autoFill.
    */
   get _shouldAutofill() {
     // First of all, check for the autoFill pref.
     if (!Prefs.autofill)
       return false;
 
+    if (!this._searchTokens.length == 1)
+      return false;
+
     // Then, we should not try to autofill if the behavior is not the default.
     // TODO (bug 751709): Ideally we should have a more fine-grained behavior
     // here, but for now it's enough to just check for default behavior.
     if (Prefs.defaultBehavior != DEFAULT_BEHAVIOR) {
       // autoFill can only cope with history or bookmarks entries
       // (typed or not).
       if (!this.hasBehavior("typed") &&
           !this.hasBehavior("history") &&
@@ -1177,28 +1242,21 @@ Search.prototype = {
       if (this.hasBehavior("title") || this.hasBehavior("tags"))
         return false;
     }
 
     // Don't try to autofill if the search term includes any whitespace.
     // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
     // tokenizer ends up trimming the search string and returning a value
     // that doesn't match it, or is even shorter.
-    if (/\s/.test(this._originalSearchString)) {
+    if (/\s/.test(this._originalSearchString))
       return false;
-    }
 
-    // Don't autoFill if the search term is recognized as a keyword, otherwise
-    // it will override default keywords behavior.  Note that keywords are
-    // hashed on first use, so while the first query may delay a little bit,
-    // next ones will just hit the memory hash.
-    if (this._searchString.length == 0 ||
-        PlacesUtils.bookmarks.getURIForKeyword(this._searchString)) {
+    if (this._searchString.length == 0)
       return false;
-    }
 
     return true;
   },
 
   /**
    * Obtains the query to search for autoFill host results.
    *
    * @return an array consisting of the correctly optimized query to search the
@@ -1216,16 +1274,39 @@ Search.prototype = {
       {
         query_type: QUERYTYPE_AUTOFILL_HOST,
         searchString: this._searchString.toLowerCase()
       }
     ];
   },
 
   /**
+   * Obtains a query to predict whether this._urlQuery is likely to return a
+   * result. We do by extracting what should be a host out of the input and
+   * performing a host query based on that.
+   */
+  get _urlPredictQuery() {
+    // We expect this to be a full URL, not just a host. We want to extract the
+    // host and use that as a guess for whether we'll get a result from a URL
+    // query.
+    let slashIndex = this._searchString.indexOf("/");
+
+    let host = this._searchString.substring(0, slashIndex);
+    host = host.toLowerCase();
+
+    return [
+      SQL_HOST_QUERY,
+      {
+        query_type: QUERYTYPE_AUTOFILL_HOST,
+        searchString: host
+      }
+    ];
+  },
+
+  /**
    * Obtains the query to search for autoFill url results.
    *
    * @return an array consisting of the correctly optimized query to search the
    *         database with and an object containing the params to bound.
    */
   get _urlQuery()  {
     let typed = Prefs.autofillTyped || this.hasBehavior("typed");
     let bookmarked =  this.hasBehavior("bookmark");
--- a/toolkit/components/places/tests/unit/test_async_transactions.js
+++ b/toolkit/components/places/tests/unit/test_async_transactions.js
@@ -1478,9 +1478,46 @@ add_task(function* test_copy() {
     yield PT.NewSeparator({ parentGUID: folderGUID });
     // And another bookmark.
     yield PT.NewBookmark({ uri: NetUtil.newURI("http://nested.bookmark")
                          , parentGUID: folderGUID });
     return folderGUID;
   });
 
   yield duplicate_and_test(filledFolderGUID);
+
+  // Cleanup
+  yield PT.clearTransactionsHistory();
 });
+
+add_task(function* test_array_input_for_transact() {
+  let rootGuid = yield PlacesUtils.promiseItemGUID(root);
+
+  let folderTxn = PT.NewFolder(yield createTestFolderInfo());
+  let folderGuid = yield PT.transact(folderTxn);
+
+  let sep1_txn = PT.NewSeparator({ parentGUID: folderGuid });
+  let sep2_txn = PT.NewSeparator({ parentGUID: folderGuid });
+  yield PT.transact([sep1_txn, sep2_txn]);
+  ensureUndoState([[sep2_txn, sep1_txn], [folderTxn]], 0);
+
+  let ensureChildCount = function* (count) {
+    let tree = yield PlacesUtils.promiseBookmarksTree(folderGuid);
+    if (count == 0)
+      Assert.ok(!("children" in tree));
+    else
+      Assert.equal(tree.children.length, count);
+  };
+
+  yield ensureChildCount(2);
+  yield PT.undo();
+  yield ensureChildCount(0);
+  yield PT.redo()
+  yield ensureChildCount(2);
+  yield PT.undo();
+  yield ensureChildCount(0);
+
+  yield PT.undo();
+  Assert.equal((yield PlacesUtils.promiseBookmarksTree(folderGuid)), null);
+
+  // Cleanup
+  yield PT.clearTransactionsHistory();
+});
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -387,34 +387,16 @@
          "testQuickFindText: find field is not focused");
 
       enterStringIntoFindField(SEARCH_TEXT);
       ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
          "testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
       testClipboardSearchString(SEARCH_TEXT);
     }
 
-    // Perform an async function in serial on each of the list items.
-    function asyncForEach(list, async, callback) {
-      let i = 0;
-      let len = list.length;
-
-      if (!len)
-        return callback();
-
-      async(list[i], function handler() {
-          i++;
-          if (i < len) {
-            async(list[i], handler, i);
-          } else {
-            callback();
-          }
-      }, i);
-    }
-
     function testFindCountUI(callback) {
       clearFocus();
       document.getElementById("cmd_find").doCommand();
 
       ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar");
       ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
          "testFindCountUI: find field is not focused");
 
@@ -439,56 +421,55 @@
         text: "texxx",
         current: 0,
         total: 0
       }];
       let regex = /([\d]*)\sof\s([\d]*)/;
       let timeout = gFindBar._matchesCountTimeoutLength + 20;
 
       function assertMatches(aTest, aMatches) {
-        window.opener.wrappedJSObject.SimpleTest.is(aTest.current, aMatches[1],
+        window.opener.wrappedJSObject.SimpleTest.is(aMatches[1], aTest.current,
           "Currently highlighted match should be at " + aTest.current);
-        window.opener.wrappedJSObject.SimpleTest.is(aTest.total, aMatches[2],
+        window.opener.wrappedJSObject.SimpleTest.is(aMatches[2], aTest.total,
           "Total amount of matches should be " + aTest.total);
       }
 
-      function testString(aTest, aNext) {
-        gFindBar.clear();
-        enterStringIntoFindField(aTest.text);
-
-        setTimeout(function() {
+      function* generatorTest() {
+        for (let test of tests) {
+          gFindBar.clear();
+          yield;
+          enterStringIntoFindField(test.text);
+          yield;
           let matches = foundMatches.value.match(regex);
-          if (!aTest.total) {
+          if (!test.total) {
             ok(!matches, "No message should be shown when 0 matches are expected");
-            aNext();
           } else {
-            assertMatches(aTest, matches);
-            let cycleTests = [];
-            let cycles = aTest.total;
-            while (--cycles) {
-              aTest.current++;
-              if (aTest.current > aTest.total)
-                aTest.current = 1;
-              cycleTests.push({
-                current: aTest.current,
-                total: aTest.total
-              });
+            assertMatches(test, matches);
+            for (let i = 1; i < test.total; i++) {
+              gFindBar.onFindAgainCommand();
+              yield;
+              // test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current
+              let current = (test.current + i - 1) % test.total + 1;
+              assertMatches({
+                current: current,
+                total: test.total
+              }, foundMatches.value.match(regex));
             }
-            asyncForEach(cycleTests, function(aCycleTest, aNextCycle) {
-              gFindBar.onFindAgainCommand();
-              setTimeout(function() {
-                assertMatches(aCycleTest, foundMatches.value.match(regex));
-                aNextCycle();
-              }, timeout);
-            }, aNext);
           }
-        }, timeout);
+        }
+        callback();
       }
-
-      asyncForEach(tests, testString, callback);
+      let test = generatorTest();
+      let resultListener = {
+        onMatchesCountResult: function() {
+          test.next();
+        }
+      };
+      gFindBar.browser.finder.addResultListener(resultListener);
+      test.next();
     }
 
     function testClipboardSearchString(aExpected) {
       if (!gHasFindClipboard)
         return;
 
       if (!aExpected)
         aExpected = "";
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -1214,17 +1214,17 @@ extends="chrome://global/content/binding
         </xul:hbox>
         <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected"
                    class="ac-ellipsis-after ac-comment"/>
         <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true">
           <xul:image class="ac-result-type-tag"/>
           <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/>
           <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
         </xul:hbox>
-        <xul:image anonid="type-image" class="ac-type-icon"/>
+        <xul:image anonid="type-image" class="ac-type-icon" xbl:inherits="selected"/>
       </xul:hbox>
       <xul:hbox align="center" class="ac-url-box">
         <xul:spacer class="ac-site-icon"/>
         <xul:image class="ac-action-icon"/>
         <xul:hbox anonid="url-box" class="ac-url" flex="1"
                   onunderflow="_doUnderflow('_url');"
                   onoverflow="_doOverflow('_url');">
           <xul:description anonid="url" class="ac-normal-text ac-url-text"
@@ -1437,16 +1437,117 @@ extends="chrome://global/content/binding
               // Otherwise, it's plain text
               aDescriptionElement.appendChild(document.createTextNode(text));
             }
           }
           ]]>
         </body>
       </method>
 
+      <!--
+        This will generate an array of emphasis pairs for use with
+        _setUpEmphasisedSections(). Each pair is a tuple (array) that
+        represents a block of text - containing the text of that block, and a
+        boolean for whether that block should have an emphasis styling applied
+        to it.
+
+        These pairs are generated by parsing a localised string (aSourceString)
+        with parameters, in the format that is used by
+        nsIStringBundle.formatStringFromName():
+
+          "textA %1$S textB textC %2$S"
+
+        Or:
+
+          "textA %S"
+
+        Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided
+        replacement strings. These are specified an array of tuples
+        (aReplacements), each containing the replacement text and a boolean for
+        whether that text should have an emphasis styling applied. This is used
+        as a 1-based array - ie, "%1$S" is replaced by the item in the first
+        index of aReplacements, "%2$S" by the second, etc. "%S" will always
+        match the first index.
+      -->
+      <method name="_generateEmphasisPairs">
+        <parameter name="aSourceString"/>
+        <parameter name="aReplacements"/>
+        <body>
+          <![CDATA[
+            let pairs = [];
+
+            // Split on %S, %1$S, %2$S, etc. ie:
+            //   "textA %S"
+            //     becomes ["textA ", "%S"]
+            //   "textA %1$S textB textC %2$S"
+            //     becomes ["textA ", "%1$S", " textB textC ", "%2$S"]
+            let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/);
+
+            for (let part of parts) {
+              // The above regex will actually give us an empty string at the
+              // end - we don't want that, as we don't want to later generate an
+              // empty text node for it.
+              if (part.length === 0)
+                continue;
+
+              // Determine if this token is a replacement token or a normal text
+              // token. If it is a replacement token, we want to extract the
+              // numerical number. However, we still want to match on "$S".
+              let match = part.match(/^%(?:([0-9]+)\$)?S$/);
+
+              if (match) {
+                // "%S" doesn't have a numerical number in it, but will always
+                // be assumed to be 1. Furthermore, the input string specifies
+                // these with a 1-based index, but we want a 0-based index.
+                let index = (match[1] || 1) - 1;
+
+                if (index >= 0 && index < aReplacements.length) {
+                  let replacement = aReplacements[index];
+                  pairs.push([...replacement]);
+                }
+              } else {
+                pairs.push([part, false]);
+              }
+            }
+
+            return pairs;
+          ]]>
+        </body>
+      </method>
+
+      <!--
+        _setUpEmphasisedSections() has the same use as _setUpDescription,
+        except instead of taking a string and highlighting given tokens, it takes
+        an array of pairs generated by _generateEmphasisPairs(). This allows
+        control over emphasising based on specific blocks of text, rather than
+        search for substrings.
+      -->
+      <method name="_setUpEmphasisedSections">
+        <parameter name="aDescriptionElement"/>
+        <parameter name="aTextPairs"/>
+        <body>
+          <![CDATA[
+          // Get rid of all previous text
+          while (aDescriptionElement.hasChildNodes())
+            aDescriptionElement.firstChild.remove();
+
+          for (let [text, emphasise] of aTextPairs) {
+            if (emphasise) {
+              let span = aDescriptionElement.appendChild(
+                document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+              span.className = "ac-emphasize-text";
+              span.textContent = text;
+            } else {
+              aDescriptionElement.appendChild(document.createTextNode(text));
+            }
+          }
+          ]]>
+        </body>
+      </method>
+
       <method name="_adjustAcItem">
         <body>
           <![CDATA[
           let url = this.getAttribute("url");
           let title = this.getAttribute("title");
           let type = this.getAttribute("type");
 
           let emphasiseTitle = true;
@@ -1493,16 +1594,30 @@ extends="chrome://global/content/binding
             [title, searchEngine] = title.split(TITLE_SEARCH_ENGINE_SEPARATOR);
             url = this._stringBundle.formatStringFromName("searchWithEngine", [searchEngine], 1);
 
             // Remove the "search" substring so that the correct style, if any,
             // is applied below.
             types.delete("search");
           }
 
+          // Check if we have an auto-fill URL
+          if (types.has("autofill")) {
+            emphasiseUrl = false;
+
+            let sourceStr = this._stringBundle.GetStringFromName("visitURL");
+            title = this._generateEmphasisPairs(sourceStr, [
+                                                 [trimURL(url), true],
+                                                ]);
+
+            types.delete("autofill");
+          }
+
+          type = [...types].join(" ");
+
           // If we have a tag match, show the tags and icon
           if (type == "tag") {
             // Configure the extra box for tags display
             this._extraBox.hidden = false;
             this._extraBox.childNodes[0].hidden = false;
             this._extraBox.childNodes[1].hidden = true;
             this._extraBox.pack = "end";
             this._titleBox.flex = 1;
@@ -1514,51 +1629,63 @@ extends="chrome://global/content/binding
             // Each tag is split by a comma in an undefined order, so sort it
             let sortedTags = tags.split(",").sort().join(", ");
 
             // Emphasize the matching text in the tags
             this._setUpDescription(this._extra, sortedTags);
 
             // Treat tagged matches as bookmarks for the star
             type = "bookmark";
-          } else if (type == "keyword") {
+          // keyword and favicon type results for search engines
+          // have an extra magnifying glass icon after them
+          } else if (type == "keyword" || type == "search favicon") {
             // Configure the extra box for keyword display
             this._extraBox.hidden = false;
             this._extraBox.childNodes[0].hidden = true;
-            this._extraBox.childNodes[1].hidden = false;
+            // The second child node is ":" and it should be hidden for non keyword types
+            this._extraBox.childNodes[1].hidden = type == "keyword" ? false : true;
             this._extraBox.pack = "start";
             this._titleBox.flex = 0;
 
             // Hide the ellipsis so it doesn't take up space.
             this._titleOverflowEllipsis.hidden = true;
 
-            // Put the parameters next to the title if we have any
-            let search = this.getAttribute("text");
-            let params = "";
-            let paramsIndex = search.indexOf(' ');
-            if (paramsIndex != -1)
-              params = search.substr(paramsIndex + 1);
+            if (type == "keyword") {
+              // Put the parameters next to the title if we have any
+              let search = this.getAttribute("text");
+              let params = "";
+              let paramsIndex = search.indexOf(" ");
+              if (paramsIndex != -1)
+                params = search.substr(paramsIndex + 1);
 
-            // Emphasize the keyword parameters
-            this._setUpDescription(this._extra, params);
+              // Emphasize the keyword parameters
+              this._setUpDescription(this._extra, params);
 
-            // Don't emphasize keyword searches in the title or url
-            this.setAttribute("text", "");
+              // Don't emphasize keyword searches in the title or url
+              this.setAttribute("text", "");
+            }
+            // If the result has the type favicon and a known search provider,
+            // customize it the same way as a keyword result.
+            type = "keyword";
           }
 
           // Give the image the icon style and a special one for the type
           this._typeImage.className = "ac-type-icon" +
             (type ? " ac-result-type-" + type : "");
 
           // Show the url as the title if we don't have a title
           if (title == "")
             title = url;
 
           // Emphasize the matching search terms for the description
-          this._setUpDescription(this._title, title, !emphasiseTitle);
+          if (Array.isArray(title))
+            this._setUpEmphasisedSections(this._title, title);
+          else
+            this._setUpDescription(this._title, title, !emphasiseTitle);
+
           this._setUpDescription(this._url, url, !emphasiseUrl);
 
           // Set up overflow on a timeout because the contents of the box
           // might not have a width yet even though we just changed them
           setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis);
           setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis);
           ]]>
         </body>
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -443,50 +443,34 @@
           if (!this._pluralForm) {
             this._pluralForm = Components.utils.import(
                                "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
           }
           return this._pluralForm;
         ]]></getter>
       </property>
 
-      <method name="_updateMatchesCountWorker">
-        <parameter name="aRes"/>
-        <body><![CDATA[
-          let word = this._findField.value;
-          if (aRes == this.nsITypeAheadFind.FIND_NOTFOUND || !word) {
-            this._foundMatches.hidden = true;
-            this._foundMatches.value = "";
-          } else {
-            let matchesCount = this.browser.finder.requestMatchesCount(
-              word, this._matchesCountLimit, this._findMode == this.FIND_LINKS);
-            window.clearTimeout(this._updateMatchesCountTimeout);
-            this._updateMatchesCountTimeout = null;
-          }
-        ]]></body>
-      </method>
-
       <!--
         - Updates the search match count after each find operation on a new string.
         - @param aRes
         -        the result of the find operation
         -->
       <method name="_updateMatchesCount">
-        <parameter name="aRes"/>
         <body><![CDATA[
           if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
             return;
 
           if (this._updateMatchesCountTimeout) {
             window.clearTimeout(this._updateMatchesCountTimeout);
-            this._updateMatchesCountTimeout = null;
           }
           this._updateMatchesCountTimeout =
-            window.setTimeout(() => this._updateMatchesCountWorker(aRes),
-                              this._matchesCountTimeoutLength);
+            window.setTimeout(() => {
+              this.browser.finder.requestMatchesCount(this._findField.value, this._matchesCountLimit,
+                  this._findMode == this.FIND_LINKS);
+            }, this._matchesCountTimeoutLength);
         ]]></body>
       </method>
 
       <!--
         - Turns highlight on or off.
         - @param aHighlight (boolean)
         -        Whether to turn the highlight on or off
         -->
--- a/toolkit/content/widgets/menulist.xml
+++ b/toolkit/content/widgets/menulist.xml
@@ -588,26 +588,16 @@
               this.open = true;
             }
           }
         ]]>
       </handler>
     </handlers>
   </binding>
 
-  <binding id="menulist-compact" display="xul:menu"
-           extends="chrome://global/content/bindings/menulist.xml#menulist">
-    <content sizetopopup="false">
-      <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
-      <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
-      <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
-      <children includes="menupopup"/>
-    </content>
-  </binding>
-
   <binding id="menulist-description" display="xul:menu"
            extends="chrome://global/content/bindings/menulist.xml#menulist">
     <content sizetopopup="pref">
       <xul:hbox class="menulist-label-box" flex="1">
         <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
         <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
         <xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
       </xul:hbox>
--- a/toolkit/locales/en-US/chrome/global/autocomplete.properties
+++ b/toolkit/locales/en-US/chrome/global/autocomplete.properties
@@ -1,9 +1,12 @@
 # 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/.
 
 # LOCALIZATION NOTE (searchWithEngine): %S will be replaced with
 # the search engine provider's name. This format was chosen because
 # the provider can also end with "Search" (e.g.: MSN Search).
 searchWithEngine = Search with %S
-switchToTab = Switch to tab
\ No newline at end of file
+switchToTab = Switch to tab
+# LOCALIZATION NOTE (visitURL):
+# %S is the URL to visit.
+visitURL = Visit %S
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -6,27 +6,30 @@ this.EXPORTED_SYMBOLS = ["Finder"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Geometry.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "TextToSubURIService",
                                          "@mozilla.org/intl/texttosuburi;1",
                                          "nsITextToSubURI");
 XPCOMUtils.defineLazyServiceGetter(this, "Clipboard",
                                          "@mozilla.org/widget/clipboard;1",
                                          "nsIClipboard");
 XPCOMUtils.defineLazyServiceGetter(this, "ClipboardHelper",
                                          "@mozilla.org/widget/clipboardhelper;1",
                                          "nsIClipboardHelper");
 
+const kHighlightIterationSizeMax = 100;
+
 function Finder(docShell) {
   this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(Ci.nsITypeAheadFind);
   this._fastFind.init(docShell);
 
   this._docShell = docShell;
   this._listeners = [];
   this._previousLink = null;
   this._searchString = null;
@@ -122,42 +125,44 @@ Finder.prototype = {
                                           Ci.nsIClipboard.kFindClipboard,
                                           this._getWindow().document);
   },
 
   set caseSensitive(aSensitive) {
     this._fastFind.caseSensitive = aSensitive;
   },
 
+  _lastFindResult: null,
+
   /**
    * Used for normal search operations, highlights the first match.
    *
    * @param aSearchString String to search for.
    * @param aLinksOnly Only consider nodes that are links for the search.
    * @param aDrawOutline Puts an outline around matched links.
    */
   fastFind: function (aSearchString, aLinksOnly, aDrawOutline) {
-    let result = this._fastFind.find(aSearchString, aLinksOnly);
+    this._lastFindResult = this._fastFind.find(aSearchString, aLinksOnly);
     let searchString = this._fastFind.searchString;
-    this._notify(searchString, result, false, aDrawOutline);
+    this._notify(searchString, this._lastFindResult, false, aDrawOutline);
   },
 
   /**
    * Repeat the previous search. Should only be called after a previous
    * call to Finder.fastFind.
    *
    * @param aFindBackwards Controls the search direction:
    *    true: before current match, false: after current match.
    * @param aLinksOnly Only consider nodes that are links for the search.
    * @param aDrawOutline Puts an outline around matched links.
    */
   findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) {
-    let result = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
+    this._lastFindResult = this._fastFind.findAgain(aFindBackwards, aLinksOnly);
     let searchString = this._fastFind.searchString;
-    this._notify(searchString, result, aFindBackwards, aDrawOutline);
+    this._notify(searchString, this._lastFindResult, aFindBackwards, aDrawOutline);
   },
 
   /**
    * Forcibly set the search string of the find clipboard to the currently
    * selected text in the window, on supported platforms (i.e. OSX).
    */
   setSearchStringToSelection: function() {
     // Find the selected text.
@@ -169,24 +174,28 @@ Finder.prototype = {
     // Empty strings are rather useless to search for.
     if (!searchString.length)
       return null;
 
     this.clipboardSearchString = searchString;
     return searchString;
   },
 
-  highlight: function (aHighlight, aWord) {
-    let found = this._highlight(aHighlight, aWord, null);
+  highlight: Task.async(function* (aHighlight, aWord) {
+    if (this._abortHighlight) {
+      this._abortHighlight();
+    }
+
+    let found = yield this._highlight(aHighlight, aWord, null);
     if (aHighlight) {
       let result = found ? Ci.nsITypeAheadFind.FIND_FOUND
                          : Ci.nsITypeAheadFind.FIND_NOTFOUND;
       this._notify(aWord, result, false, false, false);
     }
-  },
+  }),
 
   enableSelection: function() {
     this._fastFind.setSelectionModeAndRepaint(Ci.nsISelectionController.SELECTION_ON);
     this._restoreOriginalOutline();
   },
 
   removeSelection: function() {
     this._fastFind.collapseSelection();
@@ -257,38 +266,49 @@ Finder.prototype = {
         controller.scrollLine(false);
         break;
       case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
         controller.scrollLine(true);
         break;
     }
   },
 
+  _notifyMatchesCount: function(result) {
+    for (let l of this._listeners) {
+      try {
+        l.onMatchesCountResult(result);
+      } catch (ex) {}
+    }
+  },
+
   requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
+    if (this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
+        this.searchString == "") {
+      return this._notifyMatchesCount({
+        total: 0,
+        current: 0
+      });
+    }
     let window = this._getWindow();
     let result = this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, window);
 
     // Count matches in (i)frames AFTER searching through the main window.
     for (let frame of result._framesToCount) {
       // We've reached our limit; no need to do more work.
       if (result.total == -1 || result.total == aMatchLimit)
         break;
       this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, frame, result);
     }
 
     // The `_currentFound` and `_framesToCount` properties are only used for
     // internal bookkeeping between recursive calls.
     delete result._currentFound;
     delete result._framesToCount;
 
-    for (let l of this._listeners) {
-      try {
-        l.onMatchesCountResult(result);
-      } catch (ex) {}
-    }
+    this._notifyMatchesCount(result);
   },
 
   /**
    * Counts the number of matches for the searched word in the passed window's
    * content.
    * @param aWord
    *        the word to search for.
    * @param aMatchLimit
@@ -317,51 +337,47 @@ Finder.prototype = {
       aStats.total = -1;
       return aStats;
     }
 
     this._collectFrames(aWindow, aStats);
 
     let foundRange = this._fastFind.getFoundRange();
 
-    this._findIterator(aWord, aWindow, aRange => {
-      if (!aLinksOnly || this._rangeStartsInLink(aRange)) {
+    for(let range of this._findIterator(aWord, aWindow)) {
+      if (!aLinksOnly || this._rangeStartsInLink(range)) {
         ++aStats.total;
         if (!aStats._currentFound) {
           ++aStats.current;
           aStats._currentFound = (foundRange &&
-            aRange.startContainer == foundRange.startContainer &&
-            aRange.startOffset == foundRange.startOffset &&
-            aRange.endContainer == foundRange.endContainer &&
-            aRange.endOffset == foundRange.endOffset);
+            range.startContainer == foundRange.startContainer &&
+            range.startOffset == foundRange.startOffset &&
+            range.endContainer == foundRange.endContainer &&
+            range.endOffset == foundRange.endOffset);
         }
       }
       if (aStats.total == aMatchLimit) {
         aStats.total = -1;
-        return false;
+        break;
       }
-    });
+    };
 
     return aStats;
   },
 
   /**
-   * Basic wrapper around nsIFind that provides invoking a callback `aOnFind`
-   * each time an occurence of `aWord` string is found.
+   * Basic wrapper around nsIFind that provides a generator yielding
+   * a range each time an occurence of `aWord` string is found.
    *
    * @param aWord
    *        the word to search for.
    * @param aWindow
    *        the window to search in.
-   * @param aOnFind
-   *        the Function to invoke when a word is found. if Boolean `false` is
-   *        returned, the find operation will be stopped and the Function will
-   *        not be invoked again.
    */
-  _findIterator: function(aWord, aWindow, aOnFind) {
+  _findIterator: function* (aWord, aWindow) {
     let doc = aWindow.document;
     let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
                doc.body : doc.documentElement;
 
     if (!body)
       return;
 
     let searchRange = doc.createRange();
@@ -376,23 +392,44 @@ Finder.prototype = {
     let retRange = null;
 
     let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
                    .createInstance()
                    .QueryInterface(Ci.nsIFind);
     finder.caseSensitive = this._fastFind.caseSensitive;
 
     while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
-      if (aOnFind(retRange) === false)
-        break;
+      yield retRange;
       startPt = retRange.cloneRange();
       startPt.collapse(false);
     }
   },
 
+  _highlightIterator: Task.async(function* (aWord, aWindow, aOnFind) {
+    let count = 0;
+    for (let range of this._findIterator(aWord, aWindow)) {
+      aOnFind(range);
+      if (++count >= kHighlightIterationSizeMax) {
+          count = 0;
+          yield this._highlightSleep(0);
+      }
+    }
+  }),
+
+  _abortHighlight: null,
+  _highlightSleep: function(delay) {
+    return new Promise((resolve, reject) => {
+      this._abortHighlight = () => {
+        this._abortHighlight = null;
+        reject();
+      };
+      this._getWindow().setTimeout(resolve, delay);
+    });
+  },
+
   /**
    * Helper method for `_countMatchesInWindow` that recursively collects all
    * visible (i)frames inside a window.
    *
    * @param aWindow
    *        the window to extract the (i)frames from.
    * @param aStats
    *        Object that contains a Set called '_framesToCount'
@@ -516,35 +553,35 @@ Finder.prototype = {
     // Removes the outline around the last found link.
     if (this._previousLink) {
       this._previousLink.style.outline = this._tmpOutline;
       this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
       this._previousLink = null;
     }
   },
 
-  _highlight: function (aHighlight, aWord, aWindow) {
+  _highlight: Task.async(function* (aHighlight, aWord, aWindow) {
     let win = aWindow || this._getWindow();
 
     let found = false;
     for (let i = 0; win.frames && i < win.frames.length; i++) {
-      if (this._highlight(aHighlight, aWord, win.frames[i]))
+      if (yield this._highlight(aHighlight, aWord, win.frames[i]))
         found = true;
     }
 
     let controller = this._getSelectionController(win);
     let doc = win.document;
     if (!controller || !doc || !doc.documentElement) {
       // Without the selection controller,
       // we are unable to (un)highlight any matches
       return found;
     }
 
     if (aHighlight) {
-      this._findIterator(aWord, win, aRange => {
+      yield this._highlightIterator(aWord, win, aRange => {
         this._highlightRange(aRange, controller);
         found = true;
       });
     } else {
       // First, attempt to remove highlighting from main document
       let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
       sel.removeAllRanges();
 
@@ -562,17 +599,17 @@ Finder.prototype = {
         }
       }
 
       // Removing the highlighting always succeeds, so return true.
       found = true;
     }
 
     return found;
-  },
+  }),
 
   _highlightRange: function(aRange, aController) {
     let node = aRange.startContainer;
     let controller = aController;
 
     let editableNode = this._getEditableNode(node);
     if (editableNode)
       controller = editableNode.editor.selectionController;
--- a/toolkit/themes/linux/global/autocomplete.css
+++ b/toolkit/themes/linux/global/autocomplete.css
@@ -112,16 +112,25 @@ treechildren.autocomplete-treebody::-moz
   color: HighlightText;
 }
 
 .autocomplete-richlistitem {
   padding: 6px 2px;
   color: MenuText;
 }
 
+.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+  display: none;
+}
+
+.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+  margin-top: 12px;
+  margin-bottom: 12px;
+}
+
 .ac-url-box {
   margin-top: 1px;
 }
 
 .ac-site-icon {
   width: 16px; 
   height: 16px;
   margin-bottom: -2px;
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/linux/global/icons/autocomplete-search.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     x="0px" y="0px"
+     viewBox="0 0 16 16"
+     enable-background="new 0 0 16 16"
+     xml:space="preserve">
+<style>
+  use:not(:target) {
+    display: none;
+  }
+
+  use {
+    fill: GrayText;
+  }
+
+  use[id$="-inverted"] {
+    fill: highlighttext;
+  }
+</style>
+<defs style="display:none">
+  <path id="search" fill-rule="evenodd" clip-rule="evenodd" d="M9.356,1.178c-3.014,0-5.458,2.45-5.458,5.472c0,1.086,0.32,2.096,0.864,2.947
+    l-3.279,3.287c-0.396,0.397-0.396,1.041,0,1.438l0.202,0.202c0.396,0.397,1.039,0.397,1.435,0l3.275-3.283
+    c0.854,0.554,1.869,0.88,2.962,0.88c3.014,0,5.458-2.45,5.458-5.471C14.814,3.627,12.371,1.178,9.356,1.178z M9.356,10.001
+    c-1.847,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352c1.846,0,3.344,1.501,3.344,3.352
+    C12.7,8.501,11.203,10.001,9.356,10.001z"/>
+</defs>
+<use id="search-icon"             xlink:href="#search"/>
+<use id="search-icon-inverted"    xlink:href="#search"/>
+</svg>
--- a/toolkit/themes/linux/global/jar.mn
+++ b/toolkit/themes/linux/global/jar.mn
@@ -34,16 +34,17 @@ toolkit.jar:
 +  skin/classic/global/toolbarbutton.css
 +  skin/classic/global/tree.css
 +  skin/classic/global/alerts/alert.css                        (alerts/alert.css)
 +  skin/classic/global/alerts/notification-48.png              (alerts/notification-48.png)
 +  skin/classic/global/console/console.css                     (console/console.css)
 +  skin/classic/global/console/console.png                     (console/console.png)
 +  skin/classic/global/console/console-toolbar.png             (console/console-toolbar.png)
 +  skin/classic/global/dirListing/remote.png                   (dirListing/remote.png)
++  skin/classic/global/icons/autocomplete-search.svg           (icons/autocomplete-search.svg)
 +  skin/classic/global/icons/Authentication.png                (icons/Authentication.png)
 +  skin/classic/global/icons/autoscroll.png                    (icons/autoscroll.png)
 +  skin/classic/global/icons/blacklist_favicon.png             (icons/blacklist_favicon.png)
 +  skin/classic/global/icons/blacklist_large.png               (icons/blacklist_large.png)
 +  skin/classic/global/icons/close.svg                         (icons/close.svg)
 +  skin/classic/global/icons/find.png                          (icons/find.png)
 +  skin/classic/global/icons/loading_16.png                    (icons/loading_16.png)
 +  skin/classic/global/icons/panelarrow-horizontal.svg         (icons/panelarrow-horizontal.svg)
--- a/toolkit/themes/osx/global/autocomplete.css
+++ b/toolkit/themes/osx/global/autocomplete.css
@@ -97,16 +97,25 @@ treechildren.autocomplete-treebody::-moz
   color: HighlightText;
   background-image: linear-gradient(rgba(255,255,255,0.3), transparent);
 }
 
 .autocomplete-richlistitem {
   padding: 5px 2px;
 }
 
+.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+  display: none;
+}
+
+.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+  margin-top: 12px;
+  margin-bottom: 12px;
+}
+
 .ac-url-box {
   margin-top: 1px;
 }
 
 .ac-site-icon {
   width: 16px; 
   height: 16px;
   margin-bottom: -1px;
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/autocomplete-search.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     x="0px" y="0px"
+     viewBox="0 0 16 16"
+     enable-background="new 0 0 16 16"
+     xml:space="preserve">
+<style>
+  use:not(:target) {
+    display: none;
+  }
+
+  use {
+    fill: GrayText;
+  }
+
+  use[id$="-inverted"] {
+    fill: highlighttext;
+  }
+</style>
+<defs style="display:none">
+  <path id="search" fill-rule="evenodd" clip-rule="evenodd" d="M14.517,12.884l-3.279-3.287c0.545-0.851,0.864-1.861,0.864-2.947
+    c0-3.022-2.444-5.472-5.458-5.472c-3.014,0-5.458,2.45-5.458,5.472c0,3.022,2.444,5.471,5.458,5.471
+    c1.093,0,2.108-0.325,2.962-0.88l3.275,3.283c0.396,0.397,1.039,0.397,1.435,0l0.202-0.202
+    C14.913,13.925,14.913,13.281,14.517,12.884z M6.644,10.001c-1.846,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352
+    c1.847,0,3.344,1.501,3.344,3.352C9.987,8.501,8.49,10.001,6.644,10.001z"/>
+</defs>
+<use id="search-icon"             xlink:href="#search"/>
+<use id="search-icon-inverted"    xlink:href="#search"/>
+</svg>
--- a/toolkit/themes/osx/global/jar.mn
+++ b/toolkit/themes/osx/global/jar.mn
@@ -87,16 +87,17 @@ toolkit.jar:
   skin/classic/global/console/console-error-dash.gif                 (console/console-error-dash.gif)
 * skin/classic/global/console/console.css                            (console/console.css)
   skin/classic/global/dirListing/dirListing.css                      (dirListing/dirListing.css)
   skin/classic/global/dirListing/folder.png                          (dirListing/folder.png)
   skin/classic/global/dirListing/local.png                           (dirListing/folder.png)
   skin/classic/global/dirListing/remote.png                          (dirListing/remote.png)
   skin/classic/global/dirListing/up.png                              (dirListing/up.png)
   skin/classic/global/icons/autocomplete-dropmarker.png              (icons/autocomplete-dropmarker.png)
+  skin/classic/global/icons/autocomplete-search.svg                  (icons/autocomplete-search.svg)
   skin/classic/global/icons/autoscroll.png                           (icons/autoscroll.png)
   skin/classic/global/icons/blacklist_favicon.png                    (icons/blacklist_favicon.png)
   skin/classic/global/icons/blacklist_64.png                         (icons/blacklist_64.png)
   skin/classic/global/icons/chevron.png                              (icons/chevron.png)
   skin/classic/global/icons/chevron@2x.png                           (icons/chevron@2x.png)
   skin/classic/global/icons/checkbox.png                             (icons/checkbox.png)
   skin/classic/global/icons/checkbox@2x.png                          (icons/checkbox@2x.png)
   skin/classic/global/icons/close.png                                (icons/close.png)
@@ -137,17 +138,17 @@ toolkit.jar:
   skin/classic/global/icons/question-24.png                          (icons/question-24.png)
   skin/classic/global/icons/question-32.png                          (icons/question-32.png)
   skin/classic/global/icons/question-64.png                          (icons/question-64.png)
   skin/classic/global/icons/question-large.png                       (icons/question-large.png)
   skin/classic/global/icons/sslWarning.png                           (icons/sslWarning.png)
   skin/classic/global/icons/webapps-16.png                           (icons/webapps-16.png)
   skin/classic/global/icons/webapps-16@2x.png                        (icons/webapps-16@2x.png)
   skin/classic/global/icons/webapps-64.png                           (icons/webapps-64.png)
-  skin/classic/global/inContentUI/background-texture.png            (inContentUI/background-texture.png)
+  skin/classic/global/inContentUI/background-texture.png             (inContentUI/background-texture.png)
   skin/classic/global/notification/close.png                         (notification/close.png)
   skin/classic/global/notification/critical-bar-background.png       (notification/critical-bar-background.png)
   skin/classic/global/notification/error-icon.png                    (notification/error-icon.png)
   skin/classic/global/notification/info-bar-background.png           (notification/info-bar-background.png)
   skin/classic/global/notification/info-icon.png                     (notification/info-icon.png)
   skin/classic/global/notification/warning-bar-background.png        (notification/warning-bar-background.png)
   skin/classic/global/notification/warning-icon.png                  (notification/warning-icon.png)
   skin/classic/global/media/TopLevelImageDocument.css                (media/TopLevelImageDocument.css)
--- a/toolkit/themes/windows/global/autocomplete.css
+++ b/toolkit/themes/windows/global/autocomplete.css
@@ -125,16 +125,25 @@ treechildren.autocomplete-treebody::-moz
     border-radius: 6px;
     outline: 1px solid rgb(124,163,206);
     -moz-outline-radius: 3px;
     outline-offset: -2px;
   }
 }
 %endif
 
+.autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+  display: none;
+}
+
+.autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+  margin-top: 12px;
+  margin-bottom: 12px;
+}
+
 .ac-title-box {
   margin-top: 4px;
 }
 
 .ac-url-box {
   margin: 1px 0 4px;
 }
 
--- a/toolkit/themes/windows/global/globalBindings.xml
+++ b/toolkit/themes/windows/global/globalBindings.xml
@@ -47,20 +47,9 @@
            extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
     <content>
       <xul:spacer class="spacer-left"/>
       <children/>
       <xul:spacer class="spacer-right"/>
     </content>
   </binding>
 
-  <binding id="menulist-compact" display="xul:menu" 
-           extends="chrome://global/content/bindings/menulist.xml#menulist">
-    <content sizetopopup="false">
-      <xul:hbox class="menulist-label-box" flex="1">
-        <xul:image class="menulist-icon" xbl:inherits="src"/>
-        <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
-      </xul:hbox>
-      <children includes="menupopup"/>
-    </content>
-  </binding>
-
 </bindings>
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/windows/global/icons/autocomplete-search.svg
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     x="0px" y="0px"
+     viewBox="0 0 16 16"
+     enable-background="new 0 0 16 16"
+     xml:space="preserve">
+<style>
+  use:not(:target) {
+    display: none;
+  }
+
+  use {
+    fill: GrayText;
+  }
+
+  use[id$="-inverted"] {
+    fill: highlighttext;
+  }
+</style>
+<defs style="display:none">
+  <path id="search" fill-rule="evenodd" clip-rule="evenodd" d="M9.356,1.178c-3.014,0-5.458,2.45-5.458,5.472c0,1.086,0.32,2.096,0.864,2.947
+    l-3.279,3.287c-0.396,0.397-0.396,1.041,0,1.438l0.202,0.202c0.396,0.397,1.039,0.397,1.435,0l3.275-3.283
+    c0.854,0.554,1.869,0.88,2.962,0.88c3.014,0,5.458-2.45,5.458-5.471C14.814,3.627,12.371,1.178,9.356,1.178z M9.356,10.001
+    c-1.847,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352c1.846,0,3.344,1.501,3.344,3.352
+    C12.7,8.501,11.203,10.001,9.356,10.001z"/>
+</defs>
+<use id="search-icon"             xlink:href="#search"/>
+<use id="search-icon-inverted"    xlink:href="#search"/>
+</svg>
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -94,16 +94,17 @@ toolkit.jar:
         skin/classic/global/console/itemSelected.png             (console/itemSelected.png)
 *       skin/classic/global/dirListing/dirListing.css            (dirListing/dirListing.css)
         skin/classic/global/dirListing/folder.png                (dirListing/folder.png)
         skin/classic/global/dirListing/local.png                 (dirListing/local.png)
         skin/classic/global/dirListing/remote.png                (dirListing/remote.png)
         skin/classic/global/dirListing/up.png                    (dirListing/up.png)
         skin/classic/global/Filepicker.png                       (filepicker/Filepicker.png)
         skin/classic/global/icons/autoscroll.png                 (icons/autoscroll.png)
+        skin/classic/global/icons/autocomplete-search.svg        (icons/autocomplete-search.svg)
         skin/classic/global/icons/blacklist_favicon.png          (icons/blacklist_favicon.png)
         skin/classic/global/icons/blacklist_large.png            (icons/blacklist_large.png)
         skin/classic/global/icons/Close.gif                      (icons/Close.gif)
         skin/classic/global/icons/close.png                      (icons/close.png)
         skin/classic/global/icons/close-lunaBlue.png             (icons/close-lunaBlue.png)
         skin/classic/global/icons/close-lunaOlive.png            (icons/close-lunaOlive.png)
         skin/classic/global/icons/close-lunaSilver.png           (icons/close-lunaSilver.png)
         skin/classic/global/icons/collapse.png                   (icons/collapse.png)
@@ -291,16 +292,17 @@ toolkit.jar:
         skin/classic/aero/global/console/itemSelected.png                (console/itemSelected.png)
 *       skin/classic/aero/global/dirListing/dirListing.css               (dirListing/dirListing.css)
         skin/classic/aero/global/dirListing/folder.png                   (dirListing/folder-aero.png)
         skin/classic/aero/global/dirListing/local.png                    (dirListing/local-aero.png)
         skin/classic/aero/global/dirListing/remote.png                   (dirListing/remote-aero.png)
         skin/classic/aero/global/dirListing/up.png                       (dirListing/up-aero.png)
         skin/classic/aero/global/Filepicker.png                          (filepicker/Filepicker.png)
         skin/classic/aero/global/icons/autoscroll.png                    (icons/autoscroll-aero.png)
+        skin/classic/aero/global/icons/autocomplete-search.svg           (icons/autocomplete-search.svg)
         skin/classic/aero/global/icons/blacklist_favicon.png             (icons/blacklist_favicon-aero.png)
         skin/classic/aero/global/icons/blacklist_large.png               (icons/blacklist_large-aero.png)
         skin/classic/aero/global/icons/Close.gif                         (icons/Close.gif)
         skin/classic/aero/global/icons/close.png                         (icons/close.png)
         skin/classic/aero/global/icons/collapse.png                      (icons/collapse.png)
         skin/classic/aero/global/icons/Error.png                         (icons/Error-aero.png)
         skin/classic/aero/global/icons/error-16.png                      (icons/error-16-aero.png)
         skin/classic/aero/global/icons/error-24.png                      (icons/error-24-aero.png)
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -9,16 +9,17 @@
 #include "nsQAppInstance.h"
 #endif // MOZ_WIDGET_QT
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/ChaosMode.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Poison.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include "nsAppRunner.h"
 #include "mozilla/AppData.h"
@@ -2982,16 +2983,20 @@ int
 XREMain::XRE_mainInit(bool* aExitFlag)
 {
   if (!aExitFlag)
     return 1;
   *aExitFlag = false;
 
   StartupTimeline::Record(StartupTimeline::MAIN);
 
+  if (ChaosMode::isActive()) {
+    printf_stderr("*** You are running in chaos test mode. See ChaosMode.h. ***\n");
+  }
+
   nsresult rv;
   ArgResult ar;
 
 #ifdef DEBUG
   if (PR_GetEnv("XRE_MAIN_BREAK"))
     NS_BREAK();
 #endif