Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 02 Dec 2013 16:58:15 -0500
changeset 174040 612b104050ce2fe75aca45f1754a7740a6691ddb
parent 174039 646b46467e86e2bb065495eadfa020eefe113a14 (current diff)
parent 174012 c93cfe704487cf493fc4f289e015f068e49c4644 (diff)
child 174041 f0144998bab05494ca3cb9c4d12a41738488d431
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.
browser/devtools/debugger/DebuggerProcess.jsm
dom/bluetooth/BluetoothSocket.h
--- 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/
 #
 
-Another Windows WebIDL clobber needed due to bug 928195
+More Windows webidl changes 
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -434,16 +434,21 @@ pref("services.push.requestTimeout", 100
 pref("services.push.udp.wakeupEnabled", true);
 
 // NetworkStats
 #ifdef MOZ_WIDGET_GONK
 pref("dom.mozNetworkStats.enabled", true);
 pref("dom.webapps.firstRunWithSIM", true);
 #endif
 
+#ifdef MOZ_B2G_RIL
+// SingleVariant
+pref("dom.mozApps.single_variant_sourcedir", "/persist/svoperapps");
+#endif
+
 // WebSettings
 pref("dom.mozSettings.enabled", true);
 pref("dom.navigator-property.disable.mozSettings", false);
 pref("dom.mozPermissionSettings.enabled", true);
 
 // controls if we want camera support
 pref("device.camera.enabled", true);
 pref("media.realtime_decoder.enabled", true);
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "cf23b263b4bbf1024210350f24c7e6eb5872a4be", 
+    "revision": "c2506884aba22b968d3ab1ad6c28f0782b22a17f", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -513,19 +513,19 @@
                             accesskey="&devToolboxMenuItem.accesskey;"/>
                   <menuseparator id="menu_devtools_separator"/>
                   <menuitem id="menu_devToolbar"
                             observes="devtoolsMenuBroadcaster_DevToolbar"
                             accesskey="&devToolbarMenu.accesskey;"/>
                   <menuitem id="menu_devAppMgr"
                             observes="devtoolsMenuBroadcaster_DevAppMgr"
                             accesskey="&devAppMgrMenu.accesskey;"/>
-                  <menuitem id="menu_chromeDebugger"
-                            observes="devtoolsMenuBroadcaster_ChromeDebugger"
-                            accesskey="&chromeDebuggerMenu.accesskey;"/>
+                  <menuitem id="menu_browserToolbox"
+                            observes="devtoolsMenuBroadcaster_BrowserToolbox"
+                            accesskey="&browserToolboxMenu.accesskey;"/>
                   <menuitem id="menu_browserConsole"
                             observes="devtoolsMenuBroadcaster_BrowserConsole"
                             accesskey="&browserConsoleCmd.accesskey;"/>
                   <menuitem id="menu_responsiveUI"
                             observes="devtoolsMenuBroadcaster_ResponsiveUI"
                             accesskey="&responsiveDesignTool.accesskey;"/>
                   <menuitem id="menu_scratchpad"
                             observes="devtoolsMenuBroadcaster_Scratchpad"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -90,17 +90,17 @@
     <command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:DevAppMgr" oncommand="gDevToolsBrowser.openAppManager(gBrowser);" disabled="true" hidden="true"/>
-    <command id="Tools:ChromeDebugger" oncommand="BrowserDebuggerProcess.init();" disabled="true" hidden="true"/>
+    <command id="Tools:BrowserToolbox" oncommand="BrowserToolboxProcess.init();" disabled="true" hidden="true"/>
     <command id="Tools:BrowserConsole" oncommand="HUDService.toggleBrowserConsole();"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
     <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
@@ -180,19 +180,19 @@
     <broadcaster id="devtoolsMenuBroadcaster_DevToolbar"
                  label="&devToolbarMenu.label;"
                  type="checkbox" autocheck="false"
                  command="Tools:DevToolbar"
                  key="key_devToolbar"/>
     <broadcaster id="devtoolsMenuBroadcaster_DevAppMgr"
                  label="&devAppMgrMenu.label;"
                  command="Tools:DevAppMgr"/>
-    <broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
-                 label="&chromeDebuggerMenu.label;"
-                 command="Tools:ChromeDebugger"/>
+    <broadcaster id="devtoolsMenuBroadcaster_BrowserToolbox"
+                 label="&browserToolboxMenu.label;"
+                 command="Tools:BrowserToolbox"/>
     <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
                  label="&browserConsoleCmd.label;"
                  key="key_browserConsole"
                  command="Tools:BrowserConsole"/>
     <broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
                  label="&scratchpad.label;"
                  command="Tools:Scratchpad"
                  key="key_scratchpad"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -112,20 +112,20 @@ XPCOMUtils.defineLazyGetter(this, "Popup
 });
 
 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp);
   return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
 });
 
-XPCOMUtils.defineLazyGetter(this, "BrowserDebuggerProcess", function() {
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
   let tmp = {};
-  Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", tmp);
-  return tmp.BrowserDebuggerProcess;
+  Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", tmp);
+  return tmp.BrowserToolboxProcess;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Social",
   "resource:///modules/Social.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
   "resource://gre/modules/PageThumbs.jsm");
 
--- a/browser/base/content/test/social/browser_blocklist.js
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -129,27 +129,29 @@ var tests = {
     let listener = {
       _window: null,
       onOpenWindow: function(aXULWindow) {
         Services.wm.removeListener(this);
         this._window = aXULWindow;
         let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIDOMWindow);
 
-        domwindow.addEventListener("unload", function _unload() {
-          domwindow.removeEventListener("unload", _unload, false);
-          windowWasClosed = true;
+        domwindow.addEventListener("load", function _load() {
+          domwindow.removeEventListener("load", _load, false);
+
+          domwindow.addEventListener("unload", function _unload() {
+            domwindow.removeEventListener("unload", _unload, false);
+            info("blocklist window was closed");
+            windowWasClosed = true;
+          }, false);
+
+          is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
+          domwindow.close();
+
         }, false);
-        info("dialog opened, waiting for focus");
-        waitForFocus(function() {
-          is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
-          executeSoon(function() {
-            domwindow.close();
-          });
-        }, domwindow);
       },
       onCloseWindow: function(aXULWindow) { },
       onWindowTitleChange: function(aXULWindow, aNewTitle) { }
     };
 
     Services.wm.addListener(listener);
 
     setManifestPref("social.manifest.blocked", manifest_bad);
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -152,18 +152,17 @@ let CustomizableUIInternal = {
       // 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) {
-      // TODO: Bug 942915 - Place 'Metro Mode' button as a default
-      // for Windows 8 in the customization panel.
+      panelPlacements.push("switch-to-metro-button");
     }
 #endif
 #endif
 
     let showCharacterEncoding = Services.prefs.getComplexValue(
       "browser.menu.showCharacterEncoding",
       Ci.nsIPrefLocalizedString
     ).data;
--- a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
+++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
@@ -23,25 +23,25 @@ let gTests = [
       let item = document.getElementById(kTestWidget1);
       ok(!item, "There should no longer be an item");
     },
   },
   {
     desc: "Creating and destroying a widget should correctly deal with panel placeholders",
     run: function() {
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
-      is(panel.querySelectorAll(".panel-customization-placeholder").length, 3, "The number of placeholders should be correct.");
+      is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 2 : 3, "The number of placeholders should be correct.");
       CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
       let elem = document.getElementById(kTestWidget2);
       let wrapper = document.getElementById("wrapper-" + kTestWidget2);
       ok(elem, "There should be an item");
       ok(wrapper, "There should be a wrapper");
       is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
       is(wrapper.parentNode, panel, "Wrapper should be in panel");
-      is(panel.querySelectorAll(".panel-customization-placeholder").length, 2, "The number of placeholders should be correct.");
+      is(panel.querySelectorAll(".panel-customization-placeholder").length, isInWin8() ? 1 : 2, "The number of placeholders should be correct.");
       CustomizableUI.destroyWidget(kTestWidget2);
       wrapper = document.getElementById("wrapper-" + kTestWidget2);
       ok(!wrapper, "There should be a wrapper");
       let item = document.getElementById(kTestWidget2);
       ok(!item, "There should no longer be an item");
     },
     teardown: endCustomizing
   },
--- a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
+++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
@@ -16,16 +16,17 @@ let gTests = [
                                  "save-page-button",
                                  "zoom-controls",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(zoomControls, printButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       let newWindowButton = document.getElementById("new-window-button");
       simulateItemDrag(zoomControls, newWindowButton);
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
@@ -42,16 +43,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(zoomControls, savePageButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(CustomizableUI.inDefaultState, "Should be in default state.");
     },
   },
   {
     desc: "Dragging the zoom controls to be before the new-window " +
           "button should not move any widgets.",
@@ -65,16 +67,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(zoomControls, newWindowButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
     desc: "Dragging the zoom controls to be before the history-panelmenu " +
           "should move the zoom-controls in to the row higher than the " +
@@ -89,16 +92,17 @@ let gTests = [
                                  "save-page-button",
                                  "zoom-controls",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(zoomControls, historyPanelMenu);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       let newWindowButton = document.getElementById("new-window-button");
       simulateItemDrag(zoomControls, newWindowButton);
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
@@ -116,16 +120,17 @@ let gTests = [
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "zoom-controls",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(zoomControls, preferencesButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       let newWindowButton = document.getElementById("new-window-button");
       simulateItemDrag(zoomControls, newWindowButton);
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
@@ -143,16 +148,17 @@ let gTests = [
                                    "zoom-controls",
                                    "save-page-button",
                                    "print-button",
                                    "history-panelmenu",
                                    "fullscreen-button",
                                    "find-button",
                                    "preferences-button",
                                    "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterInsert);
       simulateItemDrag(developerButton, zoomControls);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       let palette = document.getElementById("customization-palette");
       // Check that the palette items are re-wrapped correctly.
       let feedWrapper = document.getElementById("wrapper-feed-button");
       let feedButton = document.getElementById("feed-button");
       is(feedButton.parentNode, feedWrapper,
@@ -181,16 +187,17 @@ let gTests = [
                                    "zoom-controls",
                                    "save-page-button",
                                    "print-button",
                                    "history-panelmenu",
                                    "fullscreen-button",
                                    "find-button",
                                    "preferences-button",
                                    "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterInsert);
       simulateItemDrag(developerButton, editControls);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       let palette = document.getElementById("customization-palette");
       // Check that the palette items are re-wrapped correctly.
       let feedWrapper = document.getElementById("wrapper-feed-button");
       let feedButton = document.getElementById("feed-button");
       is(feedButton.parentNode, feedWrapper,
@@ -216,16 +223,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, zoomControls);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
     desc: "Dragging the edit-controls to be before the new-window-button should " +
           "move the zoom-controls before the edit-controls.",
@@ -239,16 +247,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, newWindowButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(editControls, zoomControls);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
@@ -265,16 +274,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, privateBrowsingButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(editControls, zoomControls);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
@@ -291,16 +301,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, savePageButton);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(editControls, zoomControls);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
@@ -316,16 +327,17 @@ let gTests = [
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button",
                                  "edit-controls"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, panel);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(editControls, zoomControls);
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
   {
@@ -340,16 +352,17 @@ let gTests = [
                                  "privatebrowsing-button",
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       let paletteChildElementCount = palette.childElementCount;
       simulateItemDrag(editControls, palette);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       is(paletteChildElementCount + 1, palette.childElementCount,
          "The palette should have a new child, congratulations!");
       is(editControls.parentNode.id, "wrapper-edit-controls",
          "The edit-controls should be properly wrapped.");
       is(editControls.parentNode.getAttribute("place"), "palette",
@@ -363,31 +376,33 @@ let gTests = [
   },
   {
     desc: "Dragging the edit-controls to each of the panel placeholders " +
           "should append the edit-controls to the bottom of the panel.",
     setup: startCustomizing,
     run: function() {
       let editControls = document.getElementById("edit-controls");
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
-      for (let i = 0; i < 3; i++) {
+      let numPlaceholders = isInWin8() ? 2 : 3;
+      for (let i = 0; i < numPlaceholders; i++) {
         // NB: We can't just iterate over all of the placeholders
         // because each drag-drop action recreates them.
         let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i];
         let placementsAfterMove = ["zoom-controls",
                                    "new-window-button",
                                    "privatebrowsing-button",
                                    "save-page-button",
                                    "print-button",
                                    "history-panelmenu",
                                    "fullscreen-button",
                                    "find-button",
                                    "preferences-button",
                                    "add-ons-button",
                                    "edit-controls"];
+        addSwitchToMetroButtonInWindows8(placementsAfterMove);
         simulateItemDrag(editControls, placeholder);
         assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
         let zoomControls = document.getElementById("zoom-controls");
         simulateItemDrag(editControls, zoomControls);
         ok(CustomizableUI.inDefaultState, "Should still be in default state.");
       }
     },
   },
@@ -420,21 +435,26 @@ let gTests = [
                                  "save-page-button",
                                  "print-button",
                                  "history-panelmenu",
                                  "fullscreen-button",
                                  "find-button",
                                  "preferences-button",
                                  "add-ons-button",
                                  "edit-controls"];
+      addSwitchToMetroButtonInWindows8(placementsAfterMove);
       simulateItemDrag(editControls, target);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
       let itemToDrag = "sync-button";
       let button = document.getElementById(itemToDrag);
-      placementsAfterMove.push(itemToDrag);
+      if (!isInWin8()) {
+        placementsAfterMove.push(itemToDrag);
+      } else {
+        placementsAfterMove.splice(11, 0, itemToDrag);
+      }
       simulateItemDrag(button, editControls);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
 
       // Put stuff back:
       let palette = document.getElementById("customization-palette");
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(button, palette);
       simulateItemDrag(editControls, zoomControls);
--- a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
+++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
@@ -6,116 +6,155 @@ let gTests = [
   {
     desc: "One orphaned item should have two placeholders next to it.",
     setup: startCustomizing,
     run: function() {
       let btn = document.getElementById("developer-button");
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
       let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
 
-      let placementsAfterAppend = placements.concat(["developer-button"]);
-      simulateItemDrag(btn, panel);
-      assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
-      ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+      if (!isInWin8()) {
+        placements = placements.concat(["developer-button"]);
+        simulateItemDrag(btn, panel);
+        ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+      } else {
+        ok(CustomizableUI.inDefaultState, "Should be in default state.");
+      }
+
+      assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
       is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting");
 
       yield endCustomizing();
       yield startCustomizing();
       is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
 
-      let palette = document.getElementById("customization-palette");
-      simulateItemDrag(btn, palette);
+      if (!isInWin8()) {
+        let palette = document.getElementById("customization-palette");
+        simulateItemDrag(btn, palette);
+      }
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
   {
     desc: "Two orphaned items should have one placeholder next to them (case 1).",
     setup: startCustomizing,
     run: function() {
       let btn = document.getElementById("developer-button");
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
       let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
 
-      let placementsAfterAppend = placements.concat(["developer-button", "sync-button"]);
+      let placementsAfterAppend = placements.concat(["developer-button"]);
       simulateItemDrag(btn, panel);
-      btn = document.getElementById("sync-button");
-      simulateItemDrag(btn, panel);
+
+      if (!isInWin8()) {
+        placementsAfterAppend = placementsAfterAppend.concat(["sync-button"]);
+        btn = document.getElementById("sync-button");
+        simulateItemDrag(btn, panel);
+      }
+
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
 
       yield endCustomizing();
       yield startCustomizing();
       is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
 
       let palette = document.getElementById("customization-palette");
       simulateItemDrag(btn, palette);
-      btn = document.getElementById("developer-button");
-      simulateItemDrag(btn, palette);
+
+      if (!isInWin8()) {
+        btn = document.getElementById("developer-button");
+        simulateItemDrag(btn, palette);
+      }
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
   {
     desc: "Two orphaned items should have one placeholder next to them (case 2).",
     setup: startCustomizing,
     run: function() {
       let btn = document.getElementById("add-ons-button");
+      let btn2 = document.getElementById("switch-to-metro-button");
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
       let palette = document.getElementById("customization-palette");
       let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
 
       let placementsAfterAppend = placements.filter(p => p != btn.id);
       simulateItemDrag(btn, palette);
+
+      if (isInWin8()) {
+        placementsAfterAppend = placementsAfterAppend.filter(p => p != btn2.id);
+        simulateItemDrag(btn2, palette);
+      }
+
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders before exiting");
 
       yield endCustomizing();
       yield startCustomizing();
       is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholders after re-entering");
 
       simulateItemDrag(btn, panel);
+
+      if (isInWin8()) {
+        simulateItemDrag(btn2, panel);
+      }
+
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
   {
     desc: "A wide widget at the bottom of the panel should have three placeholders after it.",
     setup: startCustomizing,
     run: function() {
       let btn = document.getElementById("edit-controls");
+      let metroBtn = document.getElementById("switch-to-metro-button");
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+      let palette = document.getElementById("customization-palette");
       let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
 
+      if (isInWin8()) {
+        // Remove switch-to-metro-button
+        placements.pop();
+        simulateItemDrag(metroBtn, palette);
+      }
+
       let placementsAfterAppend = placements.concat([placements.shift()]);
       simulateItemDrag(btn, panel);
       assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
       ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
       is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
 
       yield endCustomizing();
       yield startCustomizing();
       is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
 
+      if (isInWin8()) {
+        simulateItemDrag(metroBtn, panel);
+      }
       let zoomControls = document.getElementById("zoom-controls");
       simulateItemDrag(btn, zoomControls);
       ok(CustomizableUI.inDefaultState, "Should be in default state again.");
     },
   },
   {
-    desc: "The default placements should have three placeholders at the bottom.",
+    desc: "The default placements should have three placeholders at the bottom (or 2 in win8).",
     setup: startCustomizing,
     run: function() {
+      let numPlaceholders = isInWin8() ? 2 : 3;
       let panel = document.getElementById(CustomizableUI.AREA_PANEL);
       ok(CustomizableUI.inDefaultState, "Should be in default state.");
-      is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
+      is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting");
 
       yield endCustomizing();
       yield startCustomizing();
-      is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
+      is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering");
 
       ok(CustomizableUI.inDefaultState, "Should still be in default state.");
     },
   },
 ];
 
 function asyncCleanup() {
   yield endCustomizing();
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -49,16 +49,31 @@ function removeCustomToolbars() {
   }
   gAddedToolbars.clear();
 }
 
 function resetCustomization() {
   return CustomizableUI.reset();
 }
 
+function isInWin8() {
+  let sysInfo = Services.sysinfo;
+  let osName = sysInfo.getProperty("name");
+  let version = sysInfo.getProperty("version");
+
+  // Windows 8 is version >= 6.2
+  return osName == "Windows_NT" && version >= 6.2;
+}
+
+function addSwitchToMetroButtonInWindows8(areaPanelPlacements) {
+  if (isInWin8()) {
+    areaPanelPlacements.push("switch-to-metro-button");
+  }
+}
+
 function assertAreaPlacements(areaId, expectedPlacements) {
   let actualPlacements = getAreaWidgetIds(areaId);
   is(actualPlacements.length, expectedPlacements.length,
      "Area " + areaId + " should have " + expectedPlacements.length + " items.");
   let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
   for (let i = 0; i < minItems; i++) {
     if (typeof expectedPlacements[i] == "string") {
       is(actualPlacements[i], expectedPlacements[i],
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -1449,18 +1449,22 @@ let SessionStoreInternal = {
 
   setBrowserState: function ssi_setBrowserState(aState) {
     this._handleClosedWindows();
 
     try {
       var state = JSON.parse(aState);
     }
     catch (ex) { /* invalid state object - don't restore anything */ }
-    if (!state || !state.windows)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!state) {
+      throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
+    }
+    if (!state.windows) {
+      throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     this._browserSetState = true;
 
     // Make sure the priority queue is emptied out
     this._resetRestoringState();
 
     var window = this._getMostRecentBrowserWindow();
     if (!window) {
@@ -1496,77 +1500,83 @@ let SessionStoreInternal = {
       return this._toJSONString(this._getWindowState(aWindow));
     }
 
     if (DyingWindowCache.has(aWindow)) {
       let data = DyingWindowCache.get(aWindow);
       return this._toJSONString({ windows: [data] });
     }
 
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
   },
 
   setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
-    if (!aWindow.__SSi)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!aWindow.__SSi) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
   },
 
   getTabState: function ssi_getTabState(aTab) {
-    if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!aTab.ownerDocument) {
+      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
+    }
+    if (!aTab.ownerDocument.defaultView.__SSi) {
+      throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     let tabState = TabState.collectSync(aTab);
 
     return this._toJSONString(tabState);
   },
 
   setTabState: function ssi_setTabState(aTab, aState) {
     // Remove the tab state from the cache.
     // Note that we cannot simply replace the contents of the cache
     // as |aState| can be an incomplete state that will be completed
     // by |restoreTabs|.
     let tabState = JSON.parse(aState);
     if (!tabState) {
-      debug("Empty state argument");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
     }
     if (typeof tabState != "object") {
-      debug("State argument does not represent an object");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!("entries" in tabState)) {
-      debug("State argument must contain field 'entries'");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aTab.ownerDocument) {
-      debug("Tab argument must have an owner document");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
     }
 
     let window = aTab.ownerDocument.defaultView;
     if (!("__SSi" in window)) {
-      debug("Default view of ownerDocument must have a unique identifier");
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
     if (aTab.linkedBrowser.__SS_restoreState) {
       this._resetTabRestoringState(aTab);
     }
 
     TabStateCache.delete(aTab);
     this._setWindowStateBusy(window);
     this.restoreTabs(window, [aTab], [tabState], 0);
   },
 
   duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
-    if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
-        !aWindow.getBrowser)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!aTab.ownerDocument) {
+      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
+    }
+    if (!aTab.ownerDocument.defaultView.__SSi) {
+      throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
+    if (!aWindow.getBrowser) {
+      throw Components.Exception("Invalid window object: no getBrowser", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     // Flush all data queued in the content script because we will need that
     // state to properly duplicate the given tab.
     TabState.flush(aTab.linkedBrowser);
 
     // Duplicate the tab state
     let tabState = TabState.clone(aTab);
 
@@ -1581,78 +1591,81 @@ let SessionStoreInternal = {
 
     this.restoreTabs(aWindow, [newTab], [tabState], 0,
                      true /* Load this tab right away. */);
 
     return newTab;
   },
 
   setNumberOfTabsClosedLast: function ssi_setNumberOfTabsClosedLast(aWindow, aNumber) {
-    if (this._disabledForMultiProcess)
+    if (this._disabledForMultiProcess) {
       return;
-
-    if ("__SSi" in aWindow) {
-      return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
     }
 
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!("__SSi" in aWindow)) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
   },
 
   /* Used to undo batch tab-close operations. Defaults to 1. */
   getNumberOfTabsClosedLast: function ssi_getNumberOfTabsClosedLast(aWindow) {
-    if (this._disabledForMultiProcess)
+    if (this._disabledForMultiProcess) {
       return 0;
-
-    if ("__SSi" in aWindow) {
-      // Blank tabs cannot be undo-closed, so the number returned by
-      // the NumberOfTabsClosedLastPerWindow can be greater than the
-      // return value of getClosedTabCount. We won't restore blank
-      // tabs, so we return the minimum of these two values.
-      return Math.min(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
-                      this.getClosedTabCount(aWindow));
+    }
+
+    if (!("__SSi" in aWindow)) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
-
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    // Blank tabs cannot be undo-closed, so the number returned by
+    // the NumberOfTabsClosedLastPerWindow can be greater than the
+    // return value of getClosedTabCount. We won't restore blank
+    // tabs, so we return the minimum of these two values.
+    return Math.min(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
+                    this.getClosedTabCount(aWindow));
   },
 
   getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
     if ("__SSi" in aWindow) {
       return this._windows[aWindow.__SSi]._closedTabs.length;
     }
 
-    if (DyingWindowCache.has(aWindow)) {
-      return DyingWindowCache.get(aWindow)._closedTabs.length;
+    if (!DyingWindowCache.has(aWindow)) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    return DyingWindowCache.get(aWindow)._closedTabs.length;
   },
 
   getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
     if ("__SSi" in aWindow) {
       return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
     }
 
-    if (DyingWindowCache.has(aWindow)) {
-      let data = DyingWindowCache.get(aWindow);
-      return this._toJSONString(data._closedTabs);
+    if (!DyingWindowCache.has(aWindow)) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    let data = DyingWindowCache.get(aWindow);
+    return this._toJSONString(data._closedTabs);
   },
 
   undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
-    if (!aWindow.__SSi)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!aWindow.__SSi) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
 
     // default to the most-recently closed tab
     aIndex = aIndex || 0;
-    if (!(aIndex in closedTabs))
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!(aIndex in closedTabs)) {
+      throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     // fetch the data of closed tab, while removing it from the array
     let closedTab = closedTabs.splice(aIndex, 1).shift();
     let closedTabState = closedTab.state;
 
     this._setWindowStateBusy(aWindow);
     // create a new tab
     let tabbrowser = aWindow.gBrowser;
@@ -1666,87 +1679,90 @@ let SessionStoreInternal = {
 
     // focus the tab's content area (bug 342432)
     tab.linkedBrowser.focus();
 
     return tab;
   },
 
   forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
-    if (!aWindow.__SSi)
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!aWindow.__SSi) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
 
     // default to the most-recently closed tab
     aIndex = aIndex || 0;
-    if (!(aIndex in closedTabs))
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!(aIndex in closedTabs)) {
+      throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     // remove closed tab from the array
     closedTabs.splice(aIndex, 1);
   },
 
   getClosedWindowCount: function ssi_getClosedWindowCount() {
     return this._closedWindows.length;
   },
 
   getClosedWindowData: function ssi_getClosedWindowData() {
     return this._toJSONString(this._closedWindows);
   },
 
   undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
-    if (!(aIndex in this._closedWindows))
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!(aIndex in this._closedWindows)) {
+      throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     // reopen the window
     let state = { windows: this._closedWindows.splice(aIndex, 1) };
     let window = this._openWindowWithState(state);
     this.windowToFocus = window;
     return window;
   },
 
   forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
     // default to the most-recently closed window
     aIndex = aIndex || 0;
-    if (!(aIndex in this._closedWindows))
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!(aIndex in this._closedWindows)) {
+      throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
+    }
 
     // remove closed window from the array
     this._closedWindows.splice(aIndex, 1);
   },
 
   getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
-    if (this._disabledForMultiProcess)
+    if (this._disabledForMultiProcess) {
       return "";
+    }
 
     if ("__SSi" in aWindow) {
       var data = this._windows[aWindow.__SSi].extData || {};
       return data[aKey] || "";
     }
 
     if (DyingWindowCache.has(aWindow)) {
       let data = DyingWindowCache.get(aWindow).extData || {};
       return data[aKey] || "";
     }
 
-    throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
   },
 
   setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
-    if (aWindow.__SSi) {
-      if (!this._windows[aWindow.__SSi].extData) {
-        this._windows[aWindow.__SSi].extData = {};
-      }
-      this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
-      this.saveStateDelayed(aWindow);
+    if (!("__SSi" in aWindow)) {
+      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
-    else {
-      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+    if (!this._windows[aWindow.__SSi].extData) {
+      this._windows[aWindow.__SSi].extData = {};
     }
+    this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
+    this.saveStateDelayed(aWindow);
   },
 
   deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
     if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
         this._windows[aWindow.__SSi].extData[aKey])
       delete this._windows[aWindow.__SSi].extData[aKey];
     this.saveStateDelayed(aWindow);
   },
@@ -1835,31 +1851,33 @@ let SessionStoreInternal = {
    * Restores the session state stored in LastSession. This will attempt
    * to merge data into the current session. If a window was opened at startup
    * with pinned tab(s), then the remaining data from the previous session for
    * that window will be opened into that winddow. Otherwise new windows will
    * be opened.
    */
   restoreLastSession: function ssi_restoreLastSession() {
     // Use the public getter since it also checks PB mode
-    if (!this.canRestoreLastSession)
-      throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
+    if (!this.canRestoreLastSession) {
+      throw Components.Exception("Last session can not be restored");
+    }
 
     // First collect each window with its id...
     let windows = {};
     this._forEachBrowserWindow(function(aWindow) {
       if (aWindow.__SS_lastSessionWindowID)
         windows[aWindow.__SS_lastSessionWindowID] = aWindow;
     });
 
     let lastSessionState = LastSession.getState();
 
     // This shouldn't ever be the case...
-    if (!lastSessionState.windows.length)
-      throw (Components.returnCode = Cr.NS_ERROR_UNEXPECTED);
+    if (!lastSessionState.windows.length) {
+      throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
+    }
 
     // We're technically doing a restore, so set things up so we send the
     // notification when we're done. We want to send "sessionstore-browser-state-restored".
     this._restoreCount = lastSessionState.windows.length;
     this._browserSetState = true;
 
     // We want to re-use the last opened window instead of opening a new one in
     // the case where it's "empty" and not associated with a window in the session.
--- a/browser/devtools/app-manager/content/index.js
+++ b/browser/devtools/app-manager/content/index.js
@@ -62,16 +62,19 @@ let UI = {
           this.selectTab("projects");
           break;
         case "toolbox-raise":
           this.selectTab(json.uid);
           break;
         case "toolbox-close":
           this.closeToolboxTab(json.uid);
           break;
+        case "toolbox-title":
+          // Not implemented
+          break;
         default:
           Cu.reportError("Unknown message: " + json.name);
       }
     } catch(e) { Cu.reportError(e); }
 
     // Forward message
     let panels = document.querySelectorAll(".panel");
     for (let frame of panels) {
--- a/browser/devtools/debugger/test/browser_dbg_globalactor.js
+++ b/browser/devtools/debugger/test/browser_dbg_globalactor.js
@@ -39,35 +39,24 @@ function test() {
 
           // Make sure that lazily-created actors are created only once.
           let conn = transport._serverConnection;
 
           // First we look for the pool of global actors.
           let extraPools = conn._extraPools;
           let globalPool;
 
+          let actorPrefix = conn._prefix + "test_one";
+          let count = 0;
           for (let pool of extraPools) {
-            if (Object.keys(pool._actors).some(e => {
-              // Tab actors are in the global pool.
-              let re = new RegExp(conn._prefix + "tab", "g");
-              return e.match(re) !== null;
-            })) {
-              globalPool = pool;
-              break;
-            }
+            count += Object.keys(pool._actors).filter(e => {
+              return e.startsWith(actorPrefix);
+            }).length;
           }
-
-          // Then we look if the global pool contains only one test actor.
-          let actorPrefix = conn._prefix + "test_one";
-          let actors = Object.keys(globalPool._actors).join();
-          info("Global actors: " + actors);
-
-          isnot(actors.indexOf(actorPrefix), -1,
-            "The test actor exists in the pool.");
-          is(actors.indexOf(actorPrefix), actors.lastIndexOf(actorPrefix),
-            "Only one actor exists in the pool.");
+          is(count, 2,
+            "Only two actor exists in all pools. One tab actor and one global.");
 
           gClient.close(finish);
         });
       });
     });
   });
 }
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -12,17 +12,17 @@ let { Services } = Cu.import("resource:/
 let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
 let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-let { BrowserDebuggerProcess } = Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", {});
+let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
@@ -468,19 +468,19 @@ function initDebugger(aTarget, aWindow) 
   });
 }
 
 function initChromeDebugger(aOnClose) {
   info("Initializing a chrome debugger process.");
 
   let deferred = promise.defer();
 
-  // Wait for the debugger process to start...
-  BrowserDebuggerProcess.init(aOnClose, aProcess => {
-    info("Chrome debugger process started successfully.");
+  // Wait for the toolbox process to start...
+  BrowserToolboxProcess.init(aOnClose, aProcess => {
+    info("Browser toolbox process started successfully.");
 
     prepareDebugger(aProcess);
     deferred.resolve(aProcess);
   });
 
   return deferred.promise;
 }
 
--- a/browser/devtools/devtools-clhandler.js
+++ b/browser/devtools/devtools-clhandler.js
@@ -47,31 +47,31 @@ devtoolsCommandlineHandler.prototype = {
     let remoteDebuggingEnabled = false;
     try {
       remoteDebuggingEnabled = kDebuggerPrefs.every((pref) => Services.prefs.getBoolPref(pref));
     } catch (ex) {
       Cu.reportError(ex);
       return;
     }
     if (remoteDebuggingEnabled) {
-      Cu.import("resource:///modules/devtools/DebuggerProcess.jsm");
-      BrowserDebuggerProcess.init();
+      Cu.import("resource:///modules/devtools/ToolboxProcess.jsm");
+      BrowserToolboxProcess.init();
     } else {
       let errorMsg = "Could not run chrome debugger! You need the following prefs " +
                      "to be set to true: " + kDebuggerPrefs.join(", ");
       Cu.reportError(errorMsg);
       // Dump as well, as we're doing this from a commandline, make sure people don't miss it:
       dump(errorMsg + "\n");
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   helpInfo : "  -jsconsole         Open the Browser Console.\n" +
-             "  -jsdebugger        Open the Browser Debugger.\n",
+             "  -jsdebugger        Open the Browser Toolbox.\n",
 
   classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([devtoolsCommandlineHandler]);
rename from browser/devtools/debugger/DebuggerProcess.jsm
rename to browser/devtools/framework/ToolboxProcess.jsm
--- a/browser/devtools/debugger/DebuggerProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -2,60 +2,60 @@
 /* vim: set ft=javascript 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/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
-const DBG_XUL = "chrome://browser/content/devtools/debugger.xul";
+const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul";
 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 let require = devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
-this.EXPORTED_SYMBOLS = ["BrowserDebuggerProcess"];
+this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
 
 /**
- * Constructor for creating a process that will hold a chrome debugger.
+ * Constructor for creating a process that will hold a chrome toolbox.
  *
  * @param function aOnClose [optional]
  *        A function called when the process stops running.
  * @param function aOnRun [optional]
  *        A function called when the process starts running.
  */
-this.BrowserDebuggerProcess = function BrowserDebuggerProcess(aOnClose, aOnRun) {
+this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun) {
   this._closeCallback = aOnClose;
   this._runCallback = aOnRun;
   this._telemetry = new Telemetry();
 
   this._initServer();
   this._initProfile();
   this._create();
 };
 
 /**
- * Initializes and starts a chrome debugger process.
+ * Initializes and starts a chrome toolbox process.
  * @return object
  */
-BrowserDebuggerProcess.init = function(aOnClose, aOnRun) {
-  return new BrowserDebuggerProcess(aOnClose, aOnRun);
+BrowserToolboxProcess.init = function(aOnClose, aOnRun) {
+  return new BrowserToolboxProcess(aOnClose, aOnRun);
 };
 
-BrowserDebuggerProcess.prototype = {
+BrowserToolboxProcess.prototype = {
   /**
    * Initializes the debugger server.
    */
   _initServer: function() {
-    dumpn("Initializing the chrome debugger server.");
+    dumpn("Initializing the chrome toolbox server.");
 
     if (!this.loader) {
       // Create a separate loader instance, so that we can be sure to receive a
       // separate instance of the DebuggingServer from the rest of the devtools.
       // This allows us to safely use the tools against even the actors and
       // DebuggingServer itself.
       this.loader = new DevToolsLoader();
       this.loader.main("devtools/server/main");
@@ -66,73 +66,73 @@ BrowserDebuggerProcess.prototype = {
     if (!this.debuggerServer.initialized) {
       this.debuggerServer.init();
       this.debuggerServer.addBrowserActors();
       dumpn("initialized and added the browser actors for the DebuggerServer.");
     }
 
     this.debuggerServer.openListener(Prefs.chromeDebuggingPort);
 
-    dumpn("Finished initializing the chrome debugger server.");
+    dumpn("Finished initializing the chrome toolbox server.");
     dumpn("Started listening on port: " + Prefs.chromeDebuggingPort);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function() {
-    dumpn("Initializing the chrome debugger user profile.");
+    dumpn("Initializing the chrome toolbox user profile.");
 
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
       .createInstance(Ci.nsIToolkitProfileService);
 
     let profileName;
     try {
       // Attempt to get the required chrome debugging profile name string.
       profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME;
-      dumpn("Using chrome debugger profile name: " + profileName);
+      dumpn("Using chrome toolbox profile name: " + profileName);
     } catch (e) {
       // Requested profile string could not be retrieved.
       profileName = CHROME_DEBUGGER_PROFILE_NAME;
       let msg = "Querying the current profile failed. " + e.name + ": " + e.message;
       dumpn(msg);
       Cu.reportError(msg);
     }
 
     let profileObject;
     try {
       // Attempt to get the required chrome debugging profile toolkit object.
       profileObject = profileService.getProfileByName(profileName);
-      dumpn("Using chrome debugger profile object: " + profileObject);
+      dumpn("Using chrome toolbox profile object: " + profileObject);
 
       // The profile exists but the corresponding folder may have been deleted.
       var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries;
       while (enumerator.hasMoreElements()) {
         let profileDir = enumerator.getNext().QueryInterface(Ci.nsIFile);
         if (profileDir.leafName.contains(profileName)) {
           // Requested profile was found and the folder exists.
           this._dbgProfile = profileObject;
           return;
         }
       }
       // Requested profile was found but the folder was deleted. Cleanup needed.
       profileObject.remove(true);
-      dumpn("The already existing chrome debugger profile was invalid.");
+      dumpn("The already existing chrome toolbox profile was invalid.");
     } catch (e) {
       // Requested profile object was not found.
       let msg = "Creating a profile failed. " + e.name + ": " + e.message;
       dumpn(msg);
       Cu.reportError(msg);
     }
 
     // Create a new chrome debugging profile.
     this._dbgProfile = profileService.createProfile(null, profileName);
     profileService.flush();
 
-    dumpn("Finished creating the chrome debugger user profile.");
+    dumpn("Finished creating the chrome toolbox user profile.");
     dumpn("Flushed profile service with: " + profileName);
   },
 
   /**
    * Creates and initializes the profile & process for the remote debugger.
    */
   _create: function() {
     dumpn("Initializing chrome debugging process.");
@@ -140,17 +140,17 @@ BrowserDebuggerProcess.prototype = {
     process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
 
     dumpn("Running chrome debugging process.");
     let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", DBG_XUL];
     process.runwAsync(args, args.length, { observe: () => this.close() });
 
     this._telemetry.toolOpened("jsbrowserdebugger");
 
-    dumpn("Chrome debugger is now running...");
+    dumpn("Chrome toolbox is now running...");
     if (typeof this._runCallback == "function") {
       this._runCallback.call({}, this);
     }
   },
 
   /**
    * Closes the remote debugger, removing the profile and killing the process.
    */
@@ -159,17 +159,17 @@ BrowserDebuggerProcess.prototype = {
 
     if (this._dbgProcess.isRunning) {
       this._dbgProcess.kill();
     }
 
     this._telemetry.toolClosed("jsbrowserdebugger");
     this.debuggerServer.destroy();
 
-    dumpn("Chrome debugger is now closed...");
+    dumpn("Chrome toolbox is now closed...");
     if (typeof this._closeCallback == "function") {
       this._closeCallback.call({}, this);
     }
   }
 };
 
 /**
  * Shortcuts for accessing various debugger preferences.
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -400,22 +400,22 @@ let gDevToolsBrowser = {
     if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
       win.DeveloperToolbar.show(false);
     }
 
     // Enable App Manager?
     let appMgrEnabled = Services.prefs.getBoolPref("devtools.appmanager.enabled");
     toggleCmd("Tools:DevAppMgr", appMgrEnabled);
 
-    // Enable Chrome Debugger?
+    // Enable Browser Toolbox?
     let chromeEnabled = Services.prefs.getBoolPref("devtools.chrome.enabled");
     let devtoolsRemoteEnabled = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
     let remoteEnabled = chromeEnabled && devtoolsRemoteEnabled &&
                         Services.prefs.getBoolPref("devtools.debugger.chrome-enabled");
-    toggleCmd("Tools:ChromeDebugger", remoteEnabled);
+    toggleCmd("Tools:BrowserToolbox", remoteEnabled);
 
     // Enable Error Console?
     let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
     toggleCmd("Tools:ErrorConsole", consoleEnabled);
 
     // Enable DevTools connection screen, if the preference allows this.
     toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled);
   },
--- a/browser/devtools/framework/test/browser_dynamic_tool_enabling.js
+++ b/browser/devtools/framework/test/browser_dynamic_tool_enabling.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that toggling prefs immediately (de)activates the relevant menuitem
 
 let gItemsToTest = {
   "menu_devToolbar": "devtools.toolbar.enabled",
   "menu_devAppMgr": "devtools.appmanager.enabled",
-  "menu_chromeDebugger": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled", "devtools.debugger.chrome-enabled"],
+  "menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled", "devtools.debugger.chrome-enabled"],
   "javascriptConsole": "devtools.errorconsole.enabled",
   "menu_devtools_connect": "devtools.debugger.remote-enabled",
 };
 
 function expectedAttributeValueFromPrefs(prefs) {
   return prefs.every((pref) => Services.prefs.getBoolPref(pref)) ?
          "" : "true";
 }
--- a/browser/devtools/framework/toolbox-hosts.js
+++ b/browser/devtools/framework/toolbox-hosts.js
@@ -21,17 +21,17 @@ Cu.import("resource:///modules/devtools/
  * destroy() - destroy the host's UI
  */
 
 exports.Hosts = {
   "bottom": BottomHost,
   "side": SidebarHost,
   "window": WindowHost,
   "custom": CustomHost
-}
+};
 
 /**
  * Host object for the dock on the bottom of the browser
  */
 function BottomHost(hostTab) {
   this.hostTab = hostTab;
 
   EventEmitter.decorate(this);
@@ -267,35 +267,38 @@ WindowHost.prototype = {
       this._destroyed = true;
 
       this._window.removeEventListener("unload", this._boundUnload);
       this._window.close();
     }
 
     return promise.resolve(null);
   }
-}
+};
 
 /**
  * Host object for the toolbox in its own tab
  */
 function CustomHost(hostTab, options) {
   this.frame = options.customIframe;
   this.uid = options.uid;
   EventEmitter.decorate(this);
 }
 
 CustomHost.prototype = {
   type: "custom",
 
-  _sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg) {
+  _sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg, data) {
     // It's up to the custom frame owner (parent window) to honor
     // "close" or "raise" instructions.
     let topWindow = this.frame.ownerDocument.defaultView;
-    let json = {name:"toolbox-" + msg, uid: this.uid}
+    let json = {name:"toolbox-" + msg, uid: this.uid};
+    if (data) {
+      json.data = data;
+    }
     topWindow.postMessage(JSON.stringify(json), "*");
   },
 
   /**
    * Create a new xul window to contain the toolbox.
    */
   create: function CH_create() {
     return promise.resolve(this.frame);
@@ -307,17 +310,17 @@ CustomHost.prototype = {
   raise: function CH_raise() {
     this._sendMessageToTopWindow("raise");
   },
 
   /**
    * Set the toolbox title.
    */
   setTitle: function CH_setTitle(title) {
-    // Not supported
+    this._sendMessageToTopWindow("title", { value: title });
   },
 
   /**
    * Destroy the window.
    */
   destroy: function WH_destroy() {
     if (!this._destroyed) {
       this._destroyed = true;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+let { debuggerSocketConnect, DebuggerClient } =
+  Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+let { ViewHelpers } =
+  Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
+
+/**
+ * Shortcuts for accessing various debugger preferences.
+ */
+let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
+  chromeDebuggingHost: ["Char", "chrome-debugging-host"],
+  chromeDebuggingPort: ["Int", "chrome-debugging-port"]
+});
+
+// Initiate the connection
+let transport = debuggerSocketConnect(
+  Prefs.chromeDebuggingHost,
+  Prefs.chromeDebuggingPort
+);
+let client = new DebuggerClient(transport);
+client.connect(() => {
+  client.listTabs(openToolbox);
+});
+
+let gToolbox;
+
+function openToolbox(form) {
+  let options = {
+    form: form,
+    client: client,
+    chrome: true
+  };
+  devtools.TargetFactory.forRemoteTab(options).then(target => {
+    let frame = document.getElementById("toolbox-iframe");
+    let options = { customIframe: frame };
+    gDevTools.showToolbox(target,
+                          "jsdebugger",
+                          devtools.Toolbox.HostType.CUSTOM,
+                          options)
+             .then(onNewToolbox);
+  });
+}
+
+function onNewToolbox(toolbox) {
+   gToolbox = toolbox;
+   bindToolboxHandlers();
+   raise();
+}
+
+function bindToolboxHandlers() {
+  gToolbox.once("destroyed", quitApp);
+  window.addEventListener("unload", onUnload);
+}
+
+function onUnload() {
+  window.removeEventListener("unload", onUnload);
+  window.removeEventListener("message", onMessage);
+  gToolbox.destroy();
+}
+
+function onMessage(event) {
+  try {
+    let json = JSON.parse(event.data);
+    switch (json.name) {
+      case "toolbox-raise":
+        raise();
+        break;
+      case "toolbox-title":
+        setTitle(json.data.value);
+        break;
+    }
+  } catch(e) { Cu.reportError(e); }
+}
+
+window.addEventListener("message", onMessage);
+
+function raise() {
+  window.focus();
+}
+
+function setTitle(title) {
+  document.title = title;
+}
+
+function quitApp() {
+  let quit = Cc["@mozilla.org/supports-PRBool;1"]
+             .createInstance(Ci.nsISupportsPRBool);
+  Services.obs.notifyObservers(quit, "quit-application-requested", null);
+
+  let shouldProceed = !quit.data;
+  if (shouldProceed) {
+    Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/toolbox-process-window.xul
@@ -0,0 +1,41 @@
+<?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 window [
+<!ENTITY % toolboxDTD SYSTEM "chrome://browser/locale/devtools/toolbox.dtd" >
+ %toolboxDTD;
+]>
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        id="devtools-toolbox-window"
+        macanimationtype="document"
+        fullscreenbutton="true"
+        windowtype="devtools:toolbox"
+        width="900" height="600"
+        persist="screenX screenY width height sizemode">
+
+  <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
+  <script type="text/javascript" src="toolbox-process-window.js"/>
+  <script type="text/javascript" src="chrome://global/content/viewSourceUtils.js"/>
+  <script type="text/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+  <commandset id="toolbox-commandset">
+    <command id="toolbox-cmd-close" oncommand="window.close();"/>
+  </commandset>
+
+  <keyset id="toolbox-keyset">
+    <key id="toolbox-key-close"
+         key="&closeCmd.key;"
+         command="toolbox-cmd-close"
+         modifiers="accel"/>
+  </keyset>
+
+  <!-- This will be used by the Web Console to hold any popups it may create,
+       for example when viewing network request details. -->
+  <popupset id="mainPopupSet"></popupset>
+
+  <iframe id="toolbox-iframe" flex="1"></iframe>
+</window>
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -71,16 +71,18 @@ browser.jar:
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
     content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
 *   content/browser/devtools/framework/toolbox.xul                     (framework/toolbox.xul)
     content/browser/devtools/framework/toolbox.css                     (framework/toolbox.css)
+    content/browser/devtools/framework/toolbox-process-window.xul      (framework/toolbox-process-window.xul)
+    content/browser/devtools/framework/toolbox-process-window.js       (framework/toolbox-process-window.js)
     content/browser/devtools/inspector/inspector.xul                   (inspector/inspector.xul)
     content/browser/devtools/inspector/inspector.css                   (inspector/inspector.css)
     content/browser/devtools/connect.xhtml                             (framework/connect/connect.xhtml)
     content/browser/devtools/connect.css                               (framework/connect/connect.css)
     content/browser/devtools/connect.js                                (framework/connect/connect.js)
     content/browser/devtools/app-manager/template.js                   (app-manager/content/template.js)
     content/browser/devtools/app-manager/utils.js                      (app-manager/content/utils.js)
     content/browser/devtools/app-manager/connection-footer.js          (app-manager/content/connection-footer.js)
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -50,27 +50,40 @@ const EVENTS = {
   STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
   UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
   RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
 
   // When the request post params are displayed in the UI.
   REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
 
   // When the response body is displayed in the UI.
-  RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable"
-}
+  RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
+
+  // When `onTabSelect` is fired and subsequently rendered
+  TAB_UPDATED: "NetMonitor:TabUpdated",
+
+  // Fired when Sidebar is finished being populated
+  SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
+
+  // Fired when NetworkDetailsView is finished being populated
+  NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
+
+  // Fired when NetworkDetailsView is finished being populated
+  CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated"
+};
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const Editor = require("devtools/sourceeditor/editor");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
--- a/browser/devtools/netmonitor/netmonitor-panel.js
+++ b/browser/devtools/netmonitor/netmonitor-panel.js
@@ -1,17 +1,17 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript 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/. */
 "use strict";
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
-const promise = require("sdk/core/promise");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 const EventEmitter = require("devtools/shared/event-emitter");
 
 function NetMonitorPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
   this._destroyer = null;
 
   this._view = this.panelWin.NetMonitorView;
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -1377,25 +1377,29 @@ SidebarView.prototype = {
     NetMonitorView.RequestsMenu._flushWaterfallViews(true);
   },
 
   /**
    * Populates this view with the specified data.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
+   * @return object
+   *        Returns a promise that resolves upon population of the subview.
    */
   populate: function(aData) {
-    if (aData.isCustom) {
-      NetMonitorView.CustomRequest.populate(aData);
-      $("#details-pane").selectedIndex = 0;
-    } else {
-      NetMonitorView.NetworkDetails.populate(aData);
-      $("#details-pane").selectedIndex = 1;
-    }
+    let isCustom = aData.isCustom;
+    let view = isCustom ?
+      NetMonitorView.CustomRequest :
+      NetMonitorView.NetworkDetails;
+
+    return view.populate(aData).then(() => {
+      $("#details-pane").selectedIndex = isCustom ? 0 : 1
+      window.emit(EVENTS.SIDEBAR_POPULATED)
+    });
   },
 
   /**
    * Hides this container.
    */
   reset: function() {
     this.toggle(false);
   }
@@ -1409,32 +1413,41 @@ function CustomRequestView() {
 }
 
 CustomRequestView.prototype = {
   /**
    * Populates this view with the specified data.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
+   * @return object
+   *        Returns a promise that resolves upon population the view.
    */
   populate: function(aData) {
     $("#custom-url-value").value = aData.url;
     $("#custom-method-value").value = aData.method;
     $("#custom-headers-value").value =
        writeHeaderText(aData.requestHeaders.headers);
 
+    let view = this;
+    let postDataPromise = null;
+
     if (aData.requestPostData) {
       let body = aData.requestPostData.postData.text;
 
-      gNetwork.getString(body).then(aString => {
+      postDataPromise = gNetwork.getString(body).then(aString => {
         $("#custom-postdata-value").value =  aString;
       });
+    } else {
+      postDataPromise = promise.resolve();
     }
 
-    this.updateCustomQuery(aData.url);
+    return postDataPromise
+      .then(() => view.updateCustomQuery(aData.url))
+      .then(() => window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED));
   },
 
   /**
    * Handle user input in the custom request form.
    *
    * @param object aField
    *        the field that the user updated.
    */
@@ -1579,16 +1592,18 @@ NetworkDetailsView.prototype = {
     this._dataSrc = null;
   },
 
   /**
    * Populates this view with the specified data.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
+   * @return object
+   *        Returns a promise that resolves upon population the view.
    */
   populate: function(aData) {
     $("#request-params-box").setAttribute("flex", "1");
     $("#request-params-box").hidden = false;
     $("#request-post-data-textarea-box").hidden = true;
     $("#response-content-info-header").hidden = true;
     $("#response-content-json-box").hidden = true;
     $("#response-content-textarea-box").hidden = true;
@@ -1596,53 +1611,59 @@ NetworkDetailsView.prototype = {
 
     this._headers.empty();
     this._cookies.empty();
     this._params.empty();
     this._json.empty();
 
     this._dataSrc = { src: aData, populated: [] };
     this._onTabSelect();
+    window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+
+    return promise.resolve();
   },
 
   /**
    * Listener handling the tab selection event.
    */
   _onTabSelect: function() {
     let { src, populated } = this._dataSrc || {};
     let tab = this.widget.selectedIndex;
+    let view = this;
 
     // Make sure the data source is valid and don't populate the same tab twice.
     if (!src || populated[tab]) {
       return;
     }
 
-    switch (tab) {
-      case 0: // "Headers"
-        this._setSummary(src);
-        this._setResponseHeaders(src.responseHeaders);
-        this._setRequestHeaders(src.requestHeaders);
-        break;
-      case 1: // "Cookies"
-        this._setResponseCookies(src.responseCookies);
-        this._setRequestCookies(src.requestCookies);
-        break;
-      case 2: // "Params"
-        this._setRequestGetParams(src.url);
-        this._setRequestPostParams(src.requestHeaders, src.requestPostData);
-        break;
-      case 3: // "Response"
-        this._setResponseBody(src.url, src.responseContent);
-        break;
-      case 4: // "Timings"
-        this._setTimingsInformation(src.eventTimings);
-        break;
-    }
-
-    populated[tab] = true;
+    Task.spawn(function*() {
+      switch (tab) {
+        case 0: // "Headers"
+          yield view._setSummary(src);
+          yield view._setResponseHeaders(src.responseHeaders);
+          yield view._setRequestHeaders(src.requestHeaders);
+          break;
+        case 1: // "Cookies"
+          yield view._setResponseCookies(src.responseCookies);
+          yield view._setRequestCookies(src.requestCookies);
+          break;
+        case 2: // "Params"
+          yield view._setRequestGetParams(src.url);
+          yield view._setRequestPostParams(src.requestHeaders, src.requestPostData);
+          break;
+        case 3: // "Response"
+          yield view._setResponseBody(src.url, src.responseContent);
+          break;
+        case 4: // "Timings"
+          yield view._setTimingsInformation(src.eventTimings);
+          break;
+      }
+      populated[tab] = true;
+      window.emit(EVENTS.TAB_UPDATED);
+    });
   },
 
   /**
    * Sets the network request summary shown in this view.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
    */
@@ -1679,116 +1700,135 @@ NetworkDetailsView.prototype = {
     }
   },
 
   /**
    * Sets the network request headers shown in this view.
    *
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that resolves when request headers are set.
    */
   _setRequestHeaders: function(aResponse) {
     if (aResponse && aResponse.headers.length) {
-      this._addHeaders(this._requestHeaders, aResponse);
+      return this._addHeaders(this._requestHeaders, aResponse);
     }
+    return promise.resolve();
   },
 
   /**
    * Sets the network response headers shown in this view.
    *
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that resolves when response headers are set.
    */
   _setResponseHeaders: function(aResponse) {
     if (aResponse && aResponse.headers.length) {
       aResponse.headers.sort((a, b) => a.name > b.name);
-      this._addHeaders(this._responseHeaders, aResponse);
+      return this._addHeaders(this._responseHeaders, aResponse);
     }
+    return promise.resolve();
   },
 
   /**
    * Populates the headers container in this view with the specified data.
    *
    * @param string aName
    *        The type of headers to populate (request or response).
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that resolves when headers are added.
    */
   _addHeaders: function(aName, aResponse) {
     let kb = aResponse.headersSize / 1024;
     let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
     let text = L10N.getFormatStr("networkMenu.sizeKB", size);
     let headersScope = this._headers.addScope(aName + " (" + text + ")");
     headersScope.expanded = true;
 
-    for (let header of aResponse.headers) {
+    return promise.all(aResponse.headers.map(header => {
       let headerVar = headersScope.addItem(header.name, {}, true);
-      gNetwork.getString(header.value).then(aString => headerVar.setGrip(aString));
-    }
+      return gNetwork.getString(header.value)
+             .then(aString => headerVar.setGrip(aString));
+    }));
   },
 
   /**
    * Sets the network request cookies shown in this view.
    *
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that is resolved when the request cookies are set.
    */
   _setRequestCookies: function(aResponse) {
     if (aResponse && aResponse.cookies.length) {
       aResponse.cookies.sort((a, b) => a.name > b.name);
-      this._addCookies(this._requestCookies, aResponse);
+      return this._addCookies(this._requestCookies, aResponse);
     }
+    return promise.resolve();
   },
 
   /**
    * Sets the network response cookies shown in this view.
    *
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that is resolved when the response cookies are set.
    */
   _setResponseCookies: function(aResponse) {
     if (aResponse && aResponse.cookies.length) {
-      this._addCookies(this._responseCookies, aResponse);
+      return this._addCookies(this._responseCookies, aResponse);
     }
+    return promise.resolve();
   },
 
   /**
    * Populates the cookies container in this view with the specified data.
    *
    * @param string aName
    *        The type of cookies to populate (request or response).
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        Returns a promise that resolves upon the adding of cookies.
    */
   _addCookies: function(aName, aResponse) {
     let cookiesScope = this._cookies.addScope(aName);
     cookiesScope.expanded = true;
 
-    for (let cookie of aResponse.cookies) {
+    return promise.all(aResponse.cookies.map(cookie => {
       let cookieVar = cookiesScope.addItem(cookie.name, {}, true);
-      gNetwork.getString(cookie.value).then(aString => cookieVar.setGrip(aString));
+      return gNetwork.getString(cookie.value).then(aString => {
+        cookieVar.setGrip(aString);
 
-      // By default the cookie name and value are shown. If this is the only
-      // information available, then nothing else is to be displayed.
-      let cookieProps = Object.keys(cookie);
-      if (cookieProps.length == 2) {
-        continue;
-      }
+        // By default the cookie name and value are shown. If this is the only
+        // information available, then nothing else is to be displayed.
+        let cookieProps = Object.keys(cookie);
+        if (cookieProps.length == 2) {
+          return;
+        }
 
-      // Display any other information other than the cookie name and value
-      // which may be available.
-      let rawObject = Object.create(null);
-      let otherProps = cookieProps.filter(e => e != "name" && e != "value");
-      for (let prop of otherProps) {
-        rawObject[prop] = cookie[prop];
-      }
-      cookieVar.populate(rawObject);
-      cookieVar.twisty = true;
-      cookieVar.expanded = true;
-    }
+        // Display any other information other than the cookie name and value
+        // which may be available.
+        let rawObject = Object.create(null);
+        let otherProps = cookieProps.filter(e => e != "name" && e != "value");
+        for (let prop of otherProps) {
+          rawObject[prop] = cookie[prop];
+        }
+        cookieVar.populate(rawObject);
+        cookieVar.twisty = true;
+        cookieVar.expanded = true;
+      });
+    }));
   },
 
   /**
    * Sets the network request get params shown in this view.
    *
    * @param string aUrl
    *        The request's url.
    */
@@ -1801,22 +1841,24 @@ NetworkDetailsView.prototype = {
 
   /**
    * Sets the network request post params shown in this view.
    *
    * @param object aHeadersResponse
    *        The "requestHeaders" message received from the server.
    * @param object aPostDataResponse
    *        The "requestPostData" message received from the server.
+   * @return object
+   *        A promise that is resolved when the request post params are set.
    */
   _setRequestPostParams: function(aHeadersResponse, aPostDataResponse) {
     if (!aHeadersResponse || !aPostDataResponse) {
-      return;
+      return promise.resolve();
     }
-    gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
+    return gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
       // Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
       let cType = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
       let cString = cType ? cType.value : "";
       if (cString.contains("x-www-form-urlencoded") ||
           aString.contains("x-www-form-urlencoded")) {
         let formDataGroups = aString.split(/\r\n|\n|\r/);
         for (let group of formDataGroups) {
           this._addParams(this._paramsFormData, group);
@@ -1828,22 +1870,21 @@ NetworkDetailsView.prototype = {
         // scope in the params view and place the source editor containing
         // the raw post data directly underneath.
         $("#request-params-box").removeAttribute("flex");
         let paramsScope = this._params.addScope(this._paramsPostPayload);
         paramsScope.expanded = true;
         paramsScope.locked = true;
 
         $("#request-post-data-textarea-box").hidden = false;
-        NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
+        return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
           aEditor.setText(aString);
         });
       }
-      window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
-    });
+    }).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
   },
 
   /**
    * Populates the params container in this view with the specified data.
    *
    * @param string aName
    *        The type of params to populate (get or post).
    * @param string aQueryString
@@ -1865,24 +1906,26 @@ NetworkDetailsView.prototype = {
 
   /**
    * Sets the network response body shown in this view.
    *
    * @param string aUrl
    *        The request's url.
    * @param object aResponse
    *        The message received from the server.
+   * @return object
+   *        A promise that is resolved when the response body is set
    */
   _setResponseBody: function(aUrl, aResponse) {
     if (!aResponse) {
-      return;
+      return promise.resolve();
     }
     let { mimeType, text, encoding } = aResponse.content;
 
-    gNetwork.getString(text).then(aString => {
+    return gNetwork.getString(text).then(aString => {
       // Handle json, which we tentatively identify by checking the MIME type
       // for "json" after any word boundary. This works for the standard
       // "application/json", and also for custom types like "x-bigcorp-json".
       // This should be marginally more reliable than just looking for "json".
       if (/\bjson/.test(mimeType)) {
         let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; // JSONP with callback.
         let sanitizedJSON = aString.replace(jsonpRegex, "");
         let callbackPadding = aString.match(jsonpRegex);
@@ -1898,32 +1941,32 @@ NetworkDetailsView.prototype = {
 
         // Valid JSON.
         if (jsonObject) {
           $("#response-content-json-box").hidden = false;
           let jsonScopeName = callbackPadding
             ? L10N.getFormatStr("jsonpScopeName", callbackPadding[0].slice(0, -1))
             : L10N.getStr("jsonScopeName");
 
-          this._json.controller.setSingleVariable({
+          return this._json.controller.setSingleVariable({
             label: jsonScopeName,
             rawObject: jsonObject,
-          });
+          }).expanded;
         }
         // Malformed JSON.
         else {
           $("#response-content-textarea-box").hidden = false;
-          NetMonitorView.editor("#response-content-textarea").then(aEditor => {
-            aEditor.setMode(Editor.modes.js);
-            aEditor.setText(aString);
-          });
           let infoHeader = $("#response-content-info-header");
           infoHeader.setAttribute("value", parsingError);
           infoHeader.setAttribute("tooltiptext", parsingError);
           infoHeader.hidden = false;
+          return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
+            aEditor.setMode(Editor.modes.js);
+            aEditor.setText(aString);
+          });
         }
       }
       // Handle images.
       else if (mimeType.contains("image/")) {
         $("#response-content-image-box").setAttribute("align", "center");
         $("#response-content-image-box").setAttribute("pack", "center");
         $("#response-content-image-box").hidden = false;
         $("#response-content-image").src =
@@ -1943,34 +1986,33 @@ NetworkDetailsView.prototype = {
           let { width, height } = e.target.getBoundingClientRect();
           let dimensions = (width - 2) + " x " + (height - 2);
           $("#response-content-image-dimensions-value").setAttribute("value", dimensions);
         };
       }
       // Handle anything else.
       else {
         $("#response-content-textarea-box").hidden = false;
-        NetMonitorView.editor("#response-content-textarea").then(aEditor => {
+        return NetMonitorView.editor("#response-content-textarea").then(aEditor => {
           aEditor.setMode(Editor.modes.text);
           aEditor.setText(aString);
 
           // Maybe set a more appropriate mode in the Source Editor if possible,
           // but avoid doing this for very large files.
           if (aString.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
             for (let key in CONTENT_MIME_TYPE_MAPPINGS) {
               if (mimeType.contains(key)) {
                 aEditor.setMode(CONTENT_MIME_TYPE_MAPPINGS[key]);
                 break;
               }
             }
           }
         });
       }
-      window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
-    });
+    }).then(() => window.emit(EVENTS.RESPONSE_BODY_DISPLAYED));
   },
 
   /**
    * Sets the timings information shown in this view.
    *
    * @param object aResponse
    *        The message received from the server.
    */
--- a/browser/devtools/netmonitor/test/browser_net_content-type.js
+++ b/browser/devtools/netmonitor/test/browser_net_content-type.js
@@ -70,41 +70,37 @@ function test() {
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      testResponseTab("xml")
-        .then(() => {
-          RequestsMenu.selectedIndex = 1;
-          return testResponseTab("css");
-        })
-        .then(() => {
-          RequestsMenu.selectedIndex = 2;
-          return testResponseTab("js");
-        })
-        .then(() => {
-          RequestsMenu.selectedIndex = 3;
-          return testResponseTab("json");
-        })
-        .then(() => {
-          RequestsMenu.selectedIndex = 4;
-          return testResponseTab("html");
-        })
-        .then(() => {
-          RequestsMenu.selectedIndex = 5;
-          return testResponseTab("png");
-        })
-        .then(() => {
-          return teardown(aMonitor);
-        })
-        .then(finish);
+      Task.spawn(function*() {
+        yield waitForResponseBodyDisplayed();
+        yield testResponseTab("xml");
+        RequestsMenu.selectedIndex = 1;
+        yield waitForTabUpdated();
+        yield testResponseTab("css");
+        RequestsMenu.selectedIndex = 2;
+        yield waitForTabUpdated();
+        yield testResponseTab("js");
+        RequestsMenu.selectedIndex = 3;
+        yield waitForTabUpdated();
+        yield testResponseTab("json");
+        RequestsMenu.selectedIndex = 4;
+        yield waitForTabUpdated();
+        yield testResponseTab("html");
+        RequestsMenu.selectedIndex = 5;
+        yield waitForTabUpdated();
+        yield testResponseTab("png");
+        yield teardown(aMonitor);
+        finish();
+      });
 
       function testResponseTab(aType) {
         let tab = document.querySelectorAll("#details-pane tab")[3];
         let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
         is(tab.getAttribute("selected"), "true",
           "The response tab in the network details pane should be selected.");
 
@@ -216,13 +212,21 @@ function test() {
 
               deferred.resolve();
             });
 
             return deferred.promise;
           }
         }
       }
+
+      function waitForTabUpdated () {
+        return waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
+      }
+
+      function waitForResponseBodyDisplayed () {
+        return waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+      }
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_cyrillic-01.js
@@ -21,17 +21,20 @@ function test() {
           statusText: "DA DA DA"
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
+      let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
+      waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() =>
+        NetMonitorView.editor("#response-content-textarea")
+      ).then((aEditor) => {
         is(aEditor.getText().indexOf("\u044F"), 26, // я
           "The text shown in the source editor is incorrect.");
         is(aEditor.getMode(), Editor.modes.text,
           "The mode active in the source editor is incorrect.");
 
         teardown(aMonitor).then(finish);
       });
     });
--- a/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_cyrillic-02.js
@@ -22,17 +22,20 @@ function test() {
           statusText: "OK"
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
+      let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
+      waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() =>
+        NetMonitorView.editor("#response-content-textarea")
+      ).then((aEditor) => {
         is(aEditor.getText().indexOf("\u044F"), 302, // я
           "The text shown in the source editor is incorrect.");
         is(aEditor.getMode(), Editor.modes.html,
           "The mode active in the source editor is incorrect.");
 
         teardown(aMonitor).then(finish);
       });
     });
--- a/browser/devtools/netmonitor/test/browser_net_json-malformed.js
+++ b/browser/devtools/netmonitor/test/browser_net_json-malformed.js
@@ -26,46 +26,49 @@ function test() {
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
       let tab = document.querySelectorAll("#details-pane tab")[3];
       let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
-      is(tab.getAttribute("selected"), "true",
-        "The response tab in the network details pane should be selected.");
+      let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
+      waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED).then(() => {
+        is(tab.getAttribute("selected"), "true",
+          "The response tab in the network details pane should be selected.");
 
-      is(tabpanel.querySelector("#response-content-info-header")
-        .hasAttribute("hidden"), false,
-        "The response info header doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-info-header")
-        .getAttribute("value"),
-        "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
-        "The response info header doesn't have the intended value attribute.");
-      is(tabpanel.querySelector("#response-content-info-header")
-        .getAttribute("tooltiptext"),
-        "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
-        "The response info header doesn't have the intended tooltiptext attribute.");
+        is(tabpanel.querySelector("#response-content-info-header")
+          .hasAttribute("hidden"), false,
+          "The response info header doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-info-header")
+          .getAttribute("value"),
+          "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
+          "The response info header doesn't have the intended value attribute.");
+        is(tabpanel.querySelector("#response-content-info-header")
+          .getAttribute("tooltiptext"),
+          "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data",
+          "The response info header doesn't have the intended tooltiptext attribute.");
 
-      is(tabpanel.querySelector("#response-content-json-box")
-        .hasAttribute("hidden"), true,
-        "The response content json box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-textarea-box")
-        .hasAttribute("hidden"), false,
-        "The response content textarea box doesn't have the intended visibility.");
-      is(tabpanel.querySelector("#response-content-image-box")
-        .hasAttribute("hidden"), true,
-        "The response content image box doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-json-box")
+          .hasAttribute("hidden"), true,
+          "The response content json box doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-textarea-box")
+          .hasAttribute("hidden"), false,
+          "The response content textarea box doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-image-box")
+          .hasAttribute("hidden"), true,
+          "The response content image box doesn't have the intended visibility.");
 
-      NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
-        is(aEditor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
-          "The text shown in the source editor is incorrect.");
-        is(aEditor.getMode(), Editor.modes.js,
-          "The mode active in the source editor is incorrect.");
+        NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
+          is(aEditor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
+            "The text shown in the source editor is incorrect.");
+          is(aEditor.getMode(), Editor.modes.js,
+            "The mode active in the source editor is incorrect.");
 
-        teardown(aMonitor).then(finish);
+          teardown(aMonitor).then(finish);
+        });
       });
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js
+++ b/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js
@@ -25,18 +25,21 @@ function test() {
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      testResponseTab();
-      teardown(aMonitor).then(finish);
+      let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
+      waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED)
+        .then(testResponseTab)
+        .then(() => teardown(aMonitor))
+        .then(finish);
 
       function testResponseTab() {
         let tab = document.querySelectorAll("#details-pane tab")[3];
         let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
         is(tab.getAttribute("selected"), "true",
           "The response tab in the network details pane should be selected.");
 
--- a/browser/devtools/netmonitor/test/browser_net_jsonp.js
+++ b/browser/devtools/netmonitor/test/browser_net_jsonp.js
@@ -25,18 +25,21 @@ function test() {
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      testResponseTab();
-      teardown(aMonitor).then(finish);
+      let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED;
+      waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED)
+        .then(testResponseTab)
+        .then(() => teardown(aMonitor))
+        .then(finish);
 
       function testResponseTab() {
         let tab = document.querySelectorAll("#details-pane tab")[3];
         let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
         is(tab.getAttribute("selected"), "true",
           "The response tab in the network details pane should be selected.");
 
--- a/browser/devtools/netmonitor/test/browser_net_post-data-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_post-data-01.js
@@ -35,24 +35,24 @@ function test() {
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[2]);
 
-      testParamsTab("urlencoded")
-        .then(() => {
-          RequestsMenu.selectedIndex = 1;
-          return testParamsTab("multipart");
-        })
-        .then(() => {
-          return teardown(aMonitor);
-        })
+      let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
+      waitFor(aMonitor.panelWin, TAB_UPDATED).then(() =>
+        testParamsTab("urlencoded")
+      ).then(() => {
+        RequestsMenu.selectedIndex = 1;
+        return waitFor(aMonitor.panelWin, TAB_UPDATED);
+      }).then(() => testParamsTab("multipart"))
+        .then(() => teardown(aMonitor))
         .then(finish);
 
       function testParamsTab(aType) {
         let tab = document.querySelectorAll("#details-pane tab")[2];
         let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
 
         is(tab.getAttribute("selected"), "true",
           "The params tab in the network details pane should be selected.");
--- a/browser/devtools/netmonitor/test/browser_net_post-data-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_post-data-02.js
@@ -15,48 +15,50 @@ function test() {
 
     RequestsMenu.lazyUpdate = false;
     NetworkDetails._params.lazyEmpty = false;
 
     waitForNetworkEvents(aMonitor, 0, 1).then(() => {
       NetMonitorView.toggleDetailsPane({ visible: true }, 2)
       RequestsMenu.selectedIndex = 0;
 
-      let tab = document.querySelectorAll("#event-details-pane tab")[2];
-      let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
-
-      is(tab.getAttribute("selected"), "true",
-        "The params tab in the network details pane should be selected.");
+      let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
+      waitFor(aMonitor.panelWin, TAB_UPDATED).then(() => {
+        let tab = document.querySelectorAll("#event-details-pane tab")[2];
+        let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
 
-      is(tabpanel.querySelector("#request-params-box")
-        .hasAttribute("hidden"), false,
-        "The request params box doesn't have the indended visibility.");
-      is(tabpanel.querySelector("#request-post-data-textarea-box")
-        .hasAttribute("hidden"), true,
-        "The request post data textarea box doesn't have the indended visibility.");
+        is(tab.getAttribute("selected"), "true",
+          "The params tab in the network details pane should be selected.");
 
-      is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
-        "There should be 1 param scopes displayed in this tabpanel.");
-      is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
-        "The empty notice should not be displayed in this tabpanel.");
+        is(tabpanel.querySelector("#request-params-box")
+          .hasAttribute("hidden"), false,
+          "The request params box doesn't have the indended visibility.");
+        is(tabpanel.querySelector("#request-post-data-textarea-box")
+          .hasAttribute("hidden"), true,
+          "The request post data textarea box doesn't have the indended visibility.");
 
-      let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
-      is(postScope.querySelector(".name").getAttribute("value"),
-        L10N.getStr("paramsFormData"),
-        "The post scope doesn't have the correct title.");
+        is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+          "There should be 1 param scopes displayed in this tabpanel.");
+        is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+          "The empty notice should not be displayed in this tabpanel.");
+
+        let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+        is(postScope.querySelector(".name").getAttribute("value"),
+          L10N.getStr("paramsFormData"),
+          "The post scope doesn't have the correct title.");
 
-      is(postScope.querySelectorAll(".variables-view-variable").length, 2,
-        "There should be 2 param values displayed in the post scope.");
-      is(postScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
-        "foo", "The first query param name was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
-        "\"bar\"", "The first query param value was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
-        "baz", "The second query param name was incorrect.");
-      is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
-        "\"123\"", "The second query param value was incorrect.");
-
-      teardown(aMonitor).then(finish);
+        is(postScope.querySelectorAll(".variables-view-variable").length, 2,
+          "There should be 2 param values displayed in the post scope.");
+        is(postScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
+          "foo", "The first query param name was incorrect.");
+        is(postScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
+          "\"bar\"", "The first query param value was incorrect.");
+        is(postScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
+          "baz", "The second query param name was incorrect.");
+        is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
+          "\"123\"", "The second query param value was incorrect.");
+        teardown(aMonitor).then(finish);
+      });
     });
 
     aDebuggee.performRequests();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_resend.js
+++ b/browser/devtools/netmonitor/test/browser_net_resend.js
@@ -16,41 +16,47 @@ function test() {
   initNetMonitor(POST_DATA_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     gPanelWin = aMonitor.panelWin;
     gPanelDoc = gPanelWin.document;
 
     let { NetMonitorView } = gPanelWin;
     let { RequestsMenu } = NetMonitorView;
+    let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
+    let CUSTOMREQUESTVIEW_POPULATED = aMonitor.panelWin.EVENTS.CUSTOMREQUESTVIEW_POPULATED;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 0, 2).then(() => {
       let origItem = RequestsMenu.getItemAtIndex(0);
       RequestsMenu.selectedItem = origItem;
 
-      // add a new custom request cloned from selected request
-      RequestsMenu.cloneSelectedRequest();
-      testCustomForm(origItem.attachment);
+      waitFor(aMonitor.panelWin, TAB_UPDATED).then(() => {
+        // add a new custom request cloned from selected request
+        RequestsMenu.cloneSelectedRequest();
+        return waitFor(aMonitor.panelWin, CUSTOMREQUESTVIEW_POPULATED);
+      }).then(() => {
+        testCustomForm(origItem.attachment);
 
-      let customItem = RequestsMenu.selectedItem;
-      testCustomItem(customItem, origItem);
+        let customItem = RequestsMenu.selectedItem;
+        testCustomItem(customItem, origItem);
 
-      // edit the custom request
-      editCustomForm(() => {
-        testCustomItemChanged(customItem, origItem);
+        // edit the custom request
+        editCustomForm(() => {
+          testCustomItemChanged(customItem, origItem);
 
-        waitForNetworkEvents(aMonitor, 0, 1).then(() => {
-          let sentItem = RequestsMenu.selectedItem;
-          testSentRequest(sentItem.attachment, origItem.attachment);
-          finishUp(aMonitor);
+          waitForNetworkEvents(aMonitor, 0, 1).then(() => {
+            let sentItem = RequestsMenu.selectedItem;
+            testSentRequest(sentItem.attachment, origItem.attachment);
+            finishUp(aMonitor);
+          });
+          // send the new request
+          RequestsMenu.sendCustomRequest();
         });
-        // send the new request
-        RequestsMenu.sendCustomRequest();
       });
     });
 
     aDebuggee.performRequests();
   });
 }
 
 function testCustomItem(aItem, aOrigItem) {
--- a/browser/devtools/netmonitor/test/browser_net_simple-request-details.js
+++ b/browser/devtools/netmonitor/test/browser_net_simple-request-details.js
@@ -6,20 +6,21 @@
  */
 
 function test() {
   initNetMonitor(SIMPLE_SJS).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { document, L10N, Editor, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu, NetworkDetails } = NetMonitorView;
-
+    let TAB_UPDATED = aMonitor.panelWin.EVENTS.TAB_UPDATED;
     RequestsMenu.lazyUpdate = false;
 
-    waitForNetworkEvents(aMonitor, 1).then(() => {
+    Task.spawn(function () {
+      yield waitForNetworkEvents(aMonitor, 1);
       is(RequestsMenu.selectedItem, null,
         "There shouldn't be any selected item in the requests menu.");
       is(RequestsMenu.itemCount, 1,
         "The requests menu should not be empty after the first request.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should still be hidden after the first request.");
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
@@ -27,25 +28,24 @@ function test() {
 
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
 
+      yield waitFor(aMonitor.panelWin, TAB_UPDATED)
       testHeadersTab();
       testCookiesTab();
       testParamsTab();
-      testResponseTab()
-        .then(() => {
-          testTimingsTab();
-          return teardown(aMonitor);
-        })
-        .then(finish);
+      yield testResponseTab();
+      testTimingsTab();
+      yield teardown(aMonitor);
+      finish();
     });
 
     function testHeadersTab() {
       let tab = document.querySelectorAll("#details-pane tab")[0];
       let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
 
       is(tab.getAttribute("selected"), "true",
         "The headers tab in the network details pane should be selected.");
@@ -167,36 +167,39 @@ function test() {
         .hasAttribute("hidden"), true,
         "The request post data textarea box should be hidden.");
     }
 
     function testResponseTab() {
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
-      let tab = document.querySelectorAll("#details-pane tab")[3];
-      let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+      return Task.spawn(function () {
+        yield waitFor(aMonitor.panelWin, TAB_UPDATED);
 
-      is(tab.getAttribute("selected"), "true",
-        "The response tab in the network details pane should be selected.");
+        let tab = document.querySelectorAll("#details-pane tab")[3];
+        let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+        is(tab.getAttribute("selected"), "true",
+          "The response tab in the network details pane should be selected.");
 
-      is(tabpanel.querySelector("#response-content-info-header")
-        .hasAttribute("hidden"), true,
-        "The response info header should be hidden.");
-      is(tabpanel.querySelector("#response-content-json-box")
-        .hasAttribute("hidden"), true,
-        "The response content json box should be hidden.");
-      is(tabpanel.querySelector("#response-content-textarea-box")
-        .hasAttribute("hidden"), false,
-        "The response content textarea box should not be hidden.");
-      is(tabpanel.querySelector("#response-content-image-box")
-        .hasAttribute("hidden"), true,
-        "The response content image box should be hidden.");
+        is(tabpanel.querySelector("#response-content-info-header")
+          .hasAttribute("hidden"), true,
+          "The response info header should be hidden.");
+        is(tabpanel.querySelector("#response-content-json-box")
+          .hasAttribute("hidden"), true,
+          "The response content json box should be hidden.");
+        is(tabpanel.querySelector("#response-content-textarea-box")
+          .hasAttribute("hidden"), false,
+          "The response content textarea box should not be hidden.");
+        is(tabpanel.querySelector("#response-content-image-box")
+          .hasAttribute("hidden"), true,
+          "The response content image box should be hidden.");
 
-      return NetMonitorView.editor("#response-content-textarea").then((aEditor) => {
+        let aEditor = yield NetMonitorView.editor("#response-content-textarea");
         is(aEditor.getText(), "Hello world!",
           "The text shown in the source editor is incorrect.");
         is(aEditor.getMode(), Editor.modes.text,
           "The mode active in the source editor is incorrect.");
       });
     }
 
     function testTimingsTab() {
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test/";
 
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
@@ -277,8 +278,25 @@ function verifyRequestItemTarget(aReques
     } else {
       ok(!aRequestItem.target.hasAttribute("even"),
         "Unexpected 'even' attribute for " + aRequestItem.value);
       ok(aRequestItem.target.hasAttribute("odd"),
         "Unexpected 'odd' attribute for " + aRequestItem.value);
     }
   }
 }
+
+/**
+ * Helper function for waiting for an event to fire before resolving a promise.
+ * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
+ *
+ * @param object subject
+ *        The event emitter object that is being listened to.
+ * @param string eventName
+ *        The name of the event to listen to.
+ * @return object
+ *        Returns a promise that resolves upon firing of the event.
+ */
+function waitFor (subject, eventName) {
+  let deferred = promise.defer();
+  subject.once(eventName, deferred.resolve);
+  return deferred.promise;
+}
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -171,17 +171,17 @@ function ResponsiveUI(aWindow, aTab)
   this.bound_startResizing = this.startResizing.bind(this);
   this.bound_stopResizing = this.stopResizing.bind(this);
   this.bound_onDrag = this.onDrag.bind(this);
   this.bound_onKeypress = this.onKeypress.bind(this);
 
   // Events
   this.tab.addEventListener("TabClose", this);
   this.tabContainer.addEventListener("TabSelect", this);
-  this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
+  this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, true);
 
   this.buildUI();
   this.checkMenus();
 
   this.docShell = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                       .getInterface(Ci.nsIWebNavigation)
                       .QueryInterface(Ci.nsIDocShell);
 
@@ -271,17 +271,17 @@ ResponsiveUI.prototype = {
                 "max-height: none;" +
                 "min-height: 0;";
     this.stack.setAttribute("style", style);
 
     if (this.isResizing)
       this.stopResizing();
 
     // Remove listeners.
-    this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, false);
+    this.mainWindow.document.removeEventListener("keypress", this.bound_onKeypress, true);
     this.menulist.removeEventListener("select", this.bound_presetSelected, true);
     this.tab.removeEventListener("TabClose", this);
     this.tabContainer.removeEventListener("TabSelect", this);
     this.rotatebutton.removeEventListener("command", this.bound_rotate, true);
     this.screenshotbutton.removeEventListener("command", this.bound_screenshot, true);
     this.touchbutton.removeEventListener("command", this.bound_touch, true);
     this.closebutton.removeEventListener("command", this.bound_close, true);
     this.addbutton.removeEventListener("command", this.bound_addPreset, true);
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -331,34 +331,53 @@ WebConsole.prototype = {
    * by unit tests. The callback takes one argument: the HTTP activity object as
    * received from the remote Web Console.
    *
    * @type function
    */
   get lastFinishedRequestCallback() HUDService.lastFinishedRequest.callback,
 
   /**
+   * Getter for the window that can provide various utilities that the web
+   * console makes use of, like opening links, managing popups, etc.  In
+   * most cases, this will be |this.browserWindow|, but in some uses (such as
+   * the Browser Toolbox), there is no browser window, so an alternative window
+   * hosts the utilities there.
+   * @type nsIDOMWindow
+   */
+  get chromeUtilsWindow()
+  {
+    if (this.browserWindow) {
+      return this.browserWindow;
+    }
+    return this.chromeWindow.top;
+  },
+
+  /**
    * Getter for the xul:popupset that holds any popups we open.
    * @type nsIDOMElement
    */
   get mainPopupSet()
   {
-    return this.browserWindow.document.getElementById("mainPopupSet");
+    return this.chromeUtilsWindow.document.getElementById("mainPopupSet");
   },
 
   /**
    * Getter for the output element that holds messages we display.
    * @type nsIDOMElement
    */
   get outputNode()
   {
     return this.ui ? this.ui.outputNode : null;
   },
 
-  get gViewSourceUtils() this.browserWindow.gViewSourceUtils,
+  get gViewSourceUtils()
+  {
+    return this.chromeUtilsWindow.gViewSourceUtils;
+  },
 
   /**
    * Initialize the Web Console instance.
    *
    * @return object
    *         A promise for the initialization.
    */
   init: function WC_init()
@@ -411,17 +430,17 @@ WebConsole.prototype = {
   /**
    * Open a link in a new tab.
    *
    * @param string aLink
    *        The URL you want to open in a new tab.
    */
   openLink: function WC_openLink(aLink)
   {
-    this.browserWindow.openUILinkIn(aLink, "tab");
+    this.chromeUtilsWindow.openUILinkIn(aLink, "tab");
   },
 
   /**
    * Open a link in Firefox's view source.
    *
    * @param string aSourceURL
    *        The URL of the file.
    * @param integer aSourceLine
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -239,20 +239,20 @@ These should match what Safari and other
   -  "Scratchpad" in your locale. You should feel free to find a close
   -  approximation to it or choose a word (or words) that means
   -  "simple discardable text editor". -->
 <!ENTITY scratchpad.label             "Scratchpad">
 <!ENTITY scratchpad.accesskey         "s">
 <!ENTITY scratchpad.keycode           "VK_F4">
 <!ENTITY scratchpad.keytext           "F4">
 
-<!-- LOCALIZATION NOTE (chromeDebuggerMenu.label): This is the label for the
-  -  application menu item that opens the browser debugger UI in the Tools menu. -->
-<!ENTITY chromeDebuggerMenu.label       "Browser Debugger">
-<!ENTITY chromeDebuggerMenu.accesskey   "e">
+<!-- LOCALIZATION NOTE (browserToolboxMenu.label): This is the label for the
+  -  application menu item that opens the browser toolbox UI in the Tools menu. -->
+<!ENTITY browserToolboxMenu.label     "Browser Toolbox">
+<!ENTITY browserToolboxMenu.accesskey "e">
 
 <!ENTITY devToolbarCloseButton.tooltiptext "Close Developer Toolbar">
 <!ENTITY devToolbarMenu.label              "Developer Toolbar">
 <!ENTITY devToolbarMenu.accesskey          "v">
 <!ENTITY devAppMgrMenu.label               "App Manager">
 <!ENTITY devAppMgrMenu.accesskey           "a">
 <!ENTITY devToolbar.keycode                "VK_F2">
 <!ENTITY devToolbar.keytext                "F2">
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -65,16 +65,19 @@ 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
+# to store bookmarks created in Metro mode and share bookmarks between Metro
+# and Desktop.
 firefoxTouchTitle=Firefox Touch
 
 OrganizerQueryHistory=History
 OrganizerQueryDownloads=Downloads
 OrganizerQueryAllBookmarks=All Bookmarks
 OrganizerQueryTags=Tags
 
 # LOCALIZATION NOTE (tagResultLabel) :
--- a/browser/themes/shared/devtools/responsivedesign.inc.css
+++ b/browser/themes/shared/devtools/responsivedesign.inc.css
@@ -12,16 +12,20 @@
 }
 
 .browserStack[responsivemode] {
   box-shadow: 0 0 7px black;
 }
 
 .devtools-responsiveui-toolbar {
   background: transparent;
+  /* text color is textColor from dark theme, since no theme is applied to
+   * the responsive toolbar.
+   */
+  color: hsl(210,30%,85%);
   margin: 10px 0;
   padding: 0;
   box-shadow: none;
   border-bottom-width: 0;
 }
 
 .devtools-responsiveui-toolbar > menulist,
 .devtools-responsiveui-toolbar > toolbarbutton {
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -447,17 +447,17 @@ ChannelMediaResource::OnStopRequest(nsIR
 
   return NS_OK;
 }
 
 nsresult
 ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
                                         uint32_t aFlags)
 {
-  mChannel = aNew;
+  mChannel = new nsMainThreadPtrHolder<nsIChannel>(aNew);
   SetupChannelHeaders();
   return NS_OK;
 }
 
 struct CopySegmentClosure {
   nsCOMPtr<nsIPrincipal> mPrincipal;
   ChannelMediaResource*  mResource;
 };
@@ -496,17 +496,17 @@ ChannelMediaResource::OnDataAvailable(ns
 
   {
     MutexAutoLock lock(mLock);
     mChannelStatistics->AddBytes(aCount);
   }
 
   CopySegmentClosure closure;
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
-  if (secMan && mChannel) {
+  if (secMan && mChannel.get()) {
     secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
   }
   closure.mResource = this;
 
   uint32_t count = aCount;
   while (count > 0) {
     uint32_t read;
     nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
@@ -528,31 +528,31 @@ nsresult ChannelMediaResource::Open(nsIS
     mChannelStatistics = new MediaChannelStatistics();
   }
 
   nsresult rv = mCacheStream.Init();
   if (NS_FAILED(rv))
     return rv;
   NS_ASSERTION(mOffset == 0, "Who set mOffset already?");
 
-  if (!mChannel) {
+  if (!mChannel.get()) {
     // When we're a clone, the decoder might ask us to Open even though
     // we haven't established an mChannel (because we might not need one)
     NS_ASSERTION(!aStreamListener,
                  "Should have already been given a channel if we're to return a stream listener");
     return NS_OK;
   }
 
   return OpenChannel(aStreamListener);
 }
 
 nsresult ChannelMediaResource::OpenChannel(nsIStreamListener** aStreamListener)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  NS_ENSURE_TRUE(mChannel, NS_ERROR_NULL_POINTER);
+  NS_ENSURE_TRUE(mChannel.get(), NS_ERROR_NULL_POINTER);
   NS_ASSERTION(!mListener, "Listener should have been removed by now");
 
   if (aStreamListener) {
     *aStreamListener = nullptr;
   }
 
   if (mByteRange.IsNull()) {
     // We're not making a byte range request, so set the content length,
@@ -708,17 +708,17 @@ void ChannelMediaResource::CloseChannel(
     mChannelStatistics->Stop();
   }
 
   if (mListener) {
     mListener->Revoke();
     mListener = nullptr;
   }
 
-  if (mChannel) {
+  if (mChannel.get()) {
     if (mSuspendCount > 0) {
       // Resume the channel before we cancel it
       PossiblyResume();
     }
     // The status we use here won't be passed to the decoder, since
     // we've already revoked the listener. It can however be passed
     // to nsDocumentViewer::LoadComplete if our channel is the one
     // that kicked off creation of a video document. We don't want that
@@ -806,17 +806,17 @@ void ChannelMediaResource::Suspend(bool 
     return;
   }
   dom::HTMLMediaElement* element = owner->GetMediaElement();
   if (!element) {
     // Shutting down; do nothing.
     return;
   }
 
-  if (mChannel) {
+  if (mChannel.get()) {
     if (aCloseImmediately && mCacheStream.IsTransportSeekable()) {
       // Kill off our channel right now, but don't tell anyone about it.
       mIgnoreClose = true;
       CloseChannel();
       element->DownloadSuspended();
     } else if (mSuspendCount == 0) {
       {
         MutexAutoLock lock(mLock);
@@ -844,17 +844,17 @@ void ChannelMediaResource::Resume()
   if (!element) {
     // Shutting down; do nothing.
     return;
   }
 
   NS_ASSERTION(mSuspendCount > 0, "Resume without previous Suspend!");
   --mSuspendCount;
   if (mSuspendCount == 0) {
-    if (mChannel) {
+    if (mChannel.get()) {
       // Just wake up our existing channel
       {
         MutexAutoLock lock(mLock);
         mChannelStatistics->Start();
       }
       // if an error occurs after Resume, assume it's because the server
       // timed out the connection and we should reopen it.
       mReopenOnError = true;
@@ -892,22 +892,24 @@ ChannelMediaResource::RecreateChannel()
   dom::HTMLMediaElement* element = owner->GetMediaElement();
   if (!element) {
     // The decoder is being shut down, so don't bother opening a new channel
     return NS_OK;
   }
   nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
   NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
 
-  nsresult rv = NS_NewChannel(getter_AddRefs(mChannel),
+  nsCOMPtr<nsIChannel> channel;
+  nsresult rv = NS_NewChannel(getter_AddRefs(channel),
                               mURI,
                               nullptr,
                               loadGroup,
                               nullptr,
                               loadFlags);
+  mChannel = new nsMainThreadPtrHolder<nsIChannel>(channel);
 
   // We have cached the Content-Type, which should not change. Give a hint to
   // the channel to avoid a sniffing failure, which would be expected because we
   // are probably seeking in the middle of the bitstream, and sniffing relies
   // on the presence of a magic number at the beginning of the stream.
   NS_ASSERTION(!GetContentType().IsEmpty(),
       "When recreating a channel, we should know the Content-Type.");
   mChannel->SetContentType(GetContentType());
@@ -985,17 +987,17 @@ ChannelMediaResource::CacheClientSeek(in
     --mSuspendCount;
   }
 
   mOffset = aOffset;
 
   if (mSuspendCount > 0) {
     // Close the existing channel to force the channel to be recreated at
     // the correct offset upon resume.
-    if (mChannel) {
+    if (mChannel.get()) {
       mIgnoreClose = true;
       CloseChannel();
     }
     return NS_OK;
   }
 
   nsresult rv = RecreateChannel();
   if (NS_FAILED(rv))
@@ -1335,31 +1337,31 @@ nsresult FileMediaResource::Open(nsIStre
 }
 
 nsresult FileMediaResource::Close()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   // Since mChennel is only accessed by main thread, there is no necessary to
   // take the lock.
-  if (mChannel) {
+  if (mChannel.get()) {
     mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
     mChannel = nullptr;
   }
 
   return NS_OK;
 }
 
 already_AddRefed<nsIPrincipal> FileMediaResource::GetCurrentPrincipal()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   nsCOMPtr<nsIPrincipal> principal;
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
-  if (!secMan || !mChannel)
+  if (!secMan || !mChannel.get())
     return nullptr;
   secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
   return principal.forget();
 }
 
 bool FileMediaResource::CanClone()
 {
   return true;
@@ -1518,17 +1520,17 @@ MediaResource::Create(MediaDecoder* aDec
     resource = new ChannelMediaResource(aDecoder, aChannel, uri, contentType);
   }
   return resource.forget();
 }
 
 void BaseMediaResource::MoveLoadsToBackground() {
   NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
   mLoadInBackground = true;
-  if (!mChannel) {
+  if (!mChannel.get()) {
     // No channel, resource is probably already loaded.
     return;
   }
 
   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
   if (!owner) {
     NS_WARNING("Null owner in MediaResource::MoveLoadsToBackground()");
     return;
--- a/content/media/MediaResource.h
+++ b/content/media/MediaResource.h
@@ -8,16 +8,17 @@
 
 #include "mozilla/Mutex.h"
 #include "nsIChannel.h"
 #include "nsIURI.h"
 #include "nsIStreamingProtocolController.h"
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
+#include "nsProxyRelease.h"
 #include "MediaCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TimeStamp.h"
 #include "nsThreadUtils.h"
 
 // For HTTP seeking, if number of bytes needing to be
 // seeked forward is less than this value then a read is
 // done rather than a byte range request.
@@ -390,27 +391,27 @@ public:
   }
 
 protected:
   virtual ~MediaResource() {};
 };
 
 class BaseMediaResource : public MediaResource {
 public:
-  virtual nsIURI* URI() const { return mURI; }
+  virtual nsIURI* URI() const { return const_cast<nsIURI*>(mURI.get()); }
   virtual void MoveLoadsToBackground();
 
 protected:
   BaseMediaResource(MediaDecoder* aDecoder,
                     nsIChannel* aChannel,
                     nsIURI* aURI,
                     const nsACString& aContentType) :
     mDecoder(aDecoder),
-    mChannel(aChannel),
-    mURI(aURI),
+    mChannel(new nsMainThreadPtrHolder<nsIChannel>(aChannel)),
+    mURI(new nsMainThreadPtrHolder<nsIURI>(aURI)),
     mContentType(aContentType),
     mLoadInBackground(false)
   {
     MOZ_COUNT_CTOR(BaseMediaResource);
     NS_ASSERTION(!mContentType.IsEmpty(), "Must know content type");
   }
   virtual ~BaseMediaResource()
   {
@@ -433,21 +434,21 @@ protected:
 
   // This is not an nsCOMPointer to prevent a circular reference
   // between the decoder to the media stream object. The stream never
   // outlives the lifetime of the decoder.
   MediaDecoder* mDecoder;
 
   // Channel used to download the media data. Must be accessed
   // from the main thread only.
-  nsCOMPtr<nsIChannel> mChannel;
+  nsMainThreadPtrHandle<nsIChannel> mChannel;
 
   // URI in case the stream needs to be re-opened. Access from
   // main thread only.
-  nsCOMPtr<nsIURI> mURI;
+  nsMainThreadPtrHandle<nsIURI> mURI;
 
   // Content-Type of the channel. This is copied from the nsIChannel when the
   // MediaResource is created. This is constant, so accessing from any thread
   // is safe.
   const nsAutoCString mContentType;
 
   // True if MoveLoadsToBackground() has been called, i.e. the load event
   // has been fired, and all channel loads will be in the background.
--- a/content/media/VideoSegment.cpp
+++ b/content/media/VideoSegment.cpp
@@ -27,16 +27,17 @@ VideoFrame::SetNull() {
   mIntrinsicSize = gfxIntSize(0, 0);
 }
 
 void
 VideoFrame::TakeFrom(VideoFrame* aFrame)
 {
   mImage = aFrame->mImage.forget();
   mIntrinsicSize = aFrame->mIntrinsicSize;
+  mForceBlack = aFrame->GetForceBlack();
 }
 
 VideoChunk::VideoChunk()
 {}
 
 VideoChunk::~VideoChunk()
 {}
 
--- a/content/media/VideoSegment.h
+++ b/content/media/VideoSegment.h
@@ -33,17 +33,17 @@ public:
            ((mForceBlack && aFrame.mForceBlack) || mImage == aFrame.mImage);
   }
   bool operator!=(const VideoFrame& aFrame) const
   {
     return !operator==(aFrame);
   }
 
   Image* GetImage() const { return mImage; }
-  void SetForceBlack(bool aForceBlack) { mForceBlack = true; }
+  void SetForceBlack(bool aForceBlack) { mForceBlack = aForceBlack; }
   bool GetForceBlack() const { return mForceBlack; }
   const gfxIntSize& GetIntrinsicSize() const { return mIntrinsicSize; }
   void SetNull();
   void TakeFrom(VideoFrame* aFrame);
 
 protected:
   // mImage can be null to indicate "no video" (aka "empty frame"). It can
   // still have an intrinsic size in this case.
--- a/content/media/encoder/EncodedFrameContainer.h
+++ b/content/media/encoder/EncodedFrameContainer.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef EncodedFrameContainer_H_
 #define EncodedFrameContainer_H_
 
+#include "nsAutoPtr.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 
 class EncodedFrame;
 
 /*
  * This container is used to carry video or audio encoded data from encoder to muxer.
--- a/content/media/encoder/OpusTrackEncoder.cpp
+++ b/content/media/encoder/OpusTrackEncoder.cpp
@@ -111,43 +111,42 @@ SerializeOpusCommentHeader(const nsCStri
   }
 }
 
 }  // Anonymous namespace.
 
 OpusTrackEncoder::OpusTrackEncoder()
   : AudioTrackEncoder()
   , mEncoder(nullptr)
-  , mSourceSegment(new AudioSegment())
   , mLookahead(0)
   , mResampler(nullptr)
 {
 }
 
 OpusTrackEncoder::~OpusTrackEncoder()
 {
   if (mEncoder) {
     opus_encoder_destroy(mEncoder);
   }
   if (mResampler) {
     speex_resampler_destroy(mResampler);
+    mResampler = nullptr;
   }
-
 }
 
 nsresult
 OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
 {
   // This monitor is used to wake up other methods that are waiting for encoder
   // to be completely initialized.
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   // This version of encoder API only support 1 or 2 channels,
   // So set the mChannels less or equal 2 and
   // let InterleaveTrackData downmix pcm data.
-  mChannels = aChannels > 2 ? 2 : aChannels;
+  mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
 
   if (aChannels <= 0) {
     return NS_ERROR_FAILURE;
   }
   // The granule position is required to be incremented at a rate of 48KHz, and
   // it is simply calculated as |granulepos = samples * (48000/source_rate)|,
   // that is, the source sampling rate must divide 48000 evenly.
   // If this constraint is not satisfied, we resample the input to 48kHz.
@@ -190,22 +189,22 @@ OpusTrackEncoder::GetPacketDuration()
 }
 
 already_AddRefed<TrackMetadataBase>
 OpusTrackEncoder::GetMetadata()
 {
   {
     // Wait if mEncoder is not initialized.
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    while (!mCanceled && !mEncoder) {
+    while (!mCanceled && !mInitialized) {
       mReentrantMonitor.Wait();
     }
   }
 
-  if (mCanceled || mDoneEncoding) {
+  if (mCanceled || mEncodingComplete) {
     return nullptr;
   }
 
   nsRefPtr<OpusMetadata> meta = new OpusMetadata();
 
   mLookahead = 0;
   int error = opus_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&mLookahead));
   if (error != OPUS_OK) {
@@ -233,39 +232,40 @@ OpusTrackEncoder::GetEncodedTrack(Encode
 {
   {
     // Move all the samples from mRawSegment to mSourceSegment. We only hold
     // the monitor in this block.
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
     // Wait if mEncoder is not initialized, or when not enough raw data, but is
     // not the end of stream nor is being canceled.
-    while (!mCanceled && (!mEncoder || (mRawSegment->GetDuration() +
-           mSourceSegment->GetDuration() < GetPacketDuration() &&
+    while (!mCanceled && (!mInitialized || (mRawSegment.GetDuration() +
+           mSourceSegment.GetDuration() < GetPacketDuration() &&
            !mEndOfStream))) {
       mReentrantMonitor.Wait();
     }
 
-    if (mCanceled || mDoneEncoding) {
+    if (mCanceled || mEncodingComplete) {
       return NS_ERROR_FAILURE;
     }
 
-    mSourceSegment->AppendFrom(mRawSegment);
+    mSourceSegment.AppendFrom(&mRawSegment);
 
     // Pad |mLookahead| samples to the end of source stream to prevent lost of
     // original data, the pcm duration will be calculated at rate 48K later.
-    if (mEndOfStream) {
-      mSourceSegment->AppendNullData(mLookahead);
+    if (mEndOfStream && !mEosSetInEncoder) {
+      mEosSetInEncoder = true;
+      mSourceSegment.AppendNullData(mLookahead);
     }
   }
 
   // Start encoding data.
   nsAutoTArray<AudioDataValue, 9600> pcm;
   pcm.SetLength(GetPacketDuration() * mChannels);
-  AudioSegment::ChunkIterator iter(*mSourceSegment);
+  AudioSegment::ChunkIterator iter(mSourceSegment);
   int frameCopied = 0;
   while (!iter.IsEnded() && frameCopied < GetPacketDuration()) {
     AudioChunk chunk = *iter;
 
     // Chunk to the required frame size.
     int frameToCopy = chunk.GetDuration();
     if (frameCopied + frameToCopy > GetPacketDuration()) {
       frameToCopy = GetPacketDuration() - frameCopied;
@@ -314,22 +314,22 @@ OpusTrackEncoder::GetEncodedTrack(Encode
   } else {
     // The ogg time stamping and pre-skip is always timed at 48000.
     audiodata->SetDuration(frameCopied * (kOpusSamplingRate / mSamplingRate));
   }
 
   // Remove the raw data which has been pulled to pcm buffer.
   // The value of frameCopied should equal to (or smaller than, if eos)
   // GetPacketDuration().
-  mSourceSegment->RemoveLeading(frameCopied);
+  mSourceSegment.RemoveLeading(frameCopied);
 
   // Has reached the end of input stream and all queued data has pulled for
   // encoding.
-  if (mSourceSegment->GetDuration() == 0 && mEndOfStream) {
-    mDoneEncoding = true;
+  if (mSourceSegment.GetDuration() == 0 && mEndOfStream) {
+    mEncodingComplete = true;
     LOG("[Opus] Done encoding.");
   }
 
   // Append null data to pcm buffer if the leftover data is not enough for
   // opus encoder.
   if (frameCopied < GetPacketDuration() && mEndOfStream) {
     memset(pcm.Elements() + frameCopied * mChannels, 0,
            (GetPacketDuration()-frameCopied)*mChannels*sizeof(AudioDataValue));
@@ -348,17 +348,17 @@ OpusTrackEncoder::GetEncodedTrack(Encode
   result = opus_encode_float(mEncoder, pcmBuf, GetPacketDuration(),
                              frameData.Elements(), MAX_DATA_BYTES);
 #endif
   frameData.SetLength(result >= 0 ? result : 0);
 
   if (result < 0) {
     LOG("[Opus] Fail to encode data! Result: %s.", opus_strerror(result));
   }
-  if (mDoneEncoding) {
+  if (mEncodingComplete) {
     if (mResampler) {
       speex_resampler_destroy(mResampler);
       mResampler = nullptr;
     }
   }
 
   audiodata->SetFrameData(&frameData);
   aData.AppendEncodedFrame(audiodata);
--- a/content/media/encoder/OpusTrackEncoder.h
+++ b/content/media/encoder/OpusTrackEncoder.h
@@ -32,38 +32,39 @@ public:
   OpusTrackEncoder();
   virtual ~OpusTrackEncoder();
 
   already_AddRefed<TrackMetadataBase> GetMetadata() MOZ_OVERRIDE;
 
   nsresult GetEncodedTrack(EncodedFrameContainer& aData) MOZ_OVERRIDE;
 
 protected:
-  int GetPacketDuration() MOZ_OVERRIDE;
+  int GetPacketDuration();
 
   nsresult Init(int aChannels, int aSamplingRate) MOZ_OVERRIDE;
 
 private:
   /**
    * Get the samplerate of the data to be fed to the Opus encoder. This might be
    * different from the intput samplerate if resampling occurs.
    */
   int GetOutputSampleRate();
 
   /**
    * The Opus encoder from libopus.
    */
   OpusEncoder* mEncoder;
 
   /**
-   * A local segment queue which stores the raw segments. Opus encoder only
-   * takes GetPacketDuration() samples from mSourceSegment in every encoding
-   * cycle, thus it needs to store the raw track data.
+   * A local segment queue which takes the raw data out from mRawSegment in the
+   * call of GetEncodedTrack(). Opus encoder only accepts GetPacketDuration()
+   * samples from mSourceSegment every encoding cycle, thus it needs to be
+   * global in order to store the leftover segments taken from mRawSegment.
    */
-  nsAutoPtr<AudioSegment> mSourceSegment;
+  AudioSegment mSourceSegment;
 
   /**
    * Total samples of delay added by codec, can be queried by the encoder. From
    * the perspective of decoding, real data begins this many samples late, so
    * the encoder needs to append this many null samples to the end of stream,
    * in order to align the time of input and output.
    */
   int mLookahead;
--- a/content/media/encoder/TrackEncoder.cpp
+++ b/content/media/encoder/TrackEncoder.cpp
@@ -1,125 +1,107 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "TrackEncoder.h"
+#include "AudioChannelFormat.h"
 #include "MediaStreamGraph.h"
-#include "AudioChannelFormat.h"
+#include "VideoUtils.h"
 
 #undef LOG
 #ifdef MOZ_WIDGET_GONK
 #include <android/log.h>
-#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediakEncoder", ## args);
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
 #else
 #define LOG(args, ...)
 #endif
 
 namespace mozilla {
 
-static const int  DEFAULT_CHANNELS = 1;
-static const int  DEFAULT_SAMPLING_RATE = 16000;
+static const int DEFAULT_CHANNELS = 1;
+static const int DEFAULT_SAMPLING_RATE = 16000;
+static const int DEFAULT_FRAME_WIDTH = 640;
+static const int DEFAULT_FRAME_HEIGHT = 480;
+static const int DEFAULT_TRACK_RATE = USECS_PER_S;
 
 void
 AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
                                             TrackID aID,
                                             TrackRate aTrackRate,
                                             TrackTicks aTrackOffset,
                                             uint32_t aTrackEvents,
                                             const MediaSegment& aQueuedMedia)
 {
   if (mCanceled) {
     return;
   }
 
-  AudioSegment* audio = const_cast<AudioSegment*>
-                        (static_cast<const AudioSegment*>(&aQueuedMedia));
+  const AudioSegment& audio = static_cast<const AudioSegment&>(aQueuedMedia);
 
   // Check and initialize parameters for codec encoder.
   if (!mInitialized) {
-    AudioSegment::ChunkIterator iter(*audio);
+    AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(audio));
     while (!iter.IsEnded()) {
       AudioChunk chunk = *iter;
 
       // The number of channels is determined by the first non-null chunk, and
       // thus the audio encoder is initialized at this time.
       if (!chunk.IsNull()) {
         nsresult rv = Init(chunk.mChannelData.Length(), aTrackRate);
         if (NS_FAILED(rv)) {
           LOG("[AudioTrackEncoder]: Fail to initialize the encoder!");
           NotifyCancel();
         }
         break;
-      } else {
-        mSilentDuration += chunk.mDuration;
       }
+
       iter.Next();
     }
   }
 
   // Append and consume this raw segment.
-  if (mInitialized) {
-    AppendAudioSegment(audio);
-  }
+  AppendAudioSegment(audio);
+
 
   // The stream has stopped and reached the end of track.
   if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
     LOG("[AudioTrackEncoder]: Receive TRACK_EVENT_ENDED .");
     NotifyEndOfStream();
   }
 }
 
 void
-AudioTrackEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
-{
-  // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
-  LOG("[AudioTrackEncoder]: NotifyRemoved.");
-  NotifyEndOfStream();
-}
-
-void
 AudioTrackEncoder::NotifyEndOfStream()
 {
-  // If source audio chunks are completely silent till the end of encoding,
-  // initialize the encoder with default channel counts and sampling rate, and
-  // append this many null data to the segment of track encoder.
+  // If source audio track is completely silent till the end of encoding,
+  // initialize the encoder with default channel counts and sampling rate.
   if (!mCanceled && !mInitialized) {
     Init(DEFAULT_CHANNELS, DEFAULT_SAMPLING_RATE);
-    mRawSegment->AppendNullData(mSilentDuration);
-    mSilentDuration = 0;
   }
 
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mEndOfStream = true;
   mReentrantMonitor.NotifyAll();
 }
 
 nsresult
-AudioTrackEncoder::AppendAudioSegment(MediaSegment* aSegment)
+AudioTrackEncoder::AppendAudioSegment(const AudioSegment& aSegment)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
-  AudioSegment* audio = static_cast<AudioSegment*>(aSegment);
-  AudioSegment::ChunkIterator iter(*audio);
-
-  // Append this many null data to our queued segment if there is a complete
-  // silence before the audio track encoder has initialized.
-  if (mSilentDuration > 0) {
-    mRawSegment->AppendNullData(mSilentDuration);
-    mSilentDuration = 0;
-  }
-
+  AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(aSegment));
   while (!iter.IsEnded()) {
     AudioChunk chunk = *iter;
     // Append and consume both non-null and null chunks.
-    mRawSegment->AppendAndConsumeChunk(&chunk);
+    mRawSegment.AppendAndConsumeChunk(&chunk);
     iter.Next();
   }
-  if (mRawSegment->GetDuration() >= GetPacketDuration()) {
+
+  if (mRawSegment.GetDuration() >= GetPacketDuration()) {
     mReentrantMonitor.NotifyAll();
   }
 
   return NS_OK;
 }
 
 static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
 static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
@@ -140,9 +122,108 @@ AudioTrackEncoder::InterleaveTrackData(A
                          aChunk.mVolume, mChannels, aOutput);
   } else {
     InterleaveAndConvertBuffer(aChunk.mChannelData.Elements(),
                                aChunk.mBufferFormat, aDuration, aChunk.mVolume,
                                mChannels, aOutput);
   }
 }
 
+void
+VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+                                            TrackID aID,
+                                            TrackRate aTrackRate,
+                                            TrackTicks aTrackOffset,
+                                            uint32_t aTrackEvents,
+                                            const MediaSegment& aQueuedMedia)
+{
+  if (mCanceled) {
+    return;
+  }
+
+  const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
+
+   // Check and initialize parameters for codec encoder.
+  if (!mInitialized) {
+    VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(video));
+    while (!iter.IsEnded()) {
+      VideoChunk chunk = *iter;
+      if (!chunk.IsNull()) {
+        gfxIntSize imgsize = chunk.mFrame.GetImage()->GetSize();
+        int width = (imgsize.width + 1) / 2 * 2;
+        int height = (imgsize.height + 1) / 2 * 2;
+        nsresult rv = Init(width, height, aTrackRate);
+        if (NS_FAILED(rv)) {
+          LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
+          NotifyCancel();
+        }
+        break;
+      }
+
+      iter.Next();
+    }
+  }
+
+  AppendVideoSegment(video);
+
+  // The stream has stopped and reached the end of track.
+  if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
+    LOG("[VideoTrackEncoder]: Receive TRACK_EVENT_ENDED .");
+    NotifyEndOfStream();
+  }
+
 }
+
+nsresult
+VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
+{
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+  // Append all video segments from MediaStreamGraph, including null an
+  // non-null frames.
+  VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(aSegment));
+  while (!iter.IsEnded()) {
+    VideoChunk chunk = *iter;
+    nsRefPtr<layers::Image> image = chunk.mFrame.GetImage();
+    mRawSegment.AppendFrame(image.forget(), chunk.GetDuration(),
+                            chunk.mFrame.GetIntrinsicSize());
+    iter.Next();
+  }
+
+  if (mRawSegment.GetDuration() > 0) {
+    mReentrantMonitor.NotifyAll();
+  }
+
+  return NS_OK;
+}
+
+void
+VideoTrackEncoder::NotifyEndOfStream()
+{
+  // If source video track is muted till the end of encoding, initialize the
+  // encoder with default frame width, frame height, and track rate.
+  if (!mCanceled && !mInitialized) {
+    Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_TRACK_RATE);
+  }
+
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  mEndOfStream = true;
+  mReentrantMonitor.NotifyAll();
+}
+
+void
+VideoTrackEncoder::CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer)
+{
+  NS_ENSURE_TRUE_VOID(aOutputBuffer);
+
+  // Supports YUV420 image format only.
+  int yPlaneLen = mFrameWidth * mFrameHeight;
+  int cbcrPlaneLen = yPlaneLen / 2;
+  int frameLen = yPlaneLen + cbcrPlaneLen;
+
+  aOutputBuffer->SetLength(frameLen);
+  // Fill Y plane.
+  memset(aOutputBuffer->Elements(), 0x10, yPlaneLen);
+  // Fill Cb/Cr planes.
+  memset(aOutputBuffer->Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
+}
+
+}
--- a/content/media/encoder/TrackEncoder.h
+++ b/content/media/encoder/TrackEncoder.h
@@ -4,19 +4,20 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef TrackEncoder_h_
 #define TrackEncoder_h_
 
 #include "mozilla/ReentrantMonitor.h"
 
 #include "AudioSegment.h"
+#include "EncodedFrameContainer.h"
 #include "StreamBuffer.h"
 #include "TrackMetadataBase.h"
-#include "EncodedFrameContainer.h"
+#include "VideoSegment.h"
 
 namespace mozilla {
 
 class MediaStreamGraph;
 
 /**
  * Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetimes managed by
  * MediaEncoder. Most methods can only be called on the MediaEncoder's thread,
@@ -25,116 +26,163 @@ class MediaStreamGraph;
  * NotifyQueuedTrackChanges is called on subclasses of this class from the
  * MediaStreamGraph thread, and AppendAudioSegment/AppendVideoSegment is then
  * called to store media data in the TrackEncoder. Later on, GetEncodedTrack is
  * called on MediaEncoder's thread to encode and retrieve the encoded data.
  */
 class TrackEncoder
 {
 public:
-  TrackEncoder() {}
+  TrackEncoder()
+    : mReentrantMonitor("media.TrackEncoder")
+    , mEncodingComplete(false)
+    , mEosSetInEncoder(false)
+    , mInitialized(false)
+    , mEndOfStream(false)
+    , mCanceled(false)
+  {}
+
   virtual ~TrackEncoder() {}
 
   /**
    * Notified by the same callbcak of MediaEncoder when it has received a track
    * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
   virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
                                         TrackRate aTrackRate,
                                         TrackTicks aTrackOffset,
                                         uint32_t aTrackEvents,
                                         const MediaSegment& aQueuedMedia) = 0;
 
   /**
    * Notified by the same callback of MediaEncoder when it has been removed from
    * MediaStreamGraph. Called on the MediaStreamGraph thread.
    */
-  virtual void NotifyRemoved(MediaStreamGraph* aGraph) = 0;
+  void NotifyRemoved(MediaStreamGraph* aGraph) { NotifyEndOfStream(); }
 
   /**
-   * Creates and sets up meta data for a specific codec
+   * Creates and sets up meta data for a specific codec, called on the worker
+   * thread.
    */
   virtual already_AddRefed<TrackMetadataBase> GetMetadata() = 0;
 
   /**
-   * Encodes raw segments. Result data is returned in aData.
+   * Encodes raw segments. Result data is returned in aData, and called on the
+   * worker thread.
    */
   virtual nsresult GetEncodedTrack(EncodedFrameContainer& aData) = 0;
-};
 
-class AudioTrackEncoder : public TrackEncoder
-{
-public:
-  AudioTrackEncoder()
-    : TrackEncoder()
-    , mChannels(0)
-    , mSamplingRate(0)
-    , mInitialized(false)
-    , mDoneEncoding(false)
-    , mReentrantMonitor("media.AudioEncoder")
-    , mRawSegment(new AudioSegment())
-    , mEndOfStream(false)
-    , mCanceled(false)
-    , mSilentDuration(0)
-  {}
-
-  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
-                                TrackRate aTrackRate,
-                                TrackTicks aTrackOffset,
-                                uint32_t aTrackEvents,
-                                const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
-
-  void NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
-
-  bool IsEncodingComplete()
-  {
-    return mDoneEncoding;
-  }
+  /**
+   * True if the track encoder has encoded all source segments coming from
+   * MediaStreamGraph. Call on the worker thread.
+   */
+  bool IsEncodingComplete() { return mEncodingComplete; }
 
   /**
    * Notifies from MediaEncoder to cancel the encoding, and wakes up
    * mReentrantMonitor if encoder is waiting on it.
    */
   void NotifyCancel()
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mCanceled = true;
     mReentrantMonitor.NotifyAll();
   }
 
 protected:
   /**
+   * Notifies track encoder that we have reached the end of source stream, and
+   * wakes up mReentrantMonitor if encoder is waiting for any source data.
+   */
+  virtual void NotifyEndOfStream() = 0;
+
+  /**
+   * A ReentrantMonitor to protect the pushing and pulling of mRawSegment which
+   * is declared in its subclasses, and the following flags: mInitialized,
+   * EndOfStream and mCanceled. The control of protection is managed by its
+   * subclasses.
+   */
+  ReentrantMonitor mReentrantMonitor;
+
+  /**
+   * True if the track encoder has encoded all source data.
+   */
+  bool mEncodingComplete;
+
+  /**
+   * True if flag of EOS or any form of indicating EOS has set in the codec-
+   * encoder.
+   */
+  bool mEosSetInEncoder;
+
+  /**
+   * True if the track encoder has initialized successfully, protected by
+   * mReentrantMonitor.
+   */
+  bool mInitialized;
+
+  /**
+   * True if the TrackEncoder has received an event of TRACK_EVENT_ENDED from
+   * MediaStreamGraph, or the MediaEncoder is removed from its source stream,
+   * protected by mReentrantMonitor.
+   */
+  bool mEndOfStream;
+
+  /**
+   * True if a cancellation of encoding is sent from MediaEncoder, protected by
+   * mReentrantMonitor.
+   */
+  bool mCanceled;
+};
+
+class AudioTrackEncoder : public TrackEncoder
+{
+public:
+  AudioTrackEncoder()
+    : TrackEncoder()
+    , mChannels(0)
+    , mSamplingRate(0)
+  {}
+
+  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                TrackRate aTrackRate,
+                                TrackTicks aTrackOffset,
+                                uint32_t aTrackEvents,
+                                const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
+
+protected:
+  /**
    * Number of samples per channel in a pcm buffer. This is also the value of
    * frame size required by audio encoder, and mReentrantMonitor will be
    * notified when at least this much data has been added to mRawSegment.
    */
-  virtual int GetPacketDuration() = 0;
+  virtual int GetPacketDuration() { return 0; }
 
   /**
    * Initializes the audio encoder. The call of this method is delayed until we
    * have received the first valid track from MediaStreamGraph, and the
    * mReentrantMonitor will be notified if other methods is waiting for encoder
    * to be completely initialized. This method is called on the MediaStreamGraph
    * thread.
    */
   virtual nsresult Init(int aChannels, int aSamplingRate) = 0;
 
   /**
    * Appends and consumes track data from aSegment, this method is called on
    * the MediaStreamGraph thread. mReentrantMonitor will be notified when at
    * least GetPacketDuration() data has been added to mRawSegment, wake up other
    * method which is waiting for more data from mRawSegment.
    */
-  nsresult AppendAudioSegment(MediaSegment* aSegment);
+  nsresult AppendAudioSegment(const AudioSegment& aSegment);
 
   /**
    * Notifies the audio encoder that we have reached the end of source stream,
    * and wakes up mReentrantMonitor if encoder is waiting for more track data.
    */
-  void NotifyEndOfStream();
+  virtual void NotifyEndOfStream() MOZ_OVERRIDE;
 
   /**
    * Interleaves the track data and stores the result into aOutput. Might need
    * to up-mix or down-mix the channel data if the channels number of this chunk
    * is different from mChannels. The channel data from aChunk might be modified
    * by up-mixing.
    */
   void InterleaveTrackData(AudioChunk& aChunk, int32_t aDuration,
@@ -142,49 +190,105 @@ protected:
 
   /**
    * The number of channels are used for processing PCM data in the audio encoder.
    * This value comes from the first valid audio chunk. If encoder can't support
    * the channels in the chunk, downmix PCM stream can be performed.
    * This value also be used to initialize the audio encoder.
    */
   int mChannels;
-  int mSamplingRate;
-  bool mInitialized;
-  bool mDoneEncoding;
 
   /**
-   * A ReentrantMonitor to protect the pushing and pulling of mRawSegment.
+   * The sampling rate of source audio data.
    */
-  ReentrantMonitor mReentrantMonitor;
+  int mSamplingRate;
 
   /**
    * A segment queue of audio track data, protected by mReentrantMonitor.
    */
-  nsAutoPtr<AudioSegment> mRawSegment;
-
-  /**
-   * True if we have received an event of TRACK_EVENT_ENDED from MediaStreamGraph,
-   * or the MediaEncoder is removed from its source stream, protected by
-   * mReentrantMonitor.
-   */
-  bool mEndOfStream;
-
-  /**
-   * True if a cancellation of encoding is sent from MediaEncoder, protected by
-   * mReentrantMonitor.
-   */
-  bool mCanceled;
-
-  /**
-   * The total duration of null chunks we have received from MediaStreamGraph
-   * before initializing the audio track encoder.
-   */
-  TrackTicks mSilentDuration;
+  AudioSegment mRawSegment;
 };
 
 class VideoTrackEncoder : public TrackEncoder
 {
+public:
+  VideoTrackEncoder()
+    : TrackEncoder()
+    , mFrameWidth(0)
+    , mFrameHeight(0)
+    , mTrackRate(0)
+    , mTotalFrameDuration(0)
+  {}
 
+  /**
+   * Notified by the same callbcak of MediaEncoder when it has received a track
+   * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
+   */
+  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                TrackRate aTrackRate,
+                                TrackTicks aTrackOffset,
+                                uint32_t aTrackEvents,
+                                const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
+
+protected:
+  /**
+   * Initialized the video encoder. In order to collect the value of width and
+   * height of source frames, this initialization is delayed until we have
+   * received the first valid video frame from MediaStreamGraph;
+   * mReentrantMonitor will be notified after it has successfully initialized,
+   * and this method is called on the MediaStramGraph thread.
+   */
+  virtual nsresult Init(int aWidth, int aHeight, TrackRate aTrackRate) = 0;
+
+  /**
+   * Appends source video frames to mRawSegment. We only append the source chunk
+   * if it is unique to mLastChunk. Called on the MediaStreamGraph thread.
+   */
+  nsresult AppendVideoSegment(const VideoSegment& aSegment);
+
+  /**
+   * Tells the video track encoder that we've reached the end of source stream,
+   * and wakes up mReentrantMonitor if encoder is waiting for more track data.
+   * Called on the MediaStreamGraph thread.
+   */
+  virtual void NotifyEndOfStream() MOZ_OVERRIDE;
+
+  /**
+   * Create a buffer of black image in format of YUV:420. Called on the worker
+   * thread.
+   */
+  void CreateMutedFrame(nsTArray<uint8_t>* aOutputBuffer);
+
+  /**
+   * The width of source video frame, ceiled if the source width is odd.
+   */
+  int mFrameWidth;
+
+  /**
+   * The height of source video frame, ceiled if the source height is odd.
+   */
+  int mFrameHeight;
+
+  /**
+   * The track rate of source video.
+   */
+  TrackRate mTrackRate;
+
+  /**
+   * The total duration of frames in encoded video in TrackTicks, kept track of
+   * in subclasses.
+   */
+  TrackTicks mTotalFrameDuration;
+
+  /**
+   * The last unique frame we've sent to track encoder, kept track of in
+   * subclasses.
+   */
+  VideoFrame mLastFrame;
+
+  /**
+   * A segment queue of audio track data, protected by mReentrantMonitor.
+   */
+  VideoSegment mRawSegment;
 };
 
 }
 #endif
--- a/dom/apps/src/OperatorApps.jsm
+++ b/dom/apps/src/OperatorApps.jsm
@@ -23,25 +23,29 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/ril/content-helper;1",
                                    "nsIIccProvider");
 #endif
 
 function debug(aMsg) {
   //dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
 }
 
+// Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR
+// preference.
+// if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as
+// single variant source) the value of
+// DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead.
+// SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source.
+// Apps will be stored on an app per directory basis, hanging from
+// SINGLE_VARIANT_SOURCE_DIR
 const DIRECTORY_NAME = "webappsDir";
-
-// The files will be stored on DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR
-// SINGLE_VARIANT_CONF_FILE will be stored on SINGLE_VARIANT_SOURCE_DIR
-// Apps will be stored on a app per directory basis, hanging from
-// SINGLE_VARIANT_SOURCE_DIR
 const SINGLE_VARIANT_SOURCE_DIR = "svoperapps";
 const SINGLE_VARIANT_CONF_FILE  = "singlevariantconf.json";
 const PREF_FIRST_RUN_WITH_SIM   = "dom.webapps.firstRunWithSIM";
+const PREF_SINGLE_VARIANT_DIR   = "dom.mozApps.single_variant_sourcedir";
 const METADATA                  = "metadata.json";
 const UPDATEMANIFEST            = "update.webapp";
 const MANIFEST                  = "manifest.webapp";
 const APPLICATION_ZIP           = "application.zip";
 
 function isFirstRunWithSIM() {
   try {
     if (Services.prefs.prefHasUserValue(PREF_FIRST_RUN_WITH_SIM)) {
@@ -49,16 +53,19 @@ function isFirstRunWithSIM() {
     }
   } catch(e) {
     debug ("Error getting pref. " + e);
   }
   return true;
 }
 
 #ifdef MOZ_B2G_RIL
+let File = OS.File;
+let Path = OS.Path;
+
 let iccListener = {
   notifyStkCommand: function() {},
 
   notifyStkSessionEnd: function() {},
 
   notifyCardStateChanged: function() {},
 
   notifyIccInfoChanged: function() {
@@ -83,58 +90,142 @@ this.OperatorAppsRegistry = {
 
   _baseDirectory: null,
 
   init: function() {
     debug("init");
 #ifdef MOZ_B2G_RIL
     if (isFirstRunWithSIM()) {
       debug("First Run with SIM");
-      // TODO: Bug 927709 - OperatorApps for multi-sim
-      // In Multi-sim, there is more than one client in iccProvider. Each
-      // client represents a icc service. To maintain the backward compatibility
-      // with single sim, we always use client 0 for now. Adding support for
-      // multiple sim will be addressed in bug 927709, if needed.
-      let clientId = 0;
-      let iccInfo = iccProvider.getIccInfo(clientId);
-      let mcc = 0;
-      let mnc = 0;
-      if (iccInfo && iccInfo.mcc) {
-        mcc = iccInfo.mcc;
-      }
-      if (iccInfo && iccInfo.mnc) {
-        mnc = iccInfo.mnc;
-      }
-      if (mcc && mnc) {
-        this._installOperatorApps(mcc, mnc);
-      } else {
-        iccProvider.registerIccMsg(clientId, iccListener);
-      }
+      Task.spawn(function() {
+        try {
+          yield this._initializeSourceDir();
+          // TODO: Bug 927709 - OperatorApps for multi-sim
+          // In Multi-sim, there is more than one client in iccProvider. Each
+          // client represents a icc service. To maintain the backward
+          // compatibility with single sim, we always use client 0 for now.
+          // Adding support for multiple sim will be addressed in bug 927709, if
+          // needed.
+          let clientId = 0;
+          let iccInfo = iccProvider.getIccInfo(clientId);
+          let mcc = 0;
+          let mnc = 0;
+          if (iccInfo && iccInfo.mcc) {
+            mcc = iccInfo.mcc;
+          }
+          if (iccInfo && iccInfo.mnc) {
+            mnc = iccInfo.mnc;
+          }
+          if (mcc && mnc) {
+            this._installOperatorApps(mcc, mnc);
+          } else {
+            iccProvider.registerIccMsg(clientId, iccListener);
+          }
+        } catch (e) {
+          debug("Error Initializing OperatorApps. " + e);
+        }
+      }.bind(this));
     } else {
       debug("No First Run with SIM");
     }
 #endif
   },
 
+  _copyDirectory: function(aOrg, aDst) {
+    debug("copying " + aOrg + " to " + aDst);
+    return aDst && Task.spawn(function() {
+      try {
+        let orgInfo = yield File.stat(aOrg);
+        if (!orgInfo.isDir) {
+          return;
+        }
+
+        let dirDstExists = yield File.exists(aDst);
+        if (!dirDstExists) {
+          yield File.makeDir(aDst);
+        }
+        let iterator = new File.DirectoryIterator(aOrg);
+        if (!iterator) {
+          debug("No iterator over: " + aOrg);
+          return;
+        }
+        try {
+          while (true) {
+            let entry;
+            try {
+              entry = yield iterator.next();
+            } catch (ex if ex == StopIteration) {
+              break;
+            }
+
+            if (!entry.isDir) {
+              yield File.copy(entry.path, Path.join(aDst, entry.name));
+            } else {
+              yield this._copyDirectory(entry.path,
+                                        Path.join(aDst, entry.name));
+            }
+          }
+        } finally {
+          iterator.close();
+        }
+      } catch (e) {
+        debug("Error copying " + aOrg + " to " + aDst + ". " + e);
+      }
+    }.bind(this));
+  },
+
+  _initializeSourceDir: function() {
+    return Task.spawn(function() {
+      let svFinalDirName;
+      try {
+        svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR);
+      } catch(e) {
+        debug ("Error getting pref. " + e);
+        this.appsDir = FileUtils.getFile(DIRECTORY_NAME,
+                                         [SINGLE_VARIANT_SOURCE_DIR]).path;
+        return;
+      }
+      // If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return
+      // PREF_SINGLE_VARIANT_DIR as sourceDir, else go to
+      // DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
+      // configuration file) to PREF_SINGLE_VARIANT_DIR and return
+      // PREF_SINGLE_VARIANT_DIR as sourceDir.
+      let existsDir = yield File.exists(svFinalDirName);
+      if (!existsDir) {
+        yield File.makeDir(svFinalDirName, {ignoreExisting: true});
+      }
+
+      let existsSvIndex = yield File.exists(Path.join(svFinalDirName,
+                                            SINGLE_VARIANT_CONF_FILE));
+      if (!existsSvIndex) {
+        let svSourceDirName = FileUtils.getFile(DIRECTORY_NAME,
+                                              [SINGLE_VARIANT_SOURCE_DIR]).path;
+        yield this._copyDirectory(svSourceDirName, svFinalDirName);
+        debug("removing directory:" + svSourceDirName);
+        File.removeDir(svSourceDirName, {
+          ignoreAbsent: true,
+          ignorePermissions: true
+        });
+      }
+      this.appsDir = svFinalDirName;
+    }.bind(this));
+  },
+
   set appsDir(aDir) {
     debug("appsDir SET: " + aDir);
     if (aDir) {
       this._baseDirectory = Cc["@mozilla.org/file/local;1"]
           .createInstance(Ci.nsILocalFile);
       this._baseDirectory.initWithPath(aDir);
     } else {
       this._baseDirectory = null;
     }
   },
 
   get appsDir() {
-    if (!this._baseDirectory) {
-      this._baseDirectory = FileUtils.getFile(DIRECTORY_NAME,
-                                              [SINGLE_VARIANT_SOURCE_DIR]);
-    }
     return this._baseDirectory;
   },
 
   eraseVariantAppsNotInList: function(aIdsApp) {
     if (!aIdsApp || !Array.isArray(aIdsApp)) {
       aIdsApp = [ ];
     }
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -4,16 +4,38 @@
 
 "use strict";
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
+// Possible errors thrown by the signature verifier.
+const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
+const SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11);
+
+// We need this to decide if we should accept or not files signed with expired
+// certificates.
+function buildIDToTime() {
+  let platformBuildID =
+    Cc["@mozilla.org/xre/app-info;1"]
+      .getService(Ci.nsIXULAppInfo).platformBuildID;
+  let platformBuildIDDate = new Date();
+  platformBuildIDDate.setUTCFullYear(platformBuildID.substr(0,4),
+                                      platformBuildID.substr(4,2) - 1,
+                                      platformBuildID.substr(6,2));
+  platformBuildIDDate.setUTCHours(platformBuildID.substr(8,2),
+                                  platformBuildID.substr(10,2),
+                                  platformBuildID.substr(12,2));
+  return platformBuildIDDate.getTime();
+}
+
+const PLATFORM_BUILD_ID_TIME = buildIDToTime();
+
 this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
@@ -33,16 +55,20 @@ XPCOMUtils.defineLazyGetter(this, "libcu
 #endif
 
 function debug(aMsg) {
 #ifdef MOZ_DEBUG
   dump("-*- Webapps.jsm : " + aMsg + "\n");
 #endif
 }
 
+function getNSPRErrorCode(err) {
+  return -1 * ((err) & 0xffff);
+}
+
 function supportUseCurrentProfile() {
   return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
 }
 
 function supportSystemMessages() {
   return Services.prefs.getBoolPref("dom.sysmsg.enabled");
 }
 
@@ -2480,16 +2506,20 @@ onInstallSuccessAck: function onInstallS
 
     return Task.spawn((function*() {
       yield this._ensureSufficientStorage(aNewApp);
 
       let fullPackagePath = aManifest.fullPackagePath();
 
       // Check if it's a local file install (we've downloaded/sideloaded the
       // package already or it did exist on the build).
+      // Note that this variable also controls whether files signed with expired
+      // certificates are accepted or not. If isLocalFileInstall is true and the
+      // device date is earlier than the build generation date, then the signature
+      // will be accepted even if the certificate is expired.
       let isLocalFileInstall =
         Services.io.extractScheme(fullPackagePath) === 'file';
 
       debug("About to download " + fullPackagePath);
 
       let requestChannel = this._getRequestChannel(fullPackagePath,
                                                    isLocalFileInstall,
                                                    oldApp,
@@ -2832,17 +2862,18 @@ onInstallSuccessAck: function onInstallS
   },
 
   _openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall,
                                 aIsUpdate, aManifest, aRequestChannel, aHash) {
     return Task.spawn((function*() {
       let zipReader, isSigned, newManifest;
 
       try {
-        [zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp);
+        [zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp,
+                                                        aIsLocalFileInstall);
         newManifest = yield this._readPackage(aOldApp, aNewApp,
                 aIsLocalFileInstall, aIsUpdate, aManifest, aRequestChannel,
                 aHash, zipReader, isSigned);
       } catch (e) {
         debug("package open/read error: " + e);
         // Something bad happened when opening/reading the package.
         // Unrecoverable error, don't bug the user.
         // Apps with installState 'pending' does not produce any
@@ -2863,41 +2894,55 @@ onInstallSuccessAck: function onInstallS
         }
       }
 
       return newManifest;
 
     }).bind(this));
   },
 
-  _openPackage: function(aZipFile, aApp) {
+  _openPackage: function(aZipFile, aApp, aIsLocalFileInstall) {
     return Task.spawn((function*() {
       let certDb;
       try {
         certDb = Cc["@mozilla.org/security/x509certdb;1"]
                    .getService(Ci.nsIX509CertDB);
       } catch (e) {
         debug("nsIX509CertDB error: " + e);
         // unrecoverable error, don't bug the user
         aApp.downloadAvailable = false;
         throw "CERTDB_ERROR";
       }
 
       let [result, zipReader] = yield this._openSignedPackage(aZipFile, certDb);
 
+      // We cannot really know if the system date is correct or
+      // not. What we can know is if it's after the build date or not,
+      // and assume the build date is correct (which we cannot
+      // really know either).
+      let isLaterThanBuildTime = Date.now() > PLATFORM_BUILD_ID_TIME;
+
       let isSigned;
 
       if (Components.isSuccessCode(result)) {
         isSigned = true;
       } else if (result == Cr.NS_ERROR_FILE_CORRUPTED) {
         throw "APP_PACKAGE_CORRUPTED";
-      } else if (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
+      } else if ((!aIsLocalFileInstall || isLaterThanBuildTime) &&
+                 (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)) {
         throw "INVALID_SIGNATURE";
       } else {
-        isSigned = false;
+        // If it's a localFileInstall and the validation failed
+        // because of a expired certificate, just assume it was valid
+        // and that the error occurred because the system time has not
+        // been set yet.
+        isSigned = (aIsLocalFileInstall &&
+                    (getNSPRErrorCode(result) ==
+                     SEC_ERROR_EXPIRED_CERTIFICATE));
+
         zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
                       .createInstance(Ci.nsIZipReader);
         zipReader.open(aZipFile);
       }
 
       return [zipReader, isSigned];
 
     }).bind(this));
deleted file mode 100644
--- a/dom/bluetooth/BluetoothSocket.h
+++ /dev/null
@@ -1,52 +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_BluetoothSocket_h
-#define mozilla_dom_bluetooth_BluetoothSocket_h
-
-#include "BluetoothCommon.h"
-#include "mozilla/ipc/UnixSocket.h"
-
-BEGIN_BLUETOOTH_NAMESPACE
-
-class BluetoothSocketObserver;
-
-class BluetoothSocket : public mozilla::ipc::UnixSocketConsumer
-{
-public:
-  BluetoothSocket(BluetoothSocketObserver* aObserver,
-                  BluetoothSocketType aType,
-                  bool aAuth,
-                  bool aEncrypt);
-
-  bool Connect(const nsACString& aDeviceAddress, int aChannel);
-  bool Listen(int aChannel);
-  inline void Disconnect()
-  {
-    CloseSocket();
-  }
-
-  virtual void OnConnectSuccess() MOZ_OVERRIDE;
-  virtual void OnConnectError() MOZ_OVERRIDE;
-  virtual void OnDisconnect() MOZ_OVERRIDE;
-  virtual void ReceiveSocketData(
-    nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
-
-  inline void GetAddress(nsAString& aDeviceAddress)
-  {
-    GetSocketAddr(aDeviceAddress);
-  }
-
-private:
-  BluetoothSocketObserver* mObserver;
-  BluetoothSocketType mType;
-  bool mAuth;
-  bool mEncrypt;
-};
-
-END_BLUETOOTH_NAMESPACE
-
-#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp
@@ -0,0 +1,1526 @@
+/* -*- 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());
+    mSocket->CloseDroidSocket();
+  }
+
+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)
+{
+  mDeviceAddress.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;
+  }
+
+  /**
+   * We don't start listening here as BluetoothServiceBluedroid calls Listen()
+   * immediately when BT stops.
+   *
+   * If we start listening here, the listening fails when device boots up since
+   * Listen() is called again and restarts server socket. The restart causes
+   * absence of read events when device boots up.
+   */
+
+  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 (mServerSocket) {
+    mServerSocket->Disconnect();
+    mServerSocket = nullptr;
+  }
+
+  mIsServer = false;
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || sInShutdown || mSocket) {
+    OnSocketConnectError(mSocket);
+    return;
+  }
+
+  mSocket =
+    new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
+  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;
+  }
+  sBluetoothOppManager = nullptr;
+}
+
+bool
+BluetoothOppManager::Listen()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mSocket) {
+    BT_WARNING("mSocket exists. Failed to listen.");
+    return false;
+  }
+
+  /**
+   * Restart server socket since its underlying fd becomes invalid when
+   * BT stops; otherwise no more read events would be received even if
+   * BT restarts.
+   */
+  if (mServerSocket) {
+    mServerSocket->Disconnect();
+    mServerSocket = nullptr;
+  }
+
+  mServerSocket =
+    new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
+
+  if (!mServerSocket->Listen(BluetoothReservedChannels::CHANNEL_OPUSH)) {
+    BT_WARNING("[OPP] Can't listen on RFCOMM socket!");
+    mServerSocket = 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->SendDroidSocketData(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 = mDeviceAddress;
+  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 = mDeviceAddress;
+  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 = mDeviceAddress;
+  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 = mDeviceAddress;
+  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 server socket
+   * because currently only one file-transfer session is allowed. After that,
+   * we need to make sure that server socket would be nulled out.
+   * As for outbound connections, we just notify the controller that it's done.
+   */
+  if (aSocket == mServerSocket) {
+    MOZ_ASSERT(!mSocket);
+    mServerSocket.swap(mSocket);
+  }
+
+  // Cache device address since we can't get socket address when a remote
+  // device disconnect with us.
+  mSocket->GetAddress(mDeviceAddress);
+
+  // 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");
+
+  mServerSocket = 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)
+{
+  MOZ_ASSERT(aSocket);
+  if (aSocket != mSocket) {
+    // Do nothing when a listening server socket is closed.
+    return;
+  }
+  BT_LOGR("%s: [%s]", __FUNCTION__, (mIsServer) ? "client" : "server");
+
+  /**
+   * 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();
+  mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
+  mSuccessFlag = false;
+
+  mSocket = nullptr;
+  // Listen as a server if there's no more batch to process
+  if (!ProcessNextBatch()) {
+    Listen();
+  }
+}
+
+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::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                         const nsAString& aServiceUuid,
+                                         int aChannel)
+{
+  MOZ_ASSERT(false);
+}
+
+void
+BluetoothOppManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
+{
+  MOZ_ASSERT(false);
+}
+
+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);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothOppManager.h
@@ -0,0 +1,226 @@
+/* -*- 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 mDeviceAddress;
+
+  /**
+   * 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, mServerSocket 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> mServerSocket;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothSocket.cpp
@@ -0,0 +1,734 @@
+/* -*- 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 <hardware/bluetooth.h>
+#include <hardware/bt_sock.h>
+#include <sys/socket.h>
+
+#include "base/message_loop.h"
+#include "BluetoothServiceBluedroid.h"
+#include "BluetoothSocketObserver.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#define FIRST_SOCKET_INFO_MSG_LENGTH 4
+#define TOTAL_SOCKET_INFO_LENGTH 20
+
+using namespace mozilla::ipc;
+USING_BLUETOOTH_NAMESPACE
+
+static const size_t MAX_READ_SIZE = 1 << 16;
+static const uint8_t UUID_OBEX_OBJECT_PUSH[] = {
+  0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00,
+  0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
+};
+static const btsock_interface_t* sBluetoothSocketInterface = nullptr;
+
+// helper functions
+static bool
+EnsureBluetoothSocketHalLoad()
+{
+  if (sBluetoothSocketInterface) {
+    return true;
+  }
+
+  const bt_interface_t* btInf = GetBluetoothInterface();
+  NS_ENSURE_TRUE(btInf, false);
+
+  sBluetoothSocketInterface =
+    (btsock_interface_t *) btInf->get_profile_interface(BT_PROFILE_SOCKETS_ID);
+  NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
+
+  return true;
+}
+
+static int16_t
+ReadInt16(const uint8_t* aData, size_t* aOffset)
+{
+  int16_t value = (aData[*aOffset + 1] << 8) | aData[*aOffset];
+
+  *aOffset += 2;
+  return value;
+}
+
+static int32_t
+ReadInt32(const uint8_t* aData, size_t* aOffset)
+{
+  int32_t value = (aData[*aOffset + 3] << 24) |
+                  (aData[*aOffset + 2] << 16) |
+                  (aData[*aOffset + 1] << 8) |
+                  aData[*aOffset];
+  *aOffset += 4;
+  return value;
+}
+
+static void
+ReadBdAddress(const uint8_t* aData, size_t* aOffset, nsAString& aDeviceAddress)
+{
+  char bdstr[18];
+  sprintf(bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
+          aData[*aOffset], aData[*aOffset + 1], aData[*aOffset + 2],
+          aData[*aOffset + 3], aData[*aOffset + 4], aData[*aOffset + 5]);
+
+  aDeviceAddress.AssignLiteral(bdstr);
+  *aOffset += 6;
+}
+
+class mozilla::dom::bluetooth::DroidSocketImpl
+    : public MessageLoopForIO::Watcher
+{
+public:
+  DroidSocketImpl(BluetoothSocket* aConsumer, int aFd)
+    : mConsumer(aConsumer)
+    , mIOLoop(nullptr)
+    , mFd(aFd)
+    , mShuttingDownOnIOThread(false)
+  {
+  }
+
+  ~DroidSocketImpl()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  void QueueWriteData(UnixSocketRawData* aData)
+  {
+    mOutgoingQ.AppendElement(aData);
+    OnFileCanWriteWithoutBlocking(mFd);
+  }
+
+  bool IsShutdownOnMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mConsumer == nullptr;
+  }
+
+  bool IsShutdownOnIOThread()
+  {
+    return mShuttingDownOnIOThread;
+  }
+
+  void ShutdownOnMainThread()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!IsShutdownOnMainThread());
+    mConsumer = nullptr;
+  }
+
+  void ShutdownOnIOThread()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(!mShuttingDownOnIOThread);
+
+    mReadWatcher.StopWatchingFileDescriptor();
+    mWriteWatcher.StopWatchingFileDescriptor();
+
+    mShuttingDownOnIOThread = true;
+  }
+
+  void SetUpIO(bool aWrite)
+  {
+    MOZ_ASSERT(!mIOLoop);
+    MOZ_ASSERT(mFd >= 0);
+    mIOLoop = MessageLoopForIO::current();
+
+    // Set up a read watch
+    mIOLoop->WatchFileDescriptor(mFd,
+                                 true,
+                                 MessageLoopForIO::WATCH_READ,
+                                 &mReadWatcher,
+                                 this);
+
+    if (aWrite) {
+      // Set up a write watch
+      mIOLoop->WatchFileDescriptor(mFd.get(),
+                                   false,
+                                   MessageLoopForIO::WATCH_WRITE,
+                                   &mWriteWatcher,
+                                   this);
+    }
+  }
+
+  void ConnectClientFd()
+  {
+    // Stop current read watch
+    mReadWatcher.StopWatchingFileDescriptor();
+    mIOLoop = nullptr;
+
+    // Restart read & write watch on client fd
+    SetUpIO(true);
+  }
+
+  /**
+   * Consumer pointer. Non-thread safe RefPtr, so should only be manipulated
+   * directly from main thread. All non-main-thread accesses should happen with
+   * mImpl as container.
+   */
+  RefPtr<BluetoothSocket> mConsumer;
+
+private:
+  /**
+   * libevent triggered functions that reads data from socket when available and
+   * guarenteed non-blocking. Only to be called on IO thread.
+   *
+   * @param aFd [in] File descriptor to read from
+   */
+  virtual void OnFileCanReadWithoutBlocking(int aFd);
+
+  /**
+   * libevent or developer triggered functions that writes data to socket when
+   * available and guarenteed non-blocking. Only to be called on IO thread.
+   *
+   * @param aFd [in] File descriptor to read from
+   */
+  virtual void OnFileCanWriteWithoutBlocking(int aFd);
+
+  /**
+   * Read message to get data and client fd wrapped in message header
+   *
+   * @param aFd     [in]  File descriptor to read message from
+   * @param aBuffer [out] Data buffer read
+   * @param aLength [out] Number of bytes read
+   */
+  ssize_t ReadMsg(int aFd, void *aBuffer, size_t aLength);
+
+  /**
+   * IO Loop pointer. Must be initalized and called from IO thread only.
+   */
+  MessageLoopForIO* mIOLoop;
+
+  /**
+   * Raw data queue. Must be pushed/popped from IO thread only.
+   */
+  typedef nsTArray<UnixSocketRawData* > UnixSocketRawDataQueue;
+  UnixSocketRawDataQueue mOutgoingQ;
+
+  /**
+   * Read watcher for libevent. Only to be accessed on IO Thread.
+   */
+  MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+
+  /**
+   * Write watcher for libevent. Only to be accessed on IO Thread.
+   */
+  MessageLoopForIO::FileDescriptorWatcher mWriteWatcher;
+
+  /**
+   * File descriptor to read from/write to. Connection happens on user provided
+   * thread. Read/write/close happens on IO thread.
+   */
+  mozilla::ScopedClose mFd;
+
+  /**
+   * If true, do not requeue whatever task we're running
+   */
+  bool mShuttingDownOnIOThread;
+};
+
+template<class T>
+class DeleteInstanceRunnable : public nsRunnable
+{
+public:
+  DeleteInstanceRunnable(T* aInstance)
+  : mInstance(aInstance)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    delete mInstance;
+
+    return NS_OK;
+  }
+
+private:
+  T* mInstance;
+};
+
+class RequestClosingSocketTask : public nsRunnable
+{
+public:
+  RequestClosingSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl)
+  {
+    MOZ_ASSERT(aImpl);
+  }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mImpl->IsShutdownOnMainThread()) {
+      NS_WARNING("CloseSocket has already been called!");
+      // Since we've already explicitly closed and the close happened before
+      // this, this isn't really an error. Since we've warned, return OK.
+      return NS_OK;
+    }
+
+    // Start from here, same handling flow as calling CloseSocket() from
+    // upper layer
+    mImpl->mConsumer->CloseDroidSocket();
+    return NS_OK;
+  }
+private:
+  DroidSocketImpl* mImpl;
+};
+
+class ShutdownSocketTask : public Task {
+  virtual void Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // At this point, there should be no new events on the IO thread after this
+    // one with the possible exception of a SocketAcceptTask that
+    // ShutdownOnIOThread will cancel for us. We are now fully shut down, so we
+    // can send a message to the main thread that will delete mImpl safely knowing
+    // that no more tasks reference it.
+    mImpl->ShutdownOnIOThread();
+
+    nsRefPtr<nsIRunnable> t(new DeleteInstanceRunnable<
+                                  mozilla::dom::bluetooth::DroidSocketImpl>(mImpl));
+    nsresult rv = NS_DispatchToMainThread(t);
+    NS_ENSURE_SUCCESS_VOID(rv);
+  }
+
+  DroidSocketImpl* mImpl;
+
+public:
+  ShutdownSocketTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { }
+};
+
+class SocketReceiveTask : public nsRunnable
+{
+public:
+  SocketReceiveTask(DroidSocketImpl* aImpl, UnixSocketRawData* aData) :
+    mImpl(aImpl),
+    mRawData(aData)
+  {
+    MOZ_ASSERT(aImpl);
+    MOZ_ASSERT(aData);
+  }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (mImpl->IsShutdownOnMainThread()) {
+      NS_WARNING("mConsumer is null, aborting receive!");
+      // Since we've already explicitly closed and the close happened before
+      // this, this isn't really an error. Since we've warned, return OK.
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(mImpl->mConsumer);
+    mImpl->mConsumer->ReceiveSocketData(mRawData);
+    return NS_OK;
+  }
+private:
+  DroidSocketImpl* mImpl;
+  nsAutoPtr<UnixSocketRawData> mRawData;
+};
+
+class SocketSendTask : public Task
+{
+public:
+  SocketSendTask(BluetoothSocket* aConsumer, DroidSocketImpl* aImpl,
+                 UnixSocketRawData* aData)
+    : mConsumer(aConsumer),
+      mImpl(aImpl),
+      mData(aData)
+  {
+    MOZ_ASSERT(aConsumer);
+    MOZ_ASSERT(aImpl);
+    MOZ_ASSERT(aData);
+  }
+
+  void
+  Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(!mImpl->IsShutdownOnIOThread());
+
+    mImpl->QueueWriteData(mData);
+  }
+
+private:
+  nsRefPtr<BluetoothSocket> mConsumer;
+  DroidSocketImpl* mImpl;
+  UnixSocketRawData* mData;
+};
+
+class SocketSetUpIOTask : public Task
+{
+  virtual void Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    mImpl->SetUpIO(mWrite);
+  }
+
+  DroidSocketImpl* mImpl;
+  bool mWrite;
+public:
+  SocketSetUpIOTask(DroidSocketImpl* aImpl, bool aWrite)
+  : mImpl(aImpl), mWrite(aWrite) { }
+};
+
+class SocketConnectClientFdTask : public Task
+{
+  virtual void Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    mImpl->ConnectClientFd();
+  }
+
+  DroidSocketImpl* mImpl;
+public:
+  SocketConnectClientFdTask(DroidSocketImpl* aImpl) : mImpl(aImpl) { }
+};
+
+ssize_t
+DroidSocketImpl::ReadMsg(int aFd, void *aBuffer, size_t aLength)
+{
+  ssize_t ret;
+  struct msghdr msg;
+  struct iovec iv;
+  struct cmsghdr cmsgbuf[2 * sizeof(cmsghdr) + 0x100];
+
+  memset(&msg, 0, sizeof(msg));
+  memset(&iv, 0, sizeof(iv));
+
+  iv.iov_base = (unsigned char *)aBuffer;
+  iv.iov_len = aLength;
+
+  msg.msg_iov = &iv;
+  msg.msg_iovlen = 1;
+  msg.msg_control = cmsgbuf;
+  msg.msg_controllen = sizeof(cmsgbuf);
+
+  ret = recvmsg(mFd.get(), &msg, MSG_NOSIGNAL);
+  if (ret < 0 && errno == EPIPE) {
+    // Treat this as an end of stream
+    return 0;
+  }
+
+  NS_ENSURE_FALSE(ret < 0, -1);
+  NS_ENSURE_FALSE(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE), -1);
+
+  // Extract client fd from message header
+  for (struct cmsghdr *cmsgptr = CMSG_FIRSTHDR(&msg);
+       cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
+    if (cmsgptr->cmsg_level != SOL_SOCKET) {
+      continue;
+    }
+    if (cmsgptr->cmsg_type == SCM_RIGHTS) {
+      int *pDescriptors = (int *)CMSG_DATA(cmsgptr);
+      // Overwrite fd with client fd
+      mFd.reset(pDescriptors[0]);
+      break;
+    }
+  }
+
+  return ret;
+}
+
+void
+DroidSocketImpl::OnFileCanReadWithoutBlocking(int aFd)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!mShuttingDownOnIOThread);
+
+  // Read all of the incoming data.
+  while (true) {
+    nsAutoPtr<UnixSocketRawData> incoming(new UnixSocketRawData(MAX_READ_SIZE));
+
+    ssize_t ret;
+    if (!mConsumer->IsWaitingForClientFd()) {
+      ret = read(aFd, incoming->mData, incoming->mSize);
+    } else {
+      ret = ReadMsg(aFd, incoming->mData, incoming->mSize);
+    }
+
+    if (ret <= 0) {
+      if (ret == -1) {
+        if (errno == EINTR) {
+          continue; // retry system call when interrupted
+        }
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          return; // no data available: return and re-poll
+        }
+
+        BT_WARNING("Cannot read from network");
+        // else fall through to error handling on other errno's
+      }
+
+      // We're done with our descriptors. Ensure that spurious events don't
+      // cause us to end up back here.
+      mReadWatcher.StopWatchingFileDescriptor();
+      mWriteWatcher.StopWatchingFileDescriptor();
+      nsRefPtr<RequestClosingSocketTask> t = new RequestClosingSocketTask(this);
+      NS_DispatchToMainThread(t);
+      return;
+    }
+
+    incoming->mSize = ret;
+    nsRefPtr<SocketReceiveTask> t =
+      new SocketReceiveTask(this, incoming.forget());
+    NS_DispatchToMainThread(t);
+
+    // If ret is less than MAX_READ_SIZE, there's no
+    // more data in the socket for us to read now.
+    if (ret < ssize_t(MAX_READ_SIZE)) {
+      return;
+    }
+  }
+
+  MOZ_CRASH("We returned early");
+}
+
+void
+DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(!mShuttingDownOnIOThread);
+  MOZ_ASSERT(aFd >= 0);
+
+  // Try to write the bytes of mCurrentRilRawData.  If all were written, continue.
+  //
+  // Otherwise, save the byte position of the next byte to write
+  // within mCurrentWriteOffset, and request another write when the
+  // system won't block.
+  //
+  while (true) {
+    UnixSocketRawData* data;
+    if (mOutgoingQ.IsEmpty()) {
+      return;
+    }
+    data = mOutgoingQ.ElementAt(0);
+    const uint8_t *toWrite;
+    toWrite = data->mData;
+
+    while (data->mCurrentWriteOffset < data->mSize) {
+      ssize_t write_amount = data->mSize - data->mCurrentWriteOffset;
+      ssize_t written;
+      written = write (aFd, toWrite + data->mCurrentWriteOffset,
+                       write_amount);
+      if (written > 0) {
+        data->mCurrentWriteOffset += written;
+      }
+      if (written != write_amount) {
+        break;
+      }
+    }
+
+    if (data->mCurrentWriteOffset != data->mSize) {
+      MessageLoopForIO::current()->WatchFileDescriptor(
+        aFd,
+        false,
+        MessageLoopForIO::WATCH_WRITE,
+        &mWriteWatcher,
+        this);
+      return;
+    }
+    mOutgoingQ.RemoveElementAt(0);
+    delete data;
+  }
+}
+
+BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver,
+                                 BluetoothSocketType aType,
+                                 bool aAuth,
+                                 bool aEncrypt)
+  : mObserver(aObserver)
+  , mImpl(nullptr)
+  , mAuth(aAuth)
+  , mEncrypt(aEncrypt)
+  , mReceivedSocketInfoLength(0)
+{
+  MOZ_ASSERT(aObserver);
+
+  EnsureBluetoothSocketHalLoad();
+  mDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
+}
+
+void
+BluetoothSocket::CloseDroidSocket()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mImpl) {
+    return;
+  }
+
+  // From this point on, we consider mImpl as being deleted.
+  // We sever the relationship here so any future calls to listen or connect
+  // will create a new implementation.
+  mImpl->ShutdownOnMainThread();
+  XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
+                                   new ShutdownSocketTask(mImpl));
+  mImpl = nullptr;
+
+  OnDisconnect();
+}
+
+bool
+BluetoothSocket::CreateDroidSocket(int aFd)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_FALSE(mImpl, false);
+
+  mImpl = new DroidSocketImpl(this, aFd);
+  XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
+                                   new SocketSetUpIOTask(mImpl, !mIsServer));
+
+  return true;
+}
+
+bool
+BluetoothSocket::Connect(const nsAString& aDeviceAddress, int aChannel)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
+  NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
+
+  bt_bdaddr_t remoteBdAddress;
+  StringToBdAddressType(aDeviceAddress, &remoteBdAddress);
+
+  // TODO: uuid as argument
+  int fd;
+  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
+    sBluetoothSocketInterface->connect((bt_bdaddr_t *) &remoteBdAddress,
+                                       (btsock_type_t) BTSOCK_RFCOMM,
+                                       UUID_OBEX_OBJECT_PUSH,
+                                       aChannel, &fd, (mAuth << 1) | mEncrypt),
+    false);
+  NS_ENSURE_TRUE(fd >= 0, false);
+
+  mIsServer = false;
+  return CreateDroidSocket(fd);
+}
+
+bool
+BluetoothSocket::Listen(int aChannel)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(sBluetoothSocketInterface, false);
+
+  // TODO: uuid and service name as arguments
+  nsAutoCString serviceName("OBEX Object Push");
+  int fd;
+  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
+    sBluetoothSocketInterface->listen((btsock_type_t) BTSOCK_RFCOMM,
+                                      serviceName.get(),
+                                      UUID_OBEX_OBJECT_PUSH,
+                                      aChannel, &fd, (mAuth << 1) | mEncrypt),
+    false);
+  NS_ENSURE_TRUE(fd >= 0, false);
+
+  mIsServer = true;
+  return CreateDroidSocket(fd);
+}
+
+bool
+BluetoothSocket::SendDroidSocketData(UnixSocketRawData* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mImpl, false);
+
+  MOZ_ASSERT(!mImpl->IsShutdownOnMainThread());
+  XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
+                                   new SocketSendTask(this, mImpl, aData));
+  return true;
+}
+
+bool
+BluetoothSocket::IsWaitingForClientFd()
+{
+  return (mIsServer &&
+          mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH);
+}
+
+bool
+BluetoothSocket::ReceiveSocketInfo(nsAutoPtr<UnixSocketRawData>& aMessage)
+{
+  /**
+   * 2 socket info messages (20 bytes) to receive at the beginning:
+   * - 1st message: [channel:4]
+   * - 2nd message: [size:2][bd address:6][channel:4][connection status:4]
+   */
+  if (mReceivedSocketInfoLength >= TOTAL_SOCKET_INFO_LENGTH) {
+    // We've got both socket info messages
+    return false;
+  }
+  mReceivedSocketInfoLength += aMessage->mSize;
+
+  size_t offset = 0;
+  if (mReceivedSocketInfoLength == FIRST_SOCKET_INFO_MSG_LENGTH) {
+    // 1st message: [channel:4]
+    int32_t channel = ReadInt32(aMessage->mData, &offset);
+
+    BT_LOGR("%s: channel %d", __FUNCTION__, channel);
+  } else if (mReceivedSocketInfoLength == TOTAL_SOCKET_INFO_LENGTH) {
+    // 2nd message: [size:2][bd address:6][channel:4][connection status:4]
+    int16_t size = ReadInt16(aMessage->mData, &offset);
+    ReadBdAddress(aMessage->mData, &offset, mDeviceAddress);
+    int32_t channel = ReadInt32(aMessage->mData, &offset);
+    int32_t connectionStatus = ReadInt32(aMessage->mData, &offset);
+
+    BT_LOGR("%s: size %d channel %d remote addr %s status %d", __FUNCTION__,
+      size, channel, NS_ConvertUTF16toUTF8(mDeviceAddress).get(), connectionStatus);
+
+    if (connectionStatus != 0) {
+      OnConnectError();
+      return true;
+    }
+
+    if (mIsServer) {
+      // Connect client fd on IO thread
+      XRE_GetIOMessageLoop()->PostTask(FROM_HERE,
+                                       new SocketConnectClientFdTask(mImpl));
+    }
+    OnConnectSuccess();
+  }
+
+  return true;
+}
+
+void
+BluetoothSocket::ReceiveSocketData(nsAutoPtr<UnixSocketRawData>& aMessage)
+{
+  if (ReceiveSocketInfo(aMessage)) {
+    return;
+  }
+
+  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);
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothSocket.h
@@ -0,0 +1,87 @@
+/* -*- 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_BluetoothSocket_h
+#define mozilla_dom_bluetooth_BluetoothSocket_h
+
+#include "BluetoothCommon.h"
+#include "mozilla/ipc/UnixSocket.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothSocketObserver;
+class DroidSocketImpl;
+
+class BluetoothSocket : public mozilla::ipc::UnixSocketConsumer
+{
+public:
+  BluetoothSocket(BluetoothSocketObserver* aObserver,
+                  BluetoothSocketType aType,
+                  bool aAuth,
+                  bool aEncrypt);
+
+  /**
+   * Connect to remote server as a client.
+   *
+   * The steps are as following:
+   * 1) BluetoothSocket acquires fd from bluedroid, and creates
+   *    a DroidSocketImpl to watch read/write of the fd.
+   * 2) DroidSocketImpl receives first 2 messages to get socket info.
+   * 3) Obex client session starts.
+   */
+  bool Connect(const nsAString& aDeviceAddress, int aChannel);
+
+  /**
+   * Listen to incoming connection as a server.
+   *
+   * The steps are as following:
+   * 1) BluetoothSocket acquires fd from bluedroid, and creates
+   *    a DroidSocketImpl to watch read of the fd. DroidSocketImpl
+   *    receives the 1st message immediately.
+   * 2) When there's incoming connection, DroidSocketImpl receives
+   *    2nd message to get socket info and client fd.
+   * 3) DroidSocketImpl stops watching read of original fd and
+   *    starts to watch read/write of client fd.
+   * 4) Obex server session starts.
+   */
+  bool Listen(int aChannel);
+
+  inline void Disconnect()
+  {
+    CloseDroidSocket();
+  }
+
+  virtual void OnConnectSuccess() MOZ_OVERRIDE;
+  virtual void OnConnectError() MOZ_OVERRIDE;
+  virtual void OnDisconnect() MOZ_OVERRIDE;
+  virtual void ReceiveSocketData(
+    nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
+
+  inline void GetAddress(nsAString& aDeviceAddress)
+  {
+    aDeviceAddress = mDeviceAddress;
+  }
+
+  void CloseDroidSocket();
+  bool IsWaitingForClientFd();
+  bool SendDroidSocketData(mozilla::ipc::UnixSocketRawData* aData);
+
+private:
+  BluetoothSocketObserver* mObserver;
+  DroidSocketImpl* mImpl;
+  nsString mDeviceAddress;
+  bool mAuth;
+  bool mEncrypt;
+  bool mIsServer;
+  int mReceivedSocketInfoLength;
+
+  bool CreateDroidSocket(int aFd);
+  bool ReceiveSocketInfo(nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage);
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
@@ -15,18 +15,19 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 
 #include "BluetoothServiceBluedroid.h"
 
 #include <hardware/hardware.h>
 
-#include "bluedroid/BluetoothA2dpManager.h"
-#include "bluedroid/BluetoothHfpManager.h"
+#include "BluetoothA2dpManager.h"
+#include "BluetoothHfpManager.h"
+#include "BluetoothOppManager.h"
 #include "BluetoothProfileController.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/UnixSocket.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
@@ -110,16 +111,22 @@ public:
     // Try to fire event 'AdapterAdded' to fit the original behaviour when
     // we used BlueZ as backend.
     BluetoothService* bs = BluetoothService::Get();
     NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
 
     bs->AdapterAddedReceived();
     bs->TryFiringAdapterAdded();
 
+    // Trigger BluetoothOppManager to listen
+    BluetoothOppManager* opp = BluetoothOppManager::Get();
+    if (!opp || !opp->Listen()) {
+      BT_LOGR("%s: Fail to start BluetoothOppManager listening", __FUNCTION__);
+    }
+
     return NS_OK;
   }
 };
 
 /**
  *  Static callback functions
  */
 static void
@@ -1285,32 +1292,75 @@ BluetoothServiceBluedroid::Disconnect(
 }
 
 void
 BluetoothServiceBluedroid::SendFile(const nsAString& aDeviceAddress,
                                     BlobParent* aBlobParent,
                                     BlobChild* aBlobChild,
                                     BluetoothReplyRunnable* aRunnable)
 {
+  MOZ_ASSERT(NS_IsMainThread());
 
+  // Force to stop discovery, otherwise socket connecting would fail
+  if (!IsReady() || BT_STATUS_SUCCESS != sBtInterface->cancel_discovery()) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Calling cancel_discovery() failed");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
+    return;
+  }
+
+  // Currently we only support one device sending one file at a time,
+  // so we don't need aDeviceAddress here because the target device
+  // has been determined when calling 'Connect()'. Nevertheless, keep
+  // it for future use.
+  BluetoothOppManager* opp = BluetoothOppManager::Get();
+  nsAutoString errorStr;
+  if (!opp || !opp->SendFile(aDeviceAddress, aBlobParent)) {
+    errorStr.AssignLiteral("Calling SendFile() failed");
+  }
+
+  DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothServiceBluedroid::StopSendingFile(const nsAString& aDeviceAddress,
                                            BluetoothReplyRunnable* aRunnable)
 {
+  MOZ_ASSERT(NS_IsMainThread());
 
+  // Currently we only support one device sending one file at a time,
+  // so we don't need aDeviceAddress here because the target device
+  // has been determined when calling 'Connect()'. Nevertheless, keep
+  // it for future use.
+  BluetoothOppManager* opp = BluetoothOppManager::Get();
+  nsAutoString errorStr;
+  if (!opp || !opp->StopSendingFile()) {
+    errorStr.AssignLiteral("Calling StopSendingFile() failed");
+  }
+
+  DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothServiceBluedroid::ConfirmReceivingFile(
   const nsAString& aDeviceAddress, bool aConfirm,
   BluetoothReplyRunnable* aRunnable)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Must be called from main thread!");
 
+  // Currently we only support one device sending one file at a time,
+  // so we don't need aDeviceAddress here because the target device
+  // has been determined when calling 'Connect()'. Nevertheless, keep
+  // it for future use.
+  BluetoothOppManager* opp = BluetoothOppManager::Get();
+  nsAutoString errorStr;
+  if (!opp || !opp->ConfirmReceivingFile(aConfirm)) {
+    errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed");
+  }
+
+  DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothServiceBluedroid::ConnectSco(BluetoothReplyRunnable* aRunnable)
 {
 
 }
 
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp
@@ -379,16 +379,17 @@ BluetoothHfpManager::Reset()
   mCCWA = false;
   mCLIP = false;
   mDialingRequestProcessed = true;
 #endif
   mCMEE = false;
   mCMER = false;
   mConnectScoRequest = false;
   mSlcConnected = false;
+  mHspConnected = false;
   mReceiveVgsFlag = false;
 
 #ifdef MOZ_B2G_RIL
   // We disable BSIR by default as it requires OEM implement BT SCO + SPEAKER
   // output audio path in audio driver. OEM can enable BSIR by setting
   // mBSIR=true here.
   //
   // Please see Bug 878728 for more information.
@@ -1612,16 +1613,17 @@ BluetoothHfpManager::OnSocketConnectSucc
   if (aSocket == mHandsfreeSocket) {
     MOZ_ASSERT(!mSocket);
     mHandsfreeSocket.swap(mSocket);
 
     mHeadsetSocket->Disconnect();
     mHeadsetSocket = nullptr;
   } else if (aSocket == mHeadsetSocket) {
     MOZ_ASSERT(!mSocket);
+    mHspConnected = true;
     mHeadsetSocket.swap(mSocket);
 
     mHandsfreeSocket->Disconnect();
     mHandsfreeSocket = nullptr;
   }
 
 #ifdef MOZ_B2G_RIL
   // Enumerate current calls
@@ -1797,19 +1799,19 @@ BluetoothHfpManager::ConnectSco(Bluetoot
   SocketConnectionStatus status = mScoSocket->GetConnectionStatus();
   if (status == SocketConnectionStatus::SOCKET_CONNECTED ||
       status == SocketConnectionStatus::SOCKET_CONNECTING ||
       (mScoRunnable && (mScoRunnable != aRunnable))) {
     BT_WARNING("SCO connection exists or is being established");
     return false;
   }
 
-  // Make sure Service Level Connection established before we start to
-  // set up SCO (synchronous connection).
-  if (!mSlcConnected) {
+  // If we are not using HSP, we have to make sure Service Level Connection
+  // established before we start to set up SCO (synchronous connection).
+  if (!mSlcConnected && !mHspConnected) {
     mConnectScoRequest = true;
     BT_WARNING("ConnectSco called before Service Level Connection established");
     return false;
   }
 
   mScoSocket->Disconnect();
 
   mScoRunnable = aRunnable;
--- a/dom/bluetooth/bluez/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluez/BluetoothHfpManager.h
@@ -97,18 +97,19 @@ public:
   virtual void OnSocketConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnSocketDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
 
   bool Listen();
   /**
    * This function set up a Synchronous Connection (SCO) link for HFP.
    * Service Level Connection (SLC) should be established before SCO setup
    * process.
-   * If SLC haven't been established, this function will return false and send a
-   * request to set up SCO ater HfpManager receive AT+CMER.
+   * If SLC haven't been established, this function will return false and
+   * send a request to set up SCO ater HfpManager receive AT+CMER, unless we are
+   * connecting HSP socket rather than HFP socket.
    *
    * @param  aRunnable Indicate a BluetoothReplyRunnable to execute this
    *                   function. The default value is nullpter
    * @return <code>true</code> if SCO established successfully
    */
   bool ConnectSco(BluetoothReplyRunnable* aRunnable = nullptr);
   bool DisconnectSco();
   bool ListenSco();
@@ -183,16 +184,17 @@ private:
   bool mBSIR;
   bool mCCWA;
   bool mCLIP;
 #endif
   bool mCMEE;
   bool mCMER;
   bool mConnectScoRequest;
   bool mSlcConnected;
+  bool mHspConnected;
 #ifdef MOZ_B2G_RIL
   bool mFirstCKPD;
   int mNetworkSelectionMode;
   PhoneType mPhoneType;
 #endif
   bool mReceiveVgsFlag;
 #ifdef MOZ_B2G_RIL
   bool mDialingRequestProcessed;
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp
@@ -0,0 +1,1587 @@
+/* -*- 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);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothOppManager.h
@@ -0,0 +1,228 @@
+/* -*- 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
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothSocket.cpp
@@ -0,0 +1,98 @@
+/* -*- 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);
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothSocket.h
@@ -0,0 +1,52 @@
+/* -*- 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_BluetoothSocket_h
+#define mozilla_dom_bluetooth_BluetoothSocket_h
+
+#include "BluetoothCommon.h"
+#include "mozilla/ipc/UnixSocket.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothSocketObserver;
+
+class BluetoothSocket : public mozilla::ipc::UnixSocketConsumer
+{
+public:
+  BluetoothSocket(BluetoothSocketObserver* aObserver,
+                  BluetoothSocketType aType,
+                  bool aAuth,
+                  bool aEncrypt);
+
+  bool Connect(const nsACString& aDeviceAddress, int aChannel);
+  bool Listen(int aChannel);
+  inline void Disconnect()
+  {
+    CloseSocket();
+  }
+
+  virtual void OnConnectSuccess() MOZ_OVERRIDE;
+  virtual void OnConnectError() MOZ_OVERRIDE;
+  virtual void OnDisconnect() MOZ_OVERRIDE;
+  virtual void ReceiveSocketData(
+    nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
+
+  inline void GetAddress(nsAString& aDeviceAddress)
+  {
+    GetSocketAddr(aDeviceAddress);
+  }
+
+private:
+  BluetoothSocketObserver* mObserver;
+  BluetoothSocketType mType;
+  bool mAuth;
+  bool mEncrypt;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothUnixSocketConnector.cpp
@@ -0,0 +1,285 @@
+/* -*- 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
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothUnixSocketConnector.h
@@ -0,0 +1,42 @@
+/* -*- 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/bluez/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/linux/BluetoothDBusService.cpp
@@ -2966,20 +2966,18 @@ BluetoothDBusService::SendFile(const nsA
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Currently we only support one device sending one file at a time,
   // so we don't need aDeviceAddress here because the target device
   // has been determined when calling 'Connect()'. Nevertheless, keep
   // it for future use.
   BluetoothOppManager* opp = BluetoothOppManager::Get();
-  NS_ENSURE_TRUE_VOID(opp);
-
   nsAutoString errorStr;
-  if (!opp->SendFile(aDeviceAddress, aBlobParent)) {
+  if (!opp || !opp->SendFile(aDeviceAddress, aBlobParent)) {
     errorStr.AssignLiteral("Calling SendFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothDBusService::StopSendingFile(const nsAString& aDeviceAddress,
@@ -2987,42 +2985,38 @@ BluetoothDBusService::StopSendingFile(co
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Currently we only support one device sending one file at a time,
   // so we don't need aDeviceAddress here because the target device
   // has been determined when calling 'Connect()'. Nevertheless, keep
   // it for future use.
   BluetoothOppManager* opp = BluetoothOppManager::Get();
-  NS_ENSURE_TRUE_VOID(opp);
-
   nsAutoString errorStr;
-  if (!opp->StopSendingFile()) {
+  if (!opp || !opp->StopSendingFile()) {
     errorStr.AssignLiteral("Calling StopSendingFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothDBusService::ConfirmReceivingFile(const nsAString& aDeviceAddress,
                                            bool aConfirm,
                                            BluetoothReplyRunnable* aRunnable)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
+  MOZ_ASSERT(NS_IsMainThread(), "Must be called from main thread!");
 
   // Currently we only support one device sending one file at a time,
   // so we don't need aDeviceAddress here because the target device
   // has been determined when calling 'Connect()'. Nevertheless, keep
   // it for future use.
   BluetoothOppManager* opp = BluetoothOppManager::Get();
-  NS_ENSURE_TRUE_VOID(opp);
-
   nsAutoString errorStr;
-  if (!opp->ConfirmReceivingFile(aConfirm)) {
+  if (!opp || !opp->ConfirmReceivingFile(aConfirm)) {
     errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr);
 }
 
 void
 BluetoothDBusService::ConnectSco(BluetoothReplyRunnable* aRunnable)
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -7,23 +7,20 @@
 if CONFIG['MOZ_B2G_BT']:
     PARALLEL_DIRS += ['interfaces']
 
     SOURCES += [
         'BluetoothAdapter.cpp',
         'BluetoothDevice.cpp',
         'BluetoothHidManager.cpp',
         'BluetoothManager.cpp',
-        'BluetoothOppManager.cpp',
         'BluetoothProfileController.cpp',
         'BluetoothPropertyContainer.cpp',
         'BluetoothReplyRunnable.cpp',
         'BluetoothService.cpp',
-        'BluetoothSocket.cpp',
-        'BluetoothUnixSocketConnector.cpp',
         'BluetoothUtils.cpp',
         'BluetoothUuid.cpp',
         'ipc/BluetoothChild.cpp',
         'ipc/BluetoothParent.cpp',
         'ipc/BluetoothServiceChildProcess.cpp',
         'ObexBase.cpp'
     ]
 
@@ -32,29 +29,34 @@ if CONFIG['MOZ_B2G_BT']:
             'BluetoothRilListener.cpp',
         ]
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
         if CONFIG['MOZ_B2G_BT_BLUEZ']:
             SOURCES += [
                 'bluez/BluetoothA2dpManager.cpp',
                 'bluez/BluetoothHfpManager.cpp',
+                'bluez/BluetoothOppManager.cpp',
+                'bluez/BluetoothSocket.cpp',
+                'bluez/BluetoothUnixSocketConnector.cpp',
                 'bluez/gonk/BluetoothGonkService.cpp',
                 'bluez/linux/BluetoothDBusService.cpp',
             ]
             LOCAL_INCLUDES += [
                 'bluez',
                 'bluez/gonk',
                 'bluez/linux',
             ]
             DEFINES['MOZ_B2G_BT_BLUEZ'] = True
         elif CONFIG['MOZ_B2G_BT_BLUEDROID']:
             SOURCES += [
                 'bluedroid/BluetoothA2dpManager.cpp',
                 'bluedroid/BluetoothHfpManager.cpp',
+                'bluedroid/BluetoothOppManager.cpp',
+                'bluedroid/BluetoothSocket.cpp',
                 'bluedroid/gonk/BluetoothServiceBluedroid.cpp',
             ]
             LOCAL_INCLUDES += [
                 'bluedroid',
                 'bluedroid/gonk',
             ]
             DEFINES['MOZ_B2G_BT_BLUEDROID'] = True
     elif CONFIG['MOZ_ENABLE_DBUS']:
--- a/dom/datastore/tests/test_app_install.html
+++ b/dom/datastore/tests/test_app_install.html
@@ -22,17 +22,17 @@
 
   SpecialPowers.pushPermissions(
     [{ "type": "browser", "allow": 1, "context": document },
      { "type": "embed-apps", "allow": 1, "context": document },
      { "type": "webapps-manage", "allow": 1, "context": document }],
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
                                          ["dom.promise.enabled", true],
-                                         ["geo.testing.ignore_ipc_principal", true]]}, function() {
+                                         ["dom.testing.ignore_ipc_principal", true]]}, function() {
         gGenerator.next(); });
     });
 
   function continueTest() {
     try { gGenerator.next(); }
     catch(e) { dump("Got exception: " + e + "\n"); }
   }
 
--- a/dom/datastore/tests/test_arrays.html
+++ b/dom/datastore/tests/test_arrays.html
@@ -71,25 +71,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_basic.html
+++ b/dom/datastore/tests/test_basic.html
@@ -71,25 +71,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_bug924104.html
+++ b/dom/datastore/tests/test_bug924104.html
@@ -71,25 +71,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
--- a/dom/datastore/tests/test_changes.html
+++ b/dom/datastore/tests/test_changes.html
@@ -115,25 +115,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     // Enabling mozBrowser
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
       SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true]]}, runTest);
     },
 
--- a/dom/datastore/tests/test_oop.html
+++ b/dom/datastore/tests/test_oop.html
@@ -71,25 +71,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setAllAppsLaunchable(true);
--- a/dom/datastore/tests/test_readonly.html
+++ b/dom/datastore/tests/test_readonly.html
@@ -97,12 +97,12 @@
 
   if (SpecialPowers.isMainProcess()) {
     SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
                                      ["dom.datastore.enabled", true],
-                                     ["geo.testing.ignore_ipc_principal", true]]}, runTest);
+                                     ["dom.testing.ignore_ipc_principal", true]]}, runTest);
   </script>
 </body>
 </html>
--- a/dom/datastore/tests/test_sync.html
+++ b/dom/datastore/tests/test_sync.html
@@ -71,25 +71,19 @@
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
     },
 
     // Preferences
     function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true]]}, runTest);
-    },
-
-    function() {
-      SpecialPowers.pushPrefEnv({"set": [["geo.testing.ignore_ipc_principal", true]]}, runTest);
+      SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
+                                         ["dom.datastore.enabled", true],
+                                         ["dom.testing.ignore_ipc_principal", true]]}, runTest);
     },
 
     function() {
       SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
       runTest();
     },
 
     // No confirmation needed when an app is installed
--- a/dom/icc/tests/marionette/manifest.ini
+++ b/dom/icc/tests/marionette/manifest.ini
@@ -19,11 +19,9 @@ qemu = true
 [test_stk_display_text.js]
 [test_stk_get_inkey.js]
 [test_stk_get_input.js]
 [test_stk_select_item.js]
 [test_stk_setup_menu.js]
 [test_stk_setup_idle_mode_text.js]
 [test_stk_bip_command.js]
 [test_icc_access_invalid_object.js]
-disabled = Bug 933654
 [test_icc_detected_undetected_event.js]
-disabled = Bug 933654
--- a/dom/icc/tests/marionette/test_icc_access_invalid_object.js
+++ b/dom/icc/tests/marionette/test_icc_access_invalid_object.js
@@ -1,34 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = "icc_header.js";
 
 function setRadioEnabled(enabled) {
-  SpecialPowers.addPermission("settings-write", true, document);
+  let connection = navigator.mozMobileConnections[0];
+  ok(connection);
 
-  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
-  let settings = navigator.mozSettings;
-  let setLock = settings.createLock();
-  let obj = {
-    "ril.radio.disabled": !enabled
-  };
-  let setReq = setLock.set(obj);
+  let request  = connection.setRadioEnabled(enabled);
 
-  setReq.addEventListener("success", function onSetSuccess() {
-    log("set 'ril.radio.disabled' to " + enabled);
-  });
+  request.onsuccess = function onsuccess() {
+    log('setRadioEnabled: ' + enabled);
+  };
 
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
-  });
-
-  SpecialPowers.removePermission("settings-write", document);
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 /* Test access invalid icc object */
 taskHelper.push(function testAccessRemovedIccObject() {
   setRadioEnabled(false);
   iccManager.addEventListener("iccundetected", function oniccundetected(evt) {
     log("got icc undetected event");
     iccManager.removeEventListener("iccundetected", oniccundetected);
--- a/dom/icc/tests/marionette/test_icc_detected_undetected_event.js
+++ b/dom/icc/tests/marionette/test_icc_detected_undetected_event.js
@@ -1,34 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = "icc_header.js";
 
 function setRadioEnabled(enabled) {
-  SpecialPowers.addPermission("settings-write", true, document);
+  let connection = navigator.mozMobileConnections[0];
+  ok(connection);
 
-  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
-  let settings = navigator.mozSettings;
-  let setLock = settings.createLock();
-  let obj = {
-    "ril.radio.disabled": !enabled
-  };
-  let setReq = setLock.set(obj);
+  let request  = connection.setRadioEnabled(enabled);
 
-  setReq.addEventListener("success", function onSetSuccess() {
-    log("set 'ril.radio.disabled' to " + enabled);
-  });
+  request.onsuccess = function onsuccess() {
+    log('setRadioEnabled: ' + enabled);
+  };
 
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
-  });
-
-  SpecialPowers.removePermission("settings-write", document);
+  request.onerror = function onerror() {
+    ok(false, "setRadioEnabled should be ok");
+  };
 }
 
 /* Test iccundetected event */
 taskHelper.push(function testIccUndetectedEvent() {
   setRadioEnabled(false);
   iccManager.addEventListener("iccundetected", function oniccundetected(evt) {
     log("got icc undetected event");
     iccManager.removeEventListener("iccundetected", oniccundetected);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2754,17 +2754,17 @@ ContentParent::RecvCloseAlert(const nsSt
 bool
 ContentParent::RecvSyncMessage(const nsString& aMsg,
                                const ClonedMessageData& aData,
                                const InfallibleTArray<CpowEntry>& aCpows,
                                const IPC::Principal& aPrincipal,
                                InfallibleTArray<nsString>* aRetvals)
 {
   nsIPrincipal* principal = aPrincipal;
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(this, principal)) {
     return false;
   }
 
   nsRefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
     CpowIdHolder cpows(GetCPOWManager(), aCpows);
@@ -2778,17 +2778,17 @@ ContentParent::RecvSyncMessage(const nsS
 bool
 ContentParent::AnswerRpcMessage(const nsString& aMsg,
                                 const ClonedMessageData& aData,
                                 const InfallibleTArray<CpowEntry>& aCpows,
                                 const IPC::Principal& aPrincipal,
                                 InfallibleTArray<nsString>* aRetvals)
 {
   nsIPrincipal* principal = aPrincipal;
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(this, principal)) {
     return false;
   }
 
   nsRefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
     CpowIdHolder cpows(GetCPOWManager(), aCpows);
@@ -2800,17 +2800,17 @@ ContentParent::AnswerRpcMessage(const ns
 
 bool
 ContentParent::RecvAsyncMessage(const nsString& aMsg,
                                 const ClonedMessageData& aData,
                                 const InfallibleTArray<CpowEntry>& aCpows,
                                 const IPC::Principal& aPrincipal)
 {
   nsIPrincipal* principal = aPrincipal;
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(this, principal)) {
     return false;
   }
 
   nsRefPtr<nsFrameMessageManager> ppm = mMessageManager;
   if (ppm) {
     StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
     CpowIdHolder cpows(GetCPOWManager(), aCpows);
@@ -2856,17 +2856,17 @@ AddGeolocationListener(nsIDOMGeoPosition
   return retval;
 }
 
 bool
 ContentParent::RecvAddGeolocationListener(const IPC::Principal& aPrincipal,
                                           const bool& aHighAccuracy)
 {
 #ifdef MOZ_CHILD_PERMISSIONS
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false)) {
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false)) {
     uint32_t permission = mozilla::CheckPermission(this, aPrincipal,
                                                    "geolocation");
     if (permission != nsIPermissionManager::ALLOW_ACTION) {
       return true;
     }
   }
 #endif /* MOZ_CHILD_PERMISSIONS */
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -772,17 +772,17 @@ bool
 TabParent::RecvSyncMessage(const nsString& aMessage,
                            const ClonedMessageData& aData,
                            const InfallibleTArray<CpowEntry>& aCpows,
                            const IPC::Principal& aPrincipal,
                            InfallibleTArray<nsString>* aJSONRetVal)
 {
   nsIPrincipal* principal = aPrincipal;
   ContentParent* parent = static_cast<ContentParent*>(Manager());
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(parent, principal)) {
     return false;
   }
 
   StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
   CpowIdHolder cpows(parent->GetCPOWManager(), aCpows);
   return ReceiveMessage(aMessage, true, &cloneData, &cpows, aPrincipal, aJSONRetVal);
 }
@@ -791,17 +791,17 @@ bool
 TabParent::AnswerRpcMessage(const nsString& aMessage,
                             const ClonedMessageData& aData,
                             const InfallibleTArray<CpowEntry>& aCpows,
                             const IPC::Principal& aPrincipal,
                             InfallibleTArray<nsString>* aJSONRetVal)
 {
   nsIPrincipal* principal = aPrincipal;
   ContentParent* parent = static_cast<ContentParent*>(Manager());
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(parent, principal)) {
     return false;
   }
 
   StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
   CpowIdHolder cpows(parent->GetCPOWManager(), aCpows);
   return ReceiveMessage(aMessage, true, &cloneData, &cpows, aPrincipal, aJSONRetVal);
 }
@@ -809,17 +809,17 @@ TabParent::AnswerRpcMessage(const nsStri
 bool
 TabParent::RecvAsyncMessage(const nsString& aMessage,
                             const ClonedMessageData& aData,
                             const InfallibleTArray<CpowEntry>& aCpows,
                             const IPC::Principal& aPrincipal)
 {
   nsIPrincipal* principal = aPrincipal;
   ContentParent* parent = static_cast<ContentParent*>(Manager());
-  if (!Preferences::GetBool("geo.testing.ignore_ipc_principal", false) &&
+  if (!Preferences::GetBool("dom.testing.ignore_ipc_principal", false) &&
       principal && !AssertAppPrincipal(parent, principal)) {
     return false;
   }
 
   StructuredCloneData cloneData = ipc::UnpackClonedMessageDataForParent(aData);
   CpowIdHolder cpows(parent->GetCPOWManager(), aCpows);
   return ReceiveMessage(aMessage, false, &cloneData, &cpows, aPrincipal, nullptr);
 }
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -380,17 +380,17 @@ NetworkStatsDB.prototype = {
 
     // Clear and save an empty sample to keep sync with system counters
     this.dbNewTxn("readwrite", function(aTxn, aStore) {
       let sample = null;
       let request = aStore.index("network").openCursor(network, "prev");
       request.onsuc