Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 03 Dec 2013 17:42:20 -0500
changeset 174306 8dfe4e73db8ee85a82589d2ab1c84b51e2ba0368
parent 174305 38ddf36afa9d7c40745c7ba556c92099e9ef82a7 (current diff)
parent 174259 9ac7ed427cd22058835b3f6bb700b38e41725756 (diff)
child 174307 23103305dbed3ac0d7250031f6d75ee5da8e67da
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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 inbound.
dom/bluetooth/BluetoothOppManager.cpp
dom/bluetooth/BluetoothOppManager.h
dom/bluetooth/BluetoothSocket.cpp
dom/bluetooth/BluetoothUnixSocketConnector.cpp
dom/bluetooth/BluetoothUnixSocketConnector.h
mobile/android/base/LocaleManager.java
toolkit/modules/tests/browser/browser_DeferredTask.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-More Windows webidl changes 
+Bug 915533 - Remove unused files under dom/bluetooth.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -843,10 +843,13 @@ pref("b2g.neterror.url", "app://system.g
 
 // Enable Web Speech synthesis API
 pref("media.webspeech.synth.enabled", true);
 
 // Downloads API
 pref("dom.mozDownloads.enabled", true);
 pref("dom.downloads.max_retention_days", 7);
 
+// Inactivity time in milliseconds after which we shut down the OS.File worker.
+pref("osfile.reset_worker_delay", 5000);
+
 // The URL of the Firefox Accounts auth server backend
 pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1260,80 +1260,109 @@ window.addEventListener('ContentStart', 
       channel: aData
     });
     shell.visibleNormalAudioActive = (aData == 'normal');
 }, "visible-audio-channel-changed", false);
 })();
 
 (function recordingStatusTracker() {
   // Recording status is tracked per process with following data structure:
-  // {<processId>: {count: <N>,
-  //                requestURL: <requestURL>,
-  //                isApp: <isApp>,
-  //                audioCount: <N>,
-  //                videoCount: <N>}}
+  // {<processId>: {<requestURL>: {isApp: <isApp>,
+  //                               count: <N>,
+  //                               audioCount: <N>,
+  //                               videoCount: <N>}}
   let gRecordingActiveProcesses = {};
 
   let recordingHandler = function(aSubject, aTopic, aData) {
     let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
     let processId = (props.hasKey('childID')) ? props.get('childID')
                                               : 'main';
     if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) {
-      gRecordingActiveProcesses[processId] = {count: 0,
-                                              requestURL: props.get('requestURL'),
-                                              isApp: props.get('isApp'),
-                                              audioCount: 0,
-                                              videoCount: 0 };
+      gRecordingActiveProcesses[processId] = {};
     }
 
-    let currentActive = gRecordingActiveProcesses[processId];
-    let wasActive = (currentActive['count'] > 0);
-    let wasAudioActive = (currentActive['audioCount'] > 0);
-    let wasVideoActive = (currentActive['videoCount'] > 0);
+    let commandHandler = function (requestURL, command) {
+      let currentProcess = gRecordingActiveProcesses[processId];
+      let currentActive = currentProcess[requestURL];
+      let wasActive = (currentActive['count'] > 0);
+      let wasAudioActive = (currentActive['audioCount'] > 0);
+      let wasVideoActive = (currentActive['videoCount'] > 0);
+
+      switch (command.type) {
+        case 'starting':
+          currentActive['count']++;
+          currentActive['audioCount'] += (command.isAudio) ? 1 : 0;
+          currentActive['videoCount'] += (command.isVideo) ? 1 : 0;
+          break;
+        case 'shutdown':
+          currentActive['count']--;
+          currentActive['audioCount'] -= (command.isAudio) ? 1 : 0;
+          currentActive['videoCount'] -= (command.isVideo) ? 1 : 0;
+          break;
+        case 'content-shutdown':
+          currentActive['count'] = 0;
+          currentActive['audioCount'] = 0;
+          currentActive['videoCount'] = 0;
+          break;
+      }
+
+      if (currentActive['count'] > 0) {
+        currentProcess[requestURL] = currentActive;
+      } else {
+        delete currentProcess[requestURL];
+      }
+
+      // We need to track changes if any active state is changed.
+      let isActive = (currentActive['count'] > 0);
+      let isAudioActive = (currentActive['audioCount'] > 0);
+      let isVideoActive = (currentActive['videoCount'] > 0);
+      if ((isActive != wasActive) ||
+          (isAudioActive != wasAudioActive) ||
+          (isVideoActive != wasVideoActive)) {
+        shell.sendChromeEvent({
+          type: 'recording-status',
+          active: isActive,
+          requestURL: requestURL,
+          isApp: currentActive['isApp'],
+          isAudio: isAudioActive,
+          isVideo: isVideoActive
+        });
+      }
+    };
 
     switch (aData) {
       case 'starting':
-        currentActive['count']++;
-        currentActive['audioCount'] += (props.get('isAudio')) ? 1 : 0;
-        currentActive['videoCount'] += (props.get('isVideo')) ? 1 : 0;
-        break;
       case 'shutdown':
-        currentActive['count']--;
-        currentActive['audioCount'] -= (props.get('isAudio')) ? 1 : 0;
-        currentActive['videoCount'] -= (props.get('isVideo')) ? 1 : 0;
+        // create page record if it is not existed yet.
+        let requestURL = props.get('requestURL');
+        if (requestURL &&
+            !gRecordingActiveProcesses[processId].hasOwnProperty(requestURL)) {
+          gRecordingActiveProcesses[processId][requestURL] = {isApp: props.get('isApp'),
+                                                              count: 0,
+                                                              audioCount: 0,
+                                                              videoCount: 0};
+        }
+        commandHandler(requestURL, { type: aData,
+                                     isAudio: props.get('isAudio'),
+                                     isVideo: props.get('isVideo')});
         break;
       case 'content-shutdown':
-        currentActive['count'] = 0;
-        currentActive['audioCount'] = 0;
-        currentActive['videoCount'] = 0;
+        // iterate through all the existing active processes
+        Object.keys(gRecordingActiveProcesses[processId]).foreach(function(requestURL) {
+          commandHandler(requestURL, { type: aData,
+                                       isAudio: true,
+                                       isVideo: true});
+        });
         break;
     }
 
-    if (currentActive['count'] > 0) {
-      gRecordingActiveProcesses[processId] = currentActive;
-    } else {
+    // clean up process record if no page record in it.
+    if (Object.keys(gRecordingActiveProcesses[processId]).length == 0) {
       delete gRecordingActiveProcesses[processId];
     }
-
-    // We need to track changes if any active state is changed.
-    let isActive = (currentActive['count'] > 0);
-    let isAudioActive = (currentActive['audioCount'] > 0);
-    let isVideoActive = (currentActive['videoCount'] > 0);
-    if ((isActive != wasActive) ||
-        (isAudioActive != wasAudioActive) ||
-        (isVideoActive != wasVideoActive)) {
-      shell.sendChromeEvent({
-        type: 'recording-status',
-        active: isActive,
-        requestURL: currentActive['requestURL'],
-        isApp: currentActive['isApp'],
-        isAudio: isAudioActive,
-        isVideo: isVideoActive
-      });
-    }
   };
   Services.obs.addObserver(recordingHandler, 'recording-device-events', false);
   Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false);
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     // send additional recording events if content process is being killed
     let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID');
     if (gRecordingActiveProcesses.hasOwnProperty(processId)) {
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "001672effec8835928519312c1075a842af9166c", 
+    "revision": "428cccdc7a3b4c40f40dda80b6031d26ac9d2d68", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/releng-hamachi.tt
+++ b/b2g/config/hamachi/releng-hamachi.tt
@@ -1,12 +1,12 @@
 [
 {
-"size": 85358580,
-"digest": "e26e3501b4119724f94d5d3edcdfefde00faceddab3114c9c3b118fec11118d6f2293ea3c25426ec5ba7c7e0fe14d8349dbb8fa940e2f04ea83e26694d933696",
+"size": 55073212,
+"digest": "10560463c0804186fc59a84ecab4d5ccd1ffdf298336d3b91065c38888698f4cfab4bad743d6170e73f9bc0538f47a96baf5f1381e8b4ab484d8721301bea31e",
 "algorithm": "sha512",
 "filename": "backup-hamachi.tar.xz"
 },
 {
 "size": 1570553,
 "digest": "ea03de74df73b05e939c314cd15c54aac7b5488a407b7cc4f5f263f3049a1f69642c567dd35c43d0bc3f0d599d0385a26ab2dd947a6b18f9044e4918b382eea7",
 "algorithm": "sha512",
 "filename": "Adreno200-AU_LINUX_ANDROID_ICS_CHOCO_CS.04.00.03.06.001.zip"
--- a/b2g/config/helix/releng-helix.tt
+++ b/b2g/config/helix/releng-helix.tt
@@ -1,12 +1,12 @@
 [
 {
-"size": 60589756,
-"digest": "29f6a7f09edbcc777ab155de64b4c7ff8c79ea25021bacc95a44d2ee8b7b13caa72991294b05dac1d841128a90eced3f52bcc3399ead4452f6170742e0e52fc1",
+"size": 58958768,
+"digest": "a1c7727075b481259ee1835d0f016834f52b8a20dd18be6e0dbf265421671e2fc5203ff01891588ab712f5033103350574528dae866ea795460518afa78b0c4b",
 "algorithm": "sha512",
 "filename": "helix-ics.tar.xz"
 },
 {
 "size": 1570553,
 "digest": "ea03de74df73b05e939c314cd15c54aac7b5488a407b7cc4f5f263f3049a1f69642c567dd35c43d0bc3f0d599d0385a26ab2dd947a6b18f9044e4918b382eea7",
 "algorithm": "sha512",
 "filename": "Adreno200-AU_LINUX_ANDROID_ICS_CHOCO_CS.04.00.03.06.001.zip"
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -60,15 +60,15 @@ endif
 
 ifdef MOZ_PKG_MANIFEST_P
 $(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) FORCE
 	$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) $< -o $@)
 ifdef MOZ_CHROME_MULTILOCALE
 	printf '\n[multilocale]\n' >> $@
 	for LOCALE in $(MOZ_CHROME_MULTILOCALE) ;\
 	do \
-	  printf '$(BINPATH)/chrome/$$LOCALE$(JAREXT)\n' >> $@; \
-	  printf '$(BINPATH)/chrome/$$LOCALE.manifest\n' >> $@; \
+	  printf '$(BINPATH)/chrome/'"$$LOCALE"'$(JAREXT)\n' >> $@; \
+	  printf '$(BINPATH)/chrome/'"$$LOCALE"'.manifest\n' >> $@; \
 	done
 endif
 
 GARBAGE += $(MOZ_PKG_MANIFEST)
 endif
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -16,22 +16,25 @@
 
       <footer id="PanelUI-footer">
         <!-- The parentNode is used so that the footer is presented as the anchor
              instead of just the button being the anchor. -->
         <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
                        exitLabel="&appMenuCustomizeExit.label;" tabindex="0"
                        oncommand="gCustomizeMode.toggle();"/>
         <toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
+                       tooltiptext="&helpMenu.label;"
                        oncommand="PanelUI.showHelpView(this.parentNode);"/>
         <toolbarbutton id="PanelUI-quit" tabindex="0"
 #ifdef XP_WIN
                        label="&quitApplicationCmdWin.label;"
+                       tooltiptext="&quitApplicationCmdWin.label;"
 #else
                        label="&quitApplicationCmd.label;"
+                       tooltiptext="&quitApplicationCmd.label;"
 #endif
                        command="cmd_quitApplication"/>
       </footer>
     </panelview>
 
     <panelview id="PanelUI-history" flex="1">
       <label value="&appMenuHistory.label;"/>
       <toolbarbutton id="appMenuClearRecentHistory" tabindex="0"
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -420,16 +420,20 @@
               // out if the user has customized things / we've been here before:
               if (!this._whiteListed.has(node.id)) {
                 node.setAttribute("removable", "true");
               }
               children.push(node);
             }
           }
           CustomizableUI.registerToolbarNode(this, children);
+          let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
+          for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
+            this._currentSetMigrated.add(migratedItem);
+          }
           this.evictNodes();
           // We can't easily use |this| or strong bindings for the observer fn here
           // because that creates leaky circular references when the node goes away,
           // and XBL destructors are unreliable.
           let mutationObserver = new MutationObserver(function(mutations) {
             if (!mutations.length) {
               return;
             }
@@ -452,28 +456,30 @@
             let node = this.childNodes[i];
             if (this.childNodes[i].id) {
               this.evictNode(this.childNodes[i]);
             } else {
               node.remove();
             }
           }
           this._isModifying = false;
+          this._updateMigratedSet();
         ]]></body>
       </method>
       <method name="evictNode">
         <parameter name="aNode"/>
         <body>
         <![CDATA[
           if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
             return;
           }
           const kItemMaxWidth = 100;
           let oldParent = aNode.parentNode;
           aNode.setAttribute("removable", "true");
+          this._currentSetMigrated.add(aNode.id);
 
           let movedOut = false;
           if (!this._wasCollapsed) {
             try {
               let nodeWidth = aNode.getBoundingClientRect().width;
               if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
                 throw new Error(aNode.id + " is too big (" + nodeWidth +
                                 "px wide), moving to the palette");
@@ -526,42 +532,58 @@
             return null;
           }
 
           this._isModifying = true;
           // Temporarily add it here so it can have a width, then ditch it:
           this.appendChild(node);
           this.evictNode(node);
           this._isModifying = false;
+          this._updateMigratedSet();
           // We will now have moved stuff around; kick off an aftercustomization event
           // so add-ons know we've just moved their stuff:
           if (window.gCustomizeMode) {
             window.gCustomizeMode.dispatchToolboxEvent("aftercustomization");
           }
           return node;
         ]]></body>
       </method>
+      <method name="getMigratedItems">
+        <body><![CDATA[
+          return [... this._currentSetMigrated];
+        ]]></body>
+      </method>
+      <method name="_updateMigratedSet">
+        <body><![CDATA[
+          let newMigratedItems = this.getMigratedItems().join(',');
+          if (this.getAttribute("migratedset") != newMigratedItems) {
+            this.setAttribute("migratedset", newMigratedItems);
+            this.ownerDocument.persist(this.id, "migratedset");
+          }
+        ]]></body>
+      </method>
       <property name="customizationTarget" readonly="true">
         <getter><![CDATA[
           return this;
         ]]></getter>
       </property>
       <property name="currentSet">
         <getter><![CDATA[
           return [node.id for (node of this.children)].join(',');
         ]]></getter>
         <setter><![CDATA[
           let v = val.split(',');
           let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
                                                !CustomizableUI.isSpecialWidget(x) &&
                                                !this._currentSetMigrated.has(x)));
-          for (x of newButtons) {
-            this._currentSetMigrated.add(x);
-            this.insertItem(x);
+          for (let newButton of newButtons) {
+            this._currentSetMigrated.add(newButton);
+            this.insertItem(newButton);
           }
+          this._updateMigratedSet();
         ]]></setter>
       </property>
       <property name="toolbox" readonly="true">
         <getter><![CDATA[
           if (!this._toolbox && this.parentNode &&
               this.parentNode.localName == "toolbox") {
             this._toolbox = this.parentNode;
           }
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -92,16 +92,23 @@ let gSeenWidgets = new Set();
 /**
  * gDirtyAreaCache is a set of area IDs for areas where items have been added,
  * moved or removed at least once. This set is persisted, and is used to
  * optimize building of toolbars in the default case where no toolbars should
  * be "dirty".
  */
 let gDirtyAreaCache = new Set();
 
+/**
+ * gPendingBuildAreas is a map from area IDs to map from build nodes to their
+ * existing children at the time of node registration, that are waiting
+ * for the area to be registered
+ */
+let gPendingBuildAreas = new Map();
+
 let gSavedState = null;
 let gRestoring = false;
 let gDirty = false;
 let gInBatchStack = 0;
 let gResetting = false;
 
 /**
  * gBuildAreas maps area IDs to actual area nodes within browser windows.
@@ -139,33 +146,19 @@ let CustomizableUIInternal = {
       "print-button",
       "history-panelmenu",
       "fullscreen-button",
       "find-button",
       "preferences-button",
       "add-ons-button"
     ];
 
-#ifdef XP_WIN
-#ifdef MOZ_METRO
-    // Show switch-to-metro-button if in Windows 8.
-    let isMetroCapable = false;
-    try {
-      // Windows 8 is version 6.2.
-      let version = Cc["@mozilla.org/system-info;1"]
-                      .getService(Ci.nsIPropertyBag2)
-                      .getProperty("version");
-      isMetroCapable = (parseFloat(version) >= 6.2);
-    } catch (ex) { }
-
-    if (isMetroCapable) {
+    if (gPalette.has("switch-to-metro-button")) {
       panelPlacements.push("switch-to-metro-button");
     }
-#endif
-#endif
 
     let showCharacterEncoding = Services.prefs.getComplexValue(
       "browser.menu.showCharacterEncoding",
       Ci.nsIPrefLocalizedString
     ).data;
     if (showCharacterEncoding == "true") {
       panelPlacements.push("characterencoding-button");
     }
@@ -258,38 +251,66 @@ let CustomizableUIInternal = {
     gGroupWrapperCache.set(aWidgetId, wrapper);
     return wrapper;
   },
 
   registerArea: function(aName, aProperties) {
     if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
       throw new Error("Invalid area name");
     }
-    if (gAreas.has(aName)) {
-      throw new Error("Area already registered");
-    }
 
-    let props = new Map();
+    let areaIsKnown = gAreas.has(aName);
+    let props = areaIsKnown ? gAreas.get(aName) : new Map();
     for (let key in aProperties) {
       //XXXgijs for special items, we need to make sure they have an appropriate ID
       // so we aren't perpetually in a non-default state:
       if (key == "defaultPlacements" && Array.isArray(aProperties[key])) {
         props.set(key, aProperties[key].map(x => this.isSpecialWidget(x) ? this.ensureSpecialWidgetId(x) : x ));
       } else {
         props.set(key, aProperties[key]);
       }
     }
-    gAreas.set(aName, props);
+    // Default to a toolbar:
+    if (!props.has("type")) {
+      props.set("type", CustomizableUI.TYPE_TOOLBAR);
+    }
+    // Sanity check type:
+    let allTypes = [CustomizableUI.TYPE_TOOLBAR, CustomizableUI.TYPE_MENU_PANEL];
+    if (allTypes.indexOf(props.get("type")) == -1) {
+      throw new Error("Invalid area type " + props.get("type"));
+    }
+
+    // And to no placements:
+    if (!props.has("defaultPlacements")) {
+      props.set("defaultPlacements", []);
+    }
+    // Sanity check default placements array:
+    if (!Array.isArray(props.get("defaultPlacements"))) {
+      throw new Error("Should provide an array of default placements");
+    }
 
-    if (props.get("legacy")) {
-      // Guarantee this area exists in gFuturePlacements, to avoid checking it in
-      // various places elsewhere.
-      gFuturePlacements.set(aName, new Set());
-    } else {
-      this.restoreStateForArea(aName);
+    if (!areaIsKnown) {
+      gAreas.set(aName, props);
+
+      if (props.get("legacy")) {
+        // Guarantee this area exists in gFuturePlacements, to avoid checking it in
+        // various places elsewhere.
+        gFuturePlacements.set(aName, new Set());
+      } else {
+        this.restoreStateForArea(aName);
+      }
+
+      // If we have pending build area nodes, register all of them
+      if (gPendingBuildAreas.has(aName)) {
+        let pendingNodes = gPendingBuildAreas.get(aName);
+        for (let [pendingNode, existingChildren] of pendingNodes) {
+          this.registerToolbarNode(pendingNode, existingChildren);
+        }
+        gPendingBuildAreas.delete(aName);
+      }
     }
   },
 
   unregisterArea: function(aName, aDestroyPlacements) {
     if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
       throw new Error("Invalid area name");
     }
     if (!gAreas.has(aName) && !gPlacements.has(aName)) {
@@ -326,18 +347,33 @@ let CustomizableUIInternal = {
   registerToolbarNode: function(aToolbar, aExistingChildren) {
     let area = aToolbar.id;
     if (gBuildAreas.has(area) && gBuildAreas.get(area).has(aToolbar)) {
       return;
     }
     let document = aToolbar.ownerDocument;
     let areaProperties = gAreas.get(area);
 
+    // If this area is not registered, try to do it automatically:
     if (!areaProperties) {
-      throw new Error("Unknown customization area: " + area);
+      // If there's no default set attribute at all, we assume that we should
+      // wait for registerArea to be called:
+      if (!aToolbar.hasAttribute("defaultset")) {
+        if (!gPendingBuildAreas.has(area)) {
+          gPendingBuildAreas.set(area, new Map());
+        }
+        let pendingNodes = gPendingBuildAreas.get(area);
+        pendingNodes.set(aToolbar, aExistingChildren);
+        return;
+      }
+      let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
+      let defaultsetAttribute = aToolbar.getAttribute("defaultset");
+      props.defaultPlacements = defaultsetAttribute.split(',').filter(s => s);
+      this.registerArea(area, props);
+      areaProperties = gAreas.get(area);
     }
 
     this.beginBatchUpdate();
     try {
       let placements = gPlacements.get(area);
       if (!placements && areaProperties.has("legacy")) {
         let legacyState = aToolbar.getAttribute("currentset");
         if (legacyState) {
@@ -581,17 +617,17 @@ let CustomizableUIInternal = {
 
     LOG("Searching for " + aWidgetId + " in toolbox.");
     let node = this.findWidgetInWindow(aWidgetId, aWindow);
     if (node) {
       return [ CustomizableUI.PROVIDER_XUL, node ];
     }
 
     LOG("No node for " + aWidgetId + " found.");
-    return [];
+    return [null, null];
   },
 
   registerMenuPanel: function(aPanel) {
     if (gBuildAreas.has(CustomizableUI.AREA_PANEL) &&
         gBuildAreas.get(CustomizableUI.AREA_PANEL).has(aPanel)) {
       return;
     }
 
@@ -599,21 +635,27 @@ let CustomizableUIInternal = {
 
     aPanel.toolbox = document.getElementById("navigator-toolbox");
     aPanel.customizationTarget = aPanel;
 
     this.addPanelCloseListeners(aPanel);
 
     let placements = gPlacements.get(CustomizableUI.AREA_PANEL);
     this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanel);
-    for (let btn of aPanel.querySelectorAll("toolbarbutton")) {
-      btn.setAttribute("tabindex", "0");
-      this.ensureButtonContextMenu(btn, aPanel);
-      if (!btn.hasAttribute("type")) {
-        btn.setAttribute("type", "wrap");
+    for (let child of aPanel.children) {
+      if (child.localName != "toolbarbutton") {
+        if (child.localName == "toolbaritem") {
+          this.ensureButtonContextMenu(child, aPanel);
+        }
+        continue;
+      }
+      this.ensureButtonContextMenu(child, aPanel);
+      child.setAttribute("tabindex", "0");
+      if (!child.hasAttribute("type")) {
+        child.setAttribute("type", "wrap");
       }
     }
 
     this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanel);
   },
 
   onWidgetAdded: function(aWidgetId, aArea, aPosition) {
     this.insertNode(aWidgetId, aArea, aPosition, true);
@@ -656,17 +698,17 @@ let CustomizableUIInternal = {
         if (widgetNode.getAttribute("type") == "wrap") {
           widgetNode.removeAttribute("type");
         }
         areaNode.toolbox.palette.appendChild(widgetNode);
       }
       this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
 
       if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
-        areaNode.setAttribute("currentset", areaNode.currentSet);
+        areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
       }
 
       let windowCache = gSingleWrapperCache.get(window);
       if (windowCache) {
         windowCache.delete(aWidgetId);
       }
     }
   },
@@ -725,16 +767,28 @@ let CustomizableUIInternal = {
         }
       }
     }
 
     for (let [,widget] of gPalette) {
       widget.instances.delete(document);
       this.notifyListeners("onWidgetInstanceRemoved", widget.id, document);
     }
+
+    for (let [area, areaMap] of gPendingBuildAreas) {
+      let toDelete = [];
+      for (let [areaNode, ] of areaMap) {
+        if (areaNode.ownerDocument == document) {
+          toDelete.push(areaNode);
+        }
+      }
+      for (let areaNode of toDelete) {
+        areaMap.delete(toDelete);
+      }
+    }
   },
 
   setLocationAttributes: function(aNode, aArea) {
     let props = gAreas.get(aArea);
     if (!props) {
       throw new Error("Expected area " + aArea + " to have a properties Map " +
                       "associated with it.");
     }
@@ -799,17 +853,17 @@ let CustomizableUIInternal = {
       }
     }
 
     let container = aAreaNode.customizationTarget;
     let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aNextNodeId, aAreaNode);
     this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
 
     if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) {
-      aAreaNode.setAttribute("currentset", aAreaNode.currentSet);
+      aAreaNode.setAttribute("currentset", gPlacements.get(areaId).join(','));
     }
   },
 
   findInsertionPoints: function(aNode, aNextNodeId, aAreaNode) {
     let props = gAreas.get(aAreaNode.id);
     if (props.get("type") == CustomizableUI.TYPE_TOOLBAR && props.get("overflowable") &&
         aAreaNode.getAttribute("overflowing") == "true") {
       return aAreaNode.overflowable.getOverflowedInsertionPoints(aNode, aNextNodeId);
@@ -1804,37 +1858,33 @@ let CustomizableUIInternal = {
           defaultPlacements.splice(widgetIndex, 1);
         }
       }
     }
 
     // This will not remove the widget from gPlacements - we want to keep the
     // setting so the widget gets put back in it's old position if/when it
     // returns.
-
-    let area = widget.currentArea;
-    let buildAreaNodes = area && gBuildAreas.get(area);
-    if (buildAreaNodes) {
-      for (let buildNode of buildAreaNodes) {
-        let widgetNode = buildNode.ownerDocument.getElementById(aWidgetId);
-        let windowCache = gSingleWrapperCache.get(buildNode.ownerDocument.defaultView);
-        if (windowCache) {
-          windowCache.delete(aWidgetId);
-        }
-        if (widgetNode) {
-          widgetNode.parentNode.removeChild(widgetNode);
-        }
-        if (widget.type == "view") {
-          let viewNode = buildNode.ownerDocument.getElementById(widget.viewId);
-          if (viewNode) {
-            for (let eventName of kSubviewEvents) {
-              let handler = "on" + eventName;
-              if (typeof widget[handler] == "function") {
-                viewNode.removeEventListener(eventName, widget[handler], false);
-              }
+    for (let [window, ] of gBuildWindows) {
+      let windowCache = gSingleWrapperCache.get(window);
+      if (windowCache) {
+        windowCache.delete(aWidgetId);
+      }
+      let widgetNode = window.document.getElementById(aWidgetId) ||
+                       window.gNavToolbox.palette.querySelector(idToSelector(aWidgetId));
+      if (widgetNode) {
+        widgetNode.remove();
+      }
+      if (widget.type == "view") {
+        let viewNode = window.document.getElementById(widget.viewId);
+        if (viewNode) {
+          for (let eventName of kSubviewEvents) {
+            let handler = "on" + eventName;
+            if (typeof widget[handler] == "function") {
+              viewNode.removeEventListener(eventName, widget[handler], false);
             }
           }
         }
       }
     }
 
     gPalette.delete(aWidgetId);
     gGroupWrapperCache.delete(aWidgetId);
@@ -2544,17 +2594,17 @@ OverflowableToolbar.prototype = {
     win.UpdateUrlbarSearchSplitterState();
   },
 
   _onResize: function(aEvent) {
     if (!this._lazyResizeHandler) {
       this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
                                                  LAZY_RESIZE_INTERVAL_MS);
     }
-    this._lazyResizeHandler.start();
+    this._lazyResizeHandler.arm();
   },
 
   _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) {
     let placements = gPlacements.get(this._toolbar.id);
     while (this._list.firstChild) {
       let child = this._list.firstChild;
       let minSize = this._collapsed.get(child.id);
 
@@ -2603,17 +2653,17 @@ OverflowableToolbar.prototype = {
 
     this._moveItemsBackToTheirOrigin();
   },
 
   _disable: function() {
     this._enabled = false;
     this._moveItemsBackToTheirOrigin(true);
     if (this._lazyResizeHandler) {
-      this._lazyResizeHandler.cancel();
+      this._lazyResizeHandler.disarm();
     }
   },
 
   _enable: function() {
     this._enabled = true;
     this.onOverflow();
   },
 
--- a/browser/components/customizableui/src/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/src/CustomizableWidgets.jsm
@@ -20,16 +20,24 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 const kWidePanelItemClass = "panel-wide-item";
 
 let gModuleName = "[CustomizableWidgets]";
 #include logging.js
 
+function isWin8OrHigher() {
+  let osName = Services.sysinfo.getProperty("name");
+  let version = Services.sysinfo.getProperty("version");
+
+  // Windows 8 is version >= 6.2
+  return osName == "Windows_NT" && Services.vc.compare(version, "6.2") >= 0;
+}
+
 function setAttributes(aNode, aAttrs) {
   for (let [name, value] of Iterator(aAttrs)) {
     if (!value) {
       if (aNode.hasAttribute(name))
         aNode.removeAttribute(name);
     } else {
       if (name == "label" || name == "tooltiptext")
         value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, name);
@@ -255,28 +263,16 @@ const CustomizableWidgets = [{
         items.firstChild.remove();
       }
 
       parent.appendChild(items);
       aEvent.target.removeEventListener("command",
                                         win.PanelUI.onCommandHandler);
     }
   }, {
-    id: "switch-to-metro-button",
-    removable: true,
-    defaultArea: CustomizableUI.AREA_PANEL,
-    onCommand: function(aEvent) {
-      let win = aEvent.target &&
-                aEvent.target.ownerDocument &&
-                aEvent.target.ownerDocument.defaultView;
-      if (win && typeof win.SwitchToMetro == "function") {
-        win.SwitchToMetro();
-      }
-    }
-  }, {
     id: "add-ons-button",
     removable: true,
     shortcutId: "key_openAddons",
     defaultArea: CustomizableUI.AREA_PANEL,
     onCommand: function(aEvent) {
       let win = aEvent.target &&
                 aEvent.target.ownerDocument &&
                 aEvent.target.ownerDocument.defaultView;
@@ -487,18 +483,16 @@ const CustomizableWidgets = [{
       node.classList.add("toolbaritem-combined-buttons");
       node.classList.add(kWidePanelItemClass);
 
       buttons.forEach(function(aButton, aIndex) {
         if (aIndex != 0)
           node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
         let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
         setAttributes(btnNode, aButton);
-        if (inPanel)
-          btnNode.setAttribute("tabindex", "0");
         node.appendChild(btnNode);
       });
 
       let listener = {
         onWidgetAdded: function(aWidgetId, aArea, aPosition) {
           if (aWidgetId != this.id)
             return;
           updateCombinedWidgetStyle(node, aArea);
@@ -794,8 +788,28 @@ const CustomizableWidgets = [{
   }, {
     id: "email-link-button",
     removable: true,
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForWindow(win.content);
     }
   }];
+
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+if (isWin8OrHigher()) {
+  CustomizableWidgets.push({
+    id: "switch-to-metro-button",
+    removable: true,
+    defaultArea: CustomizableUI.AREA_PANEL,
+    onCommand: function(aEvent) {
+      let win = aEvent.target &&
+        aEvent.target.ownerDocument &&
+        aEvent.target.ownerDocument.defaultView;
+      if (win && typeof win.SwitchToMetro == "function") {
+        win.SwitchToMetro();
+      }
+    }
+  });
+}
+#endif
+#endif
\ No newline at end of file
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -33,12 +33,15 @@ skip-if = true
 [browser_927717_customize_drag_empty_toolbar.js]
 
 [browser_934113_menubar_removable.js]
 # Because this test is about the menubar, it can't be run on mac
 skip-if = os == "mac"
 
 [browser_938980_navbar_collapsed.js]
 [browser_938995_indefaultstate_nonremovable.js]
+[browser_940013_registerToolbarNode_calls_registerArea.js]
 [browser_940946_removable_from_navbar_customizemode.js]
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
+[browser_943683_migration_test.js]
+[browser_944887_destroyWidget_should_destroy_in_palette.js]
 [browser_panel_toggle.js]
--- a/browser/components/customizableui/test/browser_877178_unregisterArea.js
+++ b/browser/components/customizableui/test/browser_877178_unregisterArea.js
@@ -7,19 +7,16 @@ let gTests = [
     desc: "Sanity checks",
     run: function() {
       SimpleTest.doesThrow(function() CustomizableUI.registerArea("@foo"),
                            "Registering areas with an invalid ID should throw.");
 
       SimpleTest.doesThrow(function() CustomizableUI.registerArea([]),
                            "Registering areas with an invalid ID should throw.");
 
-      SimpleTest.doesThrow(function() CustomizableUI.registerArea(CustomizableUI.AREA_NAVBAR),
-                           "Registering an area with an ID that's already registered should throw.");
-
       SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("@foo"),
                            "Unregistering areas with an invalid ID should throw.");
 
       SimpleTest.doesThrow(function() CustomizableUI.unregisterArea([]),
                            "Unregistering areas with an invalid ID should throw.");
 
       SimpleTest.doesThrow(function() CustomizableUI.unregisterArea("unknown"),
                            "Unregistering an area that's not registered should throw.");
--- a/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
+++ b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
@@ -2,17 +2,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/. */
 
 const kTestToolbarId = "test-empty-drag";
 let gTests = [
   {
     desc: "Attempting to drag an item to an empty container should work.",
     setup: function() {
-      createToolbarWithPlacements(kTestToolbarId, "");
+      createToolbarWithPlacements(kTestToolbarId, []);
     },
     run: function() {
       yield startCustomizing();
       let downloadButton = document.getElementById("downloads-button");
       let customToolbar = document.getElementById(kTestToolbarId);
       simulateItemDrag(downloadButton, customToolbar);
       assertAreaPlacements(kTestToolbarId, ["downloads-button"]);
       ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar,
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
@@ -0,0 +1,77 @@
+/* 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/. */
+
+const kToolbarId = "test-registerToolbarNode-toolbar";
+const kButtonId = "test-registerToolbarNode-button";
+let gTests = [
+  {
+    desc: "Registering a toolbar with defaultset attribute should work",
+    run: function() {
+      let btn = createDummyXULButton(kButtonId);
+      let toolbar = document.createElement("toolbar");
+      toolbar.id = kToolbarId;
+      toolbar.setAttribute("customizable", true);
+      toolbar.setAttribute("defaultset", kButtonId);
+      gNavToolbox.appendChild(toolbar);
+      ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
+         "Toolbar should have been registered automatically.");
+      is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
+         "Area should be registered as toolbar");
+      assertAreaPlacements(kToolbarId, [kButtonId]);
+      ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+      CustomizableUI.unregisterArea(kToolbarId, true);
+      toolbar.remove();
+      ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+      btn.remove();
+    }
+  },
+  {
+    desc: "Registering a toolbar without a defaultset attribute should " +
+          "wait for the registerArea call",
+    run: function() {
+      let btn = createDummyXULButton(kButtonId);
+      let toolbar = document.createElement("toolbar");
+      toolbar.id = kToolbarId;
+      toolbar.setAttribute("customizable", true);
+      gNavToolbox.appendChild(toolbar);
+      ok(CustomizableUI.areas.indexOf(kToolbarId) == -1,
+         "Toolbar should not yet have been registered automatically.");
+      CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]});
+      ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
+         "Toolbar should have been registered now.");
+      is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
+         "Area should be registered as toolbar");
+      assertAreaPlacements(kToolbarId, [kButtonId]);
+      ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+      CustomizableUI.unregisterArea(kToolbarId, true);
+      toolbar.remove();
+      ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+      btn.remove();
+    }
+  }
+];
+
+function asyncCleanup() {
+  yield resetCustomization();
+}
+
+function cleanup() {
+  let toolbar = document.getElementById(kToolbarId);
+  if (toolbar) {
+    toolbar.remove();
+  }
+  let btn = document.getElementById(kButtonId) ||
+            gNavToolbox.querySelector("#" + kButtonId);
+  if (btn) {
+    btn.remove();
+  }
+}
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(cleanup);
+  runTests(gTests, asyncCleanup);
+}
+
+
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_943683_migration_test.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+const kWidgetId = "test-addonbar-migration";
+const kWidgetId2 = "test-addonbar-migration2";
+
+let addonbar = document.getElementById(CustomizableUI.AREA_ADDONBAR);
+let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+let btn;
+let btn2;
+
+let gTests = [
+  {
+    desc: "Check we migrate normal stuff to the navbar",
+    setup: function() {
+      btn = createDummyXULButton(kWidgetId, "Test");
+      btn2 = createDummyXULButton(kWidgetId2, "Test2");
+    },
+    run: function() {
+      addonbar.insertItem(btn.id);
+      ok(btn.parentNode == navbar.customizationTarget, "Button should end up in navbar");
+      let migrationArray = addonbar.getMigratedItems();
+      is(migrationArray.length, 1, "Should have migrated 1 item");
+      is(migrationArray[0], kWidgetId, "Should have migrated our 1 item");
+
+      addonbar.currentSet = addonbar.currentSet + "," + kWidgetId2;
+      ok(btn2.parentNode == navbar.customizationTarget, "Second button should end up in the navbar");
+      migrationArray = addonbar.getMigratedItems();
+      is(migrationArray.length, 2, "Should have migrated 2 items");
+      isnot(migrationArray.indexOf(kWidgetId2), -1, "Should have migrated our second item");
+
+      let otherWindow = yield openAndLoadWindow(undefined, true);
+      try {
+        let addonBar = otherWindow.document.getElementById("addon-bar");
+        let otherMigrationArray = addonBar.getMigratedItems();
+        is(migrationArray.length, otherMigrationArray.length,
+           "Other window should have the same number of migrated items.");
+        if (migrationArray.length == otherMigrationArray.length) {
+          for (let widget of migrationArray) {
+            isnot(otherMigrationArray.indexOf(widget), -1,
+                  "Migrated widget " + widget + " should also be listed as migrated in the other window.");
+          }
+        }
+      } finally {
+        otherWindow.close();
+      }
+    },
+    teardown: function() {
+      btn.remove();
+      btn2.remove();
+      CustomizableUI.reset();
+    },
+  },
+];
+
+function test() {
+  waitForExplicitFinish();
+  runTests(gTests);
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js
@@ -0,0 +1,25 @@
+/* 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/. */
+
+const kWidgetId = "test-destroy-in-palette";
+
+let gTests = [
+  {
+    desc: "Check destroyWidget destroys the node if it's in the palette",
+    run: function() {
+      CustomizableUI.createWidget({id: kWidgetId, label: "Test destroying widgets in palette."});
+      yield startCustomizing();
+      yield endCustomizing();
+      ok(gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget still exists in palette.");
+      CustomizableUI.destroyWidget(kWidgetId);
+      ok(!gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget no longer exists in palette.");
+    },
+  },
+];
+
+function test() {
+  waitForExplicitFinish();
+  runTests(gTests);
+}
+
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1648,18 +1648,18 @@ BrowserGlue.prototype = {
                                 Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
                                 "&sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
                                 "&maxResults=" + MAX_RESULTS),
             parent: PlacesUtils.bookmarksMenuFolderId,
             position: menuIndex++,
             newInVersion: 1
           },
-          FirefoxTouch: {
-            title: bundle.GetStringFromName("firefoxTouchTitle"),
+          Windows8Touch: {
+            title: bundle.GetStringFromName("windows8TouchTitle"),
             uri: NetUtil.newURI("place:folder=" +
                                 PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {})[0] +
                                 "&queryType=" +
                                 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
                                 "&sort=" +
                                 Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
                                 "&maxResults=" + MAX_RESULTS +
                                 "&excludeQueries=1"),
--- a/browser/devtools/app-manager/content/manifest-editor.js
+++ b/browser/devtools/app-manager/content/manifest-editor.js
@@ -11,16 +11,17 @@ const VARIABLES_VIEW_URL =
   "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 function ManifestEditor(project) {
   this.project = project;
   this._onContainerReady = this._onContainerReady.bind(this);
   this._onEval = this._onEval.bind(this);
   this._onSwitch = this._onSwitch.bind(this);
   this._onDelete = this._onDelete.bind(this);
+  this._onNew = this._onNew.bind(this);
 }
 
 ManifestEditor.prototype = {
   get manifest() { return this.project.manifest; },
 
   get editable() { return this.project.type == "packaged"; },
 
   show: function(containerElement) {
@@ -49,16 +50,17 @@ ManifestEditor.prototype = {
     editor.onlyEnumVisible = true;
     editor.alignedValues = true;
     editor.actionsFirst = true;
 
     if (this.editable) {
       editor.eval = this._onEval;
       editor.switch = this._onSwitch;
       editor.delete = this._onDelete;
+      editor.new = this._onNew;
     }
 
     return this.update();
   },
 
   _onEval: function(evalString) {
     let manifest = this.manifest;
     eval("manifest" + evalString);
@@ -82,16 +84,24 @@ ManifestEditor.prototype = {
   },
 
   _onDelete: function(variable) {
     let manifest = this.manifest;
     let evalString = "delete manifest" + variable.symbolicName;
     eval(evalString);
   },
 
+  _onNew: function(variable, newName, newValue) {
+    let manifest = this.manifest;
+    let symbolicName = variable.symbolicName + "['" + newName + "']";
+    let evalString = "manifest" + symbolicName + " = " + newValue + ";";
+    eval(evalString);
+    this.update();
+  },
+
   update: function() {
     this.editor.createHierarchy();
     this.editor.rawObject = this.manifest;
     this.editor.commitHierarchy();
 
     // Wait until the animation from commitHierarchy has completed
     let deferred = promise.defer();
     setTimeout(deferred.resolve, this.editor.lazyEmptyDelay + 1);
--- a/browser/devtools/app-manager/test/browser_manifest_editor.js
+++ b/browser/devtools/app-manager/test/browser_manifest_editor.js
@@ -1,54 +1,105 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 
 const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
 
+
+let gManifestWindow, gManifestEditor;
+
 function test() {
   waitForExplicitFinish();
 
   Task.spawn(function() {
     Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, true);
     let tab = yield openAppManager();
     yield selectProjectsPanel();
     yield addSamplePackagedApp();
     yield showSampleProjectDetails();
+
+    gManifestWindow = getManifestWindow();
+    gManifestEditor = getProjectsWindow().UI.manifestEditor;
     yield changeManifestValue("name", "the best app");
+    yield addNewManifestProperty("developer", "foo", "bar");
+    gManifestWindow = null;
+    gManifestEditor = null;
+
     yield removeSamplePackagedApp();
     yield removeTab(tab);
     Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, false);
     finish();
   });
 }
 
+// Wait until the animation from commitHierarchy has completed
+function waitForUpdate() {
+  return waitForTime(gManifestEditor.editor.lazyEmptyDelay + 1);
+}
+
 function changeManifestValue(key, value) {
   return Task.spawn(function() {
-    let manifestWindow = getManifestWindow();
-    let manifestEditor = getProjectsWindow().UI.manifestEditor;
-
-    let propElem = manifestWindow.document
+    let propElem = gManifestWindow.document
                    .querySelector("[id ^= '" + key + "']");
     is(propElem.querySelector(".name").value, key,
        "Key doesn't match expected value");
 
     let valueElem = propElem.querySelector(".value");
-    EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, manifestWindow);
+    EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, gManifestWindow);
 
     let valueInput = propElem.querySelector(".element-value-input");
     valueInput.value = '"' + value + '"';
-    EventUtils.sendKey("RETURN", manifestWindow);
+    EventUtils.sendKey("RETURN", gManifestWindow);
 
-    // Wait until the animation from commitHierarchy has completed
-    yield waitForTime(manifestEditor.editor.lazyEmptyDelay + 1);
+    yield waitForUpdate();
     // Elements have all been replaced, re-select them
-    propElem = manifestWindow.document.querySelector("[id ^= '" + key + "']");
+    propElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
     valueElem = propElem.querySelector(".value");
     is(valueElem.value, '"' + value + '"',
        "Value doesn't match expected value");
 
-    is(manifestEditor.manifest[key], value,
+    is(gManifestEditor.manifest[key], value,
        "Manifest doesn't contain expected value");
   });
 }
+
+function addNewManifestProperty(parent, key, value) {
+  return Task.spawn(function() {
+    let parentElem = gManifestWindow.document
+                     .querySelector("[id ^= '" + parent + "']");
+    ok(parentElem,
+      "Found parent element");
+    let addPropertyElem = parentElem
+                          .querySelector(".variables-view-add-property");
+    ok(addPropertyElem,
+      "Found add-property button");
+
+    EventUtils.sendMouseEvent({ type: "mousedown" }, addPropertyElem, gManifestWindow);
+
+    let nameInput = parentElem.querySelector(".element-name-input");
+    nameInput.value = key;
+    EventUtils.sendKey("TAB", gManifestWindow);
+
+    let valueInput = parentElem.querySelector(".element-value-input");
+    valueInput.value = '"' + value + '"';
+    EventUtils.sendKey("RETURN", gManifestWindow);
+
+    yield waitForUpdate();
+
+    let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
+    let nameElem = newElem.querySelector(".name");
+    is(nameElem.value, key,
+       "Key doesn't match expected Key");
+
+    ok(key in gManifestEditor.manifest[parent],
+       "Manifest doesn't contain expected key");
+
+    let valueElem = newElem.querySelector(".value");
+    is(valueElem.value, '"' + value + '"',
+       "Value doesn't match expected value");
+
+    is(gManifestEditor.manifest[parent][key], value,
+       "Manifest doesn't contain expected value");
+  });
+}
--- a/browser/devtools/app-manager/test/manifest.webapp
+++ b/browser/devtools/app-manager/test/manifest.webapp
@@ -1,3 +1,6 @@
 {
-  "name": "My packaged app"
+  "name": "My packaged app",
+  "developer": {
+    "name": "Foo Bar"
+  }
 }
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-04.js
@@ -102,11 +102,49 @@ function test() {
       "Three new detail nodes should have been added in the variable tree.");
     is(testVar.get("someProp2").target.querySelector(".value").getAttribute("value"), "null",
       "The grip information for the variable wasn't set correctly (7).");
     is(testVar.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
       "The grip information for the variable wasn't set correctly (8).");
     is(testVar.get("someProp4").target.querySelector(".value").getAttribute("value"), "Object",
       "The grip information for the variable wasn't set correctly (9).");
 
+    let parent = testVar.get("someProp2");
+    let child = parent.addItem("child", {
+      value: {
+        type: "null"
+      }
+    });
+
+    is(variables.getItemForNode(parent.target), parent,
+       "VariablesView should have a record of the parent.");
+    is(variables.getItemForNode(child.target), child,
+       "VariablesView should have a record of the child.");
+    is([...parent].length, 1,
+       "Parent should have one child.");
+
+    parent.remove();
+
+    is(variables.getItemForNode(parent.target), undefined,
+       "VariablesView should not have a record of the parent anymore.");
+    is(parent.target.parentNode, null,
+       "Parent element should not have a parent.")
+    is(variables.getItemForNode(child.target), undefined,
+       "VariablesView should not have a record of the child anymore.");
+    is(child.target.parentNode, null,
+       "Child element should not have a parent.")
+    is([...parent].length, 0,
+       "Parent should have zero children.");
+
+    testScope.remove();
+
+    is([...variables].length, 0,
+       "VariablesView should have been emptied.");
+    is(Cu.nondeterministicGetWeakMapKeys(variables._itemsByElement).length, 0,
+       "VariablesView _itemsByElement map has been emptied.");
+    is(variables._currHierarchy.size, 0,
+       "VariablesView _currHierarchy map has been emptied.");
+    is(variables._list.children.length, 0,
+       "VariablesView element should have no children.");
+
     closeDebuggerAndFinish(aPanel);
   });
 }
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-data.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-data.js
@@ -54,16 +54,17 @@ function performTest() {
     someProp6: obj,
     get someProp7() { return arr; },
     set someProp7(value) { arr[0] = value }
   };
 
   gVariablesView.eval = function() {};
   gVariablesView.switch = function() {};
   gVariablesView.delete = function() {};
+  gVariablesView.new = function() {};
   gVariablesView.rawObject = test;
 
   testHierarchy();
   testHeader();
   testFirstLevelContents();
   testSecondLevelContents();
   testThirdLevelContents();
   testOriginalRawDataIntegrity(arr, obj);
@@ -498,18 +499,18 @@ function testAnonymousHeaders(fooScope, 
 
   is(anonymousVar.header, false,
     "An anonymous variable should have a header visible.");
   is(anonymousVar.target.hasAttribute("non-header"), true,
     "The non-header attribute should not be applied to variables without headers.");
 }
 
 function testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty) {
-  is(fooScope.preventDisableOnChage, gVariablesView.preventDisableOnChage,
-    "The preventDisableOnChage property should persist from the view to all scopes.");
+  is(fooScope.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+    "The preventDisableOnChange property should persist from the view to all scopes.");
   is(fooScope.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
     "The preventDescriptorModifiers property should persist from the view to all scopes.");
   is(fooScope.editableNameTooltip, gVariablesView.editableNameTooltip,
     "The editableNameTooltip property should persist from the view to all scopes.");
   is(fooScope.editableValueTooltip, gVariablesView.editableValueTooltip,
     "The editableValueTooltip property should persist from the view to all scopes.");
   is(fooScope.editButtonTooltip, gVariablesView.editButtonTooltip,
     "The editButtonTooltip property should persist from the view to all scopes.");
@@ -520,23 +521,25 @@ function testPropertyInheritance(fooScop
   is(fooScope.separatorStr, gVariablesView.separatorStr,
     "The separatorStr property should persist from the view to all scopes.");
   is(fooScope.eval, gVariablesView.eval,
     "The eval property should persist from the view to all scopes.");
   is(fooScope.switch, gVariablesView.switch,
     "The switch property should persist from the view to all scopes.");
   is(fooScope.delete, gVariablesView.delete,
     "The delete property should persist from the view to all scopes.");
+  is(fooScope.new, gVariablesView.new,
+    "The new property should persist from the view to all scopes.");
   isnot(fooScope.eval, fooScope.switch,
     "The eval and switch functions got mixed up in the scope.");
   isnot(fooScope.switch, fooScope.delete,
     "The eval and switch functions got mixed up in the scope.");
 
-  is(barVar.preventDisableOnChage, gVariablesView.preventDisableOnChage,
-    "The preventDisableOnChage property should persist from the view to all variables.");
+  is(barVar.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+    "The preventDisableOnChange property should persist from the view to all variables.");
   is(barVar.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
     "The preventDescriptorModifiers property should persist from the view to all variables.");
   is(barVar.editableNameTooltip, gVariablesView.editableNameTooltip,
     "The editableNameTooltip property should persist from the view to all variables.");
   is(barVar.editableValueTooltip, gVariablesView.editableValueTooltip,
     "The editableValueTooltip property should persist from the view to all variables.");
   is(barVar.editButtonTooltip, gVariablesView.editButtonTooltip,
     "The editButtonTooltip property should persist from the view to all variables.");
@@ -547,23 +550,25 @@ function testPropertyInheritance(fooScop
   is(barVar.separatorStr, gVariablesView.separatorStr,
     "The separatorStr property should persist from the view to all variables.");
   is(barVar.eval, gVariablesView.eval,
     "The eval property should persist from the view to all variables.");
   is(barVar.switch, gVariablesView.switch,
     "The switch property should persist from the view to all variables.");
   is(barVar.delete, gVariablesView.delete,
     "The delete property should persist from the view to all variables.");
+  is(barVar.new, gVariablesView.new,
+    "The new property should persist from the view to all variables.");
   isnot(barVar.eval, barVar.switch,
     "The eval and switch functions got mixed up in the variable.");
   isnot(barVar.switch, barVar.delete,
     "The eval and switch functions got mixed up in the variable.");
 
-  is(bazProperty.preventDisableOnChage, gVariablesView.preventDisableOnChage,
-    "The preventDisableOnChage property should persist from the view to all properties.");
+  is(bazProperty.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+    "The preventDisableOnChange property should persist from the view to all properties.");
   is(bazProperty.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
     "The preventDescriptorModifiers property should persist from the view to all properties.");
   is(bazProperty.editableNameTooltip, gVariablesView.editableNameTooltip,
     "The editableNameTooltip property should persist from the view to all properties.");
   is(bazProperty.editableValueTooltip, gVariablesView.editableValueTooltip,
     "The editableValueTooltip property should persist from the view to all properties.");
   is(bazProperty.editButtonTooltip, gVariablesView.editButtonTooltip,
     "The editButtonTooltip property should persist from the view to all properties.");
@@ -574,16 +579,18 @@ function testPropertyInheritance(fooScop
   is(bazProperty.separatorStr, gVariablesView.separatorStr,
     "The separatorStr property should persist from the view to all properties.");
   is(bazProperty.eval, gVariablesView.eval,
     "The eval property should persist from the view to all properties.");
   is(bazProperty.switch, gVariablesView.switch,
     "The switch property should persist from the view to all properties.");
   is(bazProperty.delete, gVariablesView.delete,
     "The delete property should persist from the view to all properties.");
+  is(bazProperty.new, gVariablesView.new,
+    "The new property should persist from the view to all properties.");
   isnot(bazProperty.eval, bazProperty.switch,
     "The eval and switch functions got mixed up in the property.");
   isnot(bazProperty.switch, bazProperty.delete,
     "The eval and switch functions got mixed up in the property.");
 }
 
 function testClearHierarchy() {
   gVariablesView.clearHierarchy();
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -47,17 +47,17 @@ const DEFAULT_EDITOR_CONFIG = {
   lineNumbers: true
 };
 const GENERIC_VARIABLES_VIEW_SETTINGS = {
   lazyEmpty: true,
   lazyEmptyDelay: 10, // ms
   searchEnabled: true,
   editableValueTooltip: "",
   editableNameTooltip: "",
-  preventDisableOnChage: true,
+  preventDisableOnChange: true,
   preventDescriptorModifiers: true,
   eval: () => {},
   switch: () => {}
 };
 
 /**
  * Object defining the network monitor view components.
  */
--- a/browser/devtools/shared/test/browser_outputparser.js
+++ b/browser/devtools/shared/test/browser_outputparser.js
@@ -32,36 +32,49 @@ function testParseCssProperty() {
     colorSwatchClass: "test-colorswatch"
   });
 
   let target = doc.querySelector("div");
   ok(target, "captain, we have the div");
   target.appendChild(frag);
 
   is(target.innerHTML,
-     '1px solid <span style="background-color:red" class="test-colorswatch"></span>#F00',
+     '1px solid <span style="background-color:red" class="test-colorswatch"></span><span>#F00</span>',
      "CSS property correctly parsed");
 
   target.innerHTML = "";
 
+  let frag = parser.parseCssProperty("background-image", "linear-gradient(to right, #F60 10%, rgba(0,0,0,1))", {
+    colorSwatchClass: "test-colorswatch",
+    colorClass: "test-color"
+  });
+  target.appendChild(frag);
+  is(target.innerHTML,
+     'linear-gradient(to right, <span style="background-color:#F60" class="test-colorswatch"></span><span class="test-color">#F60</span> 10%, ' +
+     '<span style="background-color:rgba(0,0,0,1)" class="test-colorswatch"></span><span class="test-color">#000</span>)',
+     "Gradient CSS property correctly parsed");
+
+  target.innerHTML = "";
+
   testParseHTMLAttribute();
 }
 
 function testParseHTMLAttribute() {
   let attrib = "color:red; font-size: 12px; background-image: " +
                "url(chrome://branding/content/about-logo.png)";
   let frag = parser.parseHTMLAttribute(attrib, {
-    urlClass: "theme-link"
+    urlClass: "theme-link",
+    colorClass: "theme-color"
   });
 
   let target = doc.querySelector("div");
   ok(target, "captain, we have the div");
   target.appendChild(frag);
 
-  let expected = 'color:#F00; font-size: 12px; ' +
+  let expected = 'color:<span class="theme-color">#F00</span>; font-size: 12px; ' +
                  'background-image: url(\'<a href="chrome://branding/content/about-logo.png" ' +
                  'class="theme-link" ' +
                  'target="_blank">chrome://branding/content/about-logo.png</a>\')';
 
   is(target.innerHTML, expected, "HTML Attribute correctly parsed");
   target.innerHTML = "";
   testParseNonCssHTMLAttribute();
 }
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -259,20 +259,29 @@ VariablesView.prototype = {
    * user interaction. If null, then deletions are disabled.
    *
    * This property is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   delete: null,
 
   /**
+   * Function called each time a property is added via user interaction. If
+   * null, then property additions are disabled.
+   *
+   * This property is applied recursively onto each scope in this view and
+   * affects only the child nodes when they're created.
+   */
+  new: null,
+
+  /**
    * Specifies if after an eval or switch operation, the variable or property
    * which has been edited should be disabled.
    */
-  preventDisableOnChage: false,
+  preventDisableOnChange: false,
 
   /**
    * Specifies if, whenever a variable or property descriptor is available,
    * configurable, enumerable, writable, frozen, sealed and extensible
    * attributes should not affect presentation.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
@@ -820,16 +829,20 @@ VariablesView.prototype = {
 
       case e.DOM_VK_DELETE:
       case e.DOM_VK_BACK_SPACE:
         // Delete the Variable or Property if allowed.
         if (item instanceof Variable) {
           item._onDelete(e);
         }
         return;
+
+      case e.DOM_VK_INSERT:
+        item._onAddProperty(e);
+        return;
     }
   },
 
   /**
    * Listener handling a key down event on the view.
    */
   _onViewKeyDown: function(e) {
     if (e.keyCode == e.DOM_VK_C) {
@@ -907,21 +920,31 @@ VariablesView.prototype = {
     if (aFlag) {
       this._parent.setAttribute("aligned-values", "");
     } else {
       this._parent.removeAttribute("aligned-values");
     }
   },
 
   /**
+   * Gets if action buttons (like delete) should be placed at the beginning or
+   * end of a line.
+   * @return boolean
+   */
+  get actionsFirst() {
+    return this._actionsFirst;
+  },
+
+  /**
    * Sets if action buttons (like delete) should be placed at the beginning or
    * end of a line.
    * @param boolean aFlag
    */
   set actionsFirst(aFlag) {
+    this._actionsFirst = aFlag;
     if (aFlag) {
       this._parent.setAttribute("actions-first", "");
     } else {
       this._parent.removeAttribute("actions-first");
     }
   },
 
   /**
@@ -951,16 +974,18 @@ VariablesView.prototype = {
   _document: null,
   _window: null,
 
   _store: null,
   _prevHierarchy: null,
   _currHierarchy: null,
   _enumVisible: true,
   _nonEnumVisible: true,
+  _alignedValues: false,
+  _actionsFirst: false,
   _parent: null,
   _list: null,
   _searchboxNode: null,
   _searchboxContainer: null,
   _searchboxPlaceholder: "",
   _emptyTextNode: null,
   _emptyTextValue: ""
 };
@@ -1147,17 +1172,18 @@ function Scope(aView, aName, aFlags = {}
   this._openNonEnum = this._openNonEnum.bind(this);
   this._batchAppend = this._batchAppend.bind(this);
 
   // Inherit properties and flags from the parent view. You can override
   // each of these directly onto any scope, variable or property instance.
   this.eval = aView.eval;
   this.switch = aView.switch;
   this.delete = aView.delete;
-  this.preventDisableOnChage = aView.preventDisableOnChage;
+  this.new = aView.new;
+  this.preventDisableOnChange = aView.preventDisableOnChange;
   this.preventDescriptorModifiers = aView.preventDescriptorModifiers;
   this.editableNameTooltip = aView.editableNameTooltip;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
@@ -1255,16 +1281,30 @@ Scope.prototype = {
 
       if (aOptions.callback) {
         aOptions.callback(item, descriptor.value);
       }
     }
   },
 
   /**
+   * Remove this Scope from its parent and remove all children recursively.
+   */
+  remove: function() {
+    let view = this._variablesView;
+    view._store.splice(view._store.indexOf(this), 1);
+    view._itemsByElement.delete(this._target);
+    view._currHierarchy.delete(this._nameString);
+    this._target.remove();
+    for (let variable of this._store.values()) {
+      variable.remove();
+    }
+  },
+
+  /**
    * Gets the variable in this container having the specified name.
    *
    * @param string aName
    *        The name of the variable to get.
    * @return Variable
    *         The matched variable, or null if nothing is found.
    */
   get: function(aName) {
@@ -1678,17 +1718,16 @@ Scope.prototype = {
     this._title.addEventListener("mousedown", this._onClick, false);
   },
 
   /**
    * The click listener for this scope's title.
    */
   _onClick: function(e) {
     if (e.button != 0 ||
-        e.target == this._inputNode ||
         e.target == this._editNode ||
         e.target == this._deleteNode) {
       return;
     }
     this.toggle();
     this.focus();
   },
 
@@ -2032,16 +2071,17 @@ Scope.prototype = {
   _topView: null,
   _document: null,
   _window: null,
 
   ownerView: null,
   eval: null,
   switch: null,
   delete: null,
+  new: null,
   editableValueTooltip: "",
   editableNameTooltip: "",
   editButtonTooltip: "",
   deleteButtonTooltip: "",
   preventDescriptorModifiers: false,
   contextMenuId: "",
   separatorStr: "",
 
@@ -2126,16 +2166,29 @@ Variable.prototype = Heritage.extend(Sco
    * @return Property
    *         The newly created child Property.
    */
   _createChild: function(aName, aDescriptor) {
     return new Property(this, aName, aDescriptor);
   },
 
   /**
+   * Remove this Variable from its parent and remove all children recursively.
+   */
+  remove: function() {
+    this.ownerView._store.delete(this._nameString);
+    this._variablesView._itemsByElement.delete(this._target);
+    this._variablesView._currHierarchy.delete(this._absoluteName);
+    this._target.remove();
+    for (let property of this._store.values()) {
+      property.remove();
+    }
+  },
+
+  /**
    * Populates this variable to contain all the properties of an object.
    *
    * @param object aObject
    *        The raw object you want to display.
    * @param object aOptions [optional]
    *        Additional options for adding the properties. Supported options:
    *        - sorted: true to sort all the properties before adding them
    *        - expanded: true to expand all the properties after adding them
@@ -2308,24 +2361,21 @@ Variable.prototype = Heritage.extend(Sco
    *        The variable's name.
    * @param object aDescriptor
    *        The variable's descriptor.
    */
   _init: function(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, "variables-view-variable variable-or-property");
 
-    // Don't allow displaying variable information there's no name available.
-    if (this._nameString) {
-      this._displayVariable();
-      this._customizeVariable();
-      this._prepareTooltips();
-      this._setAttributes();
-      this._addEventListeners();
-    }
+    this._displayVariable();
+    this._customizeVariable();
+    this._prepareTooltips();
+    this._setAttributes();
+    this._addEventListeners();
 
     this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
   },
 
   /**
    * Called when this variable has finished initializing, and is ready to
    * be attached to the owner view.
    *
@@ -2416,22 +2466,35 @@ Variable.prototype = Heritage.extend(Sco
     let descriptor = this._initialDescriptor;
 
     if (ownerView.eval && this.getter || this.setter) {
       let editNode = this._editNode = this.document.createElement("toolbarbutton");
       editNode.className = "plain variables-view-edit";
       editNode.addEventListener("mousedown", this._onEdit.bind(this), false);
       this._title.insertBefore(editNode, this._spacer);
     }
+
     if (ownerView.delete) {
       let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
       deleteNode.className = "plain variables-view-delete";
       deleteNode.addEventListener("click", this._onDelete.bind(this), false);
       this._title.appendChild(deleteNode);
     }
+
+    let { actionsFirst } = this._variablesView;
+    if (ownerView.new || actionsFirst) {
+      let addPropertyNode = this._addPropertyNode = this.document.createElement("toolbarbutton");
+      addPropertyNode.className = "plain variables-view-add-property";
+      addPropertyNode.addEventListener("mousedown", this._onAddProperty.bind(this), false);
+      if (actionsFirst && VariablesView.isPrimitive(descriptor)) {
+        addPropertyNode.setAttribute("invisible", "");
+      }
+      this._title.appendChild(addPropertyNode);
+    }
+
     if (ownerView.contextMenuId) {
       this._title.setAttribute("context", ownerView.contextMenuId);
     }
 
     if (ownerView.preventDescriptorModifiers) {
       return;
     }
 
@@ -2578,158 +2641,52 @@ Variable.prototype = Heritage.extend(Sco
    */
   _addEventListeners: function() {
     this._name.addEventListener("dblclick", this._activateNameInput, false);
     this._valueLabel.addEventListener("mousedown", this._activateValueInput, false);
     this._title.addEventListener("mousedown", this._onClick, false);
   },
 
   /**
-   * Creates a textbox node in place of a label.
-   *
-   * @param nsIDOMNode aLabel
-   *        The label to be replaced with a textbox.
-   * @param string aClassName
-   *        The class to be applied to the textbox.
-   * @param object aCallbacks
-   *        An object containing the onKeypress and onBlur callbacks.
-   */
-  _activateInput: function(aLabel, aClassName, aCallbacks) {
-    let initialString = aLabel.getAttribute("value");
-
-    // Create a texbox input element which will be shown in the current
-    // element's specified label location.
-    let input = this.document.createElement("textbox");
-    input.className = "plain " + aClassName;
-    input.setAttribute("value", initialString);
-    if (!this._variablesView.alignedValues) {
-      input.setAttribute("flex", "1");
-    }
-
-    // Replace the specified label with a textbox input element.
-    aLabel.parentNode.replaceChild(input, aLabel);
-    this._variablesView.boxObject.ensureElementIsVisible(input);
-    input.select();
-
-    // When the value is a string (displayed as "value"), then we probably want
-    // to change it to another string in the textbox, so to avoid typing the ""
-    // again, tackle with the selection bounds just a bit.
-    if (aLabel.getAttribute("value").match(/^".+"$/)) {
-      input.selectionEnd--;
-      input.selectionStart++;
-    }
-
-    input.addEventListener("keypress", aCallbacks.onKeypress, false);
-    input.addEventListener("blur", aCallbacks.onBlur, false);
-
-    this._prevExpandable = this.twisty;
-    this._prevExpanded = this.expanded;
-    this.collapse();
-    this.hideArrow();
-    this._locked = true;
-
-    this._inputNode = input;
-    this._stopThrobber();
-  },
-
-  /**
-   * Removes the textbox node in place of a label.
-   *
-   * @param nsIDOMNode aLabel
-   *        The label which was replaced with a textbox.
-   * @param object aCallbacks
-   *        An object containing the onKeypress and onBlur callbacks.
-   */
-  _deactivateInput: function(aLabel, aInput, aCallbacks) {
-    aInput.parentNode.replaceChild(aLabel, aInput);
-    this._variablesView.boxObject.scrollBy(-this._target.clientWidth, 0);
-
-    aInput.removeEventListener("keypress", aCallbacks.onKeypress, false);
-    aInput.removeEventListener("blur", aCallbacks.onBlur, false);
-
-    this._locked = false;
-    this.twisty = this._prevExpandable;
-    this.expanded = this._prevExpanded;
-
-    this._inputNode = null;
-    this._stopThrobber();
-  },
-
-  /**
    * Makes this variable's name editable.
    */
   _activateNameInput: function(e) {
-    if (e && e.button != 0) {
-      // Only allow left-click to trigger this event.
-      return;
-    }
-    if (!this.ownerView.switch) {
-      return;
-    }
-    if (e) {
-      e.preventDefault();
-      e.stopPropagation();
+    if (!this._variablesView.alignedValues) {
+      this._separatorLabel.hidden = true;
+      this._valueLabel.hidden = true;
     }
 
-    this._onNameInputKeyPress = this._onNameInputKeyPress.bind(this);
-    this._deactivateNameInput = this._deactivateNameInput.bind(this);
-
-    this._activateInput(this._name, "element-name-input", {
-      onKeypress: this._onNameInputKeyPress,
-      onBlur: this._deactivateNameInput
-    });
-    this._separatorLabel.hidden = true;
-    this._valueLabel.hidden = true;
-  },
-
-  /**
-   * Deactivates this variable's editable name mode.
-   */
-  _deactivateNameInput: function(e) {
-    this._deactivateInput(this._name, e.target, {
-      onKeypress: this._onNameInputKeyPress,
-      onBlur: this._deactivateNameInput
-    });
-    this._separatorLabel.hidden = false;
-    this._valueLabel.hidden = false;
+    EditableName.create(this, {
+      onSave: aKey => {
+        if (!this._variablesView.preventDisableOnChange) {
+          this._disable();
+        }
+        this.ownerView.switch(this, aKey);
+      },
+      onCleanup: () => {
+        if (!this._variablesView.alignedValues) {
+          this._separatorLabel.hidden = false;
+          this._valueLabel.hidden = false;
+        }
+      }
+    }, e);
   },
 
   /**
    * Makes this variable's value editable.
    */
   _activateValueInput: function(e) {
-    if (e && e.button != 0) {
-      // Only allow left-click to trigger this event.
-      return;
-    }
-    if (!this.ownerView.eval) {
-      return;
-    }
-    if (e) {
-      e.preventDefault();
-      e.stopPropagation();
-    }
-
-    this._onValueInputKeyPress = this._onValueInputKeyPress.bind(this);
-    this._deactivateValueInput = this._deactivateValueInput.bind(this);
-
-    this._activateInput(this._valueLabel, "element-value-input", {
-      onKeypress: this._onValueInputKeyPress,
-      onBlur: this._deactivateValueInput
-    });
-  },
-
-  /**
-   * Deactivates this variable's editable value mode.
-   */
-  _deactivateValueInput: function(e) {
-    this._deactivateInput(this._valueLabel, e.target, {
-      onKeypress: this._onValueInputKeyPress,
-      onBlur: this._deactivateValueInput
-    });
+    EditableValue.create(this, {
+      onSave: aString => {
+        if (!this._variablesView.preventDisableOnChange) {
+          this._disable();
+        }
+        this.ownerView.eval(this.evaluationMacro(this, aString));
+      }
+    }, e);
   },
 
   /**
    * Disables this variable prior to a new name switch or value evaluation.
    */
   _disable: function() {
     // Prevent the variable from being collapsed or expanded.
     this.hideArrow();
@@ -2738,95 +2695,22 @@ Variable.prototype = Heritage.extend(Sco
     for (let node of this._title.childNodes) {
       node.hidden = node != this._arrow && node != this._name;
     }
     this._enum.hidden = true;
     this._nonenum.hidden = true;
   },
 
   /**
-   * Deactivates this variable's editable mode and callbacks the new name.
-   */
-  _saveNameInput: function(e) {
-    let input = e.target;
-    let initialString = this._name.getAttribute("value");
-    let currentString = input.value.trim();
-    this._deactivateNameInput(e);
-
-    if (initialString != currentString) {
-      if (!this._variablesView.preventDisableOnChage) {
-        this._disable();
-        this._name.value = currentString;
-      }
-      this.ownerView.switch(this, currentString);
-    }
-  },
-
-  /**
-   * Deactivates this variable's editable mode and evaluates the new value.
-   */
-  _saveValueInput: function(e) {
-    let input = e.target;
-    let initialString = this._valueLabel.getAttribute("value");
-    let currentString = input.value.trim();
-    this._deactivateValueInput(e);
-
-    if (initialString != currentString) {
-      if (!this._variablesView.preventDisableOnChage) {
-        this._disable();
-      }
-      this.ownerView.eval(this.evaluationMacro(this, currentString.trim()));
-    }
-  },
-
-  /**
    * The current macro used to generate the string evaluated when performing
    * a variable or property value change.
    */
   evaluationMacro: VariablesView.simpleValueEvalMacro,
 
   /**
-   * The key press listener for this variable's editable name textbox.
-   */
-  _onNameInputKeyPress: function(e) {
-    e.stopPropagation();
-
-    switch(e.keyCode) {
-      case e.DOM_VK_RETURN:
-      case e.DOM_VK_ENTER:
-        this._saveNameInput(e);
-        this.focus();
-        return;
-      case e.DOM_VK_ESCAPE:
-        this._deactivateNameInput(e);
-        this.focus();
-        return;
-    }
-  },
-
-  /**
-   * The key press listener for this variable's editable value textbox.
-   */
-  _onValueInputKeyPress: function(e) {
-    e.stopPropagation();
-
-    switch(e.keyCode) {
-      case e.DOM_VK_RETURN:
-      case e.DOM_VK_ENTER:
-        this._saveValueInput(e);
-        this.focus();
-        return;
-      case e.DOM_VK_ESCAPE:
-        this._deactivateValueInput(e);
-        this.focus();
-        return;
-    }
-  },
-
-  /**
    * The click listener for the edit button.
    */
   _onEdit: function(e) {
     if (e.button != 0) {
       return;
     }
 
     e.preventDefault();
@@ -2847,25 +2731,58 @@ Variable.prototype = Heritage.extend(Sco
 
     if (this.ownerView.delete) {
       if (!this.ownerView.delete(this)) {
         this.hide();
       }
     }
   },
 
+  /**
+   * The click listener for the add property button.
+   */
+  _onAddProperty: function(e) {
+    if ("button" in e && e.button != 0) {
+      return;
+    }
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    this.expanded = true;
+
+    let item = this.addItem(" ", {
+      value: undefined,
+      configurable: true,
+      enumerable: true,
+      writable: true
+    }, true);
+
+    // Force showing the separator.
+    item._separatorLabel.hidden = false;
+
+    EditableNameAndValue.create(item, {
+      onSave: ([aKey, aValue]) => {
+        if (!this._variablesView.preventDisableOnChange) {
+          this._disable();
+        }
+        this.ownerView.new(this, aKey, aValue);
+      }
+    }, e);
+  },
+
   _symbolicName: "",
   _absoluteName: "",
   _initialDescriptor: null,
   _separatorLabel: null,
   _spacer: null,
   _valueLabel: null,
-  _inputNode: null,
   _editNode: null,
   _deleteNode: null,
+  _addPropertyNode: null,
   _tooltip: null,
   _valueGrip: null,
   _valueString: "",
   _valueClassName: "",
   _prevExpandable: false,
   _prevExpanded: false
 });
 
@@ -2890,28 +2807,25 @@ Property.prototype = Heritage.extend(Var
   /**
    * Initializes this property's id, view and binds event listeners.
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        The property's descriptor.
    */
-  _init: function(aName, aDescriptor) {
+  _init: function(aName = "", aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, "variables-view-property variable-or-property");
 
-    // Don't allow displaying property information there's no name available.
-    if (this._nameString) {
-      this._displayVariable();
-      this._customizeVariable();
-      this._prepareTooltips();
-      this._setAttributes();
-      this._addEventListeners();
-    }
+    this._displayVariable();
+    this._customizeVariable();
+    this._prepareTooltips();
+    this._setAttributes();
+    this._addEventListeners();
 
     this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
   },
 
   /**
    * Called when this property has finished initializing, and is ready to
    * be attached to the owner view.
    *
@@ -3258,8 +3172,267 @@ VariablesView.getClass = function(aGrip)
  *         A unique id.
  */
 let generateId = (function() {
   let count = 0;
   return function(aName = "") {
     return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
   };
 })();
+
+
+/**
+ * An Editable encapsulates the UI of an edit box that overlays a label,
+ * allowing the user to edit the value.
+ *
+ * @param Variable aVariable
+ *        The Variable or Property to make editable.
+ * @param object aOptions
+ *        - onSave
+ *          The callback to call with the value when editing is complete.
+ *        - onCleanup
+ *          The callback to call when the editable is removed for any reason.
+ */
+function Editable(aVariable, aOptions) {
+  this._variable = aVariable;
+  this._onSave = aOptions.onSave;
+  this._onCleanup = aOptions.onCleanup;
+}
+
+Editable.create = function(aVariable, aOptions, aEvent) {
+  let editable = new this(aVariable, aOptions);
+  editable.activate(aEvent);
+  return editable;
+};
+
+Editable.prototype = {
+  /**
+   * The class name for targeting this Editable type's label element. Overridden
+   * by inheriting classes.
+   */
+  className: null,
+
+  /**
+   * Boolean indicating whether this Editable should activate. Overridden by
+   * inheriting classes.
+   */
+  shouldActivate: null,
+
+  /**
+   * The label element for this Editable. Overridden by inheriting classes.
+   */
+  label: null,
+
+  /**
+   * Activate this editable by replacing the input box it overlays and
+   * initialize the handlers.
+   *
+   * @param Event e [optional]
+   *        Optionally, the Event object that was used to activate the Editable.
+   */
+  activate: function(e) {
+    if (!this.shouldActivate) {
+      return;
+    }
+
+    let { label } = this;
+    let initialString = label.getAttribute("value");
+
+    if (e) {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+
+    // Create a texbox input element which will be shown in the current
+    // element's specified label location.
+    let input = this._input = this._variable.document.createElement("textbox");
+    input.className = "plain " + this.className;
+    input.setAttribute("value", initialString);
+    if (!this._variable._variablesView.alignedValues) {
+      input.setAttribute("flex", "1");
+    }
+
+    // Replace the specified label with a textbox input element.
+    label.parentNode.replaceChild(input, label);
+    this._variable._variablesView.boxObject.ensureElementIsVisible(input);
+    input.select();
+
+    // When the value is a string (displayed as "value"), then we probably want
+    // to change it to another string in the textbox, so to avoid typing the ""
+    // again, tackle with the selection bounds just a bit.
+    if (initialString.match(/^".+"$/)) {
+      input.selectionEnd--;
+      input.selectionStart++;
+    }
+
+    this._onKeypress = this._onKeypress.bind(this);
+    this._onBlur = this._onBlur.bind(this);
+    input.addEventListener("keypress", this._onKeypress);
+    input.addEventListener("blur", this._onBlur);
+
+    this._prevExpandable = this._variable.twisty;
+    this._prevExpanded = this._variable.expanded;
+    this._variable.collapse();
+    this._variable.hideArrow();
+    this._variable.locked = true;
+    this._variable._stopThrobber();
+  },
+
+  /**
+   * Remove the input box and restore the Variable or Property to its previous
+   * state.
+   */
+  deactivate: function() {
+    this._input.removeEventListener("keypress", this._onKeypress);
+    this._input.removeEventListener("blur", this.deactivate);
+    this._input.parentNode.replaceChild(this.label, this._input);
+    this._input = null;
+
+    let { boxObject } = this._variable._variablesView;
+    boxObject.scrollBy(-this._variable._target, 0);
+    this._variable.locked = false;
+    this._variable.twisty = this._prevExpandable;
+    this._variable.expanded = this._prevExpanded;
+    this._variable._stopThrobber();
+  },
+
+  /**
+   * Save the current value and deactivate the Editable.
+   */
+  _save: function() {
+    let initial = this.label.getAttribute("value");
+    let current = this._input.value.trim();
+    this.deactivate();
+    if (initial != current) {
+      this._onSave(current);
+    }
+  },
+
+  /**
+   * Called when tab is pressed, allowing subclasses to link different
+   * behavior to tabbing if desired.
+   */
+  _next: function() {
+    this._save();
+  },
+
+  /**
+   * Called when escape is pressed, indicating a cancelling of editing without
+   * saving.
+   */
+  _reset: function() {
+    this.deactivate();
+    this._variable.focus();
+  },
+
+  /**
+   * Event handler for when the input loses focus.
+   */
+  _onBlur: function() {
+    this.deactivate();
+  },
+
+  /**
+   * Event handler for when the input receives a key press.
+   */
+  _onKeypress: function(e) {
+    e.stopPropagation();
+
+    switch (e.keyCode) {
+      case e.DOM_VK_TAB:
+        this._next();
+        break;
+      case e.DOM_VK_RETURN:
+      case e.DOM_VK_ENTER:
+        this._save();
+        break;
+      case e.DOM_VK_ESCAPE:
+        this._reset();
+        break;
+    }
+  },
+};
+
+
+/**
+ * An Editable specific to editing the name of a Variable or Property.
+ */
+function EditableName(aVariable, aOptions) {
+  Editable.call(this, aVariable, aOptions);
+}
+
+EditableName.create = Editable.create;
+
+EditableName.prototype = Heritage.extend(Editable.prototype, {
+  className: "element-name-input",
+
+  get label() {
+    return this._variable._name;
+  },
+
+  get shouldActivate() {
+    return !!this._variable.ownerView.switch;
+  },
+});
+
+
+/**
+ * An Editable specific to editing the value of a Variable or Property.
+ */
+function EditableValue(aVariable, aOptions) {
+  Editable.call(this, aVariable, aOptions);
+}
+
+EditableValue.create = Editable.create;
+
+EditableValue.prototype = Heritage.extend(Editable.prototype, {
+  className: "element-value-input",
+
+  get label() {
+    return this._variable._valueLabel;
+  },
+
+  get shouldActivate() {
+    return !!this._variable.ownerView.eval;
+  },
+});
+
+
+/**
+ * An Editable specific to editing the key and value of a new property.
+ */
+function EditableNameAndValue(aVariable, aOptions) {
+  EditableName.call(this, aVariable, aOptions);
+}
+
+EditableNameAndValue.create = Editable.create;
+
+EditableNameAndValue.prototype = Heritage.extend(EditableName.prototype, {
+  _reset: function(e) {
+    // Hide the Varible or Property if the user presses escape.
+    this._variable.remove();
+    this.deactivate();
+  },
+
+  _next: function(e) {
+    // Override _next so as to set both key and value at the same time.
+    let key = this._input.value;
+    this.label.setAttribute("value", key);
+
+    let valueEditable = EditableValue.create(this._variable, {
+      onSave: aValue => {
+        this._onSave([key, aValue]);
+      },
+      onCleanup: () => {
+        this._onCleanup();
+      }
+    });
+    valueEditable._reset = () => {
+      this._variable.remove();
+      valueEditable.deactivate();
+    };
+  },
+
+  _save: function(e) {
+    // Both _save and _next activate the value edit box.
+    this._next(e);
+  }
+});
--- a/browser/devtools/shared/widgets/widgets.css
+++ b/browser/devtools/shared/widgets/widgets.css
@@ -58,15 +58,21 @@
 
 .variable-or-property:not([safe-getter]) > tooltip > label[value=WebIDL],
 .variable-or-property:not([non-extensible]) > tooltip > label[value=extensible],
 .variable-or-property:not([frozen]) > tooltip > label[value=frozen],
 .variable-or-property:not([sealed]) > tooltip > label[value=sealed] {
   display: none;
 }
 
-*:not(:hover) .variables-view-delete {
+*:not(:hover) .variables-view-delete,
+*:not(:hover) .variables-view-add-property {
   visibility: hidden;
 }
 
+.variables-view-delete > .toolbarbutton-text,
+.variables-view-add-property > .toolbarbutton-text {
+  display: none;
+}
+
 .variables-view-container[aligned-values] .title > [optional-visibility] {
   display: none;
 }
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -2018,16 +2018,17 @@ TextPropertyEditor.prototype = {
     } else {
       this.element.removeAttribute("dirty");
     }
 
     let swatchClass = "ruleview-colorswatch";
     let outputParser = this.ruleEditor.ruleView._outputParser;
     let frag = outputParser.parseCssProperty(name, val, {
       colorSwatchClass: swatchClass,
+      colorClass: "ruleview-color",
       defaultColorType: !propDirty,
       urlClass: "theme-link",
       baseURI: this.sheetURI
     });
     this.valueSpan.innerHTML = "";
     this.valueSpan.appendChild(frag);
 
     // Attach the color picker tooltip to the color swatches
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -48,8 +48,9 @@ support-files =
 [browser_bug894376_css_value_completion_existing_property_value_pair.js]
 [browser_ruleview_bug_902966_revert_value_on_ESC.js]
 [browser_ruleview_pseudoelement.js]
 support-files = browser_ruleview_pseudoelement.html
 [browser_computedview_bug835808_keyboard_nav.js]
 [browser_bug913014_matched_expand.js]
 [browser_bug765105_background_image_tooltip.js]
 [browser_bug889638_rule_view_color_picker.js]
+[browser_bug940500_rule_view_pick_gradient_color.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug940500_rule_view_pick_gradient_color.js
@@ -0,0 +1,135 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that changing a color in a gradient css declaration using the tooltip
+// color picker works
+
+let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+
+let contentDoc;
+let contentWin;
+let inspector;
+let ruleView;
+
+const PAGE_CONTENT = [
+  '<style type="text/css">',
+  '  body {',
+  '    background-image: linear-gradient(to left, #f06 25%, #333 95%, #000 100%);',
+  '  }',
+  '</style>',
+  'Updating a gradient declaration with the color picker tooltip'
+].join("\n");
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    contentWin = contentDoc.defaultView;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,rule view color picker tooltip test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    startTests();
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.body);
+  inspector.once("inspector-updated", testColorParsing);
+}
+
+function endTests() {
+  executeSoon(function() {
+    gDevTools.once("toolbox-destroyed", () => {
+      contentDoc = contentWin = inspector = ruleView = null;
+      gBrowser.removeCurrentTab();
+      finish();
+    });
+    inspector._toolbox.destroy();
+  });
+}
+
+function testColorParsing() {
+  let ruleEl = getRuleViewProperty("background-image");
+  ok(ruleEl, "The background-image gradient declaration was found");
+
+  let swatchEls = ruleEl.valueSpan.querySelectorAll(".ruleview-colorswatch");
+  ok(swatchEls, "The color swatch elements were found");
+  is(swatchEls.length, 3, "There are 3 color swatches");
+
+  let colorEls = ruleEl.valueSpan.querySelectorAll(".ruleview-color");
+  ok(colorEls, "The color elements were found");
+  is(colorEls.length, 3, "There are 3 color values");
+
+  let colors = ["#F06", "#333", "#000"];
+  for (let i = 0; i < colors.length; i ++) {
+    is(colorEls[i].textContent, colors[i], "The right color value was found");
+  }
+
+  testPickingNewColor();
+}
+
+function testPickingNewColor() {
+  // Grab the first color swatch and color in the gradient
+  let ruleEl = getRuleViewProperty("background-image");
+  let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch");
+  let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color");
+
+  // Get the color picker tooltip
+  let cPicker = ruleView.colorPicker;
+
+  cPicker.tooltip.once("shown", () => {
+    simulateColorChange(cPicker, [1, 1, 1, 1]);
+
+    executeSoon(() => {
+      is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)",
+        "The color swatch's background was updated");
+      is(colorEl.textContent, "rgba(1, 1, 1, 1)",
+        "The color text was updated");
+      is(content.getComputedStyle(content.document.body).backgroundImage,
+        "linear-gradient(to left, rgb(255, 0, 102) 25%, rgb(51, 51, 51) 95%, rgb(0, 0, 0) 100%)",
+        "The gradient has been updated correctly");
+
+      cPicker.hide();
+      endTests();
+    });
+  });
+  swatchEl.click();
+}
+
+function simulateColorChange(colorPicker, newRgba) {
+  // Note that this test isn't concerned with simulating events to test how the
+  // spectrum color picker reacts, see browser_spectrum.js for this.
+  // This test only cares about the color swatch <-> color picker <-> rule view
+  // interactions. That's why there's no event simulations here
+  colorPicker.spectrum.then(spectrum => {
+    spectrum.rgb = newRgba;
+    spectrum.updateUI();
+    spectrum.onChange();
+  });
+}
+
+function getRuleViewProperty(name) {
+  let prop = null;
+  [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
+    let nameSpan = property.querySelector(".ruleview-propertyname");
+    let valueSpan = property.querySelector(".ruleview-propertyvalue");
+
+    if (nameSpan.textContent === name) {
+      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
+    }
+  });
+  return prop;
+}
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -65,16 +65,17 @@ const COMPAT = {
 
   // The indent of a console group in pixels.
   GROUP_INDENT: 12,
 };
 
 // A map from the console API call levels to the Web Console severities.
 const CONSOLE_API_LEVELS_TO_SEVERITIES = {
   error: "error",
+  exception: "error",
   warn: "warning",
   info: "info",
   log: "log",
   trace: "log",
   debug: "log",
   dir: "log",
   group: "log",
   groupCollapsed: "log",
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -181,18 +181,16 @@ support-files =
 [browser_webconsole_bug_614793_jsterm_scroll.js]
 [browser_webconsole_bug_618078_network_exceptions.js]
 [browser_webconsole_bug_618311_close_panels.js]
 [browser_webconsole_bug_621644_jsterm_dollar.js]
 [browser_webconsole_bug_622303_persistent_filters.js]
 [browser_webconsole_bug_630733_response_redirect_headers.js]
 [browser_webconsole_bug_632275_getters_document_width.js]
 [browser_webconsole_bug_632347_iterators_generators.js]
-# Too many intermittent timeouts (bug 935277)
-skip-if = os == "linux"
 [browser_webconsole_bug_632817.js]
 [browser_webconsole_bug_642108_pruneTest.js]
 [browser_webconsole_bug_642615_autocomplete.js]
 [browser_webconsole_bug_644419_log_limits.js]
 [browser_webconsole_bug_646025_console_file_location.js]
 [browser_webconsole_bug_651501_document_body_autocomplete.js]
 [browser_webconsole_bug_653531_highlighter_console_helper.js]
 [browser_webconsole_bug_658368_time_methods.js]
--- a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
@@ -29,23 +29,41 @@ function testNext() {
   });
 }
 
 function testCompletion(hud) {
   let jsterm = hud.jsterm;
   let input = jsterm.inputNode;
   let popup = jsterm.autocompletePopup;
 
+  // Test that document.title gives string methods. Native getters must execute.
+  input.value = "document.title.";
+  input.setSelectionRange(input.value.length, input.value.length);
+  jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+  yield undefined;
+
+  let newItems = popup.getItems();
+  ok(newItems.length > 0, "'document.title.' gave a list of suggestions");
+  ok(newItems.some(function(item) {
+       return item.label == "substr";
+     }), "autocomplete results do contain substr");
+  ok(newItems.some(function(item) {
+       return item.label == "toLowerCase";
+     }), "autocomplete results do contain toLowerCase");
+  ok(newItems.some(function(item) {
+       return item.label == "strike";
+     }), "autocomplete results do contain strike");
+
   // Test if 'f' gives 'foo1' but not 'foo2' or 'foo3'
   input.value = "f";
   input.setSelectionRange(1, 1);
   jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
   yield undefined;
 
-  let newItems = popup.getItems();
+  newItems = popup.getItems();
   ok(newItems.length > 0, "'f' gave a list of suggestions");
   ok(!newItems.every(function(item) {
        return item.label != "foo1";
      }), "autocomplete results do contain foo1");
   ok(!newItems.every(function(item) {
        return item.label != "foo1Obj";
      }), "autocomplete results do contain foo1Obj");
   ok(newItems.every(function(item) {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -21,54 +21,55 @@ function consoleOpened(HUD) {
 
   let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
   tmp.addDebuggerToGlobal(tmp);
   let dbg = new tmp.Debugger;
 
   let jsterm = HUD.jsterm;
   let win = content.wrappedJSObject;
   let dbgWindow = dbg.makeGlobalObjectReference(win);
+  let container = win._container;
 
   // Make sure autocomplete does not walk through iterators and generators.
-  let result = win.gen1.next();
-  let completion = JSPropertyProvider(dbgWindow, null, "gen1.");
+  let result = container.gen1.next();
+  let completion = JSPropertyProvider(dbgWindow, null, "_container.gen1.");
   isnot(completion.matches.length, 0, "Got matches for gen1");
 
-  is(result+1, win.gen1.next(), "gen1.next() did not execute");
+  is(result+1, container.gen1.next(), "gen1.next() did not execute");
 
-  result = win.gen2.next();
+  result = container.gen2.next();
 
-  completion = JSPropertyProvider(dbgWindow, null, "gen2.");
+  completion = JSPropertyProvider(dbgWindow, null, "_container.gen2.");
   isnot(completion.matches.length, 0, "Got matches for gen2");
 
-  is((result/2+1)*2, win.gen2.next(),
+  is((result/2+1)*2, container.gen2.next(),
      "gen2.next() did not execute");
 
-  result = win.iter1.next();
+  result = container.iter1.next();
   is(result[0], "foo", "iter1.next() [0] is correct");
   is(result[1], "bar", "iter1.next() [1] is correct");
 
-  completion = JSPropertyProvider(dbgWindow, null, "iter1.");
+  completion = JSPropertyProvider(dbgWindow, null, "_container.iter1.");
   isnot(completion.matches.length, 0, "Got matches for iter1");
 
-  result = win.iter1.next();
+  result = container.iter1.next();
   is(result[0], "baz", "iter1.next() [0] is correct");
   is(result[1], "baaz", "iter1.next() [1] is correct");
 
   let dbgContent = dbg.makeGlobalObjectReference(content);
-  completion = JSPropertyProvider(dbgContent, null, "iter2.");
+  completion = JSPropertyProvider(dbgContent, null, "_container.iter2.");
   isnot(completion.matches.length, 0, "Got matches for iter2");
 
-  completion = JSPropertyProvider(dbgWindow, null, "window.");
-  ok(completion, "matches available for window");
+  completion = JSPropertyProvider(dbgWindow, null, "window._container.");
+  ok(completion, "matches available for window._container");
   ok(completion.matches.length, "matches available for window (length)");
 
   jsterm.clearOutput();
 
-  jsterm.execute("window", (msg) => {
+  jsterm.execute("window._container", (msg) => {
     jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
     let anchor = msg.querySelector(".body a");
     EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
   });
 }
 
 function testVariablesView(aWebconsole, aEvent, aView) {
   findVariableViewProperties(aView, [
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -24,21 +24,24 @@ function consoleOpened(aHud) {
 
   ok(!popup.isOpen, "popup is not open");
 
   popup._panel.addEventListener("popupshown", function onShown() {
     popup._panel.removeEventListener("popupshown", onShown, false);
 
     ok(popup.isOpen, "popup is open");
 
-    // expected properties:
-    // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
-    // constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
-    // toLocaleString toSource toString unwatch valueOf watch.
-    ok(popup.itemCount >= 14, "popup.itemCount is correct");
+    is(popup.itemCount, jsterm._autocompleteCache.length,
+       "popup.itemCount is correct");
+    isnot(jsterm._autocompleteCache.indexOf("addEventListener"), -1,
+          "addEventListener is in the list of suggestions");
+    isnot(jsterm._autocompleteCache.indexOf("bgColor"), -1,
+          "bgColor is in the list of suggestions");
+    isnot(jsterm._autocompleteCache.indexOf("ATTRIBUTE_NODE"), -1,
+          "ATTRIBUTE_NODE is in the list of suggestions");
 
     popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
 
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   }, false);
 
   jsterm.setInputValue("document.body");
   EventUtils.synthesizeKey(".", {});
--- a/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
@@ -1,14 +1,16 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-// Tests that the basic console.log()-style APIs and filtering work.
+// Test that window.console functions that are not implemented yet do not
+// output anything in the web console and they do not throw any exceptions.
+// See bug 614350.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-extras.html";
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
--- a/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
@@ -36,16 +36,19 @@ function testGen() {
   yield undefined;
 
   subtestGen("warn");
   yield undefined;
 
   subtestGen("error");
   yield undefined;
 
+  subtestGen("exception");
+  yield undefined;
+
   subtestGen("debug"); // bug 616742
   yield undefined;
 
   testDriver = subtestDriver = null;
   finishTest();
 
   yield undefined;
 }
@@ -81,17 +84,28 @@ function testConsoleLoggingAPI(aMethod) 
   yield undefined;
 
   hud.jsterm.clearOutput();
 
   // now toggle the current method off - make sure no visible message
 
   // TODO: move all filtering tests into a separate test file: see bug 608135
   setStringFilter("");
-  let filter = aMethod == "debug" ? "log" : aMethod;
+  let filter;
+  switch(aMethod) {
+    case "debug":
+      filter = "log";
+      break;
+    case "exception":
+      filter = "error";
+      break;
+    default:
+      filter = aMethod;
+      break;
+  }
   hud.setFilterState(filter, false);
   console[aMethod]("foo-bar-baz");
 
   waitForSuccess({
     name: "1 message hidden for " + aMethod + " (logging turned off)",
     validatorFn: function()
     {
       return outputNode.querySelectorAll(".filtered-by-type").length == 1;
--- a/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html
+++ b/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html
@@ -9,21 +9,23 @@
 (function(){
 function genFunc() {
   var a = 5;
   while (a < 10) {
     yield a++;
   }
 }
 
-window.gen1 = genFunc();
-gen1.next();
+window._container = {};
+
+_container.gen1 = genFunc();
+_container.gen1.next();
 
 var obj = { foo: "bar", baz: "baaz", hay: "stack" };
-window.iter1 = Iterator(obj);
+_container.iter1 = Iterator(obj);
 
 function Range(low, high) {
   this.low = low;
   this.high = high;
 }
 
 function RangeIterator(range) {
   this.range = range;
@@ -37,18 +39,18 @@ RangeIterator.prototype.next = function(
     return this.current++;
   }
 }
 
 Range.prototype.__iterator__ = function() {
   return new RangeIterator(this);
 }
 
-window.iter2 = new Range(3, 15);
+_container.iter2 = new Range(3, 15);
 
-window.gen2 = (i * 2 for (i in iter2));
+_container.gen2 = (i * 2 for (i in _container.iter2));
 })();
 </script>
   </head>
   <body>
     <p>Web Console test for bug 632347 - iterators and generators.</p>
   </body>
 </html>
--- a/browser/devtools/webconsole/test/test-console-extras.html
+++ b/browser/devtools/webconsole/test/test-console-extras.html
@@ -1,16 +1,15 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
     <meta charset="utf-8">
     <title>Console extended API test</title>
     <script type="text/javascript">
       function test() {
         console.log("start");
-        console.exception()
         console.assert()
         console.clear()
         console.dirxml()
         console.profile()
         console.profileEnd()
         console.count()
         console.table()
         console.log("end");
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -112,16 +112,17 @@ const MESSAGE_PREFERENCE_KEYS = [
   [ null,         null,         null,     null,          ],  // Output
   [ "secerror",   "secwarn",    null,     null,          ],  // Security
 ];
 
 // A mapping from the console API log event levels to the Web Console
 // severities.
 const LEVELS = {
   error: SEVERITY_ERROR,
+  exception: SEVERITY_ERROR,
   warn: SEVERITY_WARNING,
   info: SEVERITY_INFO,
   log: SEVERITY_LOG,
   trace: SEVERITY_LOG,
   debug: SEVERITY_LOG,
   dir: SEVERITY_LOG,
   group: SEVERITY_LOG,
   groupCollapsed: SEVERITY_LOG,
@@ -1164,16 +1165,17 @@ WebConsoleFrame.prototype = {
       }
     });
 
     switch (level) {
       case "log":
       case "info":
       case "warn":
       case "error":
+      case "exception":
       case "debug": {
         let msg = new Messages.ConsoleGeneric(aMessage);
         node = msg.init(this.output).render().element;
         break;
       }
       case "dir": {
         body = { arguments: args };
         let clipboardArray = [];
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -26,19 +26,19 @@ open-file-button.tooltiptext = Open file
 developer-button.label = Developer
 # LOCALIZATION NOTE(developer-button.tooltiptext): %S is the keyboard shortcut
 developer-button.tooltiptext = Web Developer Tools (%S)
 
 add-ons-button.label = Add-ons
 # LOCALIZATION NOTE(add-ons-button.tooltiptext): %S is the keyboard shortcut
 add-ons-button.tooltiptext = Add-ons Manager (%S)
 
-switch-to-metro-button.label = Metro Mode
+switch-to-metro-button.label = Windows 8 Touch
 # LOCALIZATION NOTE(switch-to-metro-button.tooltiptext): %S is the brand short name
-switch-to-metro-button.tooltiptext = Relaunch in Windows 8 style %S
+switch-to-metro-button.tooltiptext = Relaunch in %S for Windows 8 Touch
 
 preferences-button.label = Preferences
 # LOCALIZATION NOTE (preferences-button.tooltiptext): Use the unicode ellipsis char,
 # \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
 preferences-button.tooltiptext = Preferences…
 # LOCALIZATION NOTE (preferences-button.labelWin): Windows-only label for Options
 preferences-button.labelWin = Options
 # LOCALIZATION NOTE (preferences-button.tooltipWin): Windows-only tooltip for Options
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -65,20 +65,20 @@ detailsPane.noItems=No items
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of items
 # example: 111 items
 detailsPane.itemsCountLabel=One item;#1 items
 
 mostVisitedTitle=Most Visited
 recentlyBookmarkedTitle=Recently Bookmarked
 recentTagsTitle=Recent Tags
-# LOCALIZATION NOTE (firefoxTouchTitle): this is the name of the folder used
+# LOCALIZATION NOTE (windows8TouchTitle): this is the name of the folder used
 # to store bookmarks created in Metro mode and share bookmarks between Metro
 # and Desktop.
-firefoxTouchTitle=Firefox Touch
+windows8TouchTitle=Windows 8 Touch
 
 OrganizerQueryHistory=History
 OrganizerQueryDownloads=Downloads
 OrganizerQueryAllBookmarks=All Bookmarks
 OrganizerQueryTags=Tags
 
 # LOCALIZATION NOTE (tagResultLabel) :
 # Noun used to describe the location bar autocomplete result type
--- a/browser/metro/base/content/apzc.js
+++ b/browser/metro/base/content/apzc.js
@@ -27,64 +27,50 @@ var APZCObserver = {
       return;
     }
 
     let os = Services.obs;
     os.addObserver(this, "apzc-transform-begin", false);
 
     // Fired by ContentAreaObserver
     window.addEventListener("SizeChanged", this, true);
-
     Elements.tabList.addEventListener("TabSelect", this, true);
-    Elements.tabList.addEventListener("TabOpen", this, true);
-    Elements.tabList.addEventListener("TabClose", this, true);
+    Elements.browsers.addEventListener("pageshow", this, true);
+    messageManager.addMessageListener("Browser:ContentScroll", this);
+    messageManager.addMessageListener("Content:ZoomToRect", this);
   },
 
   shutdown: function shutdown() {
     if (!this._enabled) {
       return;
     }
 
     let os = Services.obs;
     os.removeObserver(this, "apzc-transform-begin");
 
     window.removeEventListener("SizeChanged", this, true);
-
     Elements.tabList.removeEventListener("TabSelect", this, true);
-    Elements.tabList.removeEventListener("TabOpen", this, true);
-    Elements.tabList.removeEventListener("TabClose", this, true);
+    Elements.browsers.removeEventListener("pageshow", this, true);
+    messageManager.removeMessageListener("Browser:ContentScroll", this);
+    messageManager.removeMessageListener("Content:ZoomToRect", this);
   },
 
   handleEvent: function APZC_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "SizeChanged":
       case 'TabSelect':
         this._resetDisplayPort();
         break;
 
       case 'pageshow':
         if (aEvent.target != Browser.selectedBrowser.contentDocument) {
           break;
         }
         this._resetDisplayPort();
         break;
-
-      case 'TabOpen': {
-        let browser = aEvent.originalTarget.linkedBrowser;
-        browser.addEventListener("pageshow", this, true);
-        // Register for notifications from content about scroll actions.
-        browser.messageManager.addMessageListener("Browser:ContentScroll", this);
-        break;
-      }
-      case 'TabClose': {
-        let browser = aEvent.originalTarget.linkedBrowser;
-        browser.removeEventListener("pageshow", this, true);
-        browser.messageManager.removeMessageListener("Browser:ContentScroll", this);
-        break;
-      }
     }
   },
 
   observe: function ao_observe(aSubject, aTopic, aData) {
     if (aTopic == "apzc-transform-begin") {
       // When we're panning, hide the main scrollbars by setting imprecise
       // input (which sets a property on the browser which hides the scrollbar
       // via CSS).  This reduces jittering from left to right. We may be able
@@ -92,30 +78,57 @@ var APZCObserver = {
       if (InputSourceHelper.isPrecise) {
         InputSourceHelper._imprecise();
       }
     }
   },
 
   receiveMessage: function(aMessage) {
     let json = aMessage.json;
+    let browser = aMessage.target;
     switch (aMessage.name) {
        // Content notifies us here (syncronously) if it has scrolled
        // independent of the apz. This can happen in a lot of
        // cases: keyboard shortcuts, scroll wheel, or content script.
        // Let the apz know about this change so that it can update
        // its scroll offset data.
       case "Browser:ContentScroll": {
         let data = json.viewId + " " + json.presShellId + " (" + json.scrollOffset.x + ", " + json.scrollOffset.y + ")";
         Services.obs.notifyObservers(null, "apzc-scroll-offset-changed", data);
         break;
       }
+      case "Content:ZoomToRect": {
+        let { presShellId, viewId } = json;
+        let rect = Rect.fromRect(json.rect);
+        if (this.isRectZoomedIn(rect, browser.contentViewportBounds)) {
+          // If we're already zoomed in, zoom out instead.
+          rect = new Rect(0,0,0,0);
+        }
+        let data = [rect.x, rect.y, rect.width, rect.height, presShellId, viewId].join(",");
+        Services.obs.notifyObservers(null, "apzc-zoom-to-rect", data);
+      }
     }
   },
 
+  /**
+   * Check to see if the area of the rect visible in the viewport is
+   * approximately the max area of the rect we can show.
+   * Based on code from BrowserElementPanning.js
+   */
+  isRectZoomedIn: function (aRect, aViewport) {
+    let overlap = aViewport.intersect(aRect);
+    let overlapArea = overlap.width * overlap.height;
+    let availHeight = Math.min(aRect.width * aViewport.height / aViewport.width, aRect.height);
+    let showing = overlapArea / (aRect.width * availHeight);
+    let ratioW = (aRect.width / aViewport.width);
+    let ratioH = (aRect.height / aViewport.height);
+
+    return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9));
+  },
+
   _resetDisplayPort: function () {
     // Start off with something reasonable. The apzc will handle these
     // calculations once scrolling starts.
     let doc = Browser.selectedBrowser.contentDocument.documentElement;
     // While running tests, sometimes this can be null. If we don't have a
     // root document, there's no point in setting a scrollable display port.
     if (!doc) {
       return;
--- a/browser/metro/base/content/bindings/browser.xml
+++ b/browser/metro/base/content/bindings/browser.xml
@@ -169,18 +169,18 @@
             }
 
             let scale = 1;
             if (!ignoreScale) {
               scale = this.scale;
             }
 
             return {
-              x: (aClientX + scrollX - bcr.left) / scale,
-              y: (aClientY + scrollY - bcr.top) / scale
+              x: (aClientX - bcr.left) / scale + scrollX,
+              y: (aClientY - bcr.top) / scale + scrollY
             };
           ]]>
         </body>
       </method>
 
       <method name="rectClientToBrowser">
         <parameter name="aClientRect"/>
         <parameter name="aIgnoreScroll"/>
@@ -199,21 +199,22 @@
             }
 
             let scale = 1;
             if (!ignoreScale) {
               scale = this.scale;
             }
 
             let bcr = this.getBoundingClientRect();
+            let clientRect = Rect.fromRect(aClientRect);
             return new Rect(
-              (aClientRect.x + scrollX - bcr.left) / scale,
-              (aClientRect.y + scrollY - bcr.top) / scale,
-              aClientRect.width / scale,
-              aClientRect.height / scale
+              (clientRect.x - bcr.left) / scale + scrollX,
+              (clientRect.y - bcr.top) / scale + scrollY,
+              clientRect.width / scale,
+              clientRect.height / scale
             );
           ]]>
         </body>
       </method>
 
       <method name="ctobx">
         <parameter name="aX"/>
         <parameter name="aIgnoreScroll"/>
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -960,16 +960,17 @@ var BrowserUI = {
       case "cmd_openLocation":
       case "cmd_addBookmark":
       case "cmd_bookmarks":
       case "cmd_history":
       case "cmd_remoteTabs":
       case "cmd_quit":
       case "cmd_close":
       case "cmd_newTab":
+      case "cmd_newTabKey":
       case "cmd_closeTab":
       case "cmd_undoCloseTab":
       case "cmd_actions":
       case "cmd_panel":
       case "cmd_flyout_back":
       case "cmd_sanitize":
       case "cmd_volumeLeft":
       case "cmd_volumeRight":
@@ -1052,16 +1053,19 @@ var BrowserUI = {
         // Only close one window
         this._closeOrQuit();
         break;
       case "cmd_close":
         this._closeOrQuit();
         break;
       case "cmd_newTab":
         this.addAndShowTab();
+        break;
+      case "cmd_newTabKey":
+        this.addAndShowTab();
         // Make sure navbar is displayed before setting focus on url bar. Bug 907244
         ContextUI.displayNavbar();
         this._edit.beginEditing(false);
         break;
       case "cmd_closeTab":
         this.closeTab();
         break;
       case "cmd_undoCloseTab":
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -71,16 +71,17 @@
     <command id="cmd_go" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_openLocation" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_home" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_openFile" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_savePage" oncommand="CommandUpdater.doCommand(this.id);"/>
 
     <!-- tabs -->
     <command id="cmd_newTab" oncommand="CommandUpdater.doCommand(this.id);"/>
+    <command id="cmd_newTabKey" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_closeTab" oncommand="CommandUpdater.doCommand(this.id);"/>
     <command id="cmd_undoCloseTab" oncommand="CommandUpdater.doCommand(this.id);"/>
 #ifdef MOZ_SERVICES_SYNC
     <command id="cmd_remoteTabs" oncommand="CommandUpdater.doCommand(this.id);"/>
 #endif
 
     <!-- misc -->
     <command id="cmd_close" oncommand="CommandUpdater.doCommand(this.id);"/>
@@ -147,18 +148,18 @@
 Temporarily disabled until we can have sync prefs together with the
 Desktop browser's sync prefs.
 #ifdef MOZ_SERVICES_SYNC
     <key id="key_options" key="&syncFlyout.key;" modifiers="accel,shift" oncommand="FlyoutPanelsUI.show('SyncFlyoutPanel')" />
 #endif
 -->
 
     <!-- manage tabs -->
-    <key id="key_newTab" key="&newTab.key;" modifiers="accel" command="cmd_newTab"/>
-    <key id="key_newTab2" key="&newTab2.key;" modifiers="accel" command="cmd_newTab"/>
+    <key id="key_newTab" key="&newTab.key;" modifiers="accel" command="cmd_newTabKey"/>
+    <key id="key_newTab2" key="&newTab2.key;" modifiers="accel" command="cmd_newTabKey"/>
     <key id="key_closeTab" key="&closeTab.key;" modifiers="accel" command="cmd_closeTab"/>
     <key id="key_closeTab2" keycode="VK_F4" modifiers="accel" command="cmd_closeTab"/>
     <key id="key_undoCloseTab" key="&newTab.key;" modifiers="accel,shift" command="cmd_undoCloseTab"/>
 
     <!-- tab selection -->
     <key id="key_nextTab" oncommand="BrowserUI.selectNextTab();" keycode="VK_TAB" modifiers="accel"/>
     <key id="key_nextTab2" oncommand="BrowserUI.selectNextTab();" keycode="VK_PAGE_DOWN" modifiers="accel"/>
     <key id="key_prevTab" oncommand="BrowserUI.selectPreviousTab();" keycode="VK_TAB" modifiers="accel,shift"/>
--- a/browser/metro/base/content/contenthandlers/Content.js
+++ b/browser/metro/base/content/contenthandlers/Content.js
@@ -111,17 +111,16 @@ function getOverflowContentBoundingRect(
 
 /*
  * Content
  *
  * Browser event receiver for content.
  */
 let Content = {
   _debugEvents: false,
-  _isZoomedIn: false,
 
   get formAssistant() {
     delete this.formAssistant;
     return this.formAssistant = new FormAssistant();
   },
 
   init: function init() {
     // Asyncronous messages sent from the browser
@@ -140,17 +139,16 @@ let Content = {
     addEventListener("keyup", this);
 
     // Synchronous events caught during the bubbling phase
     addEventListener("MozApplicationManifest", this, false);
     addEventListener("DOMContentLoaded", this, false);
     addEventListener("DOMAutoComplete", this, false);
     addEventListener("DOMFormHasPassword", this, false);
     addEventListener("blur", this, false);
-    addEventListener("pagehide", this, false);
     // Attach a listener to watch for "click" events bubbling up from error
     // pages and other similar page. This lets us fix bugs like 401575 which
     // require error page UI to do privileged things, without letting error
     // pages have any privilege themselves.
     addEventListener("click", this, false);
 
     docShell.useGlobalHistory = true;
   },
@@ -205,20 +203,16 @@ let Content = {
         this._maybeNotifyErrorPage();
         break;
 
       case "DOMAutoComplete":
       case "blur":
         LoginManagerContent.onUsernameInput(aEvent);
         break;
 
-      case "pagehide":
-        this._isZoomedIn = false;
-        break;
-
       case "touchstart":
         this._onTouchStart(aEvent);
         break;
     }
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     if (this._debugEvents) Util.dumpLn("Content:", aMessage.name);
@@ -376,21 +370,16 @@ let Content = {
     let utils = Util.getWindowUtils(content);
     for (let type of ["mousemove", "mousedown", "mouseup"]) {
       utils.sendMouseEventToWindow(type, aX, aY, 0, 1, aModifiers, true, 1.0,
           Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
     }
   },
 
   _onDoubleTap: function (aX, aY) {
-    if (this._isZoomedIn) {
-      this._zoomOut();
-      return;
-    }
-
     let { element } = Content.getCurrentWindowAndOffset(aX, aY);
     while (element && !this._shouldZoomToElement(element)) {
       element = element.parentNode;
     }
 
     if (!element) {
       this._zoomOut();
     } else {
@@ -399,45 +388,41 @@ let Content = {
   },
 
   /******************************************************
    * Zoom utilities
    */
   _zoomOut: function() {
     let rect = new Rect(0,0,0,0);
     this._zoomToRect(rect);
-    this._isZoomedIn = false;
   },
 
   _zoomToElement: function(aElement) {
     let rect = getBoundingContentRect(aElement);
     this._inflateRect(rect, kZoomToElementMargin);
     this._zoomToRect(rect);
-    this._isZoomedIn = true;
   },
 
   _inflateRect: function(aRect, aMargin) {
     aRect.left -= aMargin;
     aRect.top -= aMargin;
     aRect.bottom += aMargin;
     aRect.right += aMargin;
   },
 
   _zoomToRect: function (aRect) {
     let utils = Util.getWindowUtils(content);
     let viewId = utils.getViewId(content.document.documentElement);
     let presShellId = {};
     utils.getPresShellId(presShellId);
-    let zoomData = [aRect.x,
-                    aRect.y,
-                    aRect.width,
-                    aRect.height,
-                    presShellId.value,
-                    viewId].join(",");
-    Services.obs.notifyObservers(null, "apzc-zoom-to-rect", zoomData);
+    sendAsyncMessage("Content:ZoomToRect", {
+      rect: aRect,
+      presShellId: presShellId.value,
+      viewId: viewId,
+    });
   },
 
   _shouldZoomToElement: function(aElement) {
     let win = aElement.ownerDocument.defaultView;
     if (win.getComputedStyle(aElement, null).display == "inline") {
       return false;
     }
     else if (aElement instanceof Ci.nsIDOMHTMLLIElement) {
--- a/browser/metro/base/tests/mochitest/browser_context_ui.js
+++ b/browser/metro/base/tests/mochitest/browser_context_ui.js
@@ -204,17 +204,17 @@ gTests.push({
 gTests.push({
   desc: "Bug 907244 - Opening a new tab when the page has focus doesn't correctly focus the location bar",
   run: function () {
     let mozTab = yield addTab("about:mozilla");
 
     // addTab will dismiss navbar, but lets check anyway.
     ok(!ContextUI.navbarVisible, "navbar dismissed");
 
-    BrowserUI.doCommand("cmd_newTab");
+    BrowserUI.doCommand("cmd_newTabKey");
     let newTab = Browser.selectedTab;
     yield newTab.pageShowPromise;
 
     yield waitForCondition(() => ContextUI.navbarVisible);
     ok(ContextUI.navbarVisible, "navbar visible");
 
     let edit = document.getElementById("urlbar-edit");
 
@@ -227,8 +227,32 @@ gTests.push({
     }
 
     ok(parent === edit, 'Active element is a descendant of urlbar');
 
     Browser.closeTab(newTab, { forceClose: true });
     Browser.closeTab(mozTab, { forceClose: true });
   }
 });
+
+gTests.push({
+  desc: "Bug 933989 - New tab button in tab bar always sets focus to the url bar, triggering soft keyboard",
+  run: function () {
+    let mozTab = yield addTab("about:mozilla");
+
+    // addTab will dismiss navbar, but lets check anyway.
+    ok(!ContextUI.navbarVisible, "navbar dismissed");
+
+    BrowserUI.doCommand("cmd_newTab");
+    let newTab = Browser.selectedTab;
+    yield newTab.pageShowPromise;
+
+    yield waitForCondition(() => ContextUI.navbarVisible);
+    ok(ContextUI.navbarVisible, "navbar visible");
+
+    let edit = document.getElementById("urlbar-edit");
+
+    ok(!edit.focused, "Edit is not focused");
+
+    Browser.closeTab(newTab, { forceClose: true });
+    Browser.closeTab(mozTab, { forceClose: true });
+  }
+});
--- a/browser/themes/linux/devtools/widgets.css
+++ b/browser/themes/linux/devtools/widgets.css
@@ -610,20 +610,25 @@
 }
 
 .variables-view-container[aligned-values] .title > .element-value-input {
   width: calc(70vw - 10px);
 }
 
 /* Actions first */
 
-.variables-view-container[actions-first] .variables-view-delete {
+.variables-view-container[actions-first] .variables-view-delete,
+.variables-view-container[actions-first] .variables-view-add-property {
   -moz-box-ordinal-group: 0;
 }
 
+.variables-view-container[actions-first] [invisible] {
+  visibility: hidden;
+}
+
 /* Variables and properties tooltips */
 
 .variable-or-property > tooltip > label {
   margin: 0 2px 0 2px;
 }
 
 .variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
 .variable-or-property[non-configurable] > tooltip > label[value=configurable],
@@ -649,20 +654,16 @@
 .variables-view-delete:hover {
   -moz-image-region: rect(0,32px,16px,16px);
 }
 
 .variables-view-delete:active {
   -moz-image-region: rect(0,48px,16px,32px);
 }
 
-.variables-view-delete > .toolbarbutton-text {
-  display: none;
-}
-
 .variables-view-edit {
   background: url("chrome://browser/skin/devtools/vview-edit.png") center no-repeat;
   width: 20px;
   height: 16px;
   cursor: pointer;
 }
 
 .variables-view-throbber {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -253,16 +253,17 @@ browser.jar:
   skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
   skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
   skin/classic/browser/devtools/app-manager/projects.css              (../shared/devtools/app-manager/projects.css)
   skin/classic/browser/devtools/app-manager/help.css                  (../shared/devtools/app-manager/help.css)
   skin/classic/browser/devtools/app-manager/warning.svg               (../shared/devtools/app-manager/images/warning.svg)
   skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
   skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
   skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
+  skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
   skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
   skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
   skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
--- a/browser/themes/osx/devtools/widgets.css
+++ b/browser/themes/osx/devtools/widgets.css
@@ -604,20 +604,25 @@
 }
 
 .variables-view-container[aligned-values] .title > .element-value-input {
   width: calc(70vw - 10px);
 }
 
 /* Actions first */
 
-.variables-view-container[actions-first] .variables-view-delete {
+.variables-view-container[actions-first] .variables-view-delete,
+.variables-view-container[actions-first] .variables-view-add-property {
   -moz-box-ordinal-group: 0;
 }
 
+.variables-view-container[actions-first] [invisible] {
+  visibility: hidden;
+}
+
 /* Variables and properties tooltips */
 
 .variable-or-property > tooltip > label {
   margin: 0 2px 0 2px;
 }
 
 .variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
 .variable-or-property[non-configurable] > tooltip > label[value=configurable],
@@ -643,20 +648,16 @@
 .variables-view-delete:hover {
   -moz-image-region: rect(0,32px,16px,16px);
 }
 
 .variables-view-delete:active {
   -moz-image-region: rect(0,48px,16px,32px);
 }
 
-.variables-view-delete > .toolbarbutton-text {
-  display: none;
-}
-
 .variables-view-edit {
   background: url("chrome://browser/skin/devtools/vview-edit.png") center no-repeat;
   width: 20px;
   height: 16px;
   cursor: pointer;
 }
 
 .variables-view-throbber {
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -355,16 +355,17 @@ browser.jar:
   skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
   skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
   skin/classic/browser/devtools/app-manager/projects.css              (../shared/devtools/app-manager/projects.css)
   skin/classic/browser/devtools/app-manager/help.css                  (../shared/devtools/app-manager/help.css)
   skin/classic/browser/devtools/app-manager/warning.svg               (../shared/devtools/app-manager/images/warning.svg)
   skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
   skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
   skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
+  skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
   skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
   skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
   skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/devtools/app-manager/images/add.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64">
+  <path fill="#00B2F7" d="M32.336,3.894c-15.74,0-28.5,12.76-28.5,28.5s12.76,28.5,28.5,28.5s28.5-12.76,28.5-28.5
+    S48.076,3.894,32.336,3.894z M44.86,36.966h-7.823v7.62c0,2.582-2.12,4.702-4.702,4.702c-2.584,0-4.704-2.12-4.704-4.702v-7.62
+    h-7.817c-2.52,0-4.572-2.056-4.572-4.572s2.053-4.572,4.572-4.572h7.817v-7.62c0-2.582,2.12-4.702,4.704-4.702
+    c2.582,0,4.702,2.12,4.702,4.702v7.62h7.823c2.514,0,4.57,2.056,4.57,4.572S47.374,36.966,44.86,36.966z"/>
+</svg>
--- a/browser/themes/shared/devtools/app-manager/manifest-editor.inc.css
+++ b/browser/themes/shared/devtools/app-manager/manifest-editor.inc.css
@@ -1,30 +1,31 @@
 /* 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/. */
 
 /* Manifest Editor overrides */
 
 .variables-view-container.manifest-editor {
   background-color: #F5F5F5;
-  padding: 20px 13px;
+  padding: 20px 2px;
 }
 
 .manifest-editor .variable-or-property:focus > .title {
   background-color: #EDEDED;
   color: #000;
   border-radius: 4px;
 }
 
 .manifest-editor .variables-view-property > .title > .name {
   color: #27406A;
 }
 
-.manifest-editor .variable-or-property > .title > label {
+.manifest-editor .variable-or-property > .title > label,
+.manifest-editor textbox  {
   font-family: monospace;
 }
 
 .manifest-editor .variable-or-property > .title > .token-string {
   color: #54BC6A;
   font-weight: bold;
 }
 
@@ -47,21 +48,33 @@
 }
 
 .manifest-editor .variables-view-variable {
   border-bottom: none;
 }
 
 .manifest-editor .variables-view-delete,
 .manifest-editor .variables-view-delete:hover,
-.manifest-editor .variables-view-delete:active {
+.manifest-editor .variables-view-delete:active,
+.manifest-editor .variables-view-add-property,
+.manifest-editor .variables-view-add-property:hover,
+.manifest-editor .variables-view-add-property:active {
   list-style-image: none;
   -moz-image-region: initial;
 }
 
-.manifest-editor .variables-view-delete::before {
-  width: 12px;
-  height: 12px;
+.manifest-editor .variables-view-delete::before,
+.manifest-editor .variables-view-add-property::before {
+  width: 11px;
+  height: 11px;
   content: "";
   display: inline-block;
+  background-size: 11px auto;
+}
+
+.manifest-editor .variables-view-delete::before {
   background-image: url("app-manager/remove.svg");
-  background-size: 12px auto;
 }
+
+.manifest-editor .variables-view-add-property::before {
+  background-image: url("app-manager/add.svg");
+  -moz-margin-end: 2px;
+}
--- a/browser/themes/windows/devtools/widgets.css
+++ b/browser/themes/windows/devtools/widgets.css
@@ -607,20 +607,25 @@
 }
 
 .variables-view-container[aligned-values] .title > .element-value-input {
   width: calc(70vw - 10px);
 }
 
 /* Actions first */
 
-.variables-view-container[actions-first] .variables-view-delete {
+.variables-view-container[actions-first] .variables-view-delete,
+.variables-view-container[actions-first] .variables-view-add-property {
   -moz-box-ordinal-group: 0;
 }
 
+.variables-view-container[actions-first] [invisible] {
+  visibility: hidden;
+}
+
 /* Variables and properties tooltips */
 
 .variable-or-property > tooltip > label {
   margin: 0 2px 0 2px;
 }
 
 .variable-or-property[non-enumerable] > tooltip > label[value=enumerable],
 .variable-or-property[non-configurable] > tooltip > label[value=configurable],
@@ -646,20 +651,16 @@
 .variables-view-delete:hover {
   -moz-image-region: rect(0,32px,16px,16px);
 }
 
 .variables-view-delete:active {
   -moz-image-region: rect(0,48px,16px,32px);
 }
 
-.variables-view-delete > .toolbarbutton-text {
-  display: none;
-}
-
 .variables-view-edit {
   background: url("chrome://browser/skin/devtools/vview-edit.png") center no-repeat;
   width: 20px;
   height: 16px;
   cursor: pointer;
 }
 
 .variables-view-throbber {
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -277,16 +277,17 @@ browser.jar:
         skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
         skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
         skin/classic/browser/devtools/app-manager/projects.css              (../shared/devtools/app-manager/projects.css)
         skin/classic/browser/devtools/app-manager/help.css                  (../shared/devtools/app-manager/help.css)
         skin/classic/browser/devtools/app-manager/warning.svg               (../shared/devtools/app-manager/images/warning.svg)
         skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
         skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
         skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
+        skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
         skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
         skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
         skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
@@ -578,16 +579,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
         skin/classic/aero/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
         skin/classic/aero/browser/devtools/app-manager/projects.css              (../shared/devtools/app-manager/projects.css)
         skin/classic/aero/browser/devtools/app-manager/help.css                  (../shared/devtools/app-manager/help.css)
         skin/classic/aero/browser/devtools/app-manager/warning.svg               (../shared/devtools/app-manager/images/warning.svg)
         skin/classic/aero/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
         skin/classic/aero/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
         skin/classic/aero/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
+        skin/classic/aero/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
         skin/classic/aero/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
         skin/classic/aero/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
         skin/classic/aero/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -77,16 +77,19 @@ ConsoleAPI.prototype = {
         self.queueCall("info", arguments);
       },
       warn: function CA_warn() {
         self.queueCall("warn", arguments);
       },
       error: function CA_error() {
         self.queueCall("error", arguments);
       },
+      exception: function CA_exception() {
+        self.queueCall("exception", arguments);
+      },
       debug: function CA_debug() {
         self.queueCall("debug", arguments);
       },
       trace: function CA_trace() {
         self.queueCall("trace", arguments);
       },
       // Displays an interactive listing of all the properties of an object.
       dir: function CA_dir() {
@@ -129,16 +132,17 @@ ConsoleAPI.prototype = {
         Services.obs.notifyObservers(consoleEvent, "console-api-profiler",
                                      null);  
       },
       __exposedProps__: {
         log: "r",
         info: "r",
         warn: "r",
         error: "r",
+        exception: "r",
         debug: "r",
         trace: "r",
         dir: "r",
         group: "r",
         groupCollapsed: "r",
         groupEnd: "r",
         time: "r",
         timeEnd: "r",
@@ -154,16 +158,17 @@ ConsoleAPI.prototype = {
       return { enumerable: true, configurable: true, writable: true,
                value: chromeObject[fun].bind(chromeObject) };
     }
     const properties = {
       log: genPropDesc('log'),
       info: genPropDesc('info'),
       warn: genPropDesc('warn'),
       error: genPropDesc('error'),
+      exception: genPropDesc('exception'),
       debug: genPropDesc('debug'),
       trace: genPropDesc('trace'),
       dir: genPropDesc('dir'),
       group: genPropDesc('group'),
       groupCollapsed: genPropDesc('groupCollapsed'),
       groupEnd: genPropDesc('groupEnd'),
       time: genPropDesc('time'),
       timeEnd: genPropDesc('timeEnd'),
@@ -283,16 +288,17 @@ ConsoleAPI.prototype = {
       private: meta.private,
     };
 
     switch (method) {
       case "log":
       case "info":
       case "warn":
       case "error":
+      case "exception":
       case "debug":
         consoleEvent.arguments = this.processArguments(args);
         break;
       case "trace":
         consoleEvent.stacktrace = meta.stack;
         break;
       case "group":
       case "groupCollapsed":
deleted file mode 100644
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ /dev/null
@@ -1,1587 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=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 "base/basictypes.h"
-#include "BluetoothOppManager.h"
-
-#include "BluetoothService.h"
-#include "BluetoothSocket.h"
-#include "BluetoothUtils.h"
-#include "BluetoothUuid.h"
-#include "ObexBase.h"
-
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/RefPtr.h"
-#include "mozilla/Services.h"
-#include "mozilla/StaticPtr.h"
-#include "nsAutoPtr.h"
-#include "nsCExternalHandlerService.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
-#include "nsIDOMFile.h"
-#include "nsIFile.h"
-#include "nsIInputStream.h"
-#include "nsIMIMEService.h"
-#include "nsIOutputStream.h"
-#include "nsIVolumeService.h"
-#include "nsNetUtil.h"
-#include "nsServiceManagerUtils.h"
-
-#define TARGET_SUBDIR "Download/Bluetooth/"
-
-USING_BLUETOOTH_NAMESPACE
-using namespace mozilla;
-using namespace mozilla::ipc;
-
-namespace {
-// Sending system message "bluetooth-opp-update-progress" every 50kb
-static const uint32_t kUpdateProgressBase = 50 * 1024;
-
-/*
- * The format of the header of an PUT request is
- * [opcode:1][packet length:2][headerId:1][header length:2]
- */
-static const uint32_t kPutRequestHeaderSize = 6;
-
-StaticRefPtr<BluetoothOppManager> sBluetoothOppManager;
-static bool sInShutdown = false;
-}
-
-class mozilla::dom::bluetooth::SendFileBatch {
-public:
-  SendFileBatch(const nsAString& aDeviceAddress, BlobParent* aActor)
-    : mDeviceAddress(aDeviceAddress)
-  {
-    mBlobs.AppendElement(aActor->GetBlob().get());
-  }
-
-  nsString mDeviceAddress;
-  nsCOMArray<nsIDOMBlob> mBlobs;
-};
-
-NS_IMETHODIMP
-BluetoothOppManager::Observe(nsISupports* aSubject,
-                             const char* aTopic,
-                             const PRUnichar* aData)
-{
-  MOZ_ASSERT(sBluetoothOppManager);
-
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
-    HandleShutdown();
-    return NS_OK;
-  }
-
-  MOZ_ASSERT(false, "BluetoothOppManager got unexpected topic!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-class SendSocketDataTask : public nsRunnable
-{
-public:
-  SendSocketDataTask(uint8_t* aStream, uint32_t aSize)
-    : mStream(aStream)
-    , mSize(aSize)
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    sBluetoothOppManager->SendPutRequest(mStream, mSize);
-
-    return NS_OK;
-  }
-
-private:
-  nsAutoArrayPtr<uint8_t> mStream;
-  uint32_t mSize;
-};
-
-class ReadFileTask : public nsRunnable
-{
-public:
-  ReadFileTask(nsIInputStream* aInputStream,
-               uint32_t aRemoteMaxPacketSize) : mInputStream(aInputStream)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    mAvailablePacketSize = aRemoteMaxPacketSize - kPutRequestHeaderSize;
-  }
-
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-
-    uint32_t numRead;
-    nsAutoArrayPtr<char> buf(new char[mAvailablePacketSize]);
-
-    // function inputstream->Read() only works on non-main thread
-    nsresult rv = mInputStream->Read(buf, mAvailablePacketSize, &numRead);
-    if (NS_FAILED(rv)) {
-      // Needs error handling here
-      BT_WARNING("Failed to read from input stream");
-      return NS_ERROR_FAILURE;
-    }
-
-    if (numRead > 0) {
-      sBluetoothOppManager->CheckPutFinal(numRead);
-
-      nsRefPtr<SendSocketDataTask> task =
-        new SendSocketDataTask((uint8_t*)buf.forget(), numRead);
-      if (NS_FAILED(NS_DispatchToMainThread(task))) {
-        BT_WARNING("Failed to dispatch to main thread!");
-        return NS_ERROR_FAILURE;
-      }
-    }
-
-    return NS_OK;
-  };
-
-private:
-  nsCOMPtr<nsIInputStream> mInputStream;
-  uint32_t mAvailablePacketSize;
-};
-
-class CloseSocketTask : public Task
-{
-public:
-  CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket)
-  {
-    MOZ_ASSERT(aSocket);
-  }
-
-  void Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    if (mSocket->GetConnectionStatus() ==
-        SocketConnectionStatus::SOCKET_CONNECTED) {
-      mSocket->Disconnect();
-    }
-  }
-
-private:
-  nsRefPtr<BluetoothSocket> mSocket;
-};
-
-BluetoothOppManager::BluetoothOppManager() : mConnected(false)
-                                           , mRemoteObexVersion(0)
-                                           , mRemoteConnectionFlags(0)
-                                           , mRemoteMaxPacketLength(0)
-                                           , mLastCommand(0)
-                                           , mPacketLength(0)
-                                           , mPacketReceivedLength(0)
-                                           , mBodySegmentLength(0)
-                                           , mAbortFlag(false)
-                                           , mNewFileFlag(false)
-                                           , mPutFinalFlag(false)
-                                           , mSendTransferCompleteFlag(false)
-                                           , mSuccessFlag(false)
-                                           , mIsServer(true)
-                                           , mWaitingForConfirmationFlag(false)
-                                           , mFileLength(0)
-                                           , mSentFileLength(0)
-                                           , mWaitingToSendPutFinal(false)
-                                           , mCurrentBlobIndex(-1)
-{
-  mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
-}
-
-BluetoothOppManager::~BluetoothOppManager()
-{
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE_VOID(obs);
-  if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
-    BT_WARNING("Failed to remove shutdown observer!");
-  }
-}
-
-bool
-BluetoothOppManager::Init()
-{
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE(obs, false);
-  if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
-    BT_WARNING("Failed to add shutdown observer!");
-    return false;
-  }
-
-  Listen();
-
-  return true;
-}
-
-//static
-BluetoothOppManager*
-BluetoothOppManager::Get()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // If sBluetoothOppManager already exists, exit early
-  if (sBluetoothOppManager) {
-    return sBluetoothOppManager;
-  }
-
-  // If we're in shutdown, don't create a new instance
-  NS_ENSURE_FALSE(sInShutdown, nullptr);
-
-  // Create a new instance, register, and return
-  BluetoothOppManager *manager = new BluetoothOppManager();
-  NS_ENSURE_TRUE(manager->Init(), nullptr);
-
-  sBluetoothOppManager = manager;
-  return sBluetoothOppManager;
-}
-
-void
-BluetoothOppManager::ConnectInternal(const nsAString& aDeviceAddress)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Stop listening because currently we only support one connection at a time.
-  if (mRfcommSocket) {
-    mRfcommSocket->Disconnect();
-    mRfcommSocket = nullptr;
-  }
-
-  if (mL2capSocket) {
-    mL2capSocket->Disconnect();
-    mL2capSocket = nullptr;
-  }
-
-  mIsServer = false;
-
-  BluetoothService* bs = BluetoothService::Get();
-  if (!bs || sInShutdown || mSocket) {
-    OnSocketConnectError(mSocket);
-    return;
-  }
-
-  mNeedsUpdatingSdpRecords = true;
-
-  nsString uuid;
-  BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
-
-  if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
-    OnSocketConnectError(mSocket);
-    return;
-  }
-
-  mSocket =
-    new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
-}
-
-void
-BluetoothOppManager::HandleShutdown()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  sInShutdown = true;
-
-  if (mSocket) {
-    mSocket->Disconnect();
-    mSocket = nullptr;
-  }
-  if (mRfcommSocket) {
-    mRfcommSocket->Disconnect();
-    mRfcommSocket = nullptr;
-  }
-  if (mL2capSocket) {
-    mL2capSocket->Disconnect();
-    mL2capSocket = nullptr;
-  }
-  sBluetoothOppManager = nullptr;
-}
-
-bool
-BluetoothOppManager::Listen()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mSocket) {
-    BT_WARNING("mSocket exists. Failed to listen.");
-    return false;
-  }
-
-  if (!mRfcommSocket) {
-    mRfcommSocket =
-      new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
-
-    if (!mRfcommSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) {
-      BT_WARNING("[OPP] Can't listen on RFCOMM socket!");
-      mRfcommSocket = nullptr;
-      return false;
-    }
-  }
-
-  if (!mL2capSocket) {
-    mL2capSocket =
-      new BluetoothSocket(this, BluetoothSocketType::EL2CAP, true, true);
-
-    if (!mL2capSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH_L2CAP)) {
-      BT_WARNING("[OPP] Can't listen on L2CAP socket!");
-      mRfcommSocket->Disconnect();
-      mRfcommSocket = nullptr;
-      mL2capSocket = nullptr;
-      return false;
-    }
-  }
-
-  mIsServer = true;
-
-  return true;
-}
-
-void
-BluetoothOppManager::StartSendingNextFile()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MOZ_ASSERT(!IsConnected());
-  MOZ_ASSERT(!mBatches.IsEmpty());
-  MOZ_ASSERT(mBatches[0].mBlobs.Length() > mCurrentBlobIndex + 1);
-
-  mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex];
-
-  // Before sending content, we have to send a header including
-  // information such as file name, file length and content type.
-  ExtractBlobHeaders();
-  StartFileTransfer();
-
-  if (mCurrentBlobIndex == 0) {
-    // We may have more than one file waiting for transferring, but only one
-    // CONNECT request would be sent. Therefore check if this is the very first
-    // file at the head of queue.
-    SendConnectRequest();
-  } else {
-    SendPutHeaderRequest(mFileName, mFileLength);
-    AfterFirstPut();
-  }
-}
-
-bool
-BluetoothOppManager::SendFile(const nsAString& aDeviceAddress,
-                              BlobParent* aActor)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  AppendBlobToSend(aDeviceAddress, aActor);
-  if (!mSocket) {
-    ProcessNextBatch();
-  }
-
-  return true;
-}
-
-void
-BluetoothOppManager::AppendBlobToSend(const nsAString& aDeviceAddress,
-                                      BlobParent* aActor)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  int indexTail = mBatches.Length() - 1;
-
-  /**
-   * Create a new batch if
-   * - mBatches is empty, or
-   * - aDeviceAddress differs from mDeviceAddress of the last batch
-   */
-  if (mBatches.IsEmpty() ||
-      aDeviceAddress != mBatches[indexTail].mDeviceAddress) {
-    SendFileBatch batch(aDeviceAddress, aActor);
-    mBatches.AppendElement(batch);
-  } else {
-    mBatches[indexTail].mBlobs.AppendElement(aActor->GetBlob().get());
-  }
-}
-
-void
-BluetoothOppManager::DiscardBlobsToSend()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MOZ_ASSERT(!mBatches.IsEmpty());
-  MOZ_ASSERT(!mIsServer);
-
-  int length = (int) mBatches[0].mBlobs.Length();
-  while (length > mCurrentBlobIndex + 1) {
-    mBlob = mBatches[0].mBlobs[++mCurrentBlobIndex];
-
-    BT_LOGR("%s: idx %d", __FUNCTION__, mCurrentBlobIndex);
-    ExtractBlobHeaders();
-    StartFileTransfer();
-    FileTransferComplete();
-  }
-}
-
-bool
-BluetoothOppManager::ProcessNextBatch()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Remove the processed batch.
-  // A batch is processed if we've incremented mCurrentBlobIndex for it.
-  if (mCurrentBlobIndex >= 0) {
-    ClearQueue();
-    mBatches.RemoveElementAt(0);
-    BT_LOGR("%s: REMOVE. %d remaining", __FUNCTION__, mBatches.Length());
-  }
-
-  // Process the next batch
-  if (!mBatches.IsEmpty()) {
-    ConnectInternal(mBatches[0].mDeviceAddress);
-    return true;
-  }
-
-  // No more batch to process
-  return false;
-}
-
-void
-BluetoothOppManager::ClearQueue()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MOZ_ASSERT(!mIsServer);
-  MOZ_ASSERT(!mBatches.IsEmpty());
-  MOZ_ASSERT(!mBatches[0].mBlobs.IsEmpty());
-
-  mCurrentBlobIndex = -1;
-  mBlob = nullptr;
-  mBatches[0].mBlobs.Clear();
-}
-
-bool
-BluetoothOppManager::StopSendingFile()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mIsServer) {
-    mAbortFlag = true;
-  } else if (mSocket) {
-    mSocket->Disconnect();
-  } else {
-    BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
-  }
-
-  return true;
-}
-
-bool
-BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
-{
-  NS_ENSURE_TRUE(mConnected, false);
-  NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
-
-  MOZ_ASSERT(mPacketReceivedLength == 0);
-
-  mWaitingForConfirmationFlag = false;
-
-  // For the first packet of first file
-  bool success = false;
-  if (aConfirm) {
-    StartFileTransfer();
-    if (CreateFile()) {
-      success = WriteToFile(mBodySegment.get(), mBodySegmentLength);
-    }
-  }
-
-  if (success && mPutFinalFlag) {
-    mSuccessFlag = true;
-    FileTransferComplete();
-    NotifyAboutFileChange();
-  }
-
-  ReplyToPut(mPutFinalFlag, success);
-  return true;
-}
-
-void
-BluetoothOppManager::AfterFirstPut()
-{
-  mUpdateProgressCounter = 1;
-  mPutFinalFlag = false;
-  mPacketReceivedLength = 0;
-  mSentFileLength = 0;
-  mWaitingToSendPutFinal = false;
-  mSuccessFlag = false;
-  mBodySegmentLength = 0;
-}
-
-void
-BluetoothOppManager::AfterOppConnected()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mConnected = true;
-  mAbortFlag = false;
-  mWaitingForConfirmationFlag = true;
-  AfterFirstPut();
-  // Get a mount lock to prevent the sdcard from being shared with
-  // the PC while we're doing a OPP file transfer. After OPP transaction
-  // were done, the mount lock will be freed.
-  if (!AcquireSdcardMountLock()) {
-    // If we fail to get a mount lock, abort this transaction
-    // Directly sending disconnect-request is better than abort-request
-    BT_WARNING("BluetoothOPPManager couldn't get a mount lock!");
-
-    MOZ_ASSERT(mSocket);
-    mSocket->Disconnect();
-  }
-}
-
-void
-BluetoothOppManager::AfterOppDisconnected()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mConnected = false;
-  mLastCommand = 0;
-  mPacketReceivedLength = 0;
-  mDsFile = nullptr;
-
-  // We can't reset mSuccessFlag here since this function may be called
-  // before we send system message of transfer complete
-  // mSuccessFlag = false;
-
-  if (mInputStream) {
-    mInputStream->Close();
-    mInputStream = nullptr;
-  }
-
-  if (mOutputStream) {
-    mOutputStream->Close();
-    mOutputStream = nullptr;
-  }
-
-  if (mReadFileThread) {
-    mReadFileThread->Shutdown();
-    mReadFileThread = nullptr;
-  }
-  // Release the Mount lock if file transfer completed
-  if (mMountLock) {
-    // The mount lock will be implicitly unlocked
-    mMountLock = nullptr;
-  }
-}
-
-void
-BluetoothOppManager::DeleteReceivedFile()
-{
-  if (mOutputStream) {
-    mOutputStream->Close();
-    mOutputStream = nullptr;
-  }
-
-  if (mDsFile && mDsFile->mFile) {
-    mDsFile->mFile->Remove(false);
-    mDsFile = nullptr;
-  }
-}
-
-bool
-BluetoothOppManager::CreateFile()
-{
-  MOZ_ASSERT(mPacketReceivedLength == mPacketLength);
-
-  nsString path;
-  path.AssignLiteral(TARGET_SUBDIR);
-  path.Append(mFileName);
-
-  mDsFile = DeviceStorageFile::CreateUnique(path, nsIFile::NORMAL_FILE_TYPE, 0644);
-  NS_ENSURE_TRUE(mDsFile, false);
-
-  nsCOMPtr<nsIFile> f;
-  mDsFile->mFile->Clone(getter_AddRefs(f));
-
-  /*
-   * The function CreateUnique() may create a file with a different file
-   * name from the original mFileName. Therefore we have to retrieve
-   * the file name again.
-   */
-  f->GetLeafName(mFileName);
-
-  NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f);
-  NS_ENSURE_TRUE(mOutputStream, false);
-
-  return true;
-}
-
-bool
-BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
-{
-  NS_ENSURE_TRUE(mOutputStream, false);
-
-  uint32_t wrote = 0;
-  mOutputStream->Write((const char*)aData, aDataLength, &wrote);
-  NS_ENSURE_TRUE(aDataLength == (int) wrote, false);
-
-  return true;
-}
-
-// Virtual function of class SocketConsumer
-void
-BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader)
-{
-  if (aHeader.Has(ObexHeaderId::Name)) {
-    aHeader.GetName(mFileName);
-  }
-
-  if (aHeader.Has(ObexHeaderId::Type)) {
-    aHeader.GetContentType(mContentType);
-  }
-
-  if (aHeader.Has(ObexHeaderId::Length)) {
-    aHeader.GetLength(&mFileLength);
-  }
-
-  if (aHeader.Has(ObexHeaderId::Body) ||
-      aHeader.Has(ObexHeaderId::EndOfBody)) {
-    uint8_t* bodyPtr;
-    aHeader.GetBody(&bodyPtr);
-    mBodySegment = bodyPtr;
-
-    aHeader.GetBodyLength(&mBodySegmentLength);
-  }
-}
-
-bool
-BluetoothOppManager::ExtractBlobHeaders()
-{
-  RetrieveSentFileName();
-
-  nsresult rv = mBlob->GetType(mContentType);
-  if (NS_FAILED(rv)) {
-    BT_WARNING("Can't get content type");
-    SendDisconnectRequest();
-    return false;
-  }
-
-  uint64_t fileLength;
-  rv = mBlob->GetSize(&fileLength);
-  if (NS_FAILED(rv)) {
-    BT_WARNING("Can't get file size");
-    SendDisconnectRequest();
-    return false;
-  }
-
-  // Currently we keep the size of files which were sent/received via
-  // Bluetooth not exceed UINT32_MAX because the Length header in OBEX
-  // is only 4-byte long. Although it is possible to transfer a file
-  // larger than UINT32_MAX, it needs to parse another OBEX Header
-  // and I would like to leave it as a feature.
-  if (fileLength > (uint64_t)UINT32_MAX) {
-    BT_WARNING("The file size is too large for now");
-    SendDisconnectRequest();
-    return false;
-  }
-
-  mFileLength = fileLength;
-  rv = NS_NewThread(getter_AddRefs(mReadFileThread));
-  if (NS_FAILED(rv)) {
-    BT_WARNING("Can't create thread");
-    SendDisconnectRequest();
-    return false;
-  }
-
-  return true;
-}
-
-void
-BluetoothOppManager::RetrieveSentFileName()
-{
-  mFileName.Truncate();
-
-  nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
-  if (file) {
-    file->GetName(mFileName);
-  }
-
-  /**
-   * We try our best to get the file extention to avoid interoperability issues.
-   * However, once we found that we are unable to get suitable extension or
-   * information about the content type, sending a pre-defined file name without
-   * extension would be fine.
-   */
-  if (mFileName.IsEmpty()) {
-    mFileName.AssignLiteral("Unknown");
-  }
-
-  int32_t offset = mFileName.RFindChar('/');
-  if (offset != kNotFound) {
-    mFileName = Substring(mFileName, offset + 1);
-  }
-
-  offset = mFileName.RFindChar('.');
-  if (offset == kNotFound) {
-    nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
-
-    if (mimeSvc) {
-      nsString mimeType;
-      mBlob->GetType(mimeType);
-
-      nsCString extension;
-      nsresult rv =
-        mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
-                                     EmptyCString(),
-                                     extension);
-      if (NS_SUCCEEDED(rv)) {
-        mFileName.AppendLiteral(".");
-        AppendUTF8toUTF16(extension, mFileName);
-      }
-    }
-  }
-}
-
-bool
-BluetoothOppManager::IsReservedChar(PRUnichar c)
-{
-  return (c < 0x0020 ||
-          c == PRUnichar('?') || c == PRUnichar('|') || c == PRUnichar('<') ||
-          c == PRUnichar('>') || c == PRUnichar('"') || c == PRUnichar(':') ||
-          c == PRUnichar('/') || c == PRUnichar('*') || c == PRUnichar('\\'));
-}
-
-void
-BluetoothOppManager::ValidateFileName()
-{
-  int length = mFileName.Length();
-
-  for (int i = 0; i < length; ++i) {
-    // Replace reserved char of fat file system with '_'
-    if (IsReservedChar(mFileName.CharAt(i))) {
-      mFileName.Replace(i, 1, PRUnichar('_'));
-    }
-  }
-}
-
-bool
-BluetoothOppManager::ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aMessage);
-
-  int frameHeaderLength = 0;
-
-  // See if this is the first part of each Put packet
-  if (mPacketReceivedLength == 0) {
-    // Section 3.3.3 "Put", IrOBEX 1.2
-    // [opcode:1][length:2][Headers:var]
-    frameHeaderLength = 3;
-
-    mPacketLength = ((((int)aMessage->mData[1]) << 8) | aMessage->mData[2]) -
-                      frameHeaderLength;
-    /**
-     * A PUT request from remote devices may be divided into multiple parts.
-     * In other words, one request may need to be received multiple times,
-     * so here we keep a variable mPacketLeftLength to indicate if current
-     * PUT request is done.
-     */
-    mReceivedDataBuffer = new uint8_t[mPacketLength];
-    mPutFinalFlag = (aOpCode == ObexRequestCode::PutFinal);
-  }
-
-  int dataLength = aMessage->mSize - frameHeaderLength;
-
-  // Check length before memcpy to prevent from memory pollution
-  if (dataLength < 0 ||
-      mPacketReceivedLength + dataLength > mPacketLength) {
-    BT_LOGR("%s: Received packet size is unreasonable", __FUNCTION__);
-
-    ReplyToPut(mPutFinalFlag, false);
-    DeleteReceivedFile();
-    FileTransferComplete();
-
-    return false;
-  }
-
-  memcpy(mReceivedDataBuffer.get() + mPacketReceivedLength,
-         &aMessage->mData[frameHeaderLength], dataLength);
-
-  mPacketReceivedLength += dataLength;
-
-  return (mPacketReceivedLength == mPacketLength);
-}
-
-void
-BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  uint8_t opCode;
-  int receivedLength = aMessage->mSize;
-
-  if (mPacketReceivedLength > 0) {
-    opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
-  } else {
-    opCode = aMessage->mData[0];
-
-    // When there's a Put packet right after a PutFinal packet,
-    // which means it's the start point of a new file.
-    if (mPutFinalFlag &&
-        (opCode == ObexRequestCode::Put ||
-         opCode == ObexRequestCode::PutFinal)) {
-      mNewFileFlag = true;
-      AfterFirstPut();
-    }
-  }
-
-  ObexHeaderSet pktHeaders(opCode);
-  if (opCode == ObexRequestCode::Connect) {
-    // Section 3.3.1 "Connect", IrOBEX 1.2
-    // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
-    // [Headers:var]
-    ParseHeaders(&aMessage->mData[7],
-                 receivedLength - 7,
-                 &pktHeaders);
-    ReplyToConnect();
-    AfterOppConnected();
-  } else if (opCode == ObexRequestCode::Abort) {
-    // Section 3.3.5 "Abort", IrOBEX 1.2
-    // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
-    ReplyToDisconnectOrAbort();
-    DeleteReceivedFile();
-  } else if (opCode == ObexRequestCode::Disconnect) {
-    // Section 3.3.2 "Disconnect", IrOBEX 1.2
-    // [opcode:1][length:2][Headers:var]
-    ParseHeaders(&aMessage->mData[3],
-                receivedLength - 3,
-                &pktHeaders);
-    ReplyToDisconnectOrAbort();
-    AfterOppDisconnected();
-    FileTransferComplete();
-  } else if (opCode == ObexRequestCode::Put ||
-             opCode == ObexRequestCode::PutFinal) {
-    if (!ComposePacket(opCode, aMessage)) {
-      return;
-    }
-
-    // A Put packet is received completely
-    ParseHeaders(mReceivedDataBuffer.get(), mPacketReceivedLength, &pktHeaders);
-    ExtractPacketHeaders(pktHeaders);
-    ValidateFileName();
-
-    mPacketReceivedLength = 0;
-
-    // When we cancel the transfer, delete the file and notify completion
-    if (mAbortFlag) {
-      ReplyToPut(mPutFinalFlag, false);
-      mSentFileLength += mBodySegmentLength;
-      DeleteReceivedFile();
-      FileTransferComplete();
-      return;
-    }
-
-    // Wait until get confirmation from user, then create file and write to it
-    if (mWaitingForConfirmationFlag) {
-      ReceivingFileConfirmation();
-      mSentFileLength += mBodySegmentLength;
-      return;
-    }
-
-    // Already get confirmation from user, create a new file if needed and
-    // write to output stream
-    if (mNewFileFlag) {
-      StartFileTransfer();
-      if (!CreateFile()) {
-        ReplyToPut(mPutFinalFlag, false);
-        return;
-      }
-      mNewFileFlag = false;
-    }
-
-    if (!WriteToFile(mBodySegment.get(), mBodySegmentLength)) {
-      ReplyToPut(mPutFinalFlag, false);
-      return;
-    }
-
-    ReplyToPut(mPutFinalFlag, true);
-
-    // Send progress update
-    mSentFileLength += mBodySegmentLength;
-    if (mSentFileLength > kUpdateProgressBase * mUpdateProgressCounter) {
-      UpdateProgress();
-      mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1;
-    }
-
-    // Success to receive a file and notify completion
-    if (mPutFinalFlag) {
-      mSuccessFlag = true;
-      FileTransferComplete();
-      NotifyAboutFileChange();
-    }
-  } else if (opCode == ObexRequestCode::Get ||
-             opCode == ObexRequestCode::GetFinal ||
-             opCode == ObexRequestCode::SetPath) {
-    ReplyError(ObexResponseCode::BadRequest);
-    BT_WARNING("Unsupported ObexRequestCode");
-  } else {
-    ReplyError(ObexResponseCode::NotImplemented);
-    BT_WARNING("Unrecognized ObexRequestCode");
-  }
-}
-
-void
-BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  uint8_t opCode = aMessage->mData[0];
-
-  // Check response code and send out system message as finished if the response
-  // code is somehow incorrect.
-  uint8_t expectedOpCode = ObexResponseCode::Success;
-  if (mLastCommand == ObexRequestCode::Put) {
-    expectedOpCode = ObexResponseCode::Continue;
-  }
-
-  if (opCode != expectedOpCode) {
-    if (mLastCommand == ObexRequestCode::Put ||
-        mLastCommand == ObexRequestCode::Abort ||
-        mLastCommand == ObexRequestCode::PutFinal) {
-      SendDisconnectRequest();
-    }
-    nsAutoCString str;
-    str += "[OPP] 0x";
-    str.AppendInt(mLastCommand, 16);
-    str += " failed";
-    BT_WARNING(str.get());
-    FileTransferComplete();
-    return;
-  }
-
-  if (mLastCommand == ObexRequestCode::PutFinal) {
-    mSuccessFlag = true;
-    FileTransferComplete();
-
-    if (mInputStream) {
-      mInputStream->Close();
-      mInputStream = nullptr;
-    }
-
-    if (mCurrentBlobIndex + 1 == (int) mBatches[0].mBlobs.Length()) {
-      SendDisconnectRequest();
-    } else {
-      StartSendingNextFile();
-    }
-  } else if (mLastCommand == ObexRequestCode::Abort) {
-    SendDisconnectRequest();
-    FileTransferComplete();
-  } else if (mLastCommand == ObexRequestCode::Disconnect) {
-    AfterOppDisconnected();
-    // Most devices will directly terminate connection after receiving
-    // Disconnect request, so we make a delay here. If the socket hasn't been
-    // disconnected, we will close it.
-    if (mSocket) {
-      MessageLoop::current()->
-        PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000);
-    }
-  } else if (mLastCommand == ObexRequestCode::Connect) {
-    MOZ_ASSERT(!mFileName.IsEmpty());
-    MOZ_ASSERT(mBlob);
-
-    AfterOppConnected();
-
-    // Keep remote information
-    mRemoteObexVersion = aMessage->mData[3];
-    mRemoteConnectionFlags = aMessage->mData[4];
-    mRemoteMaxPacketLength =
-      (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
-
-    SendPutHeaderRequest(mFileName, mFileLength);
-  } else if (mLastCommand == ObexRequestCode::Put) {
-    if (mWaitingToSendPutFinal) {
-      SendPutFinalRequest();
-      return;
-    }
-
-    if (kUpdateProgressBase * mUpdateProgressCounter < mSentFileLength) {
-      UpdateProgress();
-      mUpdateProgressCounter = mSentFileLength / kUpdateProgressBase + 1;
-    }
-
-    nsresult rv;
-    if (!mInputStream) {
-      rv = mBlob->GetInternalStream(getter_AddRefs(mInputStream));
-      if (NS_FAILED(rv)) {
-        BT_WARNING("Can't get internal stream of blob");
-        SendDisconnectRequest();
-        return;
-      }
-    }
-
-    nsRefPtr<ReadFileTask> task = new ReadFileTask(mInputStream,
-                                                   mRemoteMaxPacketLength);
-    rv = mReadFileThread->Dispatch(task, NS_DISPATCH_NORMAL);
-    if (NS_FAILED(rv)) {
-      BT_WARNING("Cannot dispatch read file task!");
-      SendDisconnectRequest();
-    }
-  } else {
-    BT_WARNING("Unhandled ObexRequestCode");
-  }
-}
-
-// Virtual function of class SocketConsumer
-void
-BluetoothOppManager::ReceiveSocketData(BluetoothSocket* aSocket,
-                                       nsAutoPtr<UnixSocketRawData>& aMessage)
-{
-  if (mIsServer) {
-    ServerDataHandler(aMessage);
-  } else {
-    ClientDataHandler(aMessage);
-  }
-}
-
-void
-BluetoothOppManager::SendConnectRequest()
-{
-  if (mConnected) return;
-
-  // Section 3.3.1 "Connect", IrOBEX 1.2
-  // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
-  // [Headers:var]
-  uint8_t req[255];
-  int index = 7;
-
-  req[3] = 0x10; // version=1.0
-  req[4] = 0x00; // flag=0x00
-  req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8;
-  req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH;
-
-  SendObexData(req, ObexRequestCode::Connect, index);
-}
-
-void
-BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
-                                          int aFileSize)
-{
-  if (!mConnected) return;
-
-  uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
-
-  int len = aFileName.Length();
-  uint8_t* fileName = new uint8_t[(len + 1) * 2];
-  const PRUnichar* fileNamePtr = aFileName.BeginReading();
-
-  for (int i = 0; i < len; i++) {
-    fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
-    fileName[i * 2 + 1] = (uint8_t)fileNamePtr[i];
-  }
-
-  fileName[len * 2] = 0x00;
-  fileName[len * 2 + 1] = 0x00;
-
-  int index = 3;
-  index += AppendHeaderName(&req[index], (char*)fileName, (len + 1) * 2);
-  index += AppendHeaderLength(&req[index], aFileSize);
-
-  SendObexData(req, ObexRequestCode::Put, index);
-
-  delete [] fileName;
-  delete [] req;
-}
-
-void
-BluetoothOppManager::SendPutRequest(uint8_t* aFileBody,
-                                    int aFileBodyLength)
-{
-  int packetLeftSpace = mRemoteMaxPacketLength - kPutRequestHeaderSize;
-
-  if (!mConnected) return;
-  if (aFileBodyLength > packetLeftSpace) {
-    BT_WARNING("Not allowed such a small MaxPacketLength value");
-    return;
-  }
-
-  // Section 3.3.3 "Put", IrOBEX 1.2
-  // [opcode:1][length:2][Headers:var]
-  uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
-
-  int index = 3;
-  index += AppendHeaderBody(&req[index], aFileBody, aFileBodyLength);
-
-  SendObexData(req, ObexRequestCode::Put, index);
-  delete [] req;
-
-  mSentFileLength += aFileBodyLength;
-}
-
-void
-BluetoothOppManager::SendPutFinalRequest()
-{
-  if (!mConnected) return;
-
-  /**
-   * Section 2.2.9, "End-of-Body", IrObex 1.2
-   * End-of-Body is used to identify the last chunk of the object body.
-   * For most platforms, a PutFinal packet is sent with an zero length
-   * End-of-Body header.
-   */
-
-  // [opcode:1][length:2]
-  int index = 3;
-  uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
-  index += AppendHeaderEndOfBody(&req[index]);
-
-  SendObexData(req, ObexRequestCode::PutFinal, index);
-  delete [] req;
-
-  mWaitingToSendPutFinal = false;
-}
-
-void
-BluetoothOppManager::SendDisconnectRequest()
-{
-  if (!mConnected) return;
-
-  // Section 3.3.2 "Disconnect", IrOBEX 1.2
-  // [opcode:1][length:2][Headers:var]
-  uint8_t req[255];
-  int index = 3;
-
-  SendObexData(req, ObexRequestCode::Disconnect, index);
-}
-
-void
-BluetoothOppManager::CheckPutFinal(uint32_t aNumRead)
-{
-  if (mSentFileLength + aNumRead >= mFileLength) {
-    mWaitingToSendPutFinal = true;
-  }
-}
-
-bool
-BluetoothOppManager::IsConnected()
-{
-  return (mConnected && !mSendTransferCompleteFlag);
-}
-
-void
-BluetoothOppManager::GetAddress(nsAString& aDeviceAddress)
-{
-  return mSocket->GetAddress(aDeviceAddress);
-}
-
-void
-BluetoothOppManager::ReplyToConnect()
-{
-  if (mConnected) return;
-
-  // Section 3.3.1 "Connect", IrOBEX 1.2
-  // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
-  // [Headers:var]
-  uint8_t req[255];
-  int index = 7;
-
-  req[3] = 0x10; // version=1.0
-  req[4] = 0x00; // flag=0x00
-  req[5] = BluetoothOppManager::MAX_PACKET_LENGTH >> 8;
-  req[6] = (uint8_t)BluetoothOppManager::MAX_PACKET_LENGTH;
-
-  SendObexData(req, ObexResponseCode::Success, index);
-}
-
-void
-BluetoothOppManager::ReplyToDisconnectOrAbort()
-{
-  if (!mConnected) return;
-
-  // Section 3.3.2 "Disconnect" and Section 3.3.5 "Abort", IrOBEX 1.2
-  // The format of response packet of "Disconnect" and "Abort" are the same
-  // [opcode:1][length:2][Headers:var]
-  uint8_t req[255];
-  int index = 3;
-
-  SendObexData(req, ObexResponseCode::Success, index);
-}
-
-void
-BluetoothOppManager::ReplyToPut(bool aFinal, bool aContinue)
-{
-  if (!mConnected) return;
-
-  // Section 3.3.2 "Disconnect", IrOBEX 1.2
-  // [opcode:1][length:2][Headers:var]
-  uint8_t req[255];
-  int index = 3;
-  uint8_t opcode;
-
-  if (aContinue) {
-    opcode = (aFinal)? ObexResponseCode::Success :
-                       ObexResponseCode::Continue;
-  } else {
-    opcode = (aFinal)? ObexResponseCode::Unauthorized :
-                       ObexResponseCode::Unauthorized & (~FINAL_BIT);
-  }
-
-  SendObexData(req, opcode, index);
-}
-
-void
-BluetoothOppManager::ReplyError(uint8_t aError)
-{
-  if (!mConnected) return;
-
-  // Section 3.2 "Response Format", IrOBEX 1.2
-  // [opcode:1][length:2][Headers:var]
-  uint8_t req[255];
-  int index = 3;
-
-  SendObexData(req, aError, index);
-}
-
-void
-BluetoothOppManager::SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize)
-{
-  SetObexPacketInfo(aData, aOpcode, aSize);
-
-  if (!mIsServer) {
-    mLastCommand = aOpcode;
-  }
-
-  UnixSocketRawData* s = new UnixSocketRawData(aSize);
-  memcpy(s->mData, aData, s->mSize);
-  mSocket->SendSocketData(s);
-}
-
-void
-BluetoothOppManager::FileTransferComplete()
-{
-  if (mSendTransferCompleteFlag) {
-    return;
-  }
-
-  nsString type, name;
-  BluetoothValue v;
-  InfallibleTArray<BluetoothNamedValue> parameters;
-  type.AssignLiteral("bluetooth-opp-transfer-complete");
-
-  name.AssignLiteral("address");
-  v = mConnectedDeviceAddress;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("success");
-  v = mSuccessFlag;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("received");
-  v = mIsServer;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileName");
-  v = mFileName;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileLength");
-  v = mSentFileLength;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("contentType");
-  v = mContentType;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  if (!BroadcastSystemMessage(type, parameters)) {
-    BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-complete]");
-    return;
-  }
-
-  mSendTransferCompleteFlag = true;
-}
-
-void
-BluetoothOppManager::StartFileTransfer()
-{
-  nsString type, name;
-  BluetoothValue v;
-  InfallibleTArray<BluetoothNamedValue> parameters;
-  type.AssignLiteral("bluetooth-opp-transfer-start");
-
-  name.AssignLiteral("address");
-  v = mConnectedDeviceAddress;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("received");
-  v = mIsServer;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileName");
-  v = mFileName;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileLength");
-  v = mFileLength;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("contentType");
-  v = mContentType;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  if (!BroadcastSystemMessage(type, parameters)) {
-    BT_WARNING("Failed to broadcast [bluetooth-opp-transfer-start]");
-    return;
-  }
-
-  mSendTransferCompleteFlag = false;
-}
-
-void
-BluetoothOppManager::UpdateProgress()
-{
-  nsString type, name;
-  BluetoothValue v;
-  InfallibleTArray<BluetoothNamedValue> parameters;
-  type.AssignLiteral("bluetooth-opp-update-progress");
-
-  name.AssignLiteral("address");
-  v = mConnectedDeviceAddress;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("received");
-  v = mIsServer;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("processedLength");
-  v = mSentFileLength;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileLength");
-  v = mFileLength;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  if (!BroadcastSystemMessage(type, parameters)) {
-    BT_WARNING("Failed to broadcast [bluetooth-opp-update-progress]");
-    return;
-  }
-}
-
-void
-BluetoothOppManager::ReceivingFileConfirmation()
-{
-  nsString type, name;
-  BluetoothValue v;
-  InfallibleTArray<BluetoothNamedValue> parameters;
-  type.AssignLiteral("bluetooth-opp-receiving-file-confirmation");
-
-  name.AssignLiteral("address");
-  v = mConnectedDeviceAddress;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileName");
-  v = mFileName;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("fileLength");
-  v = mFileLength;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  name.AssignLiteral("contentType");
-  v = mContentType;
-  parameters.AppendElement(BluetoothNamedValue(name, v));
-
-  if (!BroadcastSystemMessage(type, parameters)) {
-    BT_WARNING("Failed to send [bluetooth-opp-receiving-file-confirmation]");
-    return;
-  }
-}
-
-void
-BluetoothOppManager::NotifyAboutFileChange()
-{
-  NS_NAMED_LITERAL_STRING(data, "modified");
-
-  nsCOMPtr<nsIObserverService> obs =
-    mozilla::services::GetObserverService();
-  NS_ENSURE_TRUE_VOID(obs);
-
-  obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get());
-}
-
-void
-BluetoothOppManager::OnSocketConnectSuccess(BluetoothSocket* aSocket)
-{
-  BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client");
-  MOZ_ASSERT(aSocket);
-
-  /**
-   * If the created connection is an inbound connection, close another server
-   * socket because currently only one file-transfer session is allowed. After
-   * that, we need to make sure that both server socket would be nulled out.
-   * As for outbound connections, we just notify the controller that it's done.
-   */
-  if (aSocket == mRfcommSocket) {
-    MOZ_ASSERT(!mSocket);
-    mRfcommSocket.swap(mSocket);
-
-    mL2capSocket->Disconnect();
-    mL2capSocket = nullptr;
-  } else if (aSocket == mL2capSocket) {
-    MOZ_ASSERT(!mSocket);
-    mL2capSocket.swap(mSocket);
-
-    mRfcommSocket->Disconnect();
-    mRfcommSocket = nullptr;
-  }
-
-  // Cache device address since we can't get socket address when a remote
-  // device disconnect with us.
-  mSocket->GetAddress(mConnectedDeviceAddress);
-
-  // Start sending file if we connect as a client
-  if (!mIsServer) {
-    StartSendingNextFile();
-  }
-}
-
-void
-BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket)
-{
-  BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client");
-
-  mRfcommSocket = nullptr;
-  mL2capSocket = nullptr;
-  mSocket = nullptr;
-
-  if (!mIsServer) {
-    // Inform gaia of remaining blobs' sending failure
-    DiscardBlobsToSend();
-  }
-
-  // Listen as a server if there's no more batch to process
-  if (!ProcessNextBatch()) {
-    Listen();
-  }
-}
-
-void
-BluetoothOppManager::OnSocketDisconnect(BluetoothSocket* aSocket)
-{
-  BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer)? "server" : "client");
-  MOZ_ASSERT(aSocket);
-
-  if (aSocket != mSocket) {
-    // Do nothing when a listening server socket is closed.
-    return;
-  }
-
-  /**
-   * It is valid for a bluetooth device which is transfering file via OPP
-   * closing socket without sending OBEX disconnect request first. So we
-   * delete the broken file when we failed to receive a file from the remote,
-   * and notify the transfer has been completed (but failed). We also call
-   * AfterOppDisconnected here to ensure all variables will be cleaned.
-   */
-  if (!mSuccessFlag) {
-    if (mIsServer) {
-      DeleteReceivedFile();
-    }
-
-    FileTransferComplete();
-    if (!mIsServer) {
-      // Inform gaia of remaining blobs' sending failure
-      DiscardBlobsToSend();
-    }
-  }
-
-  AfterOppDisconnected();
-  mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
-  mSuccessFlag = false;
-
-  mSocket = nullptr;
-  // Listen as a server if there's no more batch to process
-  if (!ProcessNextBatch()) {
-    Listen();
-  }
-}
-
-void
-BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
-                                         const nsAString& aServiceUuid,
-                                         int aChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
-
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
-
-  if (aChannel < 0) {
-    if (mNeedsUpdatingSdpRecords) {
-      mNeedsUpdatingSdpRecords = false;
-      bs->UpdateSdpRecords(aDeviceAddress, this);
-    } else {
-      OnSocketConnectError(mSocket);
-    }
-
-    return;
-  }
-
-  if (!mSocket->Connect(NS_ConvertUTF16toUTF8(aDeviceAddress), aChannel)) {
-    OnSocketConnectError(mSocket);
-  }
-}
-
-void
-BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
-
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
-
-  nsString uuid;
-  BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
-
-  if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
-    OnSocketConnectError(mSocket);
-  }
-}
-
-NS_IMPL_ISUPPORTS1(BluetoothOppManager, nsIObserver)
-
-bool
-BluetoothOppManager::AcquireSdcardMountLock()
-{
-  nsCOMPtr<nsIVolumeService> volumeSrv =
-    do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-  NS_ENSURE_TRUE(volumeSrv, false);
-  nsresult rv;
-  rv = volumeSrv->CreateMountLock(NS_LITERAL_STRING("sdcard"),
-                                  getter_AddRefs(mMountLock));
-  NS_ENSURE_SUCCESS(rv, false);
-  return true;
-}
-
-void
-BluetoothOppManager::Connect(const nsAString& aDeviceAddress,
-                             BluetoothProfileController* aController)
-{
-  MOZ_ASSERT(false);
-}
-
-void
-BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
-{
-  MOZ_ASSERT(false);
-}
-
-void
-BluetoothOppManager::OnConnect(const nsAString& aErrorStr)
-{
-  MOZ_ASSERT(false);
-}
-
-void
-BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr)
-{
-  MOZ_ASSERT(false);
-}
deleted file mode 100644
--- a/dom/bluetooth/BluetoothOppManager.h
+++ /dev/null
@@ -1,228 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=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_dom_bluetooth_bluetoothoppmanager_h__
-#define mozilla_dom_bluetooth_bluetoothoppmanager_h__
-
-#include "BluetoothCommon.h"
-#include "BluetoothProfileManagerBase.h"
-#include "BluetoothSocketObserver.h"
-#include "DeviceStorage.h"
-#include "mozilla/dom/ipc/Blob.h"
-#include "mozilla/ipc/UnixSocket.h"
-#include "nsCOMArray.h"
-
-class nsIOutputStream;
-class nsIInputStream;
-class nsIVolumeMountLock;
-
-BEGIN_BLUETOOTH_NAMESPACE
-
-class BluetoothSocket;
-class ObexHeaderSet;
-class SendFileBatch;
-
-class BluetoothOppManager : public BluetoothSocketObserver
-                          , public BluetoothProfileManagerBase
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-  BT_DECL_PROFILE_MGR_BASE
-  virtual void GetName(nsACString& aName)
-  {
-    aName.AssignLiteral("OPP");
-  }
-
-  /*
-   * Channel of reserved services are fixed values, please check
-   * function add_reserved_service_records() in
-   * external/bluetooth/bluez/src/adapter.c for more information.
-   */
-  static const int DEFAULT_OPP_CHANNEL = 10;
-  static const int MAX_PACKET_LENGTH = 0xFFFE;
-
-  ~BluetoothOppManager();
-  static BluetoothOppManager* Get();
-  void ClientDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
-  void ServerDataHandler(mozilla::ipc::UnixSocketRawData* aMessage);
-
-  bool Listen();
-
-  bool SendFile(const nsAString& aDeviceAddress, BlobParent* aActor);
-  bool StopSendingFile();
-  bool ConfirmReceivingFile(bool aConfirm);
-
-  void SendConnectRequest();
-  void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
-  void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
-  void SendPutFinalRequest();
-  void SendDisconnectRequest();
-
-  void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
-  bool ExtractBlobHeaders();
-  void CheckPutFinal(uint32_t aNumRead);
-
-  // The following functions are inherited from BluetoothSocketObserver
-  void ReceiveSocketData(
-    BluetoothSocket* aSocket,
-    nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
-  virtual void OnSocketConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-  virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-  virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
-
-private:
-  BluetoothOppManager();
-  bool Init();
-  void HandleShutdown();
-
-  void StartFileTransfer();
-  void StartSendingNextFile();
-  void FileTransferComplete();
-  void UpdateProgress();
-  void ReceivingFileConfirmation();
-  bool CreateFile();
-  bool WriteToFile(const uint8_t* aData, int aDataLength);
-  void DeleteReceivedFile();
-  void ReplyToConnect();
-  void ReplyToDisconnectOrAbort();
-  void ReplyToPut(bool aFinal, bool aContinue);
-  void ReplyError(uint8_t aError);
-  void AfterOppConnected();
-  void AfterFirstPut();
-  void AfterOppDisconnected();
-  void ValidateFileName();
-  bool IsReservedChar(PRUnichar c);
-  void ClearQueue();
-  void RetrieveSentFileName();
-  void NotifyAboutFileChange();
-  bool AcquireSdcardMountLock();
-  void SendObexData(uint8_t* aData, uint8_t aOpcode, int aSize);
-  void AppendBlobToSend(const nsAString& aDeviceAddress, BlobParent* aActor);
-  void DiscardBlobsToSend();
-  bool ProcessNextBatch();
-  void ConnectInternal(const nsAString& aDeviceAddress);
-
-  /**
-   * Usually we won't get a full PUT packet in one operation, which means that
-   * a packet may be devided into several parts and BluetoothOppManager should
-   * be in charge of assembling.
-   *
-   * @return true if a packet has been fully received.
-   *         false if the received length exceeds/not reaches the expected
-   *         length.
-   */
-  bool ComposePacket(uint8_t aOpCode, UnixSocketRawData* aMessage);
-
-  /**
-   * OBEX session status.
-   * Set when OBEX session is established.
-   */
-  bool mConnected;
-  nsString mConnectedDeviceAddress;
-
-  /**
-   * Remote information
-   */
-  uint8_t mRemoteObexVersion;
-  uint8_t mRemoteConnectionFlags;
-  int mRemoteMaxPacketLength;
-
-  /**
-   * For sending files, we decide our next action based on current command and
-   * previous one.
-   * For receiving files, we don't need previous command and it is set to 0
-   * as a default value.
-   */
-  int mLastCommand;
-
-  int mPacketLength;
-  int mPacketReceivedLength;
-  int mBodySegmentLength;
-  int mUpdateProgressCounter;
-
-  /**
-   * When it is true and the target service on target device couldn't be found,
-   * refreshing SDP records is necessary.
-   */
-  bool mNeedsUpdatingSdpRecords;
-
-  /**
-   * Set when StopSendingFile() is called.
-   */
-  bool mAbortFlag;
-
-  /**
-   * Set when receiving the first PUT packet of a new file
-   */
-  bool mNewFileFlag;
-
-  /**
-   * Set when receiving a PutFinal packet
-   */
-  bool mPutFinalFlag;
-
-  /**
-   * Set when FileTransferComplete() is called
-   */
-  bool mSendTransferCompleteFlag;
-
-  /**
-   * Set when a transfer is successfully completed.
-   */
-  bool mSuccessFlag;
-
-  /**
-   * True: Receive file (Server)
-   * False: Send file (Client)
-   */
-  bool mIsServer;
-
-  /**
-   * Set when receiving the first PUT packet and wait for
-   * ConfirmReceivingFile() to be called.
-   */
-  bool mWaitingForConfirmationFlag;
-
-  nsString mFileName;
-  nsString mContentType;
-  uint32_t mFileLength;
-  uint32_t mSentFileLength;
-  bool mWaitingToSendPutFinal;
-
-  nsAutoArrayPtr<uint8_t> mBodySegment;
-  nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
-
-  int mCurrentBlobIndex;
-  nsCOMPtr<nsIDOMBlob> mBlob;
-  nsTArray<SendFileBatch> mBatches;
-
-  /**
-   * A seperate member thread is required because our read calls can block
-   * execution, which is not allowed to happen on the IOThread.
-   */
-  nsCOMPtr<nsIThread> mReadFileThread;
-  nsCOMPtr<nsIOutputStream> mOutputStream;
-  nsCOMPtr<nsIInputStream> mInputStream;
-  nsCOMPtr<nsIVolumeMountLock> mMountLock;
-  nsRefPtr<DeviceStorageFile> mDsFile;
-
-  // If a connection has been established, mSocket will be the socket
-  // communicating with the remote socket. We maintain the invariant that if
-  // mSocket is non-null, mRfcommSocket and mL2capSocket must be null (and vice
-  // versa).
-  nsRefPtr<BluetoothSocket> mSocket;
-
-  // Server sockets. Once an inbound connection is established, it will hand
-  // over the ownership to mSocket, and get a new server socket while Listen()
-  // is called.
-  nsRefPtr<BluetoothSocket> mRfcommSocket;
-  nsRefPtr<BluetoothSocket> mL2capSocket;
-};
-
-END_BLUETOOTH_NAMESPACE
-
-#endif
deleted file mode 100644
--- a/dom/bluetooth/BluetoothSocket.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=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 "BluetoothSocket.h"
-
-#include "BluetoothSocketObserver.h"
-#include "BluetoothUnixSocketConnector.h"
-#include "nsThreadUtils.h"
-
-using namespace mozilla::ipc;
-USING_BLUETOOTH_NAMESPACE
-
-BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
-                                 BluetoothSocketType aType,
-                                 bool aAuth,
-                                 bool aEncrypt)
-  : mObserver(aObserver)
-  , mType(aType)
-  , mAuth(aAuth)
-  , mEncrypt(aEncrypt)
-{
-  MOZ_ASSERT(aObserver);
-}
-
-bool
-BluetoothSocket::Connect(const nsACString& aDeviceAddress, int aChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
-
-  nsAutoPtr<BluetoothUnixSocketConnector> c(
-    new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
-
-  if (!ConnectSocket(c.forget(), aDeviceAddress.BeginReading())) {
-    nsAutoString addr;
-    GetAddress(addr);
-    BT_LOGD("%s failed. Current connected device address: %s",
-           __FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
-    return false;
-  }
-
-  return true;
-}
-
-bool
-BluetoothSocket::Listen(int aChannel)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsAutoPtr<BluetoothUnixSocketConnector> c(
-    new BluetoothUnixSocketConnector(mType, aChannel, mAuth, mEncrypt));
-
-  if (!ListenSocket(c.forget())) {
-    nsAutoString addr;
-    GetAddress(addr);
-    BT_LOGD("%s failed. Current connected device address: %s",
-           __FUNCTION__, NS_ConvertUTF16toUTF8(addr).get());
-    return false;
-  }
-
-  return true;
-}
-
-void
-BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mObserver);
-  mObserver->ReceiveSocketData(this, aMessage);
-}
-
-void
-BluetoothSocket::OnConnectSuccess()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mObserver);
-  mObserver->OnSocketConnectSuccess(this);
-}
-
-void
-BluetoothSocket::OnConnectError()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mObserver);
-  mObserver->OnSocketConnectError(this);
-}
-
-void
-BluetoothSocket::OnDisconnect()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mObserver);
-  mObserver->OnSocketDisconnect(this);
-}
-
deleted file mode 100644
--- a/dom/bluetooth/BluetoothUnixSocketConnector.cpp
+++ /dev/null
@@ -1,285 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/*
- * Copyright 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * NOTE: Due to being based on the dbus compatibility layer for
- * android's bluetooth implementation, this file is licensed under the
- * apache license instead of MPL.
- *
- */
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <sys/socket.h>
-#ifdef MOZ_B2G_BT_BLUEZ
-#include <bluetooth/bluetooth.h>
-#include <bluetooth/l2cap.h>
-#include <bluetooth/rfcomm.h>
-#include <bluetooth/sco.h>
-#endif
-#include "BluetoothUnixSocketConnector.h"
-#include "nsThreadUtils.h"
-
-using namespace mozilla::ipc;
-USING_BLUETOOTH_NAMESPACE
-
-static const int RFCOMM_SO_SNDBUF = 70 * 1024;  // 70 KB send buffer
-static const int L2CAP_SO_SNDBUF = 400 * 1024;  // 400 KB send buffer
-static const int L2CAP_SO_RCVBUF = 400 * 1024;  // 400 KB receive buffer
-static const int L2CAP_MAX_MTU = 65000;
-
-#ifdef MOZ_B2G_BT_BLUEZ
-static
-int get_bdaddr(const char *str, bdaddr_t *ba)
-{
-  char *d = ((char*)ba) + 5, *endp;
-  for (int i = 0; i < 6; i++) {
-    *d-- = strtol(str, &endp, 16);
-    MOZ_ASSERT(!(*endp != ':' && i != 5));
-    str = endp + 1;
-  }
-  return 0;
-}
-
-static
-void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {
-    const uint8_t *b = (const uint8_t *)ba;
-    sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
-            b[5], b[4], b[3], b[2], b[1], b[0]);
-}
-
-#endif
-
-BluetoothUnixSocketConnector::BluetoothUnixSocketConnector(
-  BluetoothSocketType aType,
-  int aChannel,
-  bool aAuth,
-  bool aEncrypt) : mType(aType)
-                 , mChannel(aChannel)
-                 , mAuth(aAuth)
-                 , mEncrypt(aEncrypt)
-{
-}
-
-bool
-BluetoothUnixSocketConnector::SetUp(int aFd)
-{
-#ifdef MOZ_B2G_BT_BLUEZ
-  int lm = 0;
-  int sndbuf, rcvbuf;
-
-  /* kernel does not yet support LM for SCO */
-  switch (mType) {
-  case BluetoothSocketType::RFCOMM:
-    lm |= mAuth ? RFCOMM_LM_AUTH : 0;
-    lm |= mEncrypt ? RFCOMM_LM_ENCRYPT : 0;
-    break;
-  case BluetoothSocketType::L2CAP:
-  case BluetoothSocketType::EL2CAP:
-    lm |= mAuth ? L2CAP_LM_AUTH : 0;
-    lm |= mEncrypt ? L2CAP_LM_ENCRYPT : 0;
-    break;
-  case BluetoothSocketType::SCO:
-    break;
-  default:
-    MOZ_CRASH("Unknown socket type!");
-  }
-
-  if (lm) {
-    if (mType == BluetoothSocketType::RFCOMM) {
-      if (setsockopt(aFd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) {
-        BT_WARNING("setsockopt(RFCOMM_LM) failed, throwing");
-        return false;
-      }
-    } else if (mType == BluetoothSocketType::L2CAP ||
-               mType == BluetoothSocketType::EL2CAP) {
-      if (setsockopt(aFd, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm))) {
-        BT_WARNING("setsockopt(L2CAP_LM) failed, throwing");
-        return false;
-      }
-    }
-  }
-
-  if (mType == BluetoothSocketType::RFCOMM) {
-    sndbuf = RFCOMM_SO_SNDBUF;
-    if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
-      BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
-      return false;
-    }
-  }
-
-  /* Setting L2CAP socket options */
-  if (mType == BluetoothSocketType::L2CAP ||
-      mType == BluetoothSocketType::EL2CAP) {
-    struct l2cap_options opts;
-    socklen_t optlen = sizeof(opts);
-    int err;
-    err = getsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen);
-    if (!err) {
-      /* setting MTU for [E]L2CAP */
-      opts.omtu = opts.imtu = L2CAP_MAX_MTU;
-
-      /* Enable ERTM for [E]L2CAP */
-      if (mType == BluetoothSocketType::EL2CAP) {
-        opts.flush_to = 0xffff; /* infinite */
-        opts.mode = L2CAP_MODE_ERTM;
-        opts.fcs = 1;
-        opts.txwin_size = 64;
-        opts.max_tx = 10;
-      }
-
-      err = setsockopt(aFd, SOL_L2CAP, L2CAP_OPTIONS, &opts, optlen);
-    }
-
-    /* Set larger SNDBUF & RCVBUF for EL2CAP connections */
-    if (mType == BluetoothSocketType::EL2CAP) {
-      sndbuf = L2CAP_SO_SNDBUF;
-      if (setsockopt(aFd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) {
-        BT_WARNING("setsockopt(SO_SNDBUF) failed, throwing");
-        return false;
-      }
-
-      rcvbuf = L2CAP_SO_RCVBUF;
-      if (setsockopt(aFd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf))) {
-        BT_WARNING("setsockopt(SO_RCVBUF) failed, throwing");
-        return false;
-      }
-    }
-  }
-#endif
-  return true;
-}
-
-bool
-BluetoothUnixSocketConnector::SetUpListenSocket(int aFd)
-{
-  // Nothing to do here.
-  return true;
-}
-
-int
-BluetoothUnixSocketConnector::Create()
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-  int fd = -1;
-
-#ifdef MOZ_B2G_BT_BLUEZ
-  switch (mType) {
-  case BluetoothSocketType::RFCOMM:
-    fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
-    break;
-  case BluetoothSocketType::SCO:
-    fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
-    break;
-  case BluetoothSocketType::L2CAP:
-    fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
-    break;
-  case BluetoothSocketType::EL2CAP:
-    fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_L2CAP);
-    break;
-  default:
-    MOZ_CRASH();
-  }
-
-  if (fd < 0) {
-    BT_WARNING("Could not open bluetooth socket!");
-    return -1;
-  }
-
-  if (!SetUp(fd)) {
-    BT_WARNING("Could not set up socket!");
-    return -1;
-  }
-#endif
-  return fd;
-}
-
-bool
-BluetoothUnixSocketConnector::CreateAddr(bool aIsServer,
-                                         socklen_t& aAddrSize,
-                                         sockaddr_any& aAddr,
-                                         const char* aAddress)
-{
-#ifdef MOZ_B2G_BT_BLUEZ
-  // Set to BDADDR_ANY, if it's not a server, we'll reset.
-  bdaddr_t bd_address_obj = {{0, 0, 0, 0, 0, 0}};
-
-  if (!aIsServer && aAddress && strlen(aAddress) > 0) {
-    if (get_bdaddr(aAddress, &bd_address_obj)) {
-      BT_WARNING("Can't get bluetooth address!");
-      return false;
-    }
-  }
-
-  // Initialize
-  memset(&aAddr, 0, sizeof(aAddr));
-
-  switch (mType) {
-  case BluetoothSocketType::RFCOMM:
-    struct sockaddr_rc addr_rc;
-    aAddrSize = sizeof(addr_rc);
-    aAddr.rc.rc_family = AF_BLUETOOTH;
-    aAddr.rc.rc_channel = mChannel;
-    memcpy(&aAddr.rc.rc_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
-    break;
-  case BluetoothSocketType::L2CAP:
-  case BluetoothSocketType::EL2CAP:
-    struct sockaddr_l2 addr_l2;
-    aAddrSize = sizeof(addr_l2);
-    aAddr.l2.l2_family = AF_BLUETOOTH;
-    aAddr.l2.l2_psm = mChannel;
-    memcpy(&aAddr.l2.l2_bdaddr, &bd_address_obj, sizeof(bdaddr_t));
-    break;
-  case BluetoothSocketType::SCO:
-    struct sockaddr_sco addr_sco;
-    aAddrSize = sizeof(addr_sco);
-    aAddr.sco.sco_family = AF_BLUETOOTH;
-    memcpy(&aAddr.sco.sco_bdaddr, &bd_address_obj, sizeof(bd_address_obj));
-    break;
-  default:
-    BT_WARNING("Socket type unknown!");
-    return false;
-  }
-#endif
-  return true;
-}
-
-void
-BluetoothUnixSocketConnector::GetSocketAddr(const sockaddr_any& aAddr,
-                                            nsAString& aAddrStr)
-{
-#ifdef MOZ_B2G_BT_BLUEZ
-  char addr[18];
-  switch (mType) {
-  case BluetoothSocketType::RFCOMM:
-    get_bdaddr_as_string((bdaddr_t*)(&aAddr.rc.rc_bdaddr), addr);
-    break;
-  case BluetoothSocketType::SCO:
-    get_bdaddr_as_string((bdaddr_t*)(&aAddr.sco.sco_bdaddr), addr);
-    break;
-  case BluetoothSocketType::L2CAP:
-  case BluetoothSocketType::EL2CAP:
-    get_bdaddr_as_string((bdaddr_t*)(&aAddr.l2.l2_bdaddr), addr);
-    break;
-  default:
-    MOZ_CRASH("Socket should be either RFCOMM or SCO!");
-  }
-  aAddrStr.AssignASCII(addr);
-#endif
-}
deleted file mode 100644
--- a/dom/bluetooth/BluetoothUnixSocketConnector.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=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_dom_bluetooth_BluetoothUnixSocketConnector_h
-#define mozilla_dom_bluetooth_BluetoothUnixSocketConnector_h
-
-#include "BluetoothCommon.h"
-#include <sys/socket.h>
-#include <mozilla/ipc/UnixSocket.h>
-
-BEGIN_BLUETOOTH_NAMESPACE
-
-class BluetoothUnixSocketConnector : public mozilla::ipc::UnixSocketConnector
-{
-public:
-  BluetoothUnixSocketConnector(BluetoothSocketType aType, int aChannel,
-                               bool aAuth, bool aEncrypt);
-  virtual ~BluetoothUnixSocketConnector()
-  {}
-  virtual int Create() MOZ_OVERRIDE;
-  virtual bool CreateAddr(bool aIsServer,
-                          socklen_t& aAddrSize,
-                          mozilla::ipc::sockaddr_any& aAddr,
-                          const char* aAddress) MOZ_OVERRIDE;
-  virtual bool SetUp(int aFd) MOZ_OVERRIDE;
-  virtual bool SetUpListenSocket(int aFd) MOZ_OVERRIDE;
-  virtual void GetSocketAddr(const mozilla::ipc::sockaddr_any& aAddr,
-                             nsAString& aAddrStr) MOZ_OVERRIDE;
-
-private:
-  BluetoothSocketType mType;
-  int mChannel;
-  bool mAuth;
-  bool mEncrypt;
-};
-
-END_BLUETOOTH_NAMESPACE
-
-#endif
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
@@ -265,25 +265,17 @@ BluetoothOppManager::ConnectInternal(con
   mSocket->Connect(aDeviceAddress, -1);
 }
 
 void
 BluetoothOppManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
-
-  if (mSocket) {
-    mSocket->Disconnect();
-    mSocket = nullptr;
-  }
-  if (mServerSocket) {
-    mServerSocket->Disconnect();
-    mServerSocket = nullptr;
-  }
+  Disconnect(nullptr);
   sBluetoothOppManager = nullptr;
 }
 
 bool
 BluetoothOppManager::Listen()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -437,20 +429,18 @@ BluetoothOppManager::ClearQueue()
 
 bool
 BluetoothOppManager::StopSendingFile()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mIsServer) {
     mAbortFlag = true;
-  } else if (mSocket) {
-    mSocket->Disconnect();
   } else {
-    BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
+    Disconnect(nullptr);
   }
 
   return true;
 }
 
 bool
 BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
 {
@@ -503,19 +493,17 @@ BluetoothOppManager::AfterOppConnected()
   AfterFirstPut();
   // Get a mount lock to prevent the sdcard from being shared with
   // the PC while we're doing a OPP file transfer. After OPP transaction
   // were done, the mount lock will be freed.
   if (!AcquireSdcardMountLock()) {
     // If we fail to get a mount lock, abort this transaction
     // Directly sending disconnect-request is better than abort-request
     BT_WARNING("BluetoothOPPManager couldn't get a mount lock!");
-
-    MOZ_ASSERT(mSocket);
-    mSocket->Disconnect();
+    Disconnect(nullptr);
   }
 }
 
 void
 BluetoothOppManager::AfterOppDisconnected()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1466,16 +1454,26 @@ BluetoothOppManager::OnSocketDisconnect(
 
   mSocket = nullptr;
   // Listen as a server if there's no more batch to process
   if (!ProcessNextBatch()) {
     Listen();
   }
 }
 
+void
+BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
+{
+  if (mSocket) {
+    mSocket->Disconnect();
+  } else {
+    BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
+  }
+}
+
 NS_IMPL_ISUPPORTS1(BluetoothOppManager, nsIObserver)
 
 bool
 BluetoothOppManager::AcquireSdcardMountLock()
 {
   nsCOMPtr<nsIVolumeService> volumeSrv =
     do_GetService(NS_VOLUMESERVICE_CONTRACTID);
   NS_ENSURE_TRUE(volumeSrv, false);
@@ -1503,22 +1501,16 @@ BluetoothOppManager::OnUpdateSdpRecords(
 void
 BluetoothOppManager::Connect(const nsAString& aDeviceAddress,
                              BluetoothProfileController* aController)
 {
   MOZ_ASSERT(false);
 }
 
 void
-BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
-{
-  MOZ_ASSERT(false);
-}
-
-void
 BluetoothOppManager::OnConnect(const nsAString& aErrorStr)
 {
   MOZ_ASSERT(false);
 }
 
 void
 BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr)
 {
--- a/dom/bluetooth/bluez/BluetoothOppManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp
@@ -276,29 +276,17 @@ BluetoothOppManager::ConnectInternal(con
     new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
 }
 
 void
 BluetoothOppManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
-
-  if (mSocket) {
-    mSocket->Disconnect();
-    mSocket = nullptr;
-  }
-  if (mRfcommSocket) {
-    mRfcommSocket->Disconnect();
-    mRfcommSocket = nullptr;
-  }
-  if (mL2capSocket) {
-    mL2capSocket->Disconnect();
-    mL2capSocket = nullptr;
-  }
+  Disconnect(nullptr);
   sBluetoothOppManager = nullptr;
 }
 
 bool
 BluetoothOppManager::Listen()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -457,20 +445,18 @@ BluetoothOppManager::ClearQueue()
 
 bool
 BluetoothOppManager::StopSendingFile()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mIsServer) {
     mAbortFlag = true;
-  } else if (mSocket) {
-    mSocket->Disconnect();
   } else {
-    BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
+    Disconnect(nullptr);
   }
 
   return true;
 }
 
 bool
 BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
 {
@@ -523,19 +509,17 @@ BluetoothOppManager::AfterOppConnected()
   AfterFirstPut();
   // Get a mount lock to prevent the sdcard from being shared with
   // the PC while we're doing a OPP file transfer. After OPP transaction
   // were done, the mount lock will be freed.
   if (!AcquireSdcardMountLock()) {
     // If we fail to get a mount lock, abort this transaction
     // Directly sending disconnect-request is better than abort-request
     BT_WARNING("BluetoothOPPManager couldn't get a mount lock!");
-
-    MOZ_ASSERT(mSocket);
-    mSocket->Disconnect();
+    Disconnect(nullptr);
   }
 }
 
 void
 BluetoothOppManager::AfterOppDisconnected()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -1498,16 +1482,26 @@ BluetoothOppManager::OnSocketDisconnect(
   mSocket = nullptr;
   // Listen as a server if there's no more batch to process
   if (!ProcessNextBatch()) {
     Listen();
   }
 }
 
 void
+BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
+{
+  if (mSocket) {
+    mSocket->Disconnect();
+  } else {
+    BT_WARNING("%s: No ongoing file transfer to stop", __FUNCTION__);
+  }
+}
+
+void
 BluetoothOppManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
                                          const nsAString& aServiceUuid,
                                          int aChannel)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aDeviceAddress.IsEmpty());
 
   BluetoothService* bs = BluetoothService::Get();
@@ -1564,22 +1558,16 @@ BluetoothOppManager::AcquireSdcardMountL
 void
 BluetoothOppManager::Connect(const nsAString& aDeviceAddress,
                              BluetoothProfileController* aController)
 {
   MOZ_ASSERT(false);
 }
 
 void
-BluetoothOppManager::Disconnect(BluetoothProfileController* aController)
-{
-  MOZ_ASSERT(false);
-}
-
-void
 BluetoothOppManager::OnConnect(const nsAString& aErrorStr)
 {
   MOZ_ASSERT(false);
 }
 
 void
 BluetoothOppManager::OnDisconnect(const nsAString& aErrorStr)
 {
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -5,16 +5,17 @@
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsDOMClassInfo.h"
 #include "nsHashPropertyBag.h"
 #include "nsThread.h"
 #include "DeviceStorage.h"
 #include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/TabChild.h"
+#include "mozilla/MediaManager.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
@@ -346,34 +347,17 @@ nsDOMCameraControl::StartRecording(JSCon
   options.rotation = 0;
   options.maxFileSizeBytes = 0;
   options.maxVideoLengthMs = 0;
   aRv = options.Init(aCx, aOptions.address());
   if (aRv.Failed()) {
     return;
   }
 
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (!obs) {
-    NS_WARNING("Could not get the Observer service for CameraControl::StartRecording.");
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
-  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
-                       "recording-device-events",
-                       NS_LITERAL_STRING("starting").get());
-  // Forward recording events to parent process.
-  // The events are gathered in chrome process and used for recording indicator
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("starting"),
-                                                                    true /* isAudio */,
-                                                                    true /* isVideo */);
-  }
+  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("starting"));
 
   #ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
       // Camera app will stop recording when it falls to the background, so no callback is necessary.
       mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
       // Video recording doesn't output any sound, so it's not necessary to check canPlay.
@@ -390,33 +374,17 @@ nsDOMCameraControl::StartRecording(JSCon
   }
   aRv = mCameraControl->StartRecording(&options, folder, filename, onSuccess,
                                        onError.WasPassed() ? onError.Value() : nullptr);
 }
 
 void
 nsDOMCameraControl::StopRecording(ErrorResult& aRv)
 {
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (!obs) {
-    NS_WARNING("Could not get the Observer service for CameraControl::StopRecording.");
-    aRv.Throw(NS_ERROR_FAILURE);
-  }
-
-  nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
-  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props) ,
-                       "recording-device-events",
-                       NS_LITERAL_STRING("shutdown").get());
-  // Forward recording events to parent process.
-  // The events are gathered in chrome process and used for recording indicator
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("shutdown"),
-                                                                    true /* isAudio */,
-                                                                    true /* isVideo */);
-  }
+  aRv = NotifyRecordingStatusChange(NS_LITERAL_STRING("shutdown"));
 
   #ifdef MOZ_B2G
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
   }
   #endif
 
@@ -606,43 +574,19 @@ nsDOMCameraControl::Shutdown()
 }
 
 nsRefPtr<ICameraControl>
 nsDOMCameraControl::GetNativeCameraControl()
 {
   return mCameraControl;
 }
 
-already_AddRefed<nsHashPropertyBag>
-nsDOMCameraControl::CreateRecordingDeviceEventsSubject()
+nsresult
+nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
 {
-  MOZ_ASSERT(mWindow);
-
-  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), true);
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), true);
-
-  nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell();
-  if (docShell) {
-    bool isApp;
-    DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE);
 
-    nsString requestURL;
-    if (isApp) {
-      rv = docShell->GetAppManifestURL(requestURL);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-    } else {
-      nsCString pageURL;
-      nsCOMPtr<nsIURI> docURI = mWindow->GetDocumentURI();
-      MOZ_ASSERT(docURI);
+  return MediaManager::NotifyRecordingStatusChange(mWindow,
+                                                   aMsg,
+                                                   true /* aIsAudio */,
+                                                   true /* aIsVideo */);
+}
 
-      rv = docURI->GetSpec(pageURL);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-      requestURL = NS_ConvertUTF8toUTF16(pageURL);
-    }
-    props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
-    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
-  }
-
-  return props.forget();
-}
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -99,17 +99,17 @@ public:
 protected:
   virtual ~nsDOMCameraControl();
 
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
-  already_AddRefed<nsHashPropertyBag> CreateRecordingDeviceEventsSubject();
+  nsresult NotifyRecordingStatusChange(const nsString& aMsg);
 
 protected:
   /* additional members */
   nsRefPtr<ICameraControl>        mCameraControl; // non-DOM camera control
   nsCOMPtr<nsICameraCapabilities> mDOMCapabilities;
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent>  mAudioChannelAgent;
   nsCOMPtr<nsPIDOMWindow> mWindow;
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -278,27 +278,28 @@ let FormAssistant = {
         this._editor.addEditorObserver(this);
       }
 
       // If our focusedElement is removed from DOM we want to handle it properly
       let MutationObserver = element.ownerDocument.defaultView.MutationObserver;
       this._observer = new MutationObserver(function(mutations) {
         var del = [].some.call(mutations, function(m) {
           return [].some.call(m.removedNodes, function(n) {
-            return n === element;
+            return n.contains(element);
           });
         });
         if (del && element === self.focusedElement) {
           // item was deleted, fake a blur so all state gets set correctly
           self.handleEvent({ target: element, type: "blur" });
         }
       });
 
-      this._observer.observe(element.parentNode, {
-        childList: true
+      this._observer.observe(element.ownerDocument.body, {
+        childList: true,
+        subtree: true
       });
     }
 
     this.focusedElement = element;
   },
 
   get documentEncoder() {
     return this._documentEncoder;
@@ -512,20 +513,26 @@ let FormAssistant = {
         event.initEvent('input', true, false);
         target.dispatchEvent(event);
         break;
       }
 
       case "Forms:Input:SendKey":
         CompositionManager.endComposition('');
 
-        ["keydown", "keypress", "keyup"].forEach(function(type) {
-          domWindowUtils.sendKeyEvent(type, json.keyCode, json.charCode,
-            json.modifiers);
-        });
+        this._editing = true;
+        let doKeypress = domWindowUtils.sendKeyEvent('keydown', json.keyCode,
+                                  json.charCode, json.modifiers);
+        if (doKeypress) {
+          domWindowUtils.sendKeyEvent('keypress', json.keyCode,
+                                  json.charCode, json.modifiers);
+        }
+        domWindowUtils.sendKeyEvent('keyup', json.keyCode,
+                                  json.charCode, json.modifiers);
+        this._editing = false;
 
         if (json.requestId) {
           sendAsyncMessage("Forms:SendKey:Result:OK", {
             requestId: json.requestId
           });
         }
         break;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3099,10 +3099,37 @@ ContentParent::ShouldSandboxContentProce
 {
 #ifdef MOZ_CONTENT_SANDBOX
   return !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX");
 #else
   return true;
 #endif
 }
 
+bool
+ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
+                                         const nsString& aPageURL,
+                                         const bool& aIsAudio,
+                                         const bool& aIsVideo)
+{
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+        // recording-device-ipc-events needs to gather more information from content process
+        nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+        props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), ChildID());
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), IsForApp());
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
+
+        nsString requestURL = IsForApp() ? AppManifestURL() : aPageURL;
+        props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
+
+        obs->NotifyObservers((nsIPropertyBag2*) props,
+                             "recording-device-ipc-events",
+                             aRecordingStatus.get());
+    } else {
+        NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
+    }
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -204,16 +204,20 @@ public:
 
     virtual PJavaScriptParent*
     AllocPJavaScriptParent() MOZ_OVERRIDE;
     virtual bool
     RecvPJavaScriptConstructor(PJavaScriptParent* aActor) MOZ_OVERRIDE {
         return PContentParent::RecvPJavaScriptConstructor(aActor);
     }
 
+    virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
+                                           const nsString& aPageURL,
+                                           const bool& aIsAudio,
+                                           const bool& aIsVideo) MOZ_OVERRIDE;
 protected:
     void OnChannelConnected(int32_t pid) MOZ_OVERRIDE;
     virtual void ActorDestroy(ActorDestroyReason why);
     void OnNuwaForkTimeout();
 
     bool ShouldContinueFromReplyTimeout() MOZ_OVERRIDE;
     bool ShouldSandboxContentProcesses();
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -298,24 +298,16 @@ parent:
 
     /**
      * Notifies the parent about a scroll event. The pres shell ID and
      * view ID identify which scrollable (sub-)frame was scrolled, and
      * the new scroll offset for that frame is sent.
      */
     UpdateScrollOffset(uint32_t aPresShellId, ViewID aViewId, CSSIntPoint aScrollOffset);
 
-    /**
-     * Notifies the parent about a recording device is starting or shutdown.
-     * @param recordingStatus starting or shutdown
-     * @param isAudio recording start with microphone
-     * @param isVideo recording start with camera
-     */
-    async RecordingDeviceEvents(nsString recordingStatus, bool isAudio, bool isVideo);
-
     __delete__();
 
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -485,15 +485,27 @@ parent:
     sync KeywordToURI(nsCString keyword)
         returns (OptionalInputStreamParams postData, OptionalURIParams uri);
 
     sync SpeakerManagerForceSpeaker(bool aEnable);
 
     sync SpeakerManagerGetSpeakerStatus()
         returns (bool value);
 
+    /**
+     * Notifies the parent about a recording device is starting or shutdown.
+     * @param recordingStatus starting or shutdown
+     * @param pageURL URL that request that changing the recording status
+     * @param isAudio recording start with microphone
+     * @param isVideo recording start with camera
+     */
+    async RecordingDeviceEvents(nsString recordingStatus,
+                                nsString pageURL,
+                                bool isAudio,
+                                bool isVideo);
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
 };
 
 }
 }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -26,17 +26,16 @@
 #include "mozilla/unused.h"
 #include "nsCOMPtr.h"
 #include "nsContentPermissionHelper.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsEventStateManager.h"
 #include "nsFocusManager.h"
 #include "nsFrameLoader.h"
-#include "nsHashPropertyBag.h"
 #include "nsIContent.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMWindow.h"
 #include "nsIDialogCreator.h"
 #include "nsIInterfaceRequestorUtils.h"
@@ -1658,44 +1657,10 @@ TabParent::RecvContentReceivedTouch(cons
                                     const bool& aPreventDefault)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ContentReceivedTouch(aGuid, aPreventDefault);
   }
   return true;
 }
 
-bool
-TabParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
-                                     const bool& aIsAudio,
-                                     const bool& aIsVideo)
-{
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-        // recording-device-ipc-events needs to gather more information from content process
-        nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-        props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), Manager()->ChildID());
-        props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), Manager()->IsForApp());
-        props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
-        props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
-
-        nsString requestURL;
-        if (Manager()->IsForApp()) {
-          requestURL = Manager()->AppManifestURL();
-        } else {
-          nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
-          NS_ENSURE_TRUE(frameLoader, true);
-
-          frameLoader->GetURL(requestURL);
-        }
-        props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
-
-        obs->NotifyObservers((nsIPropertyBag2*) props,
-                             "recording-device-ipc-events",
-                             aRecordingStatus.get());
-    } else {
-        NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
-    }
-    return true;
-}
-
 } // namespace tabs
 } // namespace mozilla
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -169,19 +169,16 @@ public:
                                            const ViewID& aViewId,
                                            const bool& aIsRoot,
                                            const bool& aAllowZoom,
                                            const CSSToScreenScale& aMinZoom,
                                            const CSSToScreenScale& aMaxZoom);
     virtual bool RecvUpdateScrollOffset(const uint32_t& aPresShellId, const ViewID& aViewId, const CSSIntPoint& aScrollOffset);
     virtual bool RecvContentReceivedTouch(const ScrollableLayerGuid& aGuid,
                                           const bool& aPreventDefault);
-    virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
-                                           const bool& aIsAudio,
-                                           const bool& aIsVideo);
     virtual PContentDialogParent* AllocPContentDialogParent(const uint32_t& aType,
                                                             const nsCString& aName,
                                                             const nsCString& aFeatures,
                                                             const InfallibleTArray<int>& aIntParams,
                                                             const InfallibleTArray<nsString>& aStringParams);
     virtual bool DeallocPContentDialogParent(PContentDialogParent* aDialog)
     {
       delete aDialog;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -5,29 +5,27 @@
 #include "MediaManager.h"
 
 #include "MediaStreamGraph.h"
 #include "GetUserMediaRequest.h"
 #include "nsHashPropertyBag.h"
 #ifdef MOZ_WIDGET_GONK
 #include "nsIAudioManager.h"
 #endif
-#include "nsIAppsService.h"
 #include "nsIDOMFile.h"
 #include "nsIEventTarget.h"
 #include "nsIUUIDGenerator.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIPopupWindowManager.h"
 #include "nsISupportsArray.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "nsIScriptSecurityManager.h"
-#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 
 #include "Latency.h"
 
 // For PR_snprintf
 #include "prprf.h"
 
@@ -138,55 +136,16 @@ static nsresult ValidateTrackConstraints
     nsresult rv = CompareDictionaries(aCx, track.mMandatory.Value(),
                                       aNormalized.mMandatory,
                                       aOutUnknownConstraint);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
-static already_AddRefed<nsHashPropertyBag>
-CreateRecordingDeviceEventsSubject(nsPIDOMWindow* aWindow,
-                                   const bool aIsAudio,
-                                   const bool aIsVideo)
-{
-  MOZ_ASSERT(aWindow);
-
-  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
-  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
-
-  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
-  if (docShell) {
-    bool isApp;
-    DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-    nsString requestURL;
-    if (isApp) {
-      rv = docShell->GetAppManifestURL(requestURL);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-    } else {
-      nsCString pageURL;
-      nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
-      MOZ_ASSERT(docURI);
-
-      rv = docURI->GetSpec(pageURL);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-      requestURL = NS_ConvertUTF8toUTF16(pageURL);
-    }
-
-    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
-    props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
-  }
-
-  return props.forget();
-}
-
 /**
  * Send an error back to content. The error is the form a string.
  * Do this only on the main thread. The success callback is also passed here
  * so it can be released correctly.
  */
 class ErrorCallbackRunnable : public nsRunnable
 {
 public:
@@ -1150,16 +1109,78 @@ MediaManager::Get() {
 /* static */ already_AddRefed<MediaManager>
 MediaManager::GetInstance()
 {
   // so we can have non-refcounted getters
   nsRefPtr<MediaManager> service = MediaManager::Get();
   return service.forget();
 }
 
+/* static */ nsresult
+MediaManager::NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
+                                          const nsString& aMsg,
+                                          const bool& aIsAudio,
+                                          const bool& aIsVideo)
+{
+  NS_ENSURE_ARG(aWindow);
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (!obs) {
+    NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
+
+  bool isApp = false;
+  nsString requestURL;
+
+  if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
+    nsresult rv = docShell->GetIsApp(&isApp);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (isApp) {
+      rv = docShell->GetAppManifestURL(requestURL);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  if (!isApp) {
+    nsCString pageURL;
+    nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
+    NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
+
+    nsresult rv = docURI->GetSpec(pageURL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    requestURL = NS_ConvertUTF8toUTF16(pageURL);
+  }
+
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
+  props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
+
+  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
+		       "recording-device-events",
+		       aMsg.get());
+
+  // Forward recording events to parent process.
+  // The events are gathered in chrome process and used for recording indicator
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    unused <<
+      dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(aMsg,
+                                                                   requestURL,
+                                                                   aIsAudio,
+                                                                   aIsVideo);
+  }
+
+  return NS_OK;
+}
+
 /**
  * The entry point for this file. A call from Navigator::mozGetUserMedia
  * will end up here. MediaManager is a singleton that is responsible
  * for handling all incoming getUserMedia calls from every window.
  */
 nsresult
 MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged,
   nsPIDOMWindow* aWindow, const MediaStreamConstraints& aRawConstraints,
@@ -1816,45 +1837,29 @@ GetUserMediaNotificationEvent::Run()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   // Make sure mStream is cleared and our reference to the DOMMediaStream
   // is dropped on the main thread, no matter what happens in this method.
   // Otherwise this object might be destroyed off the main thread,
   // releasing DOMMediaStream off the main thread, which is not allowed.
   nsRefPtr<DOMMediaStream> stream = mStream.forget();
 
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (!obs) {
-    NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
-    return NS_ERROR_FAILURE;
-  }
   nsString msg;
   switch (mStatus) {
   case STARTING:
     msg = NS_LITERAL_STRING("starting");
     stream->OnTracksAvailable(mOnTracksAvailableCallback.forget());
     break;
   case STOPPING:
     msg = NS_LITERAL_STRING("shutdown");
     if (mListener) {
       mListener->SetStopped();
     }
     break;
   }
 
   nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
-  MOZ_ASSERT(window);
-
-  nsRefPtr<nsHashPropertyBag> props = 
-    CreateRecordingDeviceEventsSubject(window, mIsAudio, mIsVideo);
+  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
-  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
-		       "recording-device-events",
-		       msg.get());
-  // Forward recording events to parent process.
-  // The events are gathered in chrome process and used for recording indicator
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << dom::TabChild::GetFrom(window)->SendRecordingDeviceEvents(msg, mIsAudio, mIsVideo);
-  }
-  return NS_OK;
+  return MediaManager::NotifyRecordingStatusChange(window, msg, mIsAudio, mIsVideo);
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -396,16 +396,21 @@ public:
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
 
   static nsIThread* GetThread() {
     return Get()->mMediaThread;
   }
 
+  static nsresult NotifyRecordingStatusChange(nsPIDOMWindow* aWindow,
+                                              const nsString& aMsg,
+                                              const bool& aIsAudio,
+                                              const bool& aIsVideo);
+
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIMEDIAMANAGERSERVICE
 
   MediaEngine* GetBackend(uint64_t aWindowId = 0);
   StreamListeners *GetWindowListeners(uint64_t aWindowId) {
     NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
 
--- a/dom/system/gonk/OpenFileFinder.cpp
+++ b/dom/system/gonk/OpenFileFinder.cpp
@@ -8,21 +8,24 @@
 #include "nsPrintfCString.h"
 
 #include <sys/stat.h>
 #include <errno.h>
 
 namespace mozilla {
 namespace system {
 
-OpenFileFinder::OpenFileFinder(const nsACString& aPath)
+OpenFileFinder::OpenFileFinder(const nsACString& aPath,
+                               bool aCheckIsB2gOrDescendant /* = true */)
   : mPath(aPath),
     mProcDir(nullptr),
     mFdDir(nullptr),
-    mPid(0)
+    mPid(0),
+    mMyPid(-1),
+    mCheckIsB2gOrDescendant(aCheckIsB2gOrDescendant)
 {
 }
 
 OpenFileFinder::~OpenFileFinder()
 {
   Close();
 }
 
@@ -81,17 +84,24 @@ OpenFileFinder::Next(OpenFileFinder::Inf
             continue;
           }
           nsPrintfCString fdSymLink("/proc/%d/fd/%s", mPid, fdEntry->d_name);
           nsCString resolvedPath;
           if (ReadSymLink(fdSymLink, resolvedPath) && PathMatches(resolvedPath)) {
             // We found an open file contained within the directory tree passed
             // into the constructor.
             FillInfo(aInfo, resolvedPath);
-            return true;
+            // If sCheckIsB2gOrDescendant is set false, the caller cares about
+            // all processes which have open files. If sCheckIsB2gOrDescendant
+            // is set false, we only care about the b2g proccess or its descendants.
+            if (!mCheckIsB2gOrDescendant || aInfo->mIsB2gOrDescendant) {
+              return true;
+            }
+            LOG("Ignore process(%d), not a b2g process or its descendant.",
+                aInfo->mPid);
           }
         }
         // We've checked all of the files for this pid, move onto the next one.
         mState = NEXT_PID;
         continue;
       }
       case DONE:
       default:
@@ -148,19 +158,51 @@ OpenFileFinder::FillInfo(OpenFileFinder:
   nsDependentCSubstring comm(&openParen[1], closeParen - openParen - 1);
   aInfo->mComm = comm;
   // There is a single character field after the comm and then
   // the parent pid (the field we're interested in).
   // ) X ppid
   // 01234
   int ppid = atoi(&closeParen[4]);
   // We assume that we're running in the parent process
-  if (ppid != getpid()) {
+  if (mMyPid == -1) {
+    mMyPid = getpid();
+  }
+
+  if (mPid == mMyPid) {
+    // This is chrome process
+    aInfo->mIsB2gOrDescendant = true;
+    DBG("Chrome process has open file(s)");
     return;
   }
+  // For the rest (non-chrome process), we recursively check the ppid to know
+  // it is a descendant of b2g or not. See bug 931456.
+  while (ppid != mMyPid && ppid != 1) {
+    DBG("Process(%d) is not forked from b2g(%d) or Init(1), keep looking",
+        ppid, mMyPid);
+    nsPrintfCString ppStatPath("/proc/%d/stat", ppid);
+    ReadSysFile(ppStatPath.get(), stat, statString.Length());
+    closeParen = strrchr(stat, ')');
+    if (!closeParen) {
+      return;
+    }
+    ppid = atoi(&closeParen[4]);
+  }
+  if (ppid == 1) {
+    // This is a not a b2g process.
+    DBG("Non-b2g process has open file(s)");
+    aInfo->mIsB2gOrDescendant = false;
+    return;
+  }
+  if (ppid == mMyPid) {
+    // This is a descendant of b2g.
+    DBG("Child process of chrome process has open file(s)");
+    aInfo->mIsB2gOrDescendant = true;
+  }
+
   // This looks like a content process. The comm field will be the
   // app name.
   aInfo->mAppName = aInfo->mComm;
 }
 
 bool
 OpenFileFinder::ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath)
 {
--- a/dom/system/gonk/OpenFileFinder.h
+++ b/dom/system/gonk/OpenFileFinder.h
@@ -4,16 +4,29 @@
 
 #ifndef mozilla_system_openfilefinder_h__
 #define mozilla_system_openfilefinder_h__
 
 #include "nsString.h"
 
 #include <dirent.h>
 
+#define USE_DEBUG 0
+
+#undef LOG
+#define LOG(args...)  __android_log_print(ANDROID_LOG_INFO,  "OpenFileFinder", ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN,  "OpenFileFinder", ## args)
+#define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "OpenFileFinder", ## args)
+
+#if USE_DEBUG
+#define DBG(args...)  __android_log_print(ANDROID_LOG_DEBUG, "OpenFileFinder" , ## args)
+#else
+#define DBG(args...)
+#endif
+
 namespace mozilla {
 namespace system {
 
 class OpenFileFinder
 {
 public:
   enum State
   {
@@ -24,19 +37,20 @@ public:
   class Info
   {
   public:
     nsCString mFileName;  // name of the the open file
     nsCString mAppName;   // App which has the file open (if it's a b2g app)
     pid_t     mPid;       // pid of the process which has the file open
     nsCString mComm;      // comm associated with pid
     nsCString mExe;       // executable name associated with pid
+    bool      mIsB2gOrDescendant; // this is b2g/its descendant or not
   };
 
-  OpenFileFinder(const nsACString& aPath);
+  OpenFileFinder(const nsACString& aPath, bool aCheckIsB2gOrDescendant = true);
   ~OpenFileFinder();
 
   bool First(Info* aInfo);  // Return the first open file
   bool Next(Info* aInfo);   // Return the next open file
   void Close();
 
 private:
 
@@ -47,14 +61,16 @@ private:
     return Substring(aPath, 0, mPath.Length()).Equals(mPath);
   }
 
   State     mState;   // Keeps track of what we're doing.
   nsCString mPath;    // Only report files contained within this directory tree
   DIR*      mProcDir; // Used for scanning /proc
   DIR*      mFdDir;   // Used for scanning /proc/PID/fd
   int       mPid;     // PID currently being processed
+  pid_t     mMyPid;   // PID of parent process, we assume we're running on it.
+  bool      mCheckIsB2gOrDescendant; // Do we care about non-b2g process?
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolume_h__
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -2329,17 +2329,22 @@ RadioInterface.prototype = {
     if (DEBUG) this.debug("handleUSSDReceived " + JSON.stringify(ussd));
     gSystemMessenger.broadcastMessage("ussd-received", ussd);
     gMessageManager.sendMobileConnectionMessage("RIL:USSDReceived",
                                                 this.clientId, ussd);
   },
 
   handleStkProactiveCommand: function handleStkProactiveCommand(message) {
     if (DEBUG) this.debug("handleStkProactiveCommand " + JSON.stringify(message));
-    gSystemMessenger.broadcastMessage("icc-stkcommand", message);
+    let iccId = this.rilContext.iccInfo && this.rilContext.iccInfo.iccid;
+    if (iccId) {
+      gSystemMessenger.broadcastMessage("icc-stkcommand",
+                                        {iccId: iccId,
+                                         command: message});
+    }
     gMessageManager.sendIccMessage("RIL:StkCommand", this.clientId, message);
   },
 
   handleExitEmergencyCbMode: function handleExitEmergencyCbMode(message) {
     if (DEBUG) this.debug("handleExitEmergencyCbMode: " + JSON.stringify(message));
     gMessageManager.sendRequestResults("RIL:ExitEmergencyCbMode", message);
   },
 
--- a/dom/tests/browser/browser_ConsoleAPITests.js
+++ b/dom/tests/browser/browser_ConsoleAPITests.js
@@ -127,17 +127,17 @@ function testConsoleGroup(aMessageObject
   is(messageWindow, gWindow, "found correct window by window ID");
 
   ok(aMessageObject.level == "group" ||
      aMessageObject.level == "groupCollapsed" ||
      aMessageObject.level == "groupEnd",
      "expected level received");
 
   is(aMessageObject.functionName, "testGroups", "functionName matches");
-  ok(aMessageObject.lineNumber >= 45 && aMessageObject.lineNumber <= 47,
+  ok(aMessageObject.lineNumber >= 45 && aMessageObject.lineNumber <= 48,
      "lineNumber matches");
   if (aMessageObject.level == "groupCollapsed") {
     is(aMessageObject.groupName, "a group", "groupCollapsed groupName matches");
     is(aMessageObject.arguments[0], "a", "groupCollapsed arguments[0] matches");
     is(aMessageObject.arguments[1], "group", "groupCollapsed arguments[0] matches");
   }
   else if (aMessageObject.level == "group") {
     is(aMessageObject.groupName, "b group", "group groupName matches");
@@ -244,16 +244,20 @@ function observeConsoleTest() {
   expect("dir", win.toString());
   win.console.dir(win);
   yield undefined;
 
   expect("error", "arg");
   win.console.error("arg");
   yield undefined;
 
+  expect("exception", "arg");
+  win.console.exception("arg");
+  yield undefined;
+
   let obj2 = { b: 2 };
   expect("log", "omg ", obj, " foo ", 4, obj2);
   win.console.log("omg %o foo %o", obj, 4, obj2);
   yield undefined;
 
   startTraceTest();
   yield undefined;
 
@@ -265,16 +269,17 @@ function consoleAPISanityTest() {
   let win = XPCNativeWrapper.unwrap(gWindow);
   ok(win.console, "we have a console attached");
   ok(win.console, "we have a console attached, 2nd attempt");
 
   ok(win.console.log, "console.log is here");
   ok(win.console.info, "console.info is here");
   ok(win.console.warn, "console.warn is here");
   ok(win.console.error, "console.error is here");
+  ok(win.console.exception, "console.exception is here");
   ok(win.console.trace, "console.trace is here");
   ok(win.console.dir, "console.dir is here");
   ok(win.console.group, "console.group is here");
   ok(win.console.groupCollapsed, "console.groupCollapsed is here");
   ok(win.console.groupEnd, "console.groupEnd is here");
   ok(win.console.time, "console.time is here");
   ok(win.console.timeEnd, "console.timeEnd is here");
 }
--- a/dom/tests/browser/test-console-api.html
+++ b/dom/tests/browser/test-console-api.html
@@ -34,16 +34,17 @@
 
       function test() {
         var str = "Test Message."
         console.foobar(str); // if this throws, we don't execute following funcs
         console.log(str);
         console.info(str);
         console.warn(str);
         console.error(str);
+        console.exception(str);
       }
 
       function testGroups() {
         console.groupCollapsed("a", "group");
         console.group("b", "group");
         console.groupEnd("b", "group");
       }
 
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -19,16 +19,17 @@ function doTest() {
     ok(false, "random property threw: " + ex);
   }
 
   var expectedProps = {
     "log": "function",
     "info": "function",
     "warn": "function",
     "error": "function",
+    "exception": "function",
     "debug": "function",
     "trace": "function",
     "dir": "function",
     "group": "function",
     "groupCollapsed": "function",
     "groupEnd": "function",
     "time": "function",
     "timeEnd": "function",
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -119,26 +119,25 @@ abstract public class BrowserApp extends
     private BrowserToolbar mBrowserToolbar;
     private HomePager mHomePager;
     private View mHomePagerContainer;
     protected Telemetry.Timer mAboutHomeStartupTimer = null;
     private ActionModeCompat mActionMode;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
-    private static class MenuItemInfo {
+    private class MenuItemInfo {
         public int id;
         public String label;
         public String icon;
         public boolean checkable = false;
         public boolean checked = false;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
-        public boolean added = false;    // So we can re-add after a locale change.
     }
 
     // The types of guest mdoe dialogs we show
     private static enum GuestModeDialog {
         ENTERING,
         LEAVING
     }
 
@@ -1635,30 +1634,16 @@ abstract public class BrowserApp extends
             hideHomePager();
         }
     }
 
     private void showHomePager(HomePager.Page page) {
         showHomePagerWithAnimator(page, null);
     }
 
-    @Override
-    public void onLocaleReady(final String locale) {
-        super.onLocaleReady(locale);
-        if (mHomePager != null) {
-            // Blow it away and rebuild it with the right strings.
-            mHomePager.redisplay(getSupportFragmentManager());
-        }
-
-        if (mMenu != null) {
-            mMenu.clear();
-            onCreateOptionsMenu(mMenu);
-        }
-    }
-
     private void showHomePagerWithAnimator(HomePager.Page page, PropertyAnimator animator) {
         if (isHomePagerVisible()) {
             return;
         }
 
         // Refresh toolbar height to possibly restore the toolbar padding
         refreshToolbarHeight();
 
@@ -1821,17 +1806,17 @@ abstract public class BrowserApp extends
                     mIsHidingTabs = false;
                 }
                 return true;
             }
             return false;
         }
     }
 
-    private static Menu findParentMenu(Menu menu, MenuItem item) {
+    private Menu findParentMenu(Menu menu, MenuItem item) {
         final int itemId = item.getItemId();
 
         final int count = (menu != null) ? menu.size() : 0;
         for (int i = 0; i < count; i++) {
             MenuItem menuItem = menu.getItem(i);
             if (menuItem.getItemId() == itemId) {
                 return menu;
             }
@@ -1841,104 +1826,84 @@ abstract public class BrowserApp extends
                     return parent;
                 }
             }
         }
 
         return null;
     }
 
-    /**
-     * Add the provided item to the provided menu, which should be
-     * the root (mMenu).
-     */
-    private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
-        info.added = true;
-        
-        final Menu destination;
+    private void addAddonMenuItem(final MenuItemInfo info) {
+        if (mMenu == null) {
+            if (mAddonMenuItemsCache == null)
+                mAddonMenuItemsCache = new Vector<MenuItemInfo>();
+
+            mAddonMenuItemsCache.add(info);
+            return;
+        }
+
+        Menu menu;
         if (info.parent == 0) {
-            destination = menu;
+            menu = mMenu;
         } else if (info.parent == GECKO_TOOLS_MENU) {
-            MenuItem tools = menu.findItem(R.id.tools);
-            destination = tools != null ? tools.getSubMenu() : menu;
+            MenuItem tools = mMenu.findItem(R.id.tools);
+            menu = tools != null ? tools.getSubMenu() : mMenu;
         } else {
-            MenuItem parent = menu.findItem(info.parent);
-            if (parent == null) {
+            MenuItem parent = mMenu.findItem(info.parent);
+            if (parent == null)
                 return;
-            }
 
-            Menu parentMenu = findParentMenu(menu, parent);
+            Menu parentMenu = findParentMenu(mMenu, parent);
 
             if (!parent.hasSubMenu()) {
                 parentMenu.removeItem(parent.getItemId());
-                destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
-                if (parent.getIcon() != null) {
-                    ((SubMenu) destination).getItem().setIcon(parent.getIcon());
-                }
+                menu = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
+                if (parent.getIcon() != null)
+                    ((SubMenu) menu).getItem().setIcon(parent.getIcon());
             } else {
-                destination = parent.getSubMenu();
+                menu = parent.getSubMenu();
             }
         }
 
-        MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
-
+        MenuItem item = menu.add(Menu.NONE, info.id, Menu.NONE, info.label);
         item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
             @Override
             public boolean onMenuItemClick(MenuItem item) {
-                Log.i(LOGTAG, "Menu item clicked");
+                Log.i(LOGTAG, "menu item clicked");
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET)));
                 return true;
             }
         });
 
-        if (info.icon == null) {
-            item.setIcon(R.drawable.ic_menu_addons_filler);
-        } else {
+        if (info.icon != null) {
             final int id = info.id;
             BitmapUtils.getDrawable(this, info.icon, new BitmapUtils.BitmapLoader() {
                 @Override
                 public void onBitmapFound(Drawable d) {
-                    // TODO: why do we re-find the item?
-                    MenuItem item = destination.findItem(id);
+                    MenuItem item = mMenu.findItem(id);
                     if (item == null) {
                         return;
                     }
                     if (d == null) {
                         item.setIcon(R.drawable.ic_menu_addons_filler);
                         return;
                     }
                     item.setIcon(d);
                 }
             });
+        } else {
+            item.setIcon(R.drawable.ic_menu_addons_filler);
         }
 
         item.setCheckable(info.checkable);
         item.setChecked(info.checked);
         item.setEnabled(info.enabled);
         item.setVisible(info.visible);
     }
 
-    private void addAddonMenuItem(final MenuItemInfo info) {
-        if (mAddonMenuItemsCache == null) {
-            mAddonMenuItemsCache = new Vector<MenuItemInfo>();
-        }
-
-        // Mark it as added if the menu was ready.
-        info.added = (mMenu != null);
-
-        // Always cache so we can rebuild after a locale switch.
-        mAddonMenuItemsCache.add(info);
-
-        if (mMenu == null) {
-            return;
-        }
-
-        addAddonMenuItemToMenu(mMenu, info);
-    }
-
     private void removeAddonMenuItem(int id) {
         // Remove add-on menu item from cache, if available.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
                  if (item.id == id) {
                      mAddonMenuItemsCache.remove(item);
                      break;
                  }
@@ -1958,55 +1923,52 @@ abstract public class BrowserApp extends
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
                 if (item.id == id) {
                     item.label = options.optString("name", item.label);
                     item.checkable = options.optBoolean("checkable", item.checkable);
                     item.checked = options.optBoolean("checked", item.checked);
                     item.enabled = options.optBoolean("enabled", item.enabled);
                     item.visible = options.optBoolean("visible", item.visible);
-                    item.added = (mMenu != null);
                     break;
                 }
             }
         }
 
-        if (mMenu == null) {
+        if (mMenu == null)
             return;
-        }
 
         MenuItem menuItem = mMenu.findItem(id);
         if (menuItem != null) {
             menuItem.setTitle(options.optString("name", menuItem.getTitle().toString()));
             menuItem.setCheckable(options.optBoolean("checkable", menuItem.isCheckable()));
             menuItem.setChecked(options.optBoolean("checked", menuItem.isChecked()));
             menuItem.setEnabled(options.optBoolean("enabled", menuItem.isEnabled()));
             menuItem.setVisible(options.optBoolean("visible", menuItem.isVisible()));
         }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
-        // Sets mMenu = menu.
         super.onCreateOptionsMenu(menu);
 
         // Inform the menu about the action-items bar. 
-        if (menu instanceof GeckoMenu &&
-            HardwareUtils.isTablet()) {
+        if (menu instanceof GeckoMenu && HardwareUtils.isTablet())
             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
-        }
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.browser_app_menu, mMenu);
 
-        // Add add-on menu items, if any exist.
+        // Add add-on menu items if any.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
-                addAddonMenuItemToMenu(mMenu, item);
+                 addAddonMenuItem(item);
             }
+
+            mAddonMenuItemsCache.clear();
         }
 
         // Action providers are available only ICS+.
         if (Build.VERSION.SDK_INT >= 14) {
             MenuItem share = mMenu.findItem(R.id.share);
             GeckoActionProvider provider = new GeckoActionProvider(this);
             share.setActionProvider(provider);
         }
--- a/mobile/android/base/ContextGetter.java
+++ b/mobile/android/base/ContextGetter.java
@@ -1,15 +1,6 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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/. */
-
 package org.mozilla.gecko;
-
 import android.content.Context;
-import android.content.SharedPreferences;
 
-public interface ContextGetter {
-    Context getContext();
-    SharedPreferences getSharedPreferences();
+interface ContextGetter {
+  Context getContext();
 }
-
--- a/mobile/android/base/GeckoActivity.java
+++ b/mobile/android/base/GeckoActivity.java
@@ -7,25 +7,16 @@ package org.mozilla.gecko;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.support.v4.app.FragmentActivity;
 
 public class GeckoActivity extends FragmentActivity implements GeckoActivityStatus {
     // has this activity recently started another Gecko activity?
     private boolean mGeckoActivityOpened = false;
 
-    /**
-     * Display any resources that show strings or encompass locale-specific
-     * representations.
-     *
-     * onLocaleReady must always be called on the UI thread.
-     */
-    public void onLocaleReady(final String locale) {
-    }
-
     @Override
     public void onPause() {
         super.onPause();
 
         if (getApplication() instanceof GeckoApplication) {
             ((GeckoApplication) getApplication()).onActivityPause(this);
         }
     }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -121,28 +121,22 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public abstract class GeckoApp
-    extends GeckoActivity 
-    implements
-    ContextGetter,
-    GeckoAppShell.GeckoInterface,
-    GeckoEventListener,
-    GeckoEventResponder,
-    GeckoMenu.Callback,
-    GeckoMenu.MenuPresenter,
-    LocationListener,
-    SensorEventListener,
-    Tabs.OnTabsChangedListener
+abstract public class GeckoApp
+                extends GeckoActivity 
+    implements GeckoEventListener, SensorEventListener, LocationListener,
+                           Tabs.OnTabsChangedListener, GeckoEventResponder,
+                           GeckoMenu.Callback, GeckoMenu.MenuPresenter,
+                           ContextGetter, GeckoAppShell.GeckoInterface
 {
     private static final String LOGTAG = "GeckoApp";
 
     private static enum StartupAction {
         NORMAL,     /* normal application start */
         URL,        /* launched with a passed URL */
         PREFETCH    /* launched with a passed URL that we prefetch */
     }
@@ -234,30 +228,20 @@ public abstract class GeckoApp
             super(message);
         }
     }
 
     void toggleChrome(final boolean aShow) { }
 
     void focusChrome() { }
 
-    @Override
     public Context getContext() {
         return sAppContext;
     }
 
-    @Override
-    public SharedPreferences getSharedPreferences() {
-        return GeckoApp.getAppSharedPreferences();
-    }
-
-    public static SharedPreferences getAppSharedPreferences() {
-        return GeckoApp.sAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
-    }
-
     public Activity getActivity() {
         return this;
     }
 
     public LocationListener getLocationListener() {
         if (mShouldReportGeoData && mPhoneStateListener == null) {
             mPhoneStateListener = new PhoneStateListener() {
                 public void onSignalStrengthsChanged(SignalStrength signalStrength) {
@@ -269,16 +253,20 @@ public abstract class GeckoApp
         }
         return this;
     }
 
     public SensorEventListener getSensorEventListener() {
         return this;
     }
 
+    public static SharedPreferences getAppSharedPreferences() {
+        return GeckoApp.sAppContext.getSharedPreferences(PREFS_NAME, 0);
+    }
+
     public View getCameraView() {
         return mCameraView;
     }
 
     public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
         mAppStateListeners.add(listener);
     }
 
@@ -710,18 +698,16 @@ public abstract class GeckoApp
                 }
                 JSONObject handlersJSON = new JSONObject();
                 handlersJSON.put("apps", new JSONArray(appList));
                 mCurrentResponse = handlersJSON.toString();
             } else if (event.equals("Intent:Open")) {
                 GeckoAppShell.openUriExternal(message.optString("url"),
                     message.optString("mime"), message.optString("packageName"),
                     message.optString("className"), message.optString("action"), message.optString("title"));
-            } else if (event.equals("Locale:Set")) {
-                setLocale(message.getString("locale"));
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     public String getResponse(JSONObject origMessage) {
         String res = mCurrentResponse;
@@ -1188,37 +1174,36 @@ public abstract class GeckoApp
 
                 if (profileName != null || profilePath != null) {
                     mProfile = GeckoProfile.get(this, profileName, profilePath);
                 }
             }
         }
 
         BrowserDB.initialize(getProfile().getName());
-        ((GeckoApplication) getApplication()).initialize();
+        ((GeckoApplication)getApplication()).initialize();
 
         sAppContext = this;
         GeckoAppShell.setContextGetter(this);
         GeckoAppShell.setGeckoInterface(this);
         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
 
         Tabs.getInstance().attachToContext(this);
         try {
             Favicons.attachToContext(this);
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
         }
 
-        // Did the OS locale change while we were backgrounded? If so,
-        // we need to die so that Gecko will re-init add-ons that touch
-        // the UI.
-        // This is using a sledgehammer to crack a nut, but it'll do for
-        // now.
-        if (LocaleManager.systemLocaleDidChange()) {
-            Log.i(LOGTAG, "System locale changed. Restarting.");
+        // When we detect a locale change, we need to restart Gecko, which
+        // actually means restarting the entire application. This logic should
+        // actually be handled elsewhere since GeckoApp may not be alive to
+        // handle this event if "Don't keep activities" is enabled (filed as
+        // bug 889082).
+        if (((GeckoApplication)getApplication()).needsRestart()) {
             doRestart();
             System.exit(0);
             return;
         }
 
         if (GeckoThread.isCreated()) {
             // This happens when the GeckoApp activity is destroyed by Android
             // without killing the entire application (see Bug 769269).
@@ -1289,21 +1274,16 @@ public abstract class GeckoApp
         }
 
         // Perform background initialization.
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
 
-                // Wait until now to set this, because we'd rather throw an exception than 
-                // have a caller of LocaleManager regress startup.
-                LocaleManager.setContextGetter(GeckoApp.this);
-                LocaleManager.initialize();
-
                 SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
                 if (previousSession.wasKilled()) {
                     Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
                 }
 
                 SharedPreferences.Editor editor = prefs.edit();
                 editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
 
@@ -1314,70 +1294,34 @@ public abstract class GeckoApp
                 editor.commit();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
                 final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
                 Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
-
                 final String osLocale = Locale.getDefault().toString();
-                String appLocale = LocaleManager.getAndApplyPersistedLocale();
-                Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
-
-                if (appLocale == null) {
-                    appLocale = osLocale;
-                }
-
+                Log.d(LOGTAG, "Locale is " + osLocale);
+
+                // Replace the duplicate `osLocale` argument when we support switchable
+                // application locales.
                 mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
                                                             profilePath,
                                                             dispatcher,
                                                             osLocale,
-                                                            appLocale,
+                                                            osLocale,    // Placeholder.
                                                             previousSession);
-
-                final String uiLocale = appLocale;
-                ThreadUtils.postToUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        GeckoApp.this.onLocaleReady(uiLocale);
-                    }
-                });
             }
         });
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
         NotificationHelper.init(getApplicationContext());
     }
 
-    /**
-     * At this point, the resource system and the rest of the browser are
-     * aware of the locale.
-     *
-     * Now we can display strings!
-     */
-    @Override
-    public void onLocaleReady(final String locale) {
-        if (!ThreadUtils.isOnUiThread()) {
-            throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
-        }
-
-        // The URL bar hint needs to be populated.
-        TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
-        if (urlBar == null) {
-            return;
-        }
-        final String hint = getResources().getString(R.string.url_bar_default_text);
-        urlBar.setHint(hint);
-
-        // Allow onConfigurationChanged to take care of the rest.
-        onConfigurationChanged(getResources().getConfiguration());
-    }
-
     protected void initializeChrome() {
         mDoorHangerPopup = new DoorHangerPopup(this, null);
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
 
         if (mCameraView == null) {
             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                 mCameraView = new SurfaceView(this);
@@ -1577,17 +1521,16 @@ public abstract class GeckoApp
         registerEventListener("Sanitize:ClearHistory");
         registerEventListener("Update:Check");
         registerEventListener("Update:Download");
         registerEventListener("Update:Install");
         registerEventListener("PrivateBrowsing:Data");
         registerEventListener("Contact:Add");
         registerEventListener("Intent:Open");
         registerEventListener("Intent:GetHandlers");
-        registerEventListener("Locale:Set");
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
 
         mPromptService = new PromptService(this);
@@ -1631,16 +1574,33 @@ public abstract class GeckoApp
                 final Context context = GeckoApp.this;
                 AnnouncementsBroadcastService.recordLastLaunch(context);
 
                 // Kick off our background services. We do this by invoking the broadcast
                 // receiver, which uses the system alarm infrastructure to perform tasks at
                 // intervals.
                 GeckoPreferences.broadcastAnnouncementsPref(context);
                 GeckoPreferences.broadcastHealthReportUploadPref(context);
+
+                /*
+                XXXX see Bug 635342.
+                We want to disable this code if possible.  It is about 145ms in runtime.
+
+                If this code ever becomes live again, you'll need to chain the
+                new locale into BrowserHealthRecorder correctly. See
+                GeckoAppShell.setSelectedLocale.
+                We pass the OS locale into the BHR constructor: we need to grab
+                that *before* we modify the current locale!
+
+                SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
+                String localeCode = settings.getString(getPackageName() + ".locale", "");
+                if (localeCode != null && localeCode.length() > 0)
+                    GeckoAppShell.setSelectedLocale(localeCode);
+                */
+
                 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
                     return;
                 }
             }
         }, 50);
 
         if (mIsRestoringActivity) {
             GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
@@ -2175,18 +2135,16 @@ public abstract class GeckoApp
             return;
         for (File file : files) {
             file.delete();
         }
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
-        LocaleManager.correctLocale(getResources(), newConfig);
         super.onConfigurationChanged(newConfig);
 
         if (mOrientation != newConfig.orientation) {
             mOrientation = newConfig.orientation;
             if (mFormAssistPopup != null)
                 mFormAssistPopup.hide();
             refreshChrome();
         }
@@ -2762,57 +2720,9 @@ public abstract class GeckoApp
         int versionCode = 0;
         try {
             versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
         } catch (NameNotFoundException e) {
             Log.wtf(LOGTAG, getPackageName() + " not found", e);
         }
         return versionCode;
     }
-
-    // FHR reason code for a session end prior to a restart for a
-    // locale change.
-    private static final String SESSION_END_LOCALE_CHANGED = "L";
-
-    /**
-     * Use LocaleManager to change our persisted and current locales,
-     * and poke BrowserHealthRecorder to tell it of our changed state.
-     */
-    private void setLocale(final String locale) {
-        if (locale == null) {
-            return;
-        }
-        final String resultant = LocaleManager.setSelectedLocale(locale);
-        if (resultant == null) {
-            return;
-        }
-
-        final BrowserHealthRecorder rec = mHealthRecorder;
-        if (rec == null) {
-            return;
-        }
-
-        final boolean startNewSession = true;
-        final boolean shouldRestart = false;
-        rec.onAppLocaleChanged(resultant);
-        rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
-
-        if (!shouldRestart) {
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    GeckoApp.this.onLocaleReady(resultant);
-                }
-            });
-            return;
-        }
-
-        // Do this in the background so that the health recorder has its
-        // time to finish.
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                GeckoApp.this.doRestart();
-                GeckoApp.this.finish();
-            }
-        });
-    }
 }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -224,17 +224,18 @@ public class GeckoAppShell
                     if (mainThread != null && thread != mainThread) {
                         Log.e(LOGTAG, "Main thread stack:");
                         for (StackTraceElement ste : mainThread.getStackTrace()) {
                             Log.e(LOGTAG, ste.toString());
                         }
                     }
 
                     if (e instanceof OutOfMemoryError) {
-                        SharedPreferences prefs = getSharedPreferences();
+                        SharedPreferences prefs =
+                            getContext().getSharedPreferences(GeckoApp.PREFS_NAME, 0);
                         SharedPreferences.Editor editor = prefs.edit();
                         editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
                         editor.commit();
                     }
                 } finally {
                     reportJavaCrash(getStackTraceString(e));
                 }
             }
@@ -1581,16 +1582,55 @@ public class GeckoAppShell
                 return LINK_TYPE_4G; // 3.9G
             case TelephonyManager.NETWORK_TYPE_UNKNOWN:
             default:
                 Log.w(LOGTAG, "Connected to an unknown mobile network!");
                 return LINK_TYPE_UNKNOWN;
         }
     }
 
+    @WrapElementForJNI
+    public static void setSelectedLocale(String localeCode) {
+        /* Bug 713464: This method is still called from Gecko side.
+           Earlier we had an option to run Firefox in a language other than system's language.
+           However, this is not supported as of now.
+           Gecko resets the locale to en-US by calling this function with an empty string.
+           This affects GeckoPreferences activity in multi-locale builds.
+
+        N.B., if this code ever becomes live again, you need to hook it up to locale
+        recording in BrowserHealthRecorder: we track the current app and OS locales
+        as part of the recorded environment.
+
+        See similar note in GeckoApp.java for the startup path.
+
+        //We're not using this, not need to save it (see bug 635342)
+        SharedPreferences settings =
+            getContext().getPreferences(Activity.MODE_PRIVATE);
+        settings.edit().putString(getContext().getPackageName() + ".locale",
+                                  localeCode).commit();
+        Locale locale;
+        int index;
+        if ((index = localeCode.indexOf('-')) != -1 ||
+            (index = localeCode.indexOf('_')) != -1) {
+            String langCode = localeCode.substring(0, index);
+            String countryCode = localeCode.substring(index + 1);
+            locale = new Locale(langCode, countryCode);
+        } else {
+            locale = new Locale(localeCode);
+        }
+        Locale.setDefault(locale);
+
+        Resources res = getContext().getBaseContext().getResources();
+        Configuration config = res.getConfiguration();
+        config.locale = locale;
+        res.updateConfiguration(config, res.getDisplayMetrics());
+        */
+    }
+
+
     @WrapElementForJNI(stubName = "GetSystemColoursWrapper")
     public static int[] getSystemColors() {
         // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
         final int[] attrsAppearance = {
             android.R.attr.textColor,
             android.R.attr.textColorPrimary,
             android.R.attr.textColorPrimaryInverse,
             android.R.attr.textColorSecondary,
@@ -2101,23 +2141,16 @@ public class GeckoAppShell
     public static Context getContext() {
         return sContextGetter.getContext();
     }
 
     public static void setContextGetter(ContextGetter cg) {
         sContextGetter = cg;
     }
 
-    public static SharedPreferences getSharedPreferences() {
-        if (sContextGetter == null) {
-            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
-        }
-        return sContextGetter.getSharedPreferences();
-    }
-
     public interface AppStateListener {
         public void onPause();
         public void onResume();
         public void onOrientationChanged();
     }
 
     public interface GeckoInterface {
         public GeckoProfile getProfile();
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -7,49 +7,30 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Application;
-import android.content.res.Configuration;
-import android.util.Log;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 
 public class GeckoApplication extends Application {
 
     private boolean mInited;
     private boolean mInBackground;
     private boolean mPausedGecko;
+    private boolean mNeedsRestart;
 
     private LightweightTheme mLightweightTheme;
 
-    /**
-     * We need to do locale work here, because we need to intercept
-     * each hit to onConfigurationChanged.
-     */
-    @Override
-    public void onConfigurationChanged(Configuration config) {
-        Log.d("GeckoApplication", "onConfigurationChanged: " + config.locale +
-                                  ", background: " + mInBackground);
-
-        // Do nothing if we're in the background. It'll simply cause a loop
-        // (Bug 936756 Comment 11), and it's not necessary.
-        if (mInBackground) {
-            super.onConfigurationChanged(config);
-            return;
-        }
-
-        // Otherwise, correct the locale. This catches some cases that GeckoApp
-        // doesn't get a chance to.
-        LocaleManager.correctLocale(getResources(), config);
-        super.onConfigurationChanged(config);
-    }
-
     protected void initialize() {
         if (mInited)
             return;
 
         // workaround for http://code.google.com/p/android/issues/detail?id=20915
         try {
             Class.forName("android.os.AsyncTask");
         } catch (ClassNotFoundException e) {}
@@ -57,16 +38,24 @@ public class GeckoApplication extends Ap
         mLightweightTheme = new LightweightTheme(this);
 
         GeckoConnectivityReceiver.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().start();
         GeckoNetworkManager.getInstance().init(getApplicationContext());
         MemoryMonitor.getInstance().init(getApplicationContext());
 
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mNeedsRestart = true;
+            }
+        };
+        registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+
         mInited = true;
     }
 
     public void onActivityPause(GeckoActivityStatus activity) {
         mInBackground = true;
 
         if ((activity.isFinishing() == false) &&
             (activity.isGeckoActivityOpened() == false)) {
@@ -96,16 +85,20 @@ public class GeckoApplication extends Ap
             mPausedGecko = false;
         }
         GeckoConnectivityReceiver.getInstance().start();
         GeckoNetworkManager.getInstance().start();
 
         mInBackground = false;
     }
 
+    protected boolean needsRestart() {
+        return mNeedsRestart;
+    }
+
     @Override
     public void onCreate() {
         HardwareUtils.init(getApplicationContext());
         Clipboard.init(getApplicationContext());
         GeckoLoader.loadMozGlue();
         super.onCreate();
     }
 
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -14,29 +14,27 @@ import org.mozilla.gecko.util.GeckoEvent
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.res.TypedArray;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class GeckoView extends LayerView
     implements GeckoEventListener, ContextGetter {
 
-    private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
     private static final String LOGTAG = "GeckoView";
 
     private ChromeDelegate mChromeDelegate;
     private ContentDelegate mContentDelegate;
 
     public GeckoView(Context context, AttributeSet attrs) {
         super(context, attrs);
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GeckoView);
@@ -302,24 +300,16 @@ public class GeckoView extends LayerView
     public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) {
         GeckoAppShell.setGeckoInterface(geckoInterface);
     }
 
     public static GeckoAppShell.GeckoInterface getGeckoInterface() {
         return GeckoAppShell.getGeckoInterface();
     }
 
-    protected String getSharedPreferencesFile() {
-        return DEFAULT_SHARED_PREFERENCES_FILE;
-    }
-
-    public SharedPreferences getSharedPreferences() {
-        return getContext().getSharedPreferences(getSharedPreferencesFile(), 0);
-    }
-
     /**
     * Wrapper for a browser in the GeckoView container. Associated with a browser
     * element in the Gecko system.
     */
     public class Browser {
         private final int mId;
         private Browser(int Id) {
             mId = Id;
deleted file mode 100644
--- a/mobile/android/base/LocaleManager.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.Log;
-
-import java.util.Locale;
-
-/**
- * This class manages persistence, application, and otherwise handling of
- * user-specified locales.
- *
- * Of note:
- * 
- * * It's a singleton, because its scope extends to that of the application,
- *   and definitionally all changes to the locale of the app must go through
- *   this.
- * * It's lazy.
- * * It has ties into the Gecko event system, because it has to tell Gecko when
- *   to switch locale.
- * * It relies on using the SharedPreferences file owned by the browser (in
- *   Fennec's case, "GeckoApp") for performance.
- */
-public class LocaleManager {
-    private static final String LOG_TAG = "GeckoLocales";
-
-    // These are both volatile because we don't impose restrictions
-    // over which thread calls our methods.
-    private static volatile ContextGetter getter = null;
-    private static volatile Locale currentLocale = null;
-
-    private static volatile boolean inited = false;
-    private static boolean systemLocaleDidChange = false;
-    private static BroadcastReceiver receiver;
-
-    public static void setContextGetter(ContextGetter getter) {
-        LocaleManager.getter = getter;
-    }
-
-    public static void initialize() {
-        if (inited) {
-            return;
-        }
-
-        receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                systemLocaleDidChange = true;
-            }
-        };
-        getContext().registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
-        inited = true;
-    }
-
-    public static boolean systemLocaleDidChange() {
-        return systemLocaleDidChange;
-    }
-
-    private static Context getContext() {
-        if (getter == null) {
-            throw new IllegalStateException("No ContextGetter; cannot fetch context.");
-        }
-        return getter.getContext();
-    }
-
-    private static SharedPreferences getSharedPreferences() {
-        if (getter == null) {
-            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.", new RuntimeException("No prefs."));
-        }
-        return getter.getSharedPreferences();
-    }
-
-    /**
-     * Every time the system gives us a new configuration, it
-     * carries the external locale. Fix it.
-     */
-    public static void correctLocale(Resources res, Configuration config) {
-        Locale current = getCurrentLocale();
-        if (current == null) {
-            return;
-        }
-
-        // I know it's tempting to short-circuit here if the config seems to be
-        // up-to-date, but the rest is necessary.
-
-        config.locale = current;
-
-        // The following two lines are heavily commented in case someone
-        // decides to chase down performance improvements and decides to
-        // question what's going on here.
-        // Both lines should be cheap, *but*...
-
-        // This is unnecessary for basic string choice, but it almost
-        // certainly comes into play when rendering numbers, deciding on RTL,
-        // etc. Take it out if you can prove that's not the case.
-        Locale.setDefault(current);
-
-        // This seems to be a no-op, but every piece of documentation under the
-        // sun suggests that it's necessary, and it certainly makes sense.
-        res.updateConfiguration(config, res.getDisplayMetrics());
-    }
-
-    private static Locale parseLocaleCode(final String localeCode) {
-        int index;
-        if ((index = localeCode.indexOf('-')) != -1 ||
-            (index = localeCode.indexOf('_')) != -1) {
-            final String langCode = localeCode.substring(0, index);
-            final String countryCode = localeCode.substring(index + 1);
-            return new Locale(langCode, countryCode);
-        } else {
-            return new Locale(localeCode);
-        }
-    }
-
-    public static Locale getCurrentLocale() {
-        if (currentLocale != null) {
-            return currentLocale;
-        }
-
-        final String current = getPersistedLocale();
-        if (current == null) {
-            return null;
-        }
-        return currentLocale = parseLocaleCode(current);
-    }
-
-    /**
-     * Returns the persisted locale if it differed from the current.
-     */
-    public static String updateLocale(String localeCode) {
-        // Fast path.
-        final Locale defaultLocale = Locale.getDefault();
-        if (defaultLocale.toString().equals(localeCode)) {
-            return null;
-        }
-
-        final Locale locale = parseLocaleCode(localeCode);
-
-        // Fast path.
-        if (defaultLocale.equals(locale)) {
-            return null;
-        }
-
-        Locale.setDefault(locale);
-        currentLocale = locale;
-
-        // Update resources.
-        Resources res = getContext().getResources();
-        Configuration config = res.getConfiguration();
-        config.locale = locale;
-        res.updateConfiguration(config, res.getDisplayMetrics());
-
-        // Tell Gecko.
-        GeckoEvent ev = GeckoEvent.createBroadcastEvent("Locale:Changed", locale.toString());
-        GeckoAppShell.sendEventToGecko(ev);
-
-        return locale.toString();
-    }
-
-    private static String getPrefName() {
-        return getContext().getPackageName() + ".locale";
-    }
-
-    public static String getPersistedLocale() {
-        final SharedPreferences settings = getSharedPreferences();
-
-        // N.B., it is expected that any per-profile settings will be
-        // implemented via SharedPreferences multiplexing in ContextGetter, not
-        // via profile-annotated preference names.
-        final String locale = settings.getString(getPrefName(), "");
-
-        if ("".equals(locale)) {
-            return null;
-        }
-        return locale;
-    }
-
-    private static void persistLocale(String localeCode) {
-        final SharedPreferences settings = getSharedPreferences();
-        settings.edit().putString(getPrefName(), localeCode).commit();
-    }
-
-    public static String getAndApplyPersistedLocale() {
-        final long t1 = android.os.SystemClock.uptimeMillis();
-        final String localeCode = getPersistedLocale();
-        if (localeCode == null) {
-            return null;
-        }
-
-        updateLocale(localeCode);
-        final long t2 = android.os.SystemClock.uptimeMillis();
-        Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
-        return localeCode;
-    }
-
-    /**
-     * Returns the set locale if it changed. Always persists.
-     */
-    public static String setSelectedLocale(String localeCode) {
-        final String resultant = updateLocale(localeCode);
-        persistLocale(localeCode);
-        return resultant;
-    }
-}
-
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -670,17 +670,17 @@ public class Tab {
     }
 
     private static boolean shouldShowProgress(final String url) {
         return AboutPages.isAboutHome(url) ||
                AboutPages.isAboutReader(url);
     }
 
     void handleDocumentStart(boolean showProgress, String url) {
-        setState(shouldShowProgress(url) ? STATE_SUCCESS : STATE_LOADING);
+        setState(showProgress ? STATE_LOADING : STATE_SUCCESS);
         updateIdentityData(null);
         setReaderEnabled(false);
     }
 
     void handleDocumentStop(boolean success) {
         setState(success ? STATE_SUCCESS : STATE_ERROR);
 
         final String oldURL = getURL();
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -442,17 +442,17 @@ public class Tabs implements GeckoEventL
                 tab.setReaderEnabled(true);
                 notifyListeners(tab, TabEvents.READER_ENABLED);
             } else if (event.equals("Content:StateChange")) {
                 int state = message.getInt("state");
                 if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
                     if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
                         boolean showProgress = message.getBoolean("showProgress");
                         tab.handleDocumentStart(showProgress, message.getString("uri"));
-                        notifyListeners(tab, Tabs.TabEvents.START, showProgress);
+                        notifyListeners(tab, Tabs.TabEvents.START);
                     } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
                         tab.handleDocumentStop(message.getBoolean("success"));
                         notifyListeners(tab, Tabs.TabEvents.STOP);
                     }
                 }
             } else if (event.equals("Content:LoadError")) {
                 notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
             } else if (event.equals("Content:PageShow")) {
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -314,17 +314,16 @@ public class BrowserHealthRecorder imple
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
         this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this);
         this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
     }
 
     public void onAppLocaleChanged(String to) {
-        Log.d(LOG_TAG, "Setting health recorder app locale to " + to);
         this.profileCache.beginInitialization();
         this.profileCache.setAppLocale(to);
     }
 
     public void onAddonChanged(String id, JSONObject json) {
         this.profileCache.beginInitialization();
         try {
             this.profileCache.updateJSONForAddon(id, json);
@@ -345,29 +344,20 @@ public class BrowserHealthRecorder imple
     /**
      * Call this when a material change might have occurred in the running
      * environment, such that a new environment should be computed and prepared
      * for use in future events.
      *
      * Invoke this method after calls that mutate the environment.
      *
      * If this change resulted in a transition between two environments, {@link
-     * #onEnvironmentTransition(int, int, boolean, String)} will be invoked on the background
+     * #onEnvironmentTransition(int, int)} will be invoked on the background
      * thread.
      */
     public synchronized void onEnvironmentChanged() {
-        onEnvironmentChanged(true, "E");
-    }
-
-    /**
-     * If `startNewSession` is false, it means no new session should begin
-     * (e.g., because we're about to restart, and we don't want to create
-     * an orphan).
-     */
-    public synchronized void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) {
         final int previousEnv = this.env;
         this.env = -1;
         try {
             profileCache.completeInitialization();
         } catch (java.io.IOException e) {
             Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
             this.state = State.INITIALIZATION_FAILED;
             return;
@@ -379,17 +369,17 @@ public class BrowserHealthRecorder imple
             updatedEnv == previousEnv) {
             Log.v(LOG_TAG, "Environment didn't change.");
             return;
         }
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 try {
-                    onEnvironmentTransition(previousEnv, updatedEnv, startNewSession, sessionEndReason);
+                    onEnvironmentTransition(previousEnv, updatedEnv);
                 } catch (Exception e) {
                     Log.w(LOG_TAG, "Could not record environment transition.", e);
                 }
             }
         });
     }
 
     protected synchronized int ensureEnvironment() {
@@ -648,31 +638,26 @@ public class BrowserHealthRecorder imple
             }
         });
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
      */
-    protected void onEnvironmentTransition(int prev, int env, boolean startNewSession, String sessionEndReason) {
+    protected void onEnvironmentTransition(int prev, int env) {
         if (this.state != State.INITIALIZED) {
             Log.d(LOG_TAG, "Not initialized: not recording env transition (" + prev + " => " + env + ").");
             return;
         }
 
         final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
         final SharedPreferences.Editor editor = prefs.edit();
 
-        recordSessionEnd(sessionEndReason, editor, prev);
-
-        if (!startNewSession) {
-            editor.commit();
-            return;
-        }
+        recordSessionEnd("E", editor, prev);
 
         final SessionInformation newSession = SessionInformation.forRuntimeTransition();
         setCurrentSession(newSession);
         newSession.recordBegin(editor);
         editor.commit();
     }
 
     @Override
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -124,21 +124,16 @@ public class HomePager extends ViewPager
                 @Override
                 public void onPageScrollStateChanged(int state) { }
             });
         }
 
         super.addView(child, index, params);
     }
 
-    public void redisplay(FragmentManager fm) {
-        final TabsAdapter adapter = (TabsAdapter) getAdapter();
-        show(fm, adapter.getCurrentPage(), null);
-    }
-
     /**
      * Loads and initializes the pager.
      *
      * @param fm FragmentManager for the adapter
      */
     public void show(FragmentManager fm, Page page, PropertyAnimator animator) {
         mLoaded = true;
         final TabsAdapter adapter = new TabsAdapter(fm);
@@ -281,22 +276,16 @@ public class HomePager extends ViewPager
                 if (info.page == page) {
                     return i;
                 }
             }
 
             return -1;
         }
 
-        public Page getCurrentPage() {
-            int currentItem = getCurrentItem();
-            TabInfo info = mTabs.get(currentItem);
-            return info.page;
-        }
-
         @Override
         public int getCount() {
             return mTabs.size();
         }
 
         @Override
         public Fragment getItem(int position) {
             TabInfo info = mTabs.get(position);
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -90,17 +90,17 @@ public class TabMenuStrip extends Linear
                     }
 
                     mPrevProgress = position;
                 }
             });
         }
     }
 
-    // Page scroll animates the drawable and its bounds from the previous to next child view.
+    // Page scroll animates the drawable and it's bounds from the previous to next child view.
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
         if (mStrip == null) {
             return;
         }
 
         setScrollingData(position, positionOffset);
 
--- a/mobile/android/base/menu/GeckoMenu.java
+++ b/mobile/android/base/menu/GeckoMenu.java
@@ -5,17 +5,16 @@
 package org.mozilla.gecko.menu;
 
 import org.mozilla.gecko.R;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.ActionProvider;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
@@ -110,17 +109,17 @@ public class GeckoMenu extends ListView
         // Attach an adapter.
         mAdapter = new MenuItemsAdapter();
         setAdapter(mAdapter);
         setOnItemClickListener(this);
 
         mItems = new ArrayList<GeckoMenuItem>();
         mActionItems = new HashMap<GeckoMenuItem, View>();
 
-        mActionItemBarPresenter = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
+        mActionItemBarPresenter =  (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
     }
 
     @Override
     public MenuItem add(CharSequence title) {
         GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
         addItem(menuItem);
         return menuItem;
     }
@@ -215,36 +214,24 @@ public class GeckoMenu extends ListView
         ((GeckoMenuItem) menuItem).setSubMenu(subMenu);
         return subMenu;
     }
 
     @Override
     public void clear() {
         for (GeckoMenuItem menuItem : mItems) {
             if (menuItem.hasSubMenu()) {
-                SubMenu sub = menuItem.getSubMenu();
-                if (sub == null) {
-                    continue;
-                }
-                try {
-                    sub.clear();
-                } catch (Exception ex) {
-                    Log.e(LOGTAG, "Couldn't clear submenu.", ex);
-                }
+                menuItem.getSubMenu().clear();
             }
         }
 
         mAdapter.clear();
+
         mItems.clear();
 
-        /*
-         * Reinflating the menu will re-add any action items to the toolbar, so
-         * remove the old ones. This also ensures that any text associated with
-         * these is switched to the correct locale.
-         */
         if (mActionItemBarPresenter != null) {
             for (View item : mActionItems.values()) {
                 mActionItemBarPresenter.removeActionItem(item);
             }
         }
         mActionItems.clear();
     }
 
--- a/mobile/android/base/menu/GeckoMenuItem.java
+++ b/mobile/android/base/menu/GeckoMenuItem.java
@@ -137,21 +137,17 @@ public class GeckoMenuItem implements Me
 
     @Override
     public int getOrder() {
         return mOrder;
     }
 
     @Override
     public SubMenu getSubMenu() {
-        // For consistency with hasSubMenu.
-        if (mActionProvider == null) {
-            return mSubMenu;
-        }
-        return null;
+        return mSubMenu;
     }
 
     @Override
     public CharSequence getTitle() {
         return mTitle;
     }
 
     @Override
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -227,17 +227,16 @@ gbjar.sources += [
     'home/TopSitesGridView.java',
     'home/TopSitesPage.java',
     'home/TopSitesThumbnailView.java',
     'home/TwoLinePageRow.java',
     'InputMethods.java',
     'JavaAddonManager.java',
     'LightweightTheme.java',
     'LightweightThemeDrawable.java',
-    'LocaleManager.java',
     'MemoryMonitor.java',
     'menu/GeckoMenu.java',
     'menu/GeckoMenuInflater.java',
     'menu/GeckoMenuItem.java',
     'menu/GeckoSubMenu.java',
     'menu/MenuItemActionBar.java',
     'menu/MenuItemActionView.java',
     'menu/MenuItemDefault.java',
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -99,16 +99,17 @@
                                                 android:layout_width="fill_parent"
                                                 android:layout_height="fill_parent"
                                                 android:layout_weight="1.0"
                                                 android:singleLine="true"
                                                 android:paddingRight="8dp"
                                                 android:textColor="@color/url_bar_title"
                                                 android:textColorHint="@color/url_bar_title_hint"
                                                 android:gravity="center_vertical|left"
+                                                android:hint="@string/url_bar_default_text"
                                                 android:layout_gravity="center_vertical"
                                                 gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
                                                     android:layout_width="wrap_content"
                                                     android:layout_height="match_parent"
                                                     android:layout_marginRight="@dimen/browser_toolbar_button_padding"
                                                     android:visibility="gone"
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -561,18 +561,17 @@ public class BrowserToolbar extends Geck
             switch (msg) {
                 case TITLE:
                     updateTitle();
                     break;
 
                 case START:
                     updateBackButton(canDoBack(tab));
                     updateForwardButton(canDoForward(tab));
-                    Boolean showProgress = (Boolean)data;
-                    if (showProgress && tab.getState() == Tab.STATE_LOADING) {
+                    if (tab.getState() == Tab.STATE_LOADING) {
                         setProgressVisibility(true);
                     }
                     setSecurityMode(tab.getSecurityMode());
                     setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
                     break;
 
                 case STOP:
                     updateBackButton(canDoBack(tab));
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -269,17 +269,16 @@ var BrowserApp = {
     dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
 
     this.deck = document.getElementById("browsers");
     BrowserEventHandler.init();
     ViewportHandler.init();
 
     Services.androidBridge.browserApp = this;
 
-    Services.obs.addObserver(this, "Locale:Changed", false);
     Services.obs.addObserver(this, "Tab:Load", false);
     Services.obs.addObserver(this, "Tab:Selected", false);
     Services.obs.addObserver(this, "Tab:Closed", false);
     Services.obs.addObserver(this, "Session:Back", false);
     Services.obs.addObserver(this, "Session:ShowHistory", false);
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
     Services.obs.addObserver(this, "Session:Stop", false);
@@ -406,24 +405,16 @@ var BrowserApp = {
 #expand    let ourmstone = "__MOZ_APP_VERSION__";
     if (ourmstone != savedmstone) {
       Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourmstone);
       return savedmstone ? "upgrade" : "new";
     }
     return "";
   },
 
-  /**
-   * Pass this a locale string, such as "fr" or "es_ES".
-   */
-  setLocale: function (locale) {
-    console.log("browser.js: requesting locale set: " + locale);
-    sendMessageToJava({ type: "Locale:Set", locale: locale });
-  },
-
   initContextMenu: function ba_initContextMenu() {
     // TODO: These should eventually move into more appropriate classes
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"),
       NativeWindow.contextmenus.linkOpenableNonPrivateContext,
       function(aTarget) {
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
         BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
 
@@ -787,17 +778,16 @@ var BrowserApp = {
     let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
     let postData = ("postData" in aParams && aParams.postData) ? aParams.postData : null;
     let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
     let charset = "charset" in aParams ? aParams.charset : null;
 
     let tab = this.getTabForBrowser(aBrowser);
     if (tab) {
       if (!tab.aboutHomePage) tab.aboutHomePage = ("aboutHomePage" in aParams) ? aParams.aboutHomePage : "";
-      if ("showProgress" in aParams) tab.showProgress = aParams.showProgress;
       if ("userSearch" in aParams) tab.userSearch = aParams.userSearch;
     }
 
     try {
       aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
     } catch(e) {
       if (tab) {
         let message = {
@@ -1398,20 +1388,16 @@ var BrowserApp = {
           if (engine) {
             params.userSearch = url;
             let submission = engine.getSubmission(url);
             url = submission.uri.spec;
             params.postData = submission.postData;
           }
         }
 
-        // Don't show progress throbber for about:home or about:reader
-        if (!shouldShowProgress(url))
-          params.showProgress = false;
-
         if (data.newTab) {
           this.addTab(url, params);
         } else {
           if (data.tabId) {
             // Use a specific browser instead of the selected browser, if it exists
             let specificBrowser = this.getTabForId(data.tabId).browser;
             if (specificBrowser)
               browser = specificBrowser;
@@ -1505,23 +1491,16 @@ var BrowserApp = {
         gViewportMargins = JSON.parse(aData);
         this.selectedTab.updateViewportSize(gScreenWidth);
         break;
 
       case "nsPref:changed":
         this.notifyPrefObservers(aData);
         break;
 
-      case "Locale:Changed":
-        // TODO: do we need to be more nuanced here -- e.g., checking for the
-        // OS locale -- or should it always be false on Fennec?
-        Services.prefs.setBoolPref("intl.locale.matchOS", false);
-        Services.prefs.setCharPref("general.useragent.locale", aData);
-        break;
-
       default:
         dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
         break;
 
     }
   },
 
   get defaultBrowserWidth() {
@@ -2595,17 +2574,16 @@ let gReflowPending = null;
 // fixed position content, and also to make sure window-sized pages take
 // into account said browser chrome.
 let gViewportMargins = { top: 0, right: 0, bottom: 0, left: 0};
 
 function Tab(aURL, aParams) {
   this.browser = null;
   this.id = 0;
   this.lastTouchedAt = Date.now();
-  this.showProgress = true;
   this._zoom = 1.0;
   this._drawZoom = 1.0;
   this._fixedMarginLeft = 0;
   this._fixedMarginTop = 0;
   this._fixedMarginRight = 0;
   this._fixedMarginBottom = 0;
   this._readerEnabled = false;
   this._readerActive = false;
@@ -2751,19 +2729,16 @@ Tab.prototype = {
       };
       this.browser.__SS_restore = true;
     } else {
       let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
       let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
       let charset = "charset" in aParams ? aParams.charset : null;
 
-      // This determines whether or not we show the progress throbber in the urlbar
-      this.showProgress = "showProgress" in aParams ? aParams.showProgress : true;
-
       // The search term the user entered to load the current URL
       this.userSearch = "userSearch" in aParams ? aParams.userSearch : "";
 
       try {
         this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData);
       } catch(e) {
         let message = {
           type: "Content:LoadError",
@@ -3733,47 +3708,44 @@ Tab.prototype = {
             visible: false
           };
 
           sendMessageToJava(newEngineMessage);
 
           this.browser.feeds = null;
       }
 
-      // Check to see if we restoring the content from a previous presentation (session)
-      // since there should be no real network activity
-      let restoring = aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING;
-      let showProgress = restoring ? false : this.showProgress;
-
       // true if the page loaded successfully (i.e., no 404s or other errors)
       let success = false; 
       let uri = "";
       try {
         // Remember original URI for UA changes on redirected pages
         this.originalURI = aRequest.QueryInterface(Components.interfaces.nsIChannel).originalURI;
 
         if (this.originalURI != null)
           uri = this.originalURI.spec;
       } catch (e) { }
       try {
         success = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel).requestSucceeded;
       } catch (e) { }
 
+      // Check to see if we restoring the content from a previous presentation (session)
+      // since there should be no real network activity
+      let restoring = aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING;
+      let showProgress = restoring ? false : shouldShowProgress(uri);
+
       let message = {
         type: "Content:StateChange",
         tabID: this.id,
         uri: uri,
         state: aStateFlags,
         showProgress: showProgress,
         success: success
       };
       sendMessageToJava(message);
-
-      // Reset showProgress after state change
-      this.showProgress = true;
     }
   },
 
   onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) {
     let contentWin = aWebProgress.DOMWindow;
 
     // Browser webapps may load content inside iframes that can not reach across the app/frame boundary
     // i.e. even though the page is loaded in an iframe window.top != webapp
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -173,17 +173,16 @@ SessionStore.prototype = {
           // Be ready to handle any restore failures by making sure we have a valid tab opened
           let window = Services.wm.getMostRecentWindow("navigator:browser");
           let restoreCleanup = {
             observe: function (aSubject, aTopic, aData) {
               Services.obs.removeObserver(restoreCleanup, "sessionstore-windows-restored");
 
               if (window.BrowserApp.tabs.length == 0) {
                 window.BrowserApp.addTab("about:home", {
-                  showProgress: false,
                   selected: true
                 });
               }
 
               // Let Java know we're done restoring tabs so tabs added after this can be animated
               this._sendMessageToJava({
                 type: "Session:RestoreEnd"
               });
--- a/mobile/android/installer/Makefile.in
+++ b/mobile/android/installer/Makefile.in
@@ -47,15 +47,15 @@ endif
 
 ifdef MOZ_PKG_MANIFEST_P
 $(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) $(GLOBAL_DEPS)
 	$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) $< -o $@)
 ifdef MOZ_CHROME_MULTILOCALE
 	printf '\n[multilocale]\n' >> $@
 	for LOCALE in en-US $(MOZ_CHROME_MULTILOCALE) ;\
 	do \
-	  printf '$(BINPATH)/chrome/$$LOCALE$(JAREXT)\n' >> $@; \
-	  printf '$(BINPATH)/chrome/$$LOCALE.manifest\n' >> $@; \
+	  printf '$(BINPATH)/chrome/'"$$LOCALE"'$(JAREXT)\n' >> $@; \
+	  printf '$(BINPATH)/chrome/'"$$LOCALE"'.manifest\n' >> $@; \
 	done
 endif
 
 GARBAGE += $(MOZ_PKG_MANIFEST)
 endif
--- a/security/build/Makefile.in
+++ b/security/build/Makefile.in
@@ -375,27 +375,27 @@ DEFFILE = nss3.def
 
 nss3.def: $(NSS_STATIC_LIBS_DEFS) $(DEPTH)/db/sqlite3/src/sqlite-processed.def
 	echo LIBRARY nss3$(DLL_SUFFIX) > $@.tmp
 	echo EXPORTS >> $@.tmp
 	grep -v -h -e ^LIBRARY -e ^EXPORTS -e ^\; $^ >> $@.tmp
 	mv $@.tmp $@
 endif
 
-endif # MOZ_FOLD_LIBS
-
-include $(topsrcdir)/config/rules.mk
-
-ifdef MOZ_FOLD_LIBS
 ifneq (,$(filter OS2 WINNT,$(OS_ARCH)))
 SDK_LIBRARY = $(IMPORT_LIBRARY)
 else
 SDK_LIBRARY = $(SHARED_LIBRARY)
 endif
 
+endif # MOZ_FOLD_LIBS
+
+include $(topsrcdir)/config/rules.mk
+
+ifdef MOZ_FOLD_LIBS
 # Force the linker to include everything from the static libraries.
 EXPAND_LIBS_EXEC += --extract
 
 $(SHARED_LIBRARY): $(SHARED_LIBRARY_LIBS)
 
 EXTRA_DSO_LDOPTS += $(REALTIME_LIBS)
 
 ifdef IMPORT_LIB_SUFFIX
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -20,16 +20,18 @@ this.EXPORTED_SYMBOLS = [
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+                                  "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
                                   "resource://gre/modules/DownloadStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
                                   "resource://gre/modules/DownloadImport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
@@ -1093,16 +1095,17 @@ this.DownloadHistoryObserver = function 
  * @param aStore
  *        The DownloadStore object used for saving.
  */
 this.DownloadAutoSaveView = function (aList, aStore)
 {
   this._list = aList;
   this._store = aStore;
   this._downloadsMap = new Map();
+  this._writer = new DeferredTask(() => this._store.save(), kSaveDelayMs);
 }
 
 this.DownloadAutoSaveView.prototype = {
   /**
    * DownloadList object linked to this view.
    */
   _list: null,
 
@@ -1133,67 +1136,27 @@ this.DownloadAutoSaveView.prototype = {
   /**
    * This map contains only Download objects that should be saved to disk, and
    * associates them with the result of their getSerializationHash function, for
    * the purpose of detecting changes to the relevant properties.
    */
   _downloadsMap: null,
 
   /**
-   * This is set to true when the save operation should be triggered.  This is
-   * required so that a new operation can be scheduled while the current one is
-   * in progress, without re-entering the save method.
-   */
-  _shouldSave: false,
-
-  /**
-   * nsITimer used for triggering the save operation after a delay, or null if
-   * saving has finished and there is no operation scheduled for execution.
-   *
-   * The logic here is different from the DeferredTask module in that multiple
-   * requests will never delay the operation for longer than the expected time
-   * (no grace delay), and the operation is never re-entered during execution.
-   */
-  _timer: null,
-
-  /**
-   * Timer callback used to serialize the list of downloads.
+   * DeferredTask for the save operation.
    */
-  _save: function ()
-  {
-    Task.spawn(function () {
-      // Any save request received during execution will be handled later.
-      this._shouldSave = false;
-
-      // Execute the asynchronous save operation.
-      try {
-        yield this._store.save();
-      } catch (ex) {
-        Cu.reportError(ex);
-      }
-
-      // Handle requests received during the operation.
-      this._timer = null;
-      if (this._shouldSave) {
-        this.saveSoon();
-      }
-    }.bind(this)).then(null, Cu.reportError);
-  },
+  _writer: null,
 
   /**
    * Called when the list of downloads changed, this triggers the asynchronous
    * serialization of the list of downloads.
    */
   saveSoon: function ()
   {
-    this._shouldSave = true;
-    if (!this._timer) {
-      this._timer = new Timer(this._save.bind(this), kSaveDelayMs,
-                              Ci.nsITimer.TYPE_ONE_SHOT);
-    }
+    this._writer.arm();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// DownloadList view
 
   onDownloadAdded: function (aDownload)
   {
     if (DownloadIntegration.shouldPersistDownload(aDownload)) {
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -22,16 +22,17 @@
 this.EXPORTED_SYMBOLS = ["OS"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 let SharedAll = {};
 Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
 Cu.import("resource://gre/modules/Deprecated.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
 
 // Boilerplate, to simplify the transition to require()
 let LOG = SharedAll.LOG.bind(SharedAll, "Controller");
 let isTypedArray = SharedAll.isTypedArray;
 
 // The constructor for file errors.
 let SysAll = {};
 if (SharedAll.Constants.Win) {
@@ -112,16 +113,36 @@ let Scheduler = {
    */
   shutdown: false,
 
   /**
    * The latest promise returned.
    */
   latestPromise: Promise.resolve("OS.File scheduler hasn't been launched yet"),
 
+  /**
+   * A timer used to automatically shut down the worker after some time.
+   */
+  resetTimer: null,
+
+  restartTimer: function(arg) {
+    let delay;
+    try {
+      delay = Services.prefs.getIntPref("osfile.reset_worker_delay");
+    } catch(e) {
+      // Don't auto-shutdown if we don't have a delay preference set.
+      return;
+    }
+
+    if (this.resetTimer) {
+      clearTimeout(this.resetTimer);
+    }
+    this.resetTimer = setTimeout(File.resetWorker, delay);
+  },
+
   post: function post(...args) {
     if (this.shutdown) {
       LOG("OS.File is not available anymore. The following request has been rejected.", args);
       return Promise.reject(new Error("OS.File has been shut down."));
     }
     if (!worker) {
       // Either the worker has never been created or it has been reset
       worker = new PromiseWorker(
@@ -134,16 +155,22 @@ let Scheduler = {
     this.launched = true;
 
     // By convention, the last argument of any message may be an |options| object.
     let methodArgs = args[1];
     let options = methodArgs ? methodArgs[methodArgs.length - 1] : null;
     let promise = worker.post.apply(worker, args);
     return this.latestPromise = promise.then(
       function onSuccess(data) {
+        // Don't restart the timer when reseting the worker, since that will
+        // lead to an endless "resetWorker()" loop.
+        if (args[0] != "Meta_reset") {
+          Scheduler.restartTimer();
+        }
+
         // Check for duration and return result.
         if (!options) {
           return data.ok;
         }
         // Check for options.outExecutionDuration.
         if (typeof options !== "object" ||
           !("outExecutionDuration" in options)) {
           return data.ok;
@@ -162,16 +189,22 @@ let Scheduler = {
         if (typeof options.outExecutionDuration == "number") {
           options.outExecutionDuration += durationMs;
         } else {
           options.outExecutionDuration = durationMs;
         }
         return data.ok;
       },
       function onError(error) {
+        // Don't restart the timer when reseting the worker, since that will
+        // lead to an endless "resetWorker()" loop.
+        if (args[0] != "Meta_reset") {
+          Scheduler.restartTimer();
+        }
+
         // Decode any serialized error
         if (error instanceof PromiseWorker.WorkerError) {
           throw OS.File.Error.fromMsg(error.data);
         }
         // Extract something meaningful from ErrorEvent
         if (error instanceof ErrorEvent) {
           let message = error.message;
           if (message == "uncaught exception: [object StopIteration]") {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -6,16 +6,18 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+  "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
@@ -2650,17 +2652,17 @@ Engine.prototype = {
     var url = this._getURLOfType(aResponseType);
     if (!url)
       FAIL("Engine object has no URL for response type " + aResponseType,
            Cr.NS_ERROR_FAILURE);
 
     url.addParam(aName, aValue);
 
     // Serialize the changes to file lazily
-    this.lazySerializeTask.start();
+    this.lazySerializeTask.arm();
   },
 
 #ifdef ANDROID
   get _defaultMobileResponseType() {
     let type = URLTYPE_SEARCH_HTML;
 
     let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
     let isTablet = sysInfo.get("tablet");
@@ -3866,17 +3868,18 @@ SearchService.prototype = {
       FAIL("Invalid template passed to addEngineWithDetails!");
     if (this._engines[aName])
       FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
 
     var engine = new Engine(getSanitizedFile(aName), SEARCH_DATA_XML, false);
     engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
                              aMethod, aTemplate);
     this._addEngineToStore(engine);
-    this.batchTask.start();
+    this.batchTask.disarm();
+    this.batchTask.arm();
   },
 
   addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
                                          aConfirm, aCallback) {
     LOG("addEngine: Adding \"" + aEngineURL + "\".");
     this._ensureInitialized();
     try {
       var uri = makeURI(aEngineURL);
@@ -3929,19 +3932,20 @@ SearchService.prototype = {
     }
 
     if (engineToRemove._readOnly) {
       // Just hide it (the "hidden" setter will notify) and remove its alias to
       // avoid future conflicts with other engines.
       engineToRemove.hidden = true;
       engineToRemove.alias = null;
     } else {
-      // Cancel the serialized task if it's running
+      // Cancel the serialized task if it's pending.  Since the task is a
+      // synchronous function, we don't need to wait on the "finalize" method.
       if (engineToRemove._lazySerializeTask) {
-        engineToRemove._lazySerializeTask.cancel();
+        engineToRemove._lazySerializeTask.disarm();
         engineToRemove._lazySerializeTask = null;
       }
 
       // Remove the engine file from disk (this might throw)
       engineToRemove._remove();
       engineToRemove._file = null;
 
       // Remove the engine from _sortedEngines
@@ -4130,32 +4134,29 @@ SearchService.prototype = {
             var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
             LOG("nsSearchService::observe: Done installation of " + engine.name
                 + ".");
             this._addEngineToStore(engine.wrappedJSObject);
             if (engine.wrappedJSObject._useNow) {
               LOG("nsSearchService::observe: setting current");
               this.currentEngine = aEngine;
             }
-            this.batchTask.start();
+            this.batchTask.disarm();
+            this.batchTask.arm();
             break;
           case SEARCH_ENGINE_CHANGED:
           case SEARCH_ENGINE_REMOVED:
-            this.batchTask.start();
+            this.batchTask.disarm();
+            this.batchTask.arm();
             break;
         }
         break;
 
       case QUIT_APPLICATION_TOPIC:
         this._removeObservers();
-        if (this._batchTask) {
-          // Flush to disk immediately
-          this._batchTask.flush();
-        }
-        engineMetadataService.flush();
         break;
 
       case "nsPref:changed":
         let currPref = BROWSER_SEARCH_PREF + "selectedEngine";
         let defPref = BROWSER_SEARCH_PREF + "defaultenginename";
         if (aVerb == currPref && !this._changingCurrentEngine) {
           this._setEngineByPref("currentEngine", currPref);
         } else if (aVerb == defPref && !this._changingDefaultEngine) {
@@ -4204,16 +4205,26 @@ SearchService.prototype = {
     } // end engine iteration
   },
 
   _addObservers: function SRCH_SVC_addObservers() {
     Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, false);
     Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "defaultenginename", this, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "selectedEngine", this, false);
+
+    AsyncShutdown.profileBeforeChange.addBlocker(
+      "Search service: shutting down",
+      () => Task.spawn(function () {
+        if (this._batchTask) {
+          yield this._batchTask.finalize().then(null, Cu.reportError);
+        }
+        yield engineMetadataService.finalize();
+      }.bind(this))
+    );
   },
 
   _removeObservers: function SRCH_SVC_removeObservers() {
     Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
     Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "defaultenginename", this);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "selectedEngine", this);
   },
@@ -4423,21 +4434,18 @@ var engineMetadataService = {
     if (changed) {
       this._commit();
     }
   },
 
   /**
    * Flush any waiting write.
    */
-  flush: function epsFlush() {
-    if (this._lazyWriter) {
-      this._lazyWriter.flush();
-    }
-  },
+  finalize: function () this._lazyWriter ? this._lazyWriter.finalize()
+                                         : Promise.resolve(),
 
   /**
    * Commit changes to disk, asynchronously.
    *
    * Calls to this function are actually delayed by LAZY_SERIALIZE_DELAY
    * (= 100ms). If the function is called again before the expiration of
    * the delay, commits are merged and the function is again delayed by
    * the same amount of time.
@@ -4464,22 +4472,24 @@ var engineMetadataService = {
         promise = promise.then(
           function onSuccess() {
             Services.obs.notifyObservers(null,
               SEARCH_SERVICE_TOPIC,
               SEARCH_SERVICE_METADATA_WRITTEN);
             LOG("metadata writeCommit: done");
           }
         );
-        TaskUtils.captureErrors(promise);
+        // Use our error logging instead of the default one.
+        return TaskUtils.captureErrors(promise).then(null, () => {});
       }
       this._lazyWriter = new DeferredTask(writeCommit, LAZY_SERIALIZE_DELAY);
     }
     LOG("metadata _commit: (re)setting timer");
-    this._lazyWriter.start();
+    this._lazyWriter.disarm();
+    this._lazyWriter.arm();
   },
   _lazyWriter: null
 };
 
 engineMetadataService._initState = engineMetadataService._InitStates.NOT_STARTED;
 
 const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
 
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -182,26 +182,27 @@ let ActiveProviders = {
   },
 
   has: function (origin) {
     return (origin in this._providers);
   },
 
   add: function (origin) {
     this._providers[origin] = 1;
-    this._deferredTask.start();
+    this._deferredTask.arm();
   },
 
   delete: function (origin) {
     delete this._providers[origin];
-    this._deferredTask.start();
+    this._deferredTask.arm();
   },
 
   flush: function () {
-    this._deferredTask.flush();
+    this._deferredTask.disarm();
+    this._persist();
   },
 
   get _deferredTask() {
     delete this._deferredTask;
     return this._deferredTask = new DeferredTask(this._persist.bind(this), 0);
   },
 
   _persist: function () {
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/head.js
@@ -0,0 +1,23 @@
+"use strict";
+
+function waitForCondition(condition, nextTest, errorMsg) {
+  var tries = 0;
+  var interval = setInterval(function() {
+    if (tries >= 30) {
+      ok(false, errorMsg);
+      moveOn();
+    }
+    var conditionPassed;
+    try {
+      conditionPassed = condition();
+    } catch (e) {
+      ok(false, e + "\n" + e.stack);
+      conditionPassed = false;
+    }
+    if (conditionPassed) {
+      moveOn();
+    }
+    tries++;
+  }, 100);
+  var moveOn = function() { clearInterval(interval); nextTest(); };
+}
--- a/toolkit/content/tests/widgets/mochitest.ini
+++ b/toolkit/content/tests/widgets/mochitest.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
+  head.js
   tree_shared.js
   videocontrols_direction-1-ref.html
   videocontrols_direction-1a.html
   videocontrols_direction-1b.html
   videocontrols_direction-1c.html
   videocontrols_direction-1d.html
   videocontrols_direction-1e.html
   videocontrols_direction-2-ref.html
--- a/toolkit/content/tests/widgets/test_videocontrols_standalone.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_standalone.html
@@ -1,14 +1,15 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Video controls test</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
@@ -18,58 +19,72 @@ const videoHeight = 240;
 function getMediaElement(aWindow) {
   return aWindow.document.getElementsByTagName("video")[0];
 }
 
 var popup = window.open("seek_with_sound.ogg");
 popup.addEventListener("load", function onLoad() {
   popup.removeEventListener("load", onLoad);
   var video = getMediaElement(popup);
-  if (video.readyState >= video.HAVE_METADATA)
+  if (!video.paused)
     runTestVideo(video);
   else {
-    video.addEventListener("loadedmetadata", function onLoadedMetaData() {
-      video.removeEventListener("loadedmetadata", onLoadedMetaData);
+    video.addEventListener("play", function onPlay() {
+      video.removeEventListener("play", onPlay);
       runTestVideo(video);
     });
   }
 });
 
 function runTestVideo(aVideo) {
-  var boundingRect = aVideo.getBoundingClientRect();
-  is(boundingRect.width, videoWidth, "Width of the video should match expectation");
-  is(boundingRect.height, videoHeight, "Height of video should match expectation");
-  popup.close();
-  runTestAudioPre();
+  var condition = function() {
+    var boundingRect = aVideo.getBoundingClientRect();
+    return boundingRect.width == videoWidth &&
+           boundingRect.height == videoHeight;
+  };
+  waitForCondition(condition, function() {
+    var boundingRect = aVideo.getBoundingClientRect();
+    is(boundingRect.width, videoWidth, "Width of the video should match expectation");
+    is(boundingRect.height, videoHeight, "Height of video should match expectation");
+    popup.close();
+    runTestAudioPre();
+  }, "The media element should eventually be resized to match the intrinsic size of the video.");
 }
 
 function runTestAudioPre() {
   popup = window.open("audio.ogg");
   popup.addEventListener("load", function onLoad() {
     popup.removeEventListener("load", onLoad);
     var audio = getMediaElement(popup);
-    if (audio.readyState >= audio.HAVE_METADATA)
+    if (!audio.paused)
       runTestAudio(audio);
     else {
-      audio.addEventListener("loadedmetadata", function onLoadedMetaData() {
-        audio.removeEventListener("loadedmetadata", onLoadedMetaData);
+      audio.addEventListener("play", function onPlay() {
+        audio.removeEventListener("play", onPlay);
         runTestAudio(audio);
       })
     }
   })
 }
 
 function runTestAudio(aAudio) {
-  var boundingRect = aAudio.getBoundingClientRect();
-  var isAndroid = (navigator.userAgent.indexOf("Android") !== -1);
+  info("User agent (help diagnose bug #943556): " + navigator.userAgent);
+  var isAndroid = navigator.userAgent.contains("Android");
   var expectedHeight = isAndroid ? 123 : 28;
-  is(boundingRect.height, expectedHeight,
-     "Height of audio element should be " + expectedHeight + ", which is equal to the controls bar.");
-  popup.close();
-  SimpleTest.finish();
+  var condition = function () {
+    var boundingRect = aAudio.getBoundingClientRect();
+    return boundingRect.height == expectedHeight;
+  };
+  waitForCondition(condition, function () {
+    var boundingRect = aAudio.getBoundingClientRect();
+    is(boundingRect.height, expectedHeight,
+       "Height of audio element should be " + expectedHeight + ", which is equal to the controls bar.");
+    popup.close();
+    SimpleTest.finish();
+  }, "The media element should eventually be resized to match the height of the audio controls.");
 }
 
 SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -161,8 +161,53 @@ function defineLazyPrototypeGetter(aObje
         writable: true,
         value: value
       });
 
       return value;
     }
   });
 }
+
+/**
+ * Safely get the property value from a Debugger.Object for a given key. Walks
+ * the prototype chain until the property is found.
+ *
+ * @param Debugger.Object aObject
+ *        The Debugger.Object to get the value from.
+ * @param String aKey
+ *        The key to look for.
+ * @return Any
+ */
+this.getProperty = function getProperty(aObj, aKey) {
+  let root = aObj;
+  try {
+    do {
+      const desc = aObj.getOwnPropertyDescriptor(aKey);
+      if (desc) {
+        if ("value" in desc) {
+          return desc.value;
+        }
+        // Call the getter if it's safe.
+        return hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
+      }
+      aObj = aObj.proto;
+    } while (aObj);
+  } catch (e) {
+    // If anything goes wrong report the error and return undefined.
+    reportException("getProperty", e);
+  }
+  return undefined;
+};
+
+/**
+ * Determines if a descriptor has a getter which doesn't call into JavaScript.
+ *
+ * @param Object aDesc
+ *        The descriptor to check for a safe getter.
+ * @return Boolean
+ *         Whether a safe getter was found.
+ */
+this.hasSafeGetter = function hasSafeGetter(aDesc) {
+  let fn = aDesc.get;
+  return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
+};
+
--- a/toolkit/devtools/DevToolsUtils.jsm
+++ b/toolkit/devtools/DevToolsUtils.jsm
@@ -19,10 +19,12 @@ Components.classes["@mozilla.org/moz/jss
   .loadSubScript("resource://gre/modules/devtools/DevToolsUtils.js", this);
 
 this.DevToolsUtils = {
   safeErrorString: safeErrorString,
   reportException: reportException,
   makeInfallible: makeInfallible,
   yieldingEach: yieldingEach,
   reportingDisabled: false , // Used by tests.
-  defineLazyPrototypeGetter: defineLazyPrototypeGetter
+  defineLazyPrototypeGetter: defineLazyPrototypeGetter,
+  getProperty: getProperty,
+  hasSafeGetter: hasSafeGetter,
 };
--- a/toolkit/devtools/output-parser.js
+++ b/toolkit/devtools/output-parser.js
@@ -299,17 +299,19 @@ OutputParser.prototype = {
         this._appendNode("span", {
           class: options.colorSwatchClass,
           style: "background-color:" + color
         });
       }
       if (options.defaultColorType) {
         color = colorObj.toString();
       }
-      this._appendTextNode(color);
+      this._appendNode("span", {
+        class: options.colorClass
+      }, color);
       return true;
     }
     return false;
   },
 
    /**
     * Append a URL to the output.
     *
@@ -357,17 +359,19 @@ OutputParser.prototype = {
    */
   _appendNode: function(tagName, attributes, value="") {
     let win = Services.appShell.hiddenDOMWindow;
     let doc = win.document;
     let node = doc.createElementNS(HTML_NS, tagName);
     let attrs = Object.getOwnPropertyNames(attributes);
 
     for (let attr of attrs) {
-      node.setAttribute(attr, attributes[attr]);
+      if (attributes[attr]) {
+        node.setAttribute(attr, attributes[attr]);
+      }
     }
 
     if (value) {
       let textNode = doc.createTextNode(value);
       node.appendChild(textNode);
     }
 
     this.parsed.push(node);
@@ -417,16 +421,18 @@ OutputParser.prototype = {
    *
    * @param  {Object} overrides
    *         The option values to override e.g. _mergeOptions({colors: false})
    *
    *         Valid options are:
    *           - defaultColorType: true // Convert colors to the default type
    *                                    // selected in the options panel.
    *           - colorSwatchClass: ""   // The class to use for color swatches.
+   *           - colorClass: ""         // The class to use for the color value
+   *                                    // that follows the swatch.
    *           - isHTMLAttribute: false // This property indicates whether we
    *                                    // are parsing an HTML attribute value.
    *                                    // When the value is passed in from an
    *                                    // HTML attribute we need to check that
    *                                    // any CSS property values are supported
    *                                    // by the property name before
    *                                    // processing the property value.
    *           - urlClass: ""           // The class to be used for url() links.
@@ -434,16 +440,17 @@ OutputParser.prototype = {
    *                                    // relative links.
    * @return {Object}
    *         Overridden options object
    */
   _mergeOptions: function(overrides) {
     let defaults = {
       defaultColorType: true,
       colorSwatchClass: "",
+      colorClass: "",
       isHTMLAttribute: false,
       urlClass: "",
       baseURI: ""
     };
 
     if (typeof overrides.baseURI === "string") {
       overrides.baseURI = Services.io.newURI(overrides.baseURI, null, null);
     }
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2656,59 +2656,16 @@ SourceActor.prototype.requestTypes = {
  *         Whether the value is non-primitive.
  */
 function isObject(aValue) {
   const type = typeof aValue;
   return type == "object" ? aValue !== null : type == "function";
 }
 
 /**
- * Determines if a descriptor has a getter which doesn't call into JavaScript.
- *
- * @param Object aDesc
- *        The descriptor to check for a safe getter.
- * @return Boolean
- *         Whether a safe getter was found.
- */
-function hasSafeGetter(aDesc) {
-  let fn = aDesc.get;
-  return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
-}
-
-/**
- * Safely get the property value from a Debugger.Object for a given key. Walks
- * the prototype chain until the property is found.
- *
- * @param Debugger.Object aObject
- *        The Debugger.Object to get the value from.
- * @param String aKey
- *        The key to look for.
- * @return Any
- */
-function getProperty(aObj, aKey) {
-  try {
-    do {
-      const desc = aObj.getOwnPropertyDescriptor(aKey);
-      if (desc) {
-        if ("value" in desc) {
-          return desc.value
-        }
-        // Call the getter if it's safe.
-        return hasSafeGetter(desc) ? desc.get.call(aObj) : undefined;
-      }
-      aObj = aObj.proto;
-    } while (aObj);
-  } catch (e) {
-    // If anything goes wrong report the error and return undefined.
-    DevToolsUtils.reportException("getProperty", e);
-  }
-  return undefined;
-}
-
-/**
  * Create a function that can safely stringify Debugger.Objects of a given
  * builtin type.
  *
  * @param Function aCtor
  *        The builtin class constructor.
  * @return Function
  *         The stringifier for the class.
  */
@@ -2720,24 +2677,24 @@ function createBuiltinStringifier(aCtor)
  * Stringify a Debugger.Object-wrapped Error instance.
  *
  * @param Debugger.Object aObj
  *        The object to stringify.
  * @return String
  *         The stringification of the object.
  */
 function errorStringify(aObj) {
-  let name = getProperty(aObj, "name");
+  let name = DevToolsUtils.getProperty(aObj, "name");
   if (name === "" || name === undefined) {
     name = aObj.class;
   } else if (isObject(name)) {
     name = stringify(name);
   }
 
-  let message = getProperty(aObj, "message");
+  let message = DevToolsUtils.getProperty(aObj, "message");
   if (isObject(message)) {
     message = stringify(message);
   }
 
   if (message === "" || message === undefined) {
     return name;
   }
   return name + ": " + message;
@@ -2785,17 +2742,17 @@ let stringifiers = {
     if (topLevel) {
       seen = new Set();
     } else if (seen.has(obj)) {
       return "";
     }
 
     seen.add(obj);
 
-    const len = getProperty(obj, "length");
+    const len = DevToolsUtils.getProperty(obj, "length");
     let string = "";
 
     // The following check is only required because the debuggee could possibly
     // be a Proxy and return any value. For normal objects, array.length is
     // always a non-negative integer.
     if (typeof len == "number" && len > 0) {
       for (let i = 0; i < len; i++) {
         const desc = obj.getOwnPropertyDescriptor(i);
@@ -2814,20 +2771,20 @@ let stringifiers = {
 
     if (topLevel) {
       seen = null;
     }
 
     return string;
   },
   DOMException: obj => {
-    const message = getProperty(obj, "message") || "<no message>";
-    const result = (+getProperty(obj, "result")).toString(16);
-    const code = getProperty(obj, "code");
-    const name = getProperty(obj, "name") || "<unknown>";
+    const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
+    const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
+    const code = DevToolsUtils.getProperty(obj, "code");
+    const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
 
     return '[Exception... "' + message + '" ' +
            'code: "' + code +'" ' +
            'nsresult: "0x' + result + ' (' + name + ')"]';
   }
 };
 
 /**
@@ -3097,17 +3054,17 @@ ObjectActor.prototype = {
       } catch (e) {
         // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
         // allowed (bug 560072).
       }
       if (!desc || desc.value !== undefined || !("get" in desc)) {
         continue;
       }
 
-      if (hasSafeGetter(desc)) {
+      if (DevToolsUtils.hasSafeGetter(desc)) {
         getters.add(name);
       }
     }
 
     aObject._safeGetters = getters;
     return getters;
   },
 
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -19,16 +19,17 @@ loader.lazyServiceGetter(this, "gActivit
                          "@mozilla.org/network/http-activity-distributor;1",
                          "nsIHttpActivityDistributor");
 
 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
 // Note that these are only used in JSTermHelpers, see $0 and pprint().
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
+loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
@@ -789,299 +790,259 @@ function JSPropertyProvider(aDbgObject, 
 
   let completionPart = inputValue.substring(beginning.startPos);
 
   // Don't complete on just an empty string.
   if (completionPart.trim() == "") {
     return null;
   }
 
-  let matches = null;
-  let matchProp = "";
-
   let lastDot = completionPart.lastIndexOf(".");
   if (lastDot > 0 &&
       (completionPart[0] == "'" || completionPart[0] == '"') &&
       completionPart[lastDot - 1] == completionPart[0]) {
     // We are completing a string literal.
-    let obj = String.prototype;
-    matchProp = completionPart.slice(lastDot + 1);
-    let matches = Object.keys(getMatchedProps(obj, {matchProp:matchProp}));
-
-    return {
-      matchProp: matchProp,
-      matches: matches,
-    };
+    let matchProp = completionPart.slice(lastDot + 1);
+    return getMatchedProps(String.prototype, matchProp);
   }
-  else {
-    // We are completing a variable / a property lookup.
-    let properties = completionPart.split(".");
-    if (properties.length > 1) {
-      matchProp = properties.pop().trimLeft();
-      let obj;
 
-      //The first property must be found in the environment or the Debugger.Object 
-      //depending of whether the debugger is paused or not
-      let prop = properties[0];
-      if (anEnvironment) {
-        obj = getVariableInEnvironment(anEnvironment, prop);
-      }
-      else {
-        obj = getPropertyInDebuggerObject(aDbgObject, prop);
-      }
-      if (obj == null) {
-        return null;
-      }
-
-      //We get the rest of the properties recursively starting from the Debugger.Object
-      // that wraps the first property
-      for (let i = 1; i < properties.length; i++) {
-        let prop = properties[i].trim();
-        if (!prop) {
-          return null;
-        }
+  // We are completing a variable / a property lookup.
+  let properties = completionPart.split(".");
+  let matchProp = properties.pop().trimLeft();
+  let obj = aDbgObject;
 
-        obj = getPropertyInDebuggerObject(obj, prop);
+  // The first property must be found in the environment if the debugger is
+  // paused.
+  if (anEnvironment) {
+    if (properties.length == 0) {
+      return getMatchedPropsInEnvironment(anEnvironment, matchProp);
+    }
+    obj = getVariableInEnvironment(anEnvironment, properties.shift());
+  }
 
-        // If obj is undefined or null (which is what "== null" does),
-        // then there is no chance to run completion on it. Exit here.
-        if (obj == null) {
-          return null;
-        }
-      }
-
-      // If the final property is a primitive
-      if (typeof obj != 'object' || obj === null) {
-        matchProp = completionPart.slice(lastDot + 1);
-        let matches = Object.keys(getMatchedProps(obj, {matchProp:matchProp}));
+  if (!isObjectUsable(obj)) {
+    return null;
+  }
 
-        return {
-          matchProp: matchProp,
-          matches: matches,
-        };
-      }
-      return getMatchedPropsInDbgObject(obj, matchProp);
+  // We get the rest of the properties recursively starting from the Debugger.Object
+  // that wraps the first property
+  for (let prop of properties) {
+    prop = prop.trim();
+    if (!prop) {
+      return null;
     }
-    else {
-      matchProp = properties[0].trimLeft();
-      if (anEnvironment) {
-        return getMatchedPropsInEnvironment(anEnvironment, matchProp);
-      }
-      else {
-        if (typeof aDbgObject != 'object' || aDbgObject === null) {
-          matchProp = completionPart.slice(lastDot + 1);
-          let matches = Object.keys(getMatchedProps(aDbgObject, {matchProp:matchProp}));
+
+    obj = DevToolsUtils.getProperty(obj, prop);
 
-          return {
-            matchProp: matchProp,
-            matches: matches,
-          };
-        }
-        return getMatchedPropsInDbgObject(aDbgObject, matchProp);
-      }
+    if (!isObjectUsable(obj)) {
+      return null;
     }
   }
+
+  // If the final property is a primitive
+  if (typeof obj != "object") {
+    return getMatchedProps(obj, matchProp);
+  }
+
+  return getMatchedPropsInDbgObject(obj, matchProp);
+}
+
+/**
+ * Check if the given Debugger.Object can be used for autocomplete.
+ *
+ * @param Debugger.Object aObject
+ *        The Debugger.Object to check.
+ * @return boolean
+ *         True if further inspection into the object is possible, or false
+ *         otherwise.
+ */
+function isObjectUsable(aObject)
+{
+  if (aObject == null) {
+    return false;
+  }
+
+  if (typeof aObject == "object" && aObject.class == "DeadObject") {
+    return false;
+  }
+
+  return true;
 }
 
 /**
- * Returns the value of aProp in anEnvironment as a debuggee value, by recursively checking the environment chain
- *
- * @param object anEnvironment
- *        A Debugger.Environment to look the aProp into.
- * @param string aProp
- *        The property that is looked up.
- * @returns null or object
- *        A Debugger.Object if aProp exists in the environment chain, null otherwise.
+ * @see getExactMatch_impl()
  */
-function getVariableInEnvironment(anEnvironment, aProp)
+function getVariableInEnvironment(anEnvironment, aName)
+{
+  return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedPropsInEnvironment(anEnvironment, aMatch)
 {
-  for (let env = anEnvironment; env; env = env.parent) {
-    try {
-      let obj = env.getVariable(aProp);
-      if (obj) {
-        return obj;
-      }
-    }
-    catch (ex) {
-      return null;
-    }
+  return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedPropsInDbgObject(aDbgObject, aMatch)
+{
+  return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
+}
+
+/**
+ * @see getMatchedProps_impl()
+ */
+function getMatchedProps(aObj, aMatch)
+{
+  if (typeof aObj != "object") {
+    aObj = aObj.constructor.prototype;
   }
-  return null;
+  return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
 }
 
 /**
- * Returns the value of aProp in aDbgObject as a debuggee value, by recursively checking the prototype chain
+ * Get all properties in the given object (and its parent prototype chain) that
+ * match a given prefix.
  *
- * @param object aDbgObject
- *        A Debugger.Object to look the aProp into.
- * @param string aProp
- *        The property that is looked up.
- * @returns null or object
- *        A Debugger.Object if aProp exists in the prototype chain, null otherwise.
- */
-function getPropertyInDebuggerObject(aDbgObject, aProp)
-{
-  let dbgObject = aDbgObject;
-  while (dbgObject) {
-    try {
-      let desc = dbgObject.getOwnPropertyDescriptor(aProp)
-      if (desc) {
-        let obj = desc.value;
-        if (obj)
-          return obj;
-        obj = desc.get;
-        if (obj)
-          return obj;
-      }
-      dbgObject = dbgObject.proto;
-    }
-    catch (ex) {
-      return null;
-    }
-  }
-  return null;
-}
-
-/**
- * Get all properties on the given Debugger.Environment (and its parent chain) that match a given prefix.
- *
- * @param Debugger.Environment anEnvironment
- *        Debugger.Environment whose properties we want to filter.
- *
- * @param string matchProp Filter for properties that match this one.
- *
+ * @param mixed aObj
+ *        Object whose properties we want to filter.
+ * @param string aMatch
+ *        Filter for properties that match this string.
  * @return object
  *         Object that contains the matchProp and the list of names.
  */
-function getMatchedPropsInEnvironment(anEnvironment, matchProp)
+function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
 {
-  let names = Object.create(null);
-  let c = MAX_COMPLETIONS;
-  for (let env = anEnvironment; env; env = env.parent) {
-    let ownNames = env.names();
-    for (let i = 0; i < ownNames.length; i++) {
-      if (ownNames[i].indexOf(matchProp) != 0 ||
-        ownNames[i] in names) {
+  let matches = new Set();
+
+  // We need to go up the prototype chain.
+  let iter = chainIterator(aObj);
+  for (let obj of iter) {
+    let props = getProperties(obj);
+    for (let prop of props) {
+      if (prop.indexOf(aMatch) != 0) {
         continue;
       }
-      c--;
-      if (c < 0) {
-        return {
-          matchProp: matchProp,
-          matches: Object.keys(names)
-        };
+
+      // If it is an array index, we can't take it.
+      // This uses a trick: converting a string to a number yields NaN if
+      // the operation failed, and NaN is not equal to itself.
+      if (+prop != +prop) {
+        matches.add(prop);
       }
-      names[ownNames[i]] = true;
+
+      if (matches.size > MAX_COMPLETIONS) {
+        break;
+      }
+    }
+
+    if (matches.size > MAX_COMPLETIONS) {
+      break;
     }
   }
+
   return {
-    matchProp: matchProp,
-    matches: Object.keys(names)
+    matchProp: aMatch,
+    matches: [...matches],
   };
 }
 
 /**
- * Get all properties on the given Debugger.Object (and the prototype chain of the wrapped value) that match a given prefix.
- *
- * @param Debugger.Object aDbgObject
- *        Debugger.Object whose properties we want to filter.
+ * Returns a property value based on its name from the given object, by
+ * recursively checking the object's prototype.
  *
- * @param string matchProp Filter for properties that match this one.
- *
- * @return object
- *         Object that contains the matchProp and the list of names.
+ * @param object aObj
+ *        An object to look the property into.
+ * @param string aName
+ *        The property that is looked up.
+ * @returns object|undefined
+ *        A Debugger.Object if the property exists in the object's prototype
+ *        chain, undefined otherwise.
  */
-function getMatchedPropsInDbgObject(aDbgObject, matchProp)
+function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
 {
-  let names = Object.create(null);
-  let c = MAX_COMPLETIONS;
-  for (let dbg = aDbgObject; dbg; dbg = dbg.proto) {
-    let raw = dbg.unsafeDereference();
-    if (Cu.isDeadWrapper(raw)) {
-      return null;
-    }
-    let ownNames = dbg.getOwnPropertyNames();
-    for (let i = 0; i < ownNames.length; i++) {
-      if (ownNames[i].indexOf(matchProp) != 0 ||
-        ownNames[i] in names) {
-        continue;
-      }
-      c--;
-      if (c < 0) {
-        return {
-          matchProp: matchProp,
-          matches: Object.keys(names)
-        };
-      }
-      names[ownNames[i]] = true;
+  // We need to go up the prototype chain.
+  let iter = chainIterator(aObj);
+  for (let obj of iter) {
+    let prop = getProperty(obj, aName, aObj);