Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 21 Sep 2015 14:06:24 +0200
changeset 263578 857d14f69fa30aa615795b0b3b36f72af6dce349
parent 263577 ea66ea508edd2f1000aaa0ec662ed071cd98d377 (current diff)
parent 263524 5da1fc351aba2e08f33983e9a011306dde4a81af (diff)
child 263585 197af2fb7e29ff8e4b3b6ced723b6172e954e17d
child 263658 5da58dc9aa0eb9e901f770d6c14fe9870a48ba22
push id65352
push userkwierso@gmail.com
push dateMon, 21 Sep 2015 16:52:59 +0000
treeherdermozilla-inbound@ee92eb117e92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to b2g-inbound
browser/components/translation/translation.manifest
dom/base/nsGkAtomList.h
dom/network/TCPServerSocket.js
dom/network/TCPSocket.js
dom/network/TCPSocket.manifest
dom/network/TCPSocketParentIntermediary.js
dom/network/interfaces/nsIDOMTCPServerSocket.idl
dom/network/interfaces/nsIDOMTCPSocket.idl
dom/network/interfaces/nsITCPServerSocketChild.idl
dom/network/interfaces/nsITCPServerSocketParent.idl
dom/network/interfaces/nsITCPSocketChild.idl
dom/network/interfaces/nsITCPSocketParent.idl
dom/network/tests/unit/test_tcpsocket.js
dom/network/tests/unit/xpcshell.ini
dom/network/tests/unit_ipc/test_tcpsocket_ipc.js
dom/network/tests/unit_ipc/xpcshell.ini
dom/push/PushServiceHttp2Crypto.jsm
dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys.js
editor/libeditor/tests/test_bug772796.html
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_height.ico
image/test/reftest/ico/ico-bmp-corrupted/invalid_ico_width.ico
testing/web-platform/mozilla/meta/service-workers/service-worker/fetch-csp.https.html.ini
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -2053,17 +2053,17 @@ DocAccessible::UpdateTreeInternal(Access
 void
 DocAccessible::ValidateARIAOwned()
 {
   for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
     nsTArray<nsIContent*>* childEls = it.UserData();
     for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
       nsIContent* childEl = childEls->ElementAt(idx);
       Accessible* child = GetAccessible(childEl);
-      if (child && !child->GetFrame()) {
+      if (child && child->IsInDocument() && !child->GetFrame()) {
         UpdateTreeOnRemoval(child->Parent(), childEl);
       }
     }
   }
 }
 
 void
 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -57,17 +57,16 @@ pref("browser.cache.disk.smart_size.firs
 
 pref("browser.cache.memory.enable", true);
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 pref("browser.cache.memory_limit", 2048); // 2 MB
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
-pref("image.high_quality_downscaling.enabled", false);
 pref("canvas.image.cache.limit", 20971520); // 20 MB
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", false);
 pref("browser.cache.offline.enable", true);
 pref("offline-apps.allow_by_default", true);
 
 /* protocol warning prefs */
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -674,21 +674,16 @@
 @RESPATH@/components/HCIEventTransactionSystemMessageConfigurator.js
 
 @RESPATH@/components/Activities.manifest
 @RESPATH@/components/ActivityProxy.js
 @RESPATH@/components/ActivityRequestHandler.js
 @RESPATH@/components/ActivityWrapper.js
 @RESPATH@/components/ActivityMessageConfigurator.js
 
-@RESPATH@/components/TCPSocket.js
-@RESPATH@/components/TCPServerSocket.js
-@RESPATH@/components/TCPSocketParentIntermediary.js
-@RESPATH@/components/TCPSocket.manifest
-
 @RESPATH@/components/Payment.js
 @RESPATH@/components/PaymentFlowInfo.js
 @RESPATH@/components/PaymentProvider.js
 @RESPATH@/components/Payment.manifest
 
 @RESPATH@/components/DownloadsAPI.js
 @RESPATH@/components/DownloadsAPI.manifest
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1688,16 +1688,17 @@ pref("shumway.disabled", true);
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 // (This is intentionally on the high side; see bug 746055.)
 pref("image.mem.max_decoded_image_kb", 256000);
 
 pref("loop.enabled", true);
 pref("loop.textChat.enabled", true);
 pref("loop.server", "https://loop.services.mozilla.com/v0");
+pref("loop.linkClicker.url", "https://hello.firefox.com/");
 pref("loop.gettingStarted.seen", false);
 pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
 pref("loop.gettingStarted.resumeOnFirstJoin", false);
 pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
 pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
 pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
 pref("loop.do_not_disturb", false);
 pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/ringtone.ogg");
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -209,20 +209,16 @@ var gFxAccounts = {
     panel.hidden = false;
     panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
   },
 
   showSyncStartedDoorhanger: function () {
     this.showDoorhanger("sync-start-panel");
   },
 
-  showSyncFailedDoorhanger: function () {
-    this.showDoorhanger("sync-error-panel");
-  },
-
   updateUI: function () {
     this.updateAppMenuItem();
     this.updateMigrationNotification();
   },
 
   // Note that updateAppMenuItem() returns a Promise that's only used by tests.
   updateAppMenuItem: function () {
     if (this._migrationInfo) {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -444,38 +444,16 @@
           <description id="sync-start-panel-title"
                        value="&syncStartPanel2.heading;"/>
           <description id="sync-start-panel-subtitle"
                        value="&syncStartPanel2.subTitle;"/>
         </vbox>
       </hbox>
     </panel>
 
-    <!-- Sync Error Panel -->
-    <panel id="sync-error-panel" class="sync-panel" type="arrow" hidden="true"
-           noautofocus="true" onclick="this.hidePopup();"
-           flip="slide">
-      <hbox class="sync-panel-outer">
-        <image class="sync-panel-icon"/>
-        <vbox class="sync-panel-inner">
-          <description id="sync-error-panel-title"
-                       value="&syncErrorPanel.heading;"/>
-          <description id="sync-error-panel-subtitle"
-                       value="&syncErrorPanel.subTitle;"/>
-          <hbox class="sync-panel-button-box">
-            <spacer flex="1"/>
-            <button class="sync-panel-button"
-                    label="&syncErrorPanel.signInButton.label;"
-                    accesskey="&syncErrorPanel.signInButton.accesskey;"
-                    onclick="gFxAccounts.openSignInAgainPage();"/>
-          </hbox>
-        </vbox>
-      </hbox>
-    </panel>
-
     <!-- Bookmarks and history tooltip -->
     <tooltip id="bhTooltip"/>
 
     <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
 
     <tooltip id="back-button-tooltip">
       <label class="tooltip-label" value="&backButton.tooltip;"/>
 #ifdef XP_MACOSX
--- a/browser/components/loop/.eslintrc-gecko
+++ b/browser/components/loop/.eslintrc-gecko
@@ -45,16 +45,17 @@
     "MozLoopService": true,
     "OS": false,
     "roomsPushNotification": true,
     "Services": false,
     "Social": false,
     "SocialShare": false,
     "Task": false,
     "UITour": false,
+    "WebChannel": false,
     "XPCOMUtils": false,
     "uuidgen": true,
     // Test Related
     "Assert": false,
   },
   "rules": {
     "arrow-parens": 0,         // TBD
     "arrow-spacing": 2,
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -602,99 +602,16 @@ html[dir="rtl"] .room-entry-context-acti
   display: none;
 }
 
 .room-entry-context-item > a > img {
   height: 16px;
   width: 16px;
 }
 
-/* Buttons */
-
-.button-group {
-  display: flex;
-  flex-direction: row;
-  width: 100%;
-  padding-top: 6px;
-}
-
-.button-group > .button {
-  flex: 1;
-  margin: 0 5px;
-  min-height: 3rem;
-  font-size: 1.2rem;
-  line-height: 1rem;
-  font-weight: 300;
-  border-radius: 4px;
-}
-
-.button-group > .button:first-child {
-  -moz-margin-start: 0;
-}
-
-.button-group > .button:last-child {
-  -moz-margin-end: 0;
-}
-
-.button {
-  padding: 2px 5px;
-  background-color: #fbfbfb;
-  color: #333;
-  border-radius: 2px;
-  min-height: 26px;
-  font-size: 1.2rem;
-  line-height: 1.2rem;
-  border: none;
-}
-
-.button:hover {
-  background-color: #ebebeb;
-}
-
-.button:active {
-  background-color: #ccc;
-  color: #fff;
-}
-
-.button.button-accept {
-  background-color: #00a9dc;
-  color: #fff;
-}
-
-.button.button-accept:hover,
-.button.button-accept:hover:active {
-  background-color: #5cccee;
-  color: #fff;
-}
-
-.button.button-cancel {
-  background-color: #ebebeb;
-  border: 0;
-  color: #000;
-  width: 105px; /* based on fixed width of Cancel button from mockup */
-  flex: 0 0 auto;
-}
-
-.button.button-cancel:hover,
-.button.button-cancel:hover:active {
-  background-color: #dcd6d6;
-  color: #000;
-}
-
-.button.button-cancel:disabled {
-  background-color: #ebebeb;
-  color: #c3c3c3;
-}
-
-.button.button-accept:active {
-  background-color: #3aa689;
-  border-color: #3aa689;
-  color: #fff;
-}
-
 .button-close {
   background-color: transparent;
   background-image: url(../shared/img/icons-10x10.svg#close);
   background-repeat: no-repeat;
   background-size: 8px 8px;
   border: none;
   padding: 0;
   height: 8px;
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -714,16 +714,17 @@ loop.conversationViews = (function(mozL1
             useDesktopPaths: true}, 
             React.createElement(loop.shared.views.ConversationToolbar, {
               audio: this.props.audio, 
               dispatcher: this.props.dispatcher, 
               hangup: this.hangup, 
               mozLoop: this.props.mozLoop, 
               publishStream: this.publishStream, 
               settingsMenuItems: settingsMenuItems, 
+              show: true, 
               video: this.props.video})
           )
         )
       );
     }
   });
 
   /**
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -714,16 +714,17 @@ loop.conversationViews = (function(mozL1
             useDesktopPaths={true}>
             <loop.shared.views.ConversationToolbar
               audio={this.props.audio}
               dispatcher={this.props.dispatcher}
               hangup={this.hangup}
               mozLoop={this.props.mozLoop}
               publishStream={this.publishStream}
               settingsMenuItems={settingsMenuItems}
+              show={true}
               video={this.props.video} />
           </sharedViews.MediaLayoutView>
         </div>
       );
     }
   });
 
   /**
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -970,17 +970,17 @@ loop.panel = (function(_, mozL10n) {
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         React.createElement("div", {className: "new-room-view"}, 
           React.createElement("div", {className: contextClasses}, 
             React.createElement(Checkbox, {checked: this.state.checked, 
-                      label: mozL10n.get("context_inroom_label"), 
+                      label: mozL10n.get("context_inroom_label2"), 
                       onChange: this.onCheckboxChange}), 
             React.createElement(sharedViews.ContextUrlView, {
               allowClick: false, 
               description: this.state.description, 
               showContextTitle: false, 
               thumbnail: this.state.previewImage, 
               url: this.state.url, 
               useDesktopPaths: true})
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -970,17 +970,17 @@ loop.panel = (function(_, mozL10n) {
         hide: !hostname ||
           !this.props.mozLoop.getLoopPref("contextInConversations.enabled")
       });
 
       return (
         <div className="new-room-view">
           <div className={contextClasses}>
             <Checkbox checked={this.state.checked}
-                      label={mozL10n.get("context_inroom_label")}
+                      label={mozL10n.get("context_inroom_label2")}
                       onChange={this.onCheckboxChange} />
             <sharedViews.ContextUrlView
               allowClick={false}
               description={this.state.description}
               showContextTitle={false}
               thumbnail={this.state.previewImage}
               url={this.state.url}
               useDesktopPaths={true} />
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -444,35 +444,16 @@ loop.roomViews = (function(mozL10n) {
       }
 
       var mozLoop = this.props.mozLoop;
       mozLoop.openURL(url.location);
 
       mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1);
     },
 
-    handleCheckboxChange: function(state) {
-      if (state.checked) {
-        // The checkbox was checked, prefill the fields with the values available
-        // in `availableContext`.
-        var context = this.state.availableContext;
-        this.setState({
-          newRoomURL: context.url,
-          newRoomDescription: context.description,
-          newRoomThumbnail: context.previewImage
-        });
-      } else {
-        this.setState({
-          newRoomURL: "",
-          newRoomDescription: "",
-          newRoomThumbnail: ""
-        });
-      }
-    },
-
     handleFormSubmit: function(event) {
       event && event.preventDefault();
 
       this.props.dispatcher.dispatch(new sharedActions.UpdateRoomContext({
         roomToken: this.props.roomData.roomToken,
         newRoomName: this.state.newRoomName,
         newRoomURL: this.state.newRoomURL,
         newRoomDescription: this.state.newRoomDescription,
@@ -511,37 +492,23 @@ loop.roomViews = (function(mozL10n) {
 
       var url = this._getURL();
       var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
       var urlDescription = url && url.description || "";
       var location = url && url.location || "";
 
       var cx = React.addons.classSet;
       var availableContext = this.state.availableContext;
-      // The checkbox shows as checked when there's already context data
-      // attached to this room.
-      var checked = !!urlDescription;
-      var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
-        availableContext.description : "");
-
       return (
         React.createElement("div", {className: "room-context"}, 
           React.createElement("p", {className: cx({"error": !!this.props.error,
                             "error-display-area": true})}, 
             mozL10n.get("rooms_change_failed_label")
           ), 
-          React.createElement("div", {className: "room-context-label"}, mozL10n.get("context_inroom_label")), 
-          React.createElement(sharedViews.Checkbox, {
-            additionalClass: cx({ hide: !checkboxLabel }), 
-            checked: checked, 
-            disabled: checked, 
-            label: checkboxLabel, 
-            onChange: this.handleCheckboxChange, 
-            useEllipsis: true, 
-            value: location}), 
+          React.createElement("h2", {className: "room-context-header"}, mozL10n.get("context_inroom_header")), 
           React.createElement("form", {onSubmit: this.handleFormSubmit}, 
             React.createElement("input", {className: "room-context-name", 
               maxLength: this.maxRoomNameLength, 
               onKeyDown: this.handleTextareaKeyDown, 
               placeholder: mozL10n.get("context_edit_name_placeholder"), 
               type: "text", 
               valueLink: this.linkState("newRoomName")}), 
             React.createElement("input", {className: "room-context-url", 
@@ -549,26 +516,27 @@ loop.roomViews = (function(mozL10n) {
               onKeyDown: this.handleTextareaKeyDown, 
               placeholder: "https://", 
               type: "text", 
               valueLink: this.linkState("newRoomURL")}), 
             React.createElement("textarea", {className: "room-context-comments", 
               onKeyDown: this.handleTextareaKeyDown, 
               placeholder: mozL10n.get("context_edit_comments_placeholder"), 
               rows: "2", type: "text", 
-              valueLink: this.linkState("newRoomDescription")})
-          ), 
-          React.createElement("button", {className: "btn btn-info", 
-                  disabled: this.props.savingContext, 
-                  onClick: this.handleFormSubmit}, 
-            mozL10n.get("context_save_label2")
-          ), 
-          React.createElement("button", {className: "room-context-btn-close", 
-                  onClick: this.handleCloseClick, 
-                  title: mozL10n.get("cancel_button")})
+              valueLink: this.linkState("newRoomDescription")}), 
+            React.createElement(sharedViews.ButtonGroup, null, 
+              React.createElement(sharedViews.Button, {additionalClass: "button-cancel", 
+                caption: mozL10n.get("context_cancel_label"), 
+                onClick: this.handleCloseClick}), 
+              React.createElement(sharedViews.Button, {additionalClass: "button-accept", 
+                caption: mozL10n.get("context_done_label"), 
+                disabled: this.props.savingContext, 
+                onClick: this.handleFormSubmit})
+            )
+          )
         )
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
@@ -814,16 +782,17 @@ loop.roomViews = (function(mozL10n) {
                 React.createElement(sharedViews.ConversationToolbar, {
                   audio: {enabled: !this.state.audioMuted, visible: true}, 
                   dispatcher: this.props.dispatcher, 
                   hangup: this.leaveRoom, 
                   mozLoop: this.props.mozLoop, 
                   publishStream: this.publishStream, 
                   screenShare: screenShareData, 
                   settingsMenuItems: settingsMenuItems, 
+                  show: !shouldRenderEditContextView, 
                   video: {enabled: !this.state.videoMuted, visible: true}}), 
                 React.createElement(DesktopRoomInvitationView, {
                   dispatcher: this.props.dispatcher, 
                   error: this.state.error, 
                   mozLoop: this.props.mozLoop, 
                   onAddContextClick: this.handleAddContextClick, 
                   onEditContextClose: this.handleEditContextClose, 
                   roomData: roomData, 
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -444,35 +444,16 @@ loop.roomViews = (function(mozL10n) {
       }
 
       var mozLoop = this.props.mozLoop;
       mozLoop.openURL(url.location);
 
       mozLoop.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1);
     },
 
-    handleCheckboxChange: function(state) {
-      if (state.checked) {
-        // The checkbox was checked, prefill the fields with the values available
-        // in `availableContext`.
-        var context = this.state.availableContext;
-        this.setState({
-          newRoomURL: context.url,
-          newRoomDescription: context.description,
-          newRoomThumbnail: context.previewImage
-        });
-      } else {
-        this.setState({
-          newRoomURL: "",
-          newRoomDescription: "",
-          newRoomThumbnail: ""
-        });
-      }
-    },
-
     handleFormSubmit: function(event) {
       event && event.preventDefault();
 
       this.props.dispatcher.dispatch(new sharedActions.UpdateRoomContext({
         roomToken: this.props.roomData.roomToken,
         newRoomName: this.state.newRoomName,
         newRoomURL: this.state.newRoomURL,
         newRoomDescription: this.state.newRoomDescription,
@@ -511,37 +492,23 @@ loop.roomViews = (function(mozL10n) {
 
       var url = this._getURL();
       var thumbnail = url && url.thumbnail || "loop/shared/img/icons-16x16.svg#globe";
       var urlDescription = url && url.description || "";
       var location = url && url.location || "";
 
       var cx = React.addons.classSet;
       var availableContext = this.state.availableContext;
-      // The checkbox shows as checked when there's already context data
-      // attached to this room.
-      var checked = !!urlDescription;
-      var checkboxLabel = urlDescription || (availableContext && availableContext.url ?
-        availableContext.description : "");
-
       return (
         <div className="room-context">
           <p className={cx({"error": !!this.props.error,
                             "error-display-area": true})}>
             {mozL10n.get("rooms_change_failed_label")}
           </p>
-          <div className="room-context-label">{mozL10n.get("context_inroom_label")}</div>
-          <sharedViews.Checkbox
-            additionalClass={cx({ hide: !checkboxLabel })}
-            checked={checked}
-            disabled={checked}
-            label={checkboxLabel}
-            onChange={this.handleCheckboxChange}
-            useEllipsis={true}
-            value={location} />
+          <h2 className="room-context-header">{mozL10n.get("context_inroom_header")}</h2>
           <form onSubmit={this.handleFormSubmit}>
             <input className="room-context-name"
               maxLength={this.maxRoomNameLength}
               onKeyDown={this.handleTextareaKeyDown}
               placeholder={mozL10n.get("context_edit_name_placeholder")}
               type="text"
               valueLink={this.linkState("newRoomName")} />
             <input className="room-context-url"
@@ -550,25 +517,26 @@ loop.roomViews = (function(mozL10n) {
               placeholder="https://"
               type="text"
               valueLink={this.linkState("newRoomURL")} />
             <textarea className="room-context-comments"
               onKeyDown={this.handleTextareaKeyDown}
               placeholder={mozL10n.get("context_edit_comments_placeholder")}
               rows="2" type="text"
               valueLink={this.linkState("newRoomDescription")} />
+            <sharedViews.ButtonGroup>
+              <sharedViews.Button additionalClass="button-cancel"
+                caption={mozL10n.get("context_cancel_label")}
+                onClick={this.handleCloseClick} />
+              <sharedViews.Button additionalClass="button-accept"
+                caption={mozL10n.get("context_done_label")}
+                disabled={this.props.savingContext}
+                onClick={this.handleFormSubmit} />
+            </sharedViews.ButtonGroup>
           </form>
-          <button className="btn btn-info"
-                  disabled={this.props.savingContext}
-                  onClick={this.handleFormSubmit}>
-            {mozL10n.get("context_save_label2")}
-          </button>
-          <button className="room-context-btn-close"
-                  onClick={this.handleCloseClick}
-                  title={mozL10n.get("cancel_button")}/>
         </div>
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
@@ -814,16 +782,17 @@ loop.roomViews = (function(mozL10n) {
                 <sharedViews.ConversationToolbar
                   audio={{enabled: !this.state.audioMuted, visible: true}}
                   dispatcher={this.props.dispatcher}
                   hangup={this.leaveRoom}
                   mozLoop={this.props.mozLoop}
                   publishStream={this.publishStream}
                   screenShare={screenShareData}
                   settingsMenuItems={settingsMenuItems}
+                  show={!shouldRenderEditContextView}
                   video={{enabled: !this.state.videoMuted, visible: true}} />
                 <DesktopRoomInvitationView
                   dispatcher={this.props.dispatcher}
                   error={this.state.error}
                   mozLoop={this.props.mozLoop}
                   onAddContextClick={this.handleAddContextClick}
                   onEditContextClose={this.handleEditContextClose}
                   roomData={roomData}
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -70,16 +70,97 @@ p {
 .tc {
   text-align: center;
 }
 
 .full-width {
   width: 100%;
 }
 
+/* Buttons */
+
+.button-group {
+  display: flex;
+  flex-direction: row;
+  width: 100%;
+  padding-top: 6px;
+}
+
+.button-group > .button {
+  flex: 1;
+  margin: 0 5px;
+  min-height: 3rem;
+  font-size: 1.2rem;
+  line-height: 1rem;
+  font-weight: 300;
+  border-radius: 4px;
+}
+
+.button-group > .button:first-child {
+  -moz-margin-start: 0;
+}
+
+.button-group > .button:last-child {
+  -moz-margin-end: 0;
+}
+
+.button {
+  padding: 2px 5px;
+  background-color: #fbfbfb;
+  color: #333;
+  border-radius: 2px;
+  min-height: 26px;
+  font-size: 1.2rem;
+  line-height: 1.2rem;
+  border: none;
+}
+
+.button:hover {
+  background-color: #ebebeb;
+}
+
+.button:active {
+  background-color: #ccc;
+  color: #fff;
+}
+
+.button.button-accept {
+  background-color: #00a9dc;
+  color: #fff;
+}
+
+.button.button-accept:hover,
+.button.button-accept:hover:active {
+  background-color: #5cccee;
+  color: #fff;
+}
+
+.button.button-cancel {
+  background-color: #ebebeb;
+  border: 0;
+  color: #000;
+}
+
+.button.button-cancel:hover,
+.button.button-cancel:hover:active {
+  background-color: #dcd6d6;
+  color: #000;
+}
+
+.button.button-cancel:disabled {
+  background-color: #ebebeb;
+  color: #c3c3c3;
+}
+
+.button.button-accept:active {
+  background-color: #3aa689;
+  border-color: #3aa689;
+  color: #fff;
+}
+
 /* A reset for all button-appearing elements, with the lowest-common
  * denominator of the needed rules.  Intended to be used as a base class
  * together with .btn-*
  */
 
 .btn {
   display: inline-block;
   margin: 0;
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -910,23 +910,16 @@ html[dir="rtl"] .room-conversation-wrapp
 .room-invitation-content {
   flex: 1 1 auto;
   display: flex;
   flex-flow: column nowrap;
   justify-content: center;
   align-items: center;
 }
 
-.room-invitation-overlay input[type="text"] {
-  display: block;
-  background-color: rgba(0,0,0,.5);
-  border-radius: 3px;
-  padding: .5em;
-}
-
 .room-invitation-overlay .btn-group {
   padding: 0 0 5rem 0;
 }
 
 .room-invitation-addcontext {
   color: #0095dd;
   padding-left: 1.5em;
   margin-bottom: 1em;
@@ -994,20 +987,19 @@ body[platform="win"] .share-service-drop
   background-color: #dbf7ff;
 }
 
 .showing-room-name > .text-chat-entries > .text-chat-scroller > .context-url-view-wrapper {
   padding-top: 0;
 }
 
 .room-context {
-  background: rgba(0,0,0,.8);
+  background: #fff;
   border-top: 2px solid #444;
   border-bottom: 2px solid #444;
-  padding: .5rem;
   position: absolute;
   left: 0;
   bottom: 0;
   width: 100%;
   height: 100%;
   font-size: .9em;
   display: flex;
   flex-flow: column nowrap;
@@ -1044,23 +1036,21 @@ body[platform="win"] .share-service-drop
   text-shadow: 1px 1px 0 rgba(0,0,0,.3);
 }
 
 .room-context > .checkbox-wrapper {
   margin-bottom: .5em;
   width: 100%;
 }
 
-.room-context-label {
-  margin-bottom: 1em;
-}
-
-.room-context-label,
-.room-context > .checkbox-wrapper > label {
-  color: #fff;
+.room-context-header {
+  color: #333;
+  font-size: 1.2rem;
+  font-weight: bold;
+  margin: 1rem auto;
 }
 
 .room-context-comment {
   color: #707070;
 }
 
 .room-context-comment {
   word-wrap: break-word;
@@ -1074,81 +1064,50 @@ body[platform="win"] .share-service-drop
   cursor: pointer;
 }
 
 .room-context-url:hover {
   text-decoration: underline;
 }
 
 .room-context > form {
+  margin-bottom: 1rem;
+  padding: .5rem;
   width: 100%;
 }
 
 .room-context > form > textarea,
 .room-context > form > input[type="text"] {
   display: block;
-  background: rgba(0,0,0,.5);
-  font-family: "Helvetica Neue", Arial, sans;
-  border: 1px solid rgba(255,255,255,.2);
   width: 100%;
-  padding: .5em;
-  border-radius: 3px;
-  resize: none;
-  color: #fff;
+  outline: none;
+  border-radius: 4px;
+  margin: 10px 0;
+  border: 1px solid #c3c3c3;
+  height: 2.6rem;
+  padding: 6px;
+  font-size: 1.1rem;
+  color: #4a4a4a;
+  box-shadow: none;
 }
 
 .room-context > form > textarea {
-  font-size: 1em;
-}
-
-.room-context > form > input:not([disabled]).room-context-url {
-  color: #0095dd;
-}
-
-.room-context > form > input[disabled] {
-  background-color: rgba(255,255,255,.2);
-  color: rgba(255,255,255,.4);
-}
-
-.room-context > form > textarea:not(:last-of-type),
-.room-context > form > input[type="text"] {
-  margin: 0 0 .5em 0;
-}
-
-.room-context > .btn {
-  margin: .5em 0 0;
-  font-size: 1.1em;
-  padding: 0 .5em;
-  align-self: flex-end;
+  font-family: inherit;
+  height: 5.2rem;
+  resize: none;
 }
 
-.room-context-btn-close {
-  position: absolute;
-  right: 8px;
-  /* 8px offset + 2px border-top */
-  top: 10px;
-  width: 8px;
-  height: 8px;
-  background-color: transparent;
-  background-image: url("../img/icons-10x10.svg#close-darkergrey");
-  background-size: 8px 8px;
-  background-repeat: no-repeat;
-  border: 0;
-  padding: 0;
-  cursor: pointer;
+.room-context > form > textarea::-moz-placeholder,
+.room-context > form > input::-moz-placeholder {
+  color: #999;
 }
 
-.room-context-btn-close:hover,
-.room-context-btn-close:hover:active {
-  background-image: url("../img/icons-10x10.svg#close-active");
-}
-
-html[dir="rtl"] .room-context-btn-close {
-  right: auto;
-  left: 8px;
+.room-context > form > textarea:focus,
+.room-context > form > input:focus {
+  border: 0.1rem solid #5cccee;
 }
 
 .media-layout {
   height: 100%;
 }
 
 .standalone-room-wrapper > .media-layout {
   /* 50px is the header, 3em is the footer. */
--- a/browser/components/loop/content/shared/js/views.js
+++ b/browser/components/loop/content/shared/js/views.js
@@ -354,16 +354,17 @@ loop.shared.views = (function(_, mozL10n
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       enableHangup: React.PropTypes.bool,
       hangup: React.PropTypes.func.isRequired,
       hangupButtonLabel: React.PropTypes.string,
       mozLoop: React.PropTypes.object,
       publishStream: React.PropTypes.func.isRequired,
       screenShare: React.PropTypes.object,
       settingsMenuItems: React.PropTypes.array,
+      show: React.PropTypes.bool.isRequired,
       video: React.PropTypes.object.isRequired
     },
 
     handleClickHangup: function() {
       this.props.hangup();
     },
 
     handleToggleVideo: function() {
@@ -435,16 +436,20 @@ loop.shared.views = (function(_, mozL10n
       }.bind(this), 6000);
     },
 
     _getHangupButtonLabel: function() {
       return this.props.hangupButtonLabel || mozL10n.get("hangup_button_caption2");
     },
 
     render: function() {
+      if (!this.props.show) {
+        return null;
+      }
+
       var cx = React.addons.classSet;
       var conversationToolbarCssClasses = cx({
         "conversation-toolbar": true,
         "idle": this.state.idle
       });
       var mediaButtonGroupCssClasses = cx({
         "conversation-toolbar-media-btn-group-box": true,
         "hide": (!this.props.video.visible && !this.props.audio.visible)
@@ -793,17 +798,17 @@ loop.shared.views = (function(_, mozL10n
     /**
      * Renders the context title ("Let's talk about") if necessary.
      */
     renderContextTitle: function() {
       if (!this.props.showContextTitle) {
         return null;
       }
 
-      return React.createElement("p", null, mozL10n.get("context_inroom_label"));
+      return React.createElement("p", null, mozL10n.get("context_inroom_label2"));
     },
 
     render: function() {
       var hostname;
 
       try {
         hostname = new URL(this.props.url).hostname;
       } catch (ex) {
--- a/browser/components/loop/content/shared/js/views.jsx
+++ b/browser/components/loop/content/shared/js/views.jsx
@@ -354,16 +354,17 @@ loop.shared.views = (function(_, mozL10n
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       enableHangup: React.PropTypes.bool,
       hangup: React.PropTypes.func.isRequired,
       hangupButtonLabel: React.PropTypes.string,
       mozLoop: React.PropTypes.object,
       publishStream: React.PropTypes.func.isRequired,
       screenShare: React.PropTypes.object,
       settingsMenuItems: React.PropTypes.array,
+      show: React.PropTypes.bool.isRequired,
       video: React.PropTypes.object.isRequired
     },
 
     handleClickHangup: function() {
       this.props.hangup();
     },
 
     handleToggleVideo: function() {
@@ -435,16 +436,20 @@ loop.shared.views = (function(_, mozL10n
       }.bind(this), 6000);
     },
 
     _getHangupButtonLabel: function() {
       return this.props.hangupButtonLabel || mozL10n.get("hangup_button_caption2");
     },
 
     render: function() {
+      if (!this.props.show) {
+        return null;
+      }
+
       var cx = React.addons.classSet;
       var conversationToolbarCssClasses = cx({
         "conversation-toolbar": true,
         "idle": this.state.idle
       });
       var mediaButtonGroupCssClasses = cx({
         "conversation-toolbar-media-btn-group-box": true,
         "hide": (!this.props.video.visible && !this.props.audio.visible)
@@ -793,17 +798,17 @@ loop.shared.views = (function(_, mozL10n
     /**
      * Renders the context title ("Let's talk about") if necessary.
      */
     renderContextTitle: function() {
       if (!this.props.showContextTitle) {
         return null;
       }
 
-      return <p>{mozL10n.get("context_inroom_label")}</p>;
+      return <p>{mozL10n.get("context_inroom_label2")}</p>;
     },
 
     render: function() {
       var hostname;
 
       try {
         hostname = new URL(this.props.url).hostname;
       } catch (ex) {
--- a/browser/components/loop/modules/LoopRooms.jsm
+++ b/browser/components/loop/modules/LoopRooms.jsm
@@ -10,16 +10,19 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
 const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+                                  "resource://gre/modules/WebChannel.jsm");
+
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
   return new EventEmitter();
 });
 XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/loop/loop.properties");
 });
 
@@ -40,29 +43,34 @@ const CLIENT_MAX_SIZE = 2;
 
 // Wait at least 5 seconds before doing opportunistic encryption.
 const MIN_TIME_BEFORE_ENCRYPTION = 5 * 1000;
 // Wait at maximum of 30 minutes before doing opportunistic encryption.
 const MAX_TIME_BEFORE_ENCRYPTION = 30 * 60 * 1000;
 // Wait time between individual re-encryption cycles (1 second).
 const TIME_BETWEEN_ENCRYPTIONS = 1000;
 
+// This is the pref name for the url of the standalone pages.
+const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
+
 const roomsPushNotification = function(version, channelID) {
   return LoopRoomsInternal.onNotification(version, channelID);
 };
 
 // Since the LoopRoomsInternal.rooms map as defined below is a local cache of
 // room objects that are retrieved from the server, this is list may become out
 // of date. The Push server may notify us of this event, which will set the global
 // 'dirty' flag to TRUE.
 var gDirty = true;
 // Global variable that keeps track of the currently used account.
 var gCurrentUser = null;
 // Global variable that keeps track of the room cache.
 var gRoomsCache = null;
+// Global variable that keeps track of the link clicker channel.
+var gLinkClickerChannel = null;
 
 /**
  * Extend a `target` object with the properties defined in `source`.
  *
  * @param {Object} target The target object to receive properties defined in `source`
  * @param {Object} source The source object to copy properties from
  */
 const extend = function(target, source) {
@@ -173,16 +181,50 @@ var LoopRoomsInternal = {
     timer: null,
     reset: function() {
       this.queue = [];
       this.timer = null;
     }
   },
 
   /**
+   * Initialises the rooms, sets up the link clicker listener.
+   */
+  init: function() {
+    Services.prefs.addObserver(LINKCLICKER_URL_PREFNAME,
+      this.setupLinkClickerListener.bind(this), false);
+
+    this.setupLinkClickerListener();
+  },
+
+  /**
+   * Sets up a WebChannel listener for the link clicker so that we can open
+   * rooms in the Firefox UI.
+   */
+  setupLinkClickerListener: function() {
+    // Ensure any existing channel is tidied up.
+    if (gLinkClickerChannel) {
+      gLinkClickerChannel.stopListening();
+      gLinkClickerChannel = null;
+    }
+
+    let linkClickerUrl = Services.prefs.getCharPref(LINKCLICKER_URL_PREFNAME);
+
+    // Don't do anything if there's no url.
+    if (!linkClickerUrl) {
+      return;
+    }
+
+    let uri = Services.io.newURI(linkClickerUrl, null, null);
+
+    gLinkClickerChannel = new WebChannel("loop-link-clicker", uri);
+    gLinkClickerChannel.listen(this._handleLinkClickerMessage.bind(this));
+  },
+
+  /**
    * @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'.
    */
   get sessionType() {
     return MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
                                         LOOP_SESSION_TYPE.GUEST;
   },
 
   /**
@@ -900,16 +942,52 @@ var LoopRoomsInternal = {
 
     gCurrentUser = user;
     if (!gDirty) {
       gDirty = true;
       this.rooms.clear();
       eventEmitter.emit("refresh");
       this.getAll(null, () => {});
     }
+  },
+
+  /**
+   * Handles a message received from the content channel.
+   *
+   * @param {String} id              The channel id.
+   * @param {Object} message         The message received.
+   * @param {Object} sendingContext  The context for the sending location.
+   */
+  _handleLinkClickerMessage: function(id, message, sendingContext) {
+    if (!message) {
+      return;
+    }
+
+    let sendResponse = response => {
+      gLinkClickerChannel.send({
+        response: response
+      }, sendingContext);
+    };
+
+    let hasRoom = this.rooms.has(message.roomToken);
+
+    switch (message.command) {
+      case "checkWillOpenRoom":
+        sendResponse(hasRoom);
+        break;
+      case "openRoom":
+        if (hasRoom) {
+          this.open(message.roomToken);
+        }
+        sendResponse(hasRoom);
+        break;
+      default:
+        sendResponse(false);
+        break;
+    }
   }
 };
 Object.freeze(LoopRoomsInternal);
 
 /**
  * Public Loop Rooms API.
  *
  * LoopRooms implements the EventEmitter interface by exposing three methods -
@@ -921,16 +999,20 @@ Object.freeze(LoopRoomsInternal);
  *  - 'update[:{room-id}]': A room object was successfully updated with changed
  *                          properties in the data store.
  *  - 'joined[:{room-id}]': A participant joined a room.
  *  - 'left[:{room-id}]':   A participant left a room.
  *
  * See the internal code for the API documentation.
  */
 this.LoopRooms = {
+  init: function() {
+    LoopRoomsInternal.init();
+  },
+
   get participantsCount() {
     return LoopRoomsInternal.participantsCount;
   },
 
   getAll: function(version, callback) {
     return LoopRoomsInternal.getAll(version, callback);
   },
 
@@ -1011,11 +1093,29 @@ this.LoopRooms = {
       });
     });
   },
 
   on: (...params) => eventEmitter.on(...params),
 
   once: (...params) => eventEmitter.once(...params),
 
-  off: (...params) => eventEmitter.off(...params)
+  off: (...params) => eventEmitter.off(...params),
+
+  /**
+   * Expose the internal rooms map for testing purposes only. This avoids
+   * needing to mock the server interfaces.
+   *
+   * @param {Map} roomsCache The new cache data to set for testing purposes. If
+   *                         not specified, it will reset the cache.
+   */
+  _setRoomsCache: function(roomsCache) {
+    LoopRoomsInternal.rooms.clear();
+
+    if (roomsCache) {
+      // Need a clone as the internal map is read-only.
+      for (let [key, value] of roomsCache) {
+        LoopRoomsInternal.rooms.set(key, value);
+      }
+    }
+  }
 };
 Object.freeze(this.LoopRooms);
--- a/browser/components/loop/modules/MozLoopService.jsm
+++ b/browser/components/loop/modules/MozLoopService.jsm
@@ -1206,16 +1206,19 @@ this.MozLoopService = {
     }
 
     gServiceInitialized = true;
 
     // Do this here, rather than immediately after definition, so that we can
     // stub out API functions for unit testing
     Object.freeze(this);
 
+    // Initialise anything that needs it in rooms.
+    LoopRooms.init();
+
     // Don't do anything if loop is not enabled.
     if (!Services.prefs.getBoolPref("loop.enabled")) {
       return Promise.reject(new Error("loop is not enabled"));
     }
 
     if (Services.prefs.getPrefType("loop.fxa.enabled") == Services.prefs.PREF_BOOL) {
       gFxAEnabled = Services.prefs.getBoolPref("loop.fxa.enabled");
       if (!gFxAEnabled) {
--- a/browser/components/loop/run-all-loop-tests.sh
+++ b/browser/components/loop/run-all-loop-tests.sh
@@ -41,21 +41,16 @@ fi
 # The check to make sure that the media devices can be used in Loop without
 # prompting is in browser_devices_get_user_media_about_urls.js. It's possible
 # to mess this up with CSP handling, and probably other changes, too.
 
 TESTS="
   ${LOOPDIR}/test/mochitest
   browser/components/uitour/test/browser_UITour_loop.js
   browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
+  browser/base/content/test/general/browser_parsable_css.js
 "
 
 ./mach mochitest $TESTS
 
 if [ "$1" != "--skip-e10s" ]; then
   ./mach mochitest --e10s $TESTS
 fi
-
-# This is currently disabled because the test itself is busted.  Once bug
-# 1062821 is landed, we should see if things work again, and then re-enable it.
-# The re-enabling is tracked in bug 1113350.
-#
-#  browser/base/content/test/general/browser_parsable_css.js \
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -577,16 +577,17 @@ loop.standaloneRoomViews = (function(moz
             React.createElement(sharedViews.ConversationToolbar, {
               audio: {enabled: !this.state.audioMuted,
                       visible: this._roomIsActive()}, 
               dispatcher: this.props.dispatcher, 
               enableHangup: this._roomIsActive(), 
               hangup: this.leaveRoom, 
               hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
               publishStream: this.publishStream, 
+              show: true, 
               video: {enabled: !this.state.videoMuted,
                       visible: this._roomIsActive()}})
           ), 
           React.createElement(StandaloneRoomFooter, {dispatcher: this.props.dispatcher})
         )
       );
     }
   });
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -577,16 +577,17 @@ loop.standaloneRoomViews = (function(moz
             <sharedViews.ConversationToolbar
               audio={{enabled: !this.state.audioMuted,
                       visible: this._roomIsActive()}}
               dispatcher={this.props.dispatcher}
               enableHangup={this._roomIsActive()}
               hangup={this.leaveRoom}
               hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
               publishStream={this.publishStream}
+              show={true}
               video={{enabled: !this.state.videoMuted,
                       visible: this._roomIsActive()}} />
           </sharedViews.MediaLayoutView>
           <StandaloneRoomFooter dispatcher={this.props.dispatcher} />
         </div>
       );
     }
   });
--- a/browser/components/loop/standalone/content/l10n/en-US/loop.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -79,14 +79,14 @@ rooms_read_while_wait_offer=Want somethi
 ## by the name of the conversation and {{clientShortname}} will be
 ## replaced by the brand name.
 standalone_title_with_room_name={{roomName}} — {{clientShortname}}
 status_error=Something went wrong
 
 # Text chat strings
 
 chat_textbox_placeholder=Type here…
-# LOCALIZATION NOTE (context_inroom_label): this string is followed by the
+# LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
 # title/URL of the website you are having a conversation about, displayed on a
 # separate line. If this structure doesn't work for your locale, you might want
 # to consider this as a stand-alone title. See example screenshot:
 # https://bug1084991.bugzilla.mozilla.org/attachment.cgi?id=8614721
-context_inroom_label=Let's talk about:
+context_inroom_label2=Let's Talk About:
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -703,26 +703,23 @@ describe("loop.roomViews", function () {
         expect(node.querySelector(".room-context")).to.eql(null);
 
         var editButton = node.querySelector(".settings-menu > li.entry-settings-edit");
         React.addons.TestUtils.Simulate.click(editButton);
 
         expect(view.getDOMNode().querySelector(".room-context")).to.not.eql(null);
       });
 
-      it("should hide the form when the edit button is clicked again", function() {
+      it("should not have a settings menu when the edit button is clicked", function() {
         view = mountTestComponent();
 
         var editButton = view.getDOMNode().querySelector(".settings-menu > li.entry-settings-edit");
         React.addons.TestUtils.Simulate.click(editButton);
 
-        // Click again.
-        React.addons.TestUtils.Simulate.click(editButton);
-
-        expect(view.getDOMNode().querySelector(".room-context")).to.eql(null);
+        expect(view.getDOMNode().querySelector(".settings-menu")).to.eql(null);
       });
     });
   });
 
   describe("SocialShareDropdown", function() {
     var fakeProvider;
 
     beforeEach(function() {
@@ -835,22 +832,22 @@ describe("loop.roomViews", function () {
       it("should not render the component when 'show' is false", function() {
         view = mountTestComponent({
           show: false
         });
 
         expect(view.getDOMNode()).to.eql(null);
       });
 
-      it("should close the view when the close button is clicked", function() {
+      it("should close the view when the cancel button is clicked", function() {
         view = mountTestComponent({
           roomData: { roomContextUrls: [fakeContextURL] }
         });
 
-        var closeBtn = view.getDOMNode().querySelector(".room-context-btn-close");
+        var closeBtn = view.getDOMNode().querySelector(".button-cancel");
         React.addons.TestUtils.Simulate.click(closeBtn);
         expect(view.getDOMNode()).to.eql(null);
       });
 
       it("should render the view correctly", function() {
         var roomName = "Hello, is it me you're looking for?";
         view = mountTestComponent({
           roomData: {
@@ -861,45 +858,16 @@ describe("loop.roomViews", function () {
 
         var node = view.getDOMNode();
         expect(node.querySelector("form")).to.not.eql(null);
         // Check the contents of the form fields.
         expect(node.querySelector(".room-context-name").value).to.eql(roomName);
         expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
         expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
       });
-
-      it("should show the checkbox as disabled when context is already set", function() {
-        view = mountTestComponent({
-          roomData: {
-            roomToken: "fakeToken",
-            roomName: "fakeName",
-            roomContextUrls: [fakeContextURL]
-          }
-        });
-
-        var checkbox = view.getDOMNode().querySelector(".checkbox");
-        expect(checkbox.classList.contains("disabled")).to.eql(true);
-      });
-
-      it("should hide the checkbox when no context data is stored or available", function() {
-        view = mountTestComponent({
-          roomData: {
-            roomToken: "fakeToken",
-            roomName: "Hello, is it me you're looking for?"
-          }
-        });
-
-        // First check if availableContext is set correctly.
-        expect(view.state.availableContext).to.not.eql(null);
-        expect(view.state.availableContext.previewImage).to.eql(favicon);
-
-        var node = view.getDOMNode();
-        expect(node.querySelector(".checkbox-wrapper").classList.contains("hide")).to.eql(true);
-      });
     });
 
     describe("Update Room", function() {
       var roomNameBox;
 
       beforeEach(function() {
         view = mountTestComponent({
           editMode: true,
@@ -914,17 +882,17 @@ describe("loop.roomViews", function () {
       });
 
       it("should dispatch a UpdateRoomContext action when the save button is clicked",
         function() {
           React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
             value: "reallyFake"
           }});
 
-          React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-info"));
+          React.addons.TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-accept"));
 
           sinon.assert.calledOnce(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch,
             new sharedActions.UpdateRoomContext({
               roomToken: "fakeToken",
               newRoomName: "reallyFake",
               newRoomDescription: fakeContextURL.description,
               newRoomURL: fakeContextURL.location,
@@ -950,67 +918,28 @@ describe("loop.roomViews", function () {
               newRoomThumbnail: fakeContextURL.thumbnail
             }));
         });
 
       it("should close the edit form when context was saved successfully", function(done) {
         view.setProps({ savingContext: true }, function() {
           var node = view.getDOMNode();
           // The button should show up as disabled.
-          expect(node.querySelector(".btn-info").hasAttribute("disabled")).to.eql(true);
+          expect(node.querySelector(".button-accept").hasAttribute("disabled")).to.eql(true);
 
           // Now simulate a successful save.
           view.setProps({ savingContext: false }, function() {
             // The 'show flag should be updated.
             expect(view.state.show).to.eql(false);
             done();
           });
         });
       });
     });
 
-    describe("#handleCheckboxChange", function() {
-      var node, checkbox;
-
-      beforeEach(function() {
-        fakeMozLoop.getSelectedTabMetadata = sinon.stub().callsArgWith(0, {
-          favicon: fakeContextURL.thumbnail,
-          title: fakeContextURL.description,
-          url: fakeContextURL.location
-        });
-        view = mountTestComponent({
-          roomData: {
-            roomToken: "fakeToken",
-            roomName: "fakeName"
-          }
-        });
-
-        node = view.getDOMNode();
-        checkbox = node.querySelector(".checkbox");
-      });
-
-      it("should prefill the form with available context data when clicked", function() {
-        React.addons.TestUtils.Simulate.click(checkbox);
-
-        expect(node.querySelector(".room-context-name").value).to.eql("fakeName");
-        expect(node.querySelector(".room-context-url").value).to.eql(fakeContextURL.location);
-        expect(node.querySelector(".room-context-comments").value).to.eql(fakeContextURL.description);
-      });
-
-      it("should undo prefill when clicking the checkbox again", function() {
-        React.addons.TestUtils.Simulate.click(checkbox);
-        // Twice.
-        React.addons.TestUtils.Simulate.click(checkbox);
-
-        expect(node.querySelector(".room-context-name").value).to.eql("fakeName");
-        expect(node.querySelector(".room-context-url").value).to.eql("");
-        expect(node.querySelector(".room-context-comments").value).to.eql("");
-      });
-    });
-
     describe("#handleContextClick", function() {
       var fakeEvent;
 
       beforeEach(function() {
         fakeEvent = {
           preventDefault: sinon.stub(),
           stopPropagation: sinon.stub()
         };
--- a/browser/components/loop/test/mochitest/.eslintrc
+++ b/browser/components/loop/test/mochitest/.eslintrc
@@ -27,10 +27,13 @@
     "promiseOAuthParamsSetup": false,
     "promiseObserverNotified": false,
     "promiseWaitForCondition": false,
     "resetFxA": true,
     // Loop specific items
     "MozLoopServiceInternal": true,
     "LoopRoomsInternal": true,
     "LoopUI": false,
+    // Other items
+    "Chat": true,
+    "WebChannel": true
   }
 }
--- a/browser/components/loop/test/mochitest/browser.ini
+++ b/browser/components/loop/test/mochitest/browser.ini
@@ -2,24 +2,27 @@
 support-files =
     fixtures/google_auth.txt
     fixtures/google_contacts.txt
     fixtures/google_groups.txt
     fixtures/google_token.txt
     google_service.sjs
     head.js
     loop_fxa.sjs
+    test_loopLinkClicker_channel.html
     ../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
 
 [browser_CardDavImporter.js]
 [browser_fxa_login.js]
 [browser_GoogleImporter.js]
 skip-if = e10s
 [browser_loop_fxa_server.js]
 [browser_LoopContacts.js]
+[browser_LoopRooms_channel.js]
+skip-if = asan && e10s # Bug 1206457
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_context.js]
 [browser_mozLoop_prefs.js]
 [browser_mozLoop_doNotDisturb.js]
 skip-if = buildapp == 'mulet'
 [browser_mozLoop_pluralStrings.js]
 [browser_mozLoop_socialShare.js]
 [browser_mozLoop_sharingListeners.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_LoopRooms_channel.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This file contains tests for checking the channel from the standalone to
+ * LoopRooms works for checking if rooms can be opened within the conversation
+ * window.
+ */
+"use strict";
+
+var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
+var {Chat} = Cu.import("resource:///modules/Chat.jsm", {});
+
+const TEST_URI =
+  "example.com/browser/browser/components/loop/test/mochitest/test_loopLinkClicker_channel.html";
+const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
+const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
+
+const ROOM_TOKEN = "fake1234";
+const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
+
+var openChatOrig = Chat.open;
+
+var fakeRoomList = new Map([[ ROOM_TOKEN, { roomToken: ROOM_TOKEN } ]]);
+
+// Loads the specified URI in a new tab and waits for it to send us data on our
+// test web-channel and resolves with that data.
+function promiseNewChannelResponse(uri, hash) {
+  let waitForChannelPromise = new Promise((resolve, reject) => {
+    let channel = new WebChannel("test-loop-link-clicker-backchannel", uri);
+    channel.listen((id, data, target) => {
+      channel.stopListening();
+      resolve(data);
+    });
+  });
+
+  return BrowserTestUtils.withNewTab({
+    gBrowser: gBrowser,
+    url: uri.spec + "#" + hash
+  }, () => waitForChannelPromise);
+}
+
+add_task(function* test_loopRooms_webChannel_permissions() {
+  // We haven't set the allowed web page yet - so even the "good" URI should fail.
+  let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
+  // Should have no data.
+  Assert.ok(got.message === undefined, "should have failed to get any data");
+
+  // Add a permission manager entry for our URI.
+  Services.prefs.setCharPref(LINKCLICKER_URL_PREFNAME, TEST_URI_GOOD.spec);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref(LINKCLICKER_URL_PREFNAME);
+  });
+
+  // Try again - now we are expecting a response with actual data.
+  got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
+
+  // The room doesn't exist, so we should get a negative response.
+  Assert.equal(got.message.response, false, "should have got a response of false");
+
+  // Now a http:// URI - should get nothing even with the permission setup.
+  got = yield promiseNewChannelResponse(TEST_URI_BAD, "checkWillOpenRoom");
+  Assert.ok(got.message === undefined, "should have failed to get any data");
+});
+
+add_task(function* test_loopRooms_webchannel_checkWillOpenRoom() {
+  // We've already tested if the room doesn't exist above, so here we add the
+  // room and check the result.
+  LoopRooms._setRoomsCache(fakeRoomList);
+
+  let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "checkWillOpenRoom");
+
+  Assert.equal(got.message.response, true, "should have got a response of true");
+});
+
+add_task(function* test_loopRooms_webchannel_openRoom() {
+ let openedUrl;
+  Chat.open = function(contentWindow, origin, title, url) {
+    openedUrl = url;
+  };
+  registerCleanupFunction(() => {
+    Chat.open = openChatOrig;
+  });
+
+  // Test when the room doesn't exist
+  LoopRooms._setRoomsCache();
+
+  let got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
+
+  Assert.ok(!openedUrl, "should not open a chat window");
+  Assert.equal(got.message.response, false, "should have got a response of false");
+
+  // Now add a room & check it.
+  LoopRooms._setRoomsCache(fakeRoomList);
+
+  got = yield promiseNewChannelResponse(TEST_URI_GOOD, "openRoom");
+
+  // Check the room was opened.
+  Assert.ok(openedUrl, "should open a chat window");
+
+  let windowId = openedUrl.match(/about:loopconversation\#(\w+)$/)[1];
+  let windowData = MozLoopService.getConversationWindowData(windowId);
+
+  Assert.equal(windowData.type, "room", "window data should contain room as the type");
+  Assert.equal(windowData.roomToken, ROOM_TOKEN, "window data should have the roomToken");
+
+  Assert.equal(got.message.response, true, "should have got a response of true");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/test_loopLinkClicker_channel.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+"use strict";
+
+// Add a listener for responses to our remote requests.
+window.addEventListener("WebChannelMessageToContent", function (event) {
+  if (event.detail.id == "loop-link-clicker") {
+    // Send what we got back to the test.
+    var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+      detail: {
+        id: "test-loop-link-clicker-backchannel",
+        message: {
+          message: event.detail.message
+        }
+      }
+    });
+    window.dispatchEvent(backEvent);
+    // and stick it in our DOM just for good measure/diagnostics.
+    document.getElementById("troubleshooting").textContent =
+      JSON.stringify(event.detail.message, null, 2);
+  }
+});
+
+// Send a message on load requesting that the room is opened.
+window.onload = function() {
+  var hash = window.location.hash;
+
+  var event = new window.CustomEvent("WebChannelMessageToChrome", {
+    detail: {
+      id: "loop-link-clicker",
+      message: {
+        command: hash.substring(1, hash.length),
+        roomToken: "fake1234"
+      }
+    }
+  });
+  window.dispatchEvent(event);
+};
+</script>
+
+<body>
+  <pre id="troubleshooting"/>
+</body>
+
+</html>
--- a/browser/components/loop/test/shared/views_test.js
+++ b/browser/components/loop/test/shared/views_test.js
@@ -424,32 +424,43 @@ describe("loop.shared.views", function()
   });
 
   describe("ConversationToolbar", function() {
     var clock, hangup, publishStream;
 
     function mountTestComponent(props) {
       props = _.extend({
         dispatcher: dispatcher,
-        mozLoop: {}
+        mozLoop: {},
+        show: true
       }, props || {});
       return TestUtils.renderIntoDocument(
         React.createElement(sharedViews.ConversationToolbar, props));
     }
 
     beforeEach(function() {
       hangup = sandbox.stub();
       publishStream = sandbox.stub();
       clock = sinon.useFakeTimers();
     });
 
     afterEach(function() {
       clock.restore();
     });
 
+    it("should not render the component when 'show' is false", function() {
+      var comp = mountTestComponent({
+        hangup: hangup,
+        publishStream: publishStream,
+        show: false
+      });
+
+      expect(comp.getDOMNode()).to.eql(null);
+    });
+
     it("should start no idle", function() {
       var comp = mountTestComponent({
         hangupButtonLabel: "foo",
         hangup: hangup,
         publishStream: publishStream
       });
       expect(comp.getDOMNode().classList.contains("idle")).eql(false);
     });
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1113,44 +1113,47 @@
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: true, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
                                        screenShare: { state: SCREEN_SHARE_STATES.INACTIVE, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
+                                       show: true, 
                                        video: { enabled: true, visible: true}})
                 )
               ), 
               React.createElement(FramedExample, {dashed: true, 
                              height: 56, 
                              summary: "Video muted, Screen share pending", 
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: true, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
                                        screenShare: { state: SCREEN_SHARE_STATES.PENDING, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
+                                       show: true, 
                                        video: { enabled: false, visible: true}})
                 )
               ), 
               React.createElement(FramedExample, {dashed: true, 
                              height: 56, 
                              summary: "Audio muted, Screen share active", 
                              width: 300}, 
                 React.createElement("div", {className: "fx-embedded"}, 
                   React.createElement(ConversationToolbar, {audio: { enabled: false, visible: true}, 
                                        dispatcher: dispatcher, 
                                        hangup: noop, 
                                        publishStream: noop, 
                                        screenShare: { state: SCREEN_SHARE_STATES.ACTIVE, visible: true}, 
                                        settingsMenuItems: [{ id: "feedback" }], 
+                                       show: true, 
                                        video: { enabled: true, visible: true}})
                 )
               )
             )
           ), 
 
           React.createElement(Section, {name: "PendingConversationView (Desktop)"}, 
             React.createElement(FramedExample, {dashed: true, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1113,44 +1113,47 @@
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: true, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
                                        screenShare={{ state: SCREEN_SHARE_STATES.INACTIVE, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
+                                       show={true}
                                        video={{ enabled: true, visible: true }} />
                 </div>
               </FramedExample>
               <FramedExample dashed={true}
                              height={56}
                              summary="Video muted, Screen share pending"
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: true, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
                                        screenShare={{ state: SCREEN_SHARE_STATES.PENDING, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
+                                       show={true}
                                        video={{ enabled: false, visible: true }} />
                 </div>
               </FramedExample>
               <FramedExample dashed={true}
                              height={56}
                              summary="Audio muted, Screen share active"
                              width={300}>
                 <div className="fx-embedded">
                   <ConversationToolbar audio={{ enabled: false, visible: true }}
                                        dispatcher={dispatcher}
                                        hangup={noop}
                                        publishStream={noop}
                                        screenShare={{ state: SCREEN_SHARE_STATES.ACTIVE, visible: true }}
                                        settingsMenuItems={[{ id: "feedback" }]}
+                                       show={true}
                                        video={{ enabled: true, visible: true }} />
                 </div>
               </FramedExample>
             </div>
           </Section>
 
           <Section name="PendingConversationView (Desktop)">
             <FramedExample dashed={true}
--- a/browser/components/preferences/in-content/security.xul
+++ b/browser/components/preferences/in-content/security.xul
@@ -65,21 +65,21 @@
               label="&blockWebForgeries.label;"
               accesskey="&blockWebForgeries.accesskey;"
               preference="browser.safebrowsing.enabled" />
   </vbox>
 </groupbox>
 
 <!-- Passwords -->
 <groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true">
-  <caption><label>&passwords.label;</label></caption>
+  <caption><label>&logins.label;</label></caption>
 
   <hbox id="savePasswordsBox">
     <checkbox id="savePasswords"
-              label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;"
+              label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
               preference="signon.rememberSignons"
               onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
     <spacer flex="1"/>
     <button id="passwordExceptions"
             label="&passwordExceptions.label;"
             accesskey="&passwordExceptions.accesskey;"
             preference="pref.privacy.disable_button.view_passwords_exceptions"/>
   </hbox>
@@ -98,14 +98,14 @@
         </hbox>
         <button id="changeMasterPassword"
                 label="&changeMasterPassword.label;"
                 accesskey="&changeMasterPassword.accesskey;"/>
       </row>
       <row id="showPasswordRow">
         <hbox id="showPasswordsBox"/>
         <button id="showPasswords"
-                label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;"
+                label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
                 preference="pref.privacy.disable_button.view_passwords"/>
       </row>
     </rows>
   </grid>
 </groupbox>
--- a/browser/components/translation/moz.build
+++ b/browser/components/translation/moz.build
@@ -17,12 +17,8 @@ JAR_MANIFESTS += ['jar.mn']
 
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini'
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini'
 ]
-
-EXTRA_PP_COMPONENTS += [
-    'translation.manifest',
-]
deleted file mode 100644
--- a/browser/components/translation/translation.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-#ifdef MOZ_SERVICES_HEALTHREPORT
-category healthreport-js-provider-default TranslationProvider resource:///modules/translation/Translation.jsm
-#endif
--- a/browser/devtools/animationinspector/components.js
+++ b/browser/devtools/animationinspector/components.js
@@ -679,18 +679,21 @@ AnimationsTimeline.prototype = {
       });
     }
   },
 
   getAnimationTooltipText: function(state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
                             L10N.numberWithDecimals(time / 1000, 2));
 
-    let title = L10N.getFormatStr("timeline." + state.type + ".nameLabel",
-                                  state.name);
+    // The type isn't always available, older servers don't send it.
+    let title =
+      state.type
+      ? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
+      : state.name;
     let delay = L10N.getStr("player.animationDelayLabel") + " " +
                 getTime(state.delay);
     let duration = L10N.getStr("player.animationDurationLabel") + " " +
                    getTime(state.duration);
     let iterations = L10N.getStr("player.animationIterationCountLabel") + " " +
                      (state.iterationCount ||
                       L10N.getStr("player.infiniteIterationCountText"));
 
--- a/browser/devtools/webide/themes/panel-listing.css
+++ b/browser/devtools/webide/themes/panel-listing.css
@@ -38,17 +38,17 @@ label,
   font-weight: 700;
   width: 100%;
 }
 
 .panel-header:first-child {
   margin-top: 0;
 }
 
-.panel-header[hidden] {
+.panel-header[hidden], .panel-item[hidden] {
   display: none;
 }
 
 #runtime-panel-simulator,
 .panel-item-complex {
   clear: both;
   position: relative;
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -381,17 +381,16 @@
 @RESPATH@/browser/components/nsSetDefaultBrowser.manifest
 @RESPATH@/browser/components/nsSetDefaultBrowser.js
 @RESPATH@/browser/components/devtools-clhandler.manifest
 @RESPATH@/browser/components/devtools-clhandler.js
 @RESPATH@/browser/components/webideCli.js
 @RESPATH@/browser/components/webideComponents.manifest
 @RESPATH@/browser/components/Experiments.manifest
 @RESPATH@/browser/components/ExperimentsService.js
-@RESPATH@/browser/components/translation.manifest
 @RESPATH@/components/Downloads.manifest
 @RESPATH@/components/DownloadLegacy.js
 @RESPATH@/components/BrowserPageThumbs.manifest
 @RESPATH@/components/crashmonitor.manifest
 @RESPATH@/components/nsCrashMonitor.js
 @RESPATH@/components/SiteSpecificUserAgent.js
 @RESPATH@/components/SiteSpecificUserAgent.manifest
 @RESPATH@/components/toolkitsearch.manifest
@@ -562,21 +561,16 @@
 
 #ifndef RELEASE_BUILD
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
 @RESPATH@/components/InterAppMessagePort.js
 #endif
 
-@RESPATH@/components/TCPSocket.js
-@RESPATH@/components/TCPServerSocket.js
-@RESPATH@/components/TCPSocketParentIntermediary.js
-@RESPATH@/components/TCPSocket.manifest
-
 #ifdef MOZ_ACTIVITIES
 @RESPATH@/components/SystemMessageCache.js
 @RESPATH@/components/SystemMessageInternal.js
 @RESPATH@/components/SystemMessageManager.js
 @RESPATH@/components/SystemMessageManager.manifest
 
 @RESPATH@/components/Activities.manifest
 @RESPATH@/components/ActivityProxy.js
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -110,20 +110,16 @@ These should match what Safari and other
 <!ENTITY showAllTabsCmd.accesskey "A">
 
 <!ENTITY fxaSignIn.label "Sign in to &syncBrand.shortName.label;">
 <!ENTITY fxaSignedIn.tooltip "Open &syncBrand.shortName.label; preferences">
 <!ENTITY fxaSignInError.label "Reconnect to &syncBrand.shortName.label;">
 <!ENTITY fxaUnverified.label "Verify Your Account">
 <!ENTITY syncStartPanel2.heading "&syncBrand.shortName.label; enabled">
 <!ENTITY syncStartPanel2.subTitle "&brandShortName; will begin syncing momentarily.">
-<!ENTITY syncErrorPanel.heading "Cannot connect to &syncBrand.shortName.label;">
-<!ENTITY syncErrorPanel.subTitle "Please sign in to resume syncing.">
-<!ENTITY syncErrorPanel.signInButton.label "Sign In">
-<!ENTITY syncErrorPanel.signInButton.accesskey "S">
 
 
 <!ENTITY fullScreenMinimize.tooltip "Minimize">
 <!ENTITY fullScreenRestore.tooltip "Restore">
 <!ENTITY fullScreenClose.tooltip "Close">
 <!ENTITY fullScreenAutohide.label "Hide Toolbars">
 <!ENTITY fullScreenAutohide.accesskey "H">
 <!ENTITY fullScreenExit.label "Exit Full Screen Mode">
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -26,16 +26,25 @@ sign_in_again_button=Sign In
 sign_in_again_use_as_guest_button2=Use {{clientSuperShortname}} as a Guest
 
 first_time_experience_button_label=Get Started
 ## LOCALIZATION_NOTE(first_time_experience_subheading): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading=Join the conversation
 
 invite_header_text=Invite someone to join you.
+invite_header_text2=Invite a friend to join you
+invite_facebook_button=share on Facebook
+invite_facebook_triggered=shared!
+invite_contacts_button=share with contacts
+invite_contacts_triggered=shared!
+invite_copy_button=copy link
+invite_copy_triggered=copied!
+invite_email_button=email link
+invite_email_triggered=emailed!
 
 # Status text
 display_name_guest=Guest
 display_name_dnd_status=Do Not Disturb
 display_name_available_status=Available
 
 # Error bars
 ## LOCALIZATION NOTE(unable_retrieve_url,session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
@@ -338,31 +347,36 @@ rooms_signout_alert=Open conversations w
 infobar_screenshare_browser_message=Users in your conversation will now be able to see the contents of any tab you click on.
 infobar_button_gotit_label=Got it!
 infobar_button_gotit_accesskey=G
 infobar_menuitem_dontshowagain_label=Don't show this again
 infobar_menuitem_dontshowagain_accesskey=D
 
 # Context in conversation strings
 
-# LOCALIZATION NOTE (context_inroom_label): this string is followed by the
-# title/URL of the website you are having a conversation about, displayed on a
+# LOCALIZATION NOTE (context_inroom_header): this string is displayed in the
+# conversation window when the user edits context. It is a header to the edit
+# section.
+context_inroom_header=Let's Talk About…
+# LOCALIZATION NOTE (context_inroom_label2): this string is followed by the
+# title and domain of the website you are having a conversation about, displayed on a
 # separate line. If this structure doesn't work for your locale, you might want
 # to consider this as a stand-alone title. See example screenshot:
 # https://bug1115342.bugzilla.mozilla.org/attachment.cgi?id=8563677
-context_inroom_label=Let's talk about:
+context_inroom_label2=Let's Talk About:
 ## LOCALIZATION_NOTE (context_edit_activate_label): {{title}} will be replaced
 ## by the title of the active tab, also known as the title of an HTML document.
 ## The quotes around the title are intentional.
 context_edit_activate_label=Talk about "{{title}}"
 context_edit_name_placeholder=Conversation Name
 context_edit_comments_placeholder=Comments
 context_add_some_label=Add some context
 context_show_tooltip=Show Context
-context_save_label2=Save
+context_cancel_label=Cancel
+context_done_label=Done
 context_link_modified=This link was modified.
 context_learn_more_link_label=Learn more.
 conversation_settings_menu_edit_context=Edit Context
 conversation_settings_menu_hide_context=Hide Context
 
 
 # Text chat strings
 
--- a/browser/locales/en-US/chrome/browser/preferences/security.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/security.dtd
@@ -19,22 +19,22 @@
 
 <!ENTITY  blockWebForgeries.label     "Block reported web forgeries">
 <!ENTITY  blockWebForgeries.accesskey "B">
 
 <!ENTITY  addonExceptions.label         "Exceptions…">
 <!ENTITY  addonExceptions.accesskey     "E">
 
 
-<!ENTITY  passwords.label               "Passwords">
+<!ENTITY  logins.label                  "Logins">
 
-<!ENTITY  rememberPasswords.label       "Remember passwords for sites">
-<!ENTITY  rememberPasswords.accesskey   "R">
+<!ENTITY  rememberLogins.label          "Remember logins for sites">
+<!ENTITY  rememberLogins.accesskey      "R">
 <!ENTITY  passwordExceptions.label      "Exceptions…">
 <!ENTITY  passwordExceptions.accesskey  "x">
 
 <!ENTITY  useMasterPassword.label        "Use a master password">
 <!ENTITY  useMasterPassword.accesskey    "U">
 <!ENTITY  changeMasterPassword.label     "Change Master Password…">
 <!ENTITY  changeMasterPassword.accesskey "M">
 
-<!ENTITY  savedPasswords.label            "Saved Passwords…">
-<!ENTITY  savedPasswords.accesskey        "P">
+<!ENTITY  savedLogins.label              "Saved Logins…">
+<!ENTITY  savedLogins.accesskey          "L">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1720,25 +1720,23 @@ toolbarbutton.chevron > .toolbarbutton-i
   width: 0;
   padding-left: 10px;
 }
 
 .sync-panel-button-box {
   margin-top: 1em;
 }
 
-#sync-error-panel-title,
 #sync-start-panel-title {
   font-size: 120%;
   font-weight: bold;
   margin-bottom: 5px;
 }
 
-#sync-start-panel-subtitle,
-#sync-error-panel-subtitle {
+#sync-start-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3270,25 +3270,23 @@ notification[value="loop-sharing-notific
 .sync-panel-button:hover:active {
   @hudButtonPressed@
 }
 
 .sync-panel-button:-moz-focusring {
   @hudButtonFocused@
 }
 
-#sync-error-panel-title,
 #sync-start-panel-title {
   font-size: 120%;
   font-weight: bold;
   margin-bottom: 5px;
 }
 
-#sync-start-panel-subtitle,
-#sync-error-panel-subtitle {
+#sync-start-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -1,11 +1,10 @@
 #PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
   border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
-  color: -moz-FieldText;
   background-color: hsla(210, 4%, 10%, 0.07);
   padding: 6px 0;
   -moz-padding-start: 44px;
   -moz-padding-end: 6px;
   background-image: url("chrome://browser/skin/info.svg");
   background-clip: padding-box;
   background-position: 20px center;
   background-repeat: no-repeat;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2468,25 +2468,23 @@ notification[value="loop-sharing-notific
   width: 0;
   padding-left: 10px;
 }
 
 .sync-panel-button-box {
   margin-top: 1em;
 }
 
-#sync-error-panel-title,
 #sync-start-panel-title {
   font-size: 120%;
   font-weight: bold;
   margin-bottom: 5px;
 }
 
-#sync-start-panel-subtitle,
-#sync-error-panel-subtitle {
+#sync-start-panel-subtitle {
   margin: 0;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
--- a/caps/nsNullPrincipal.cpp
+++ b/caps/nsNullPrincipal.cpp
@@ -168,16 +168,19 @@ nsNullPrincipal::Read(nsIObjectInputStre
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::Write(nsIObjectOutputStream* aStream)
 {
+  NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+                 NS_ERROR_INVALID_ARG);
+
   nsAutoCString suffix;
   OriginAttributesRef().CreateSuffix(suffix);
 
   nsresult rv = aStream->WriteStringZ(suffix.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -422,16 +422,18 @@ nsPrincipal::Read(nsIObjectInputStream* 
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrincipal::Write(nsIObjectOutputStream* aStream)
 {
   NS_ENSURE_STATE(mCodebase);
+  NS_ENSURE_TRUE(mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+                 NS_ERROR_INVALID_ARG);
 
   nsresult rv = NS_WriteOptionalCompoundObject(aStream, mCodebase, NS_GET_IID(nsIURI),
                                                true);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   rv = NS_WriteOptionalCompoundObject(aStream, mDomain, NS_GET_IID(nsIURI),
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -98,16 +98,30 @@ function run_test() {
   var exampleOrg_addon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy'});
   checkOriginAttributes(exampleOrg_addon, { addonId: "dummy" }, '^addonId=dummy');
   do_check_eq(exampleOrg_addon.origin, 'http://example.org^addonId=dummy');
 
   // Make sure that we refuse to create .origin for principals with UNKNOWN_APP_ID.
   var simplePrin = ssm.getSimpleCodebasePrincipal(makeURI('http://example.com'));
   try { simplePrin.origin; do_check_true(false); } catch (e) { do_check_true(true); }
 
+  // Make sure we don't crash when serializing them either.
+  try {
+    let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+                       createInstance(Ci.nsIObjectOutputStream);
+    let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+    pipe.init(false, false, 0, 0xffffffff, null);
+    binaryStream.setOutputStream(pipe.outputStream);
+    binaryStream.writeCompoundObject(simplePrin, Ci.nsISupports, true);
+    binaryStream.close();
+  } catch (e) {
+    do_check_true(true);
+  }
+
+
   // Just userContext.
   var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI('http://example.org'), {userContextId: 42});
   checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, '^userContextId=42');
   do_check_eq(exampleOrg_userContext.origin, 'http://example.org^userContextId=42');
 
   // UserContext and Addon.
   var exampleOrg_userContextAddon = ssm.createCodebasePrincipal(makeURI('http://example.org'), {addonId: 'dummy', userContextId: 42});
   var nullPrin_userContextAddon = ssm.createNullPrincipal({addonId: 'dummy', userContextId: 42});
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -163,17 +163,19 @@ const mozilla::Module::ContractIDEntry k
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "credits", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+#ifdef NIGHTLY_BUILD
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+#endif
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "serviceworkers", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "telemetry", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "webrtc", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID },
   { NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &kNS_DOCUMENTLOADER_SERVICE_CID },
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -38,16 +38,17 @@
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
+#include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/Telephony.h"
 #include "mozilla/dom/Voicemail.h"
 #include "mozilla/dom/TVManager.h"
 #include "mozilla/dom/VRDevice.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
@@ -1800,16 +1801,23 @@ Navigator::GetInputPortManager(ErrorResu
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
   return mInputPortManager;
 }
 
+already_AddRefed<LegacyMozTCPSocket>
+Navigator::MozTCPSocket()
+{
+  nsRefPtr<LegacyMozTCPSocket> socket = new LegacyMozTCPSocket(GetWindow());
+  return socket.forget();
+}
+
 #ifdef MOZ_B2G
 already_AddRefed<Promise>
 Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
                                 ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -94,16 +94,17 @@ class PowerManager;
 class CellBroadcast;
 class IccManager;
 class Telephony;
 class Voicemail;
 class TVManager;
 class InputPortManager;
 class DeviceStorageAreaListener;
 class Presentation;
+class LegacyMozTCPSocket;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -237,16 +238,17 @@ public:
   DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
   CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
   IccManager* GetMozIccManager(ErrorResult& aRv);
   MobileMessageManager* GetMozMobileMessage();
   Telephony* GetMozTelephony(ErrorResult& aRv);
   Voicemail* GetMozVoicemail(ErrorResult& aRv);
   TVManager* GetTv();
   InputPortManager* GetInputPortManager(ErrorResult& aRv);
+  already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
   network::Connection* GetConnection(ErrorResult& aRv);
   nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
   void MozSetMessageHandler(const nsAString& aType,
                             systemMessageCallback* aCallback,
                             ErrorResult& aRv);
   bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
   void MozSetMessageHandlerPromise(Promise& aPromise, ErrorResult& aRv);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -748,25 +748,27 @@ GK_ATOM(onDOMCharacterDataModified, "onD
 GK_ATOM(onDOMFocusIn, "onDOMFocusIn")
 GK_ATOM(onDOMFocusOut, "onDOMFocusOut")
 GK_ATOM(onDOMMouseScroll, "onDOMMouseScroll")
 GK_ATOM(onDOMNodeInserted, "onDOMNodeInserted")
 GK_ATOM(onDOMNodeInsertedIntoDocument, "onDOMNodeInsertedIntoDocument")
 GK_ATOM(onDOMNodeRemoved, "onDOMNodeRemoved")
 GK_ATOM(onDOMNodeRemovedFromDocument, "onDOMNodeRemovedFromDocument")
 GK_ATOM(onDOMSubtreeModified, "onDOMSubtreeModified")
+GK_ATOM(ondata, "ondata")
 GK_ATOM(ondrag, "ondrag")
 GK_ATOM(ondragdrop, "ondragdrop")
 GK_ATOM(ondragend, "ondragend")
 GK_ATOM(ondragenter, "ondragenter")
 GK_ATOM(ondragexit, "ondragexit")
 GK_ATOM(ondraggesture, "ondraggesture")
 GK_ATOM(ondragleave, "ondragleave")
 GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
+GK_ATOM(ondrain, "ondrain")
 GK_ATOM(ondrop, "ondrop")
 GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onenterpincodereq, "onenterpincodereq")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onevicted, "onevicted")
 GK_ATOM(onfacesdetected, "onfacesdetected")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -691,16 +691,21 @@ DOMInterfaces = {
     'nativeType': 'nsIInputStream',
     'notflattened': True
 },
 
 'KeyEvent': {
     'concrete': False
 },
 
+'LegacyMozTCPSocket': {
+    'headerFile': 'TCPSocket.h',
+    'wrapperCache': False,
+},
+
 'LocalMediaStream': {
     'headerFile': 'DOMMediaStream.h',
     'nativeType': 'mozilla::DOMLocalMediaStream'
 },
 
 'Location': {
     'nativeType': 'nsLocation',
 },
@@ -1303,16 +1308,20 @@ DOMInterfaces = {
 'TextEncoder': {
     'wrapperCache': False
 },
 
 'TextMetrics': {
     'wrapperCache': False
 },
 
+'TCPSocket': {
+    'implicitJSContext': ['send']
+},
+
 'ThreadSafeChromeUtils': {
     # The codegen is dumb, and doesn't understand that this interface is only a
     # collection of static methods, so we have this `concrete: False` hack.
     'concrete': False,
     'headerFile': 'mozilla/dom/ChromeUtils.h',
     'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
 },
 
--- a/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html
+++ b/dom/canvas/test/crossorigin/test_canvas2d_crossorigin.html
@@ -200,17 +200,13 @@ function beginTest() {
           }
         }
         testImage(url, attrValData[0], expected_error);
       }
     }
   }
 }
 
-var prefs = [
-  [ "canvas.capturestream.enabled", true ],
-];
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
-
+beginTest();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/canvas/test/crossorigin/test_video_crossorigin.html
+++ b/dom/canvas/test/crossorigin/test_video_crossorigin.html
@@ -206,19 +206,14 @@ function beginTest() {
   try {
     document.gl = createCanvas(16, 16).getContext("experimental-webgl");
   } catch (ex) {
     // Mac OS X 10.5 doesn't support WebGL, so we won't run the WebGL tests
   }
   document.manager.runTests(corsTests, startTest);
 }
 
-var prefs = [
-  [ "canvas.capturestream.enabled", true ],
-];
-
 SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
-
+beginTest();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -15,17 +15,17 @@ pref(webgl.force-layers-readback,true)  
 == webgl-clear-test.html?depth wrapper.html?green.png
 == webgl-clear-test.html?stencil wrapper.html?green.png
 == webgl-clear-test.html?depth&stencil wrapper.html?green.png
 
 # Check that resize works:
 == webgl-resize-test.html  wrapper.html?green.png
 
 # Check that captureStream() displays in a local video element
-pref(canvas.capturestream.enabled,true) skip-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-capturestream-test.html?preserve wrapper.html?green.png
+skip-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-capturestream-test.html?preserve wrapper.html?green.png
 
 # Some of the failure conditions are a little crazy. I'm (jgilbert) setting these based on
 # failures encountered when running on Try, and then targetting the Try config by
 # differences in the `sandbox` contents. That is, I'm labeling based on symptoms rather
 # than cause.
 # Lin-R-e10s: gtkWidget && browserIsRemote
 # WinXP-R: winWidget && layersGPUAccelerated && !d2d
 # Win7+-R: winWidget && layersGPUAccelerated && d2d
@@ -152,11 +152,11 @@ skip-if(!winWidget) pref(webgl.disable-a
 # Bug 815648
 == stroketext-shadow.html stroketext-shadow-ref.html
 
 # focus rings
 pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
 pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
-pref(canvas.capturestream.enabled,true) skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
+skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png
 
 fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
--- a/dom/canvas/test/test_capture.html
+++ b/dom/canvas/test/test_capture.html
@@ -102,14 +102,11 @@ function beginTest() {
     .then(checkRequestFrameOrderGuarantee)
     .then(checkDrawColorGreen) // Restore video elements to green.
     .then(checkDrawImageNotCleanRed)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
-var prefs = [
-  [ "canvas.capturestream.enabled", true ],
-];
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
+beginTest();
 </script>
 
--- a/dom/canvas/test/webgl-mochitest/test_capture.html
+++ b/dom/canvas/test/webgl-mochitest/test_capture.html
@@ -166,14 +166,11 @@ function beginTest() {
     .then(checkDrawColorGreen)
     .then(checkClearColorRed)
     .then(checkRequestFrameOrderGuarantee)
     .then(finish);
 }
 
 SimpleTest.waitForExplicitFinish();
 
-var prefs = [
-  [ "canvas.capturestream.enabled", true ],
-];
-SpecialPowers.pushPrefEnv({ "set" : prefs }, beginTest);
+beginTest();
 </script>
 
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -244,16 +244,17 @@ public:
   already_AddRefed<DOMRequest> CreateFileDescriptor(const nsAString& aPath,
                                                     DeviceStorageFileDescriptor* aDSFD,
                                                     ErrorResult& aRv);
 
   bool CanBeMounted();
   bool CanBeFormatted();
   bool CanBeShared();
   bool IsRemovable();
+  bool LowDiskSpace();
   bool Default();
   void GetStorageName(nsAString& aStorageName);
 
   already_AddRefed<Promise>
   GetRoot(ErrorResult& aRv);
 
   static void
   CreateDeviceStorageFor(nsPIDOMWindow* aWin,
--- a/dom/devicestorage/DeviceStorageStatics.cpp
+++ b/dom/devicestorage/DeviceStorageStatics.cpp
@@ -67,16 +67,17 @@ DeviceStorageStatics::InitializeDirs()
   }
 
   MOZ_ASSERT(sInstance->mInitialized);
 }
 
 DeviceStorageStatics::DeviceStorageStatics()
   : mInitialized(false)
   , mPromptTesting(false)
+  , mLowDiskSpace(false)
 {
   DS_LOG_INFO("");
 }
 
 DeviceStorageStatics::~DeviceStorageStatics()
 {
   DS_LOG_INFO("");
 }
@@ -353,16 +354,26 @@ DeviceStorageStatics::IsPromptTesting()
 {
   StaticMutexAutoLock lock(sMutex);
   if (NS_WARN_IF(!sInstance)) {
     return false;
   }
   return sInstance->mPromptTesting;
 }
 
+/* static */ bool
+DeviceStorageStatics::LowDiskSpace()
+{
+  StaticMutexAutoLock lock(sMutex);
+  if (NS_WARN_IF(!sInstance)) {
+    return false;
+  }
+  return sInstance->mLowDiskSpace;
+}
+
 /* static */ void
 DeviceStorageStatics::GetWritableName(nsString& aName)
 {
   StaticMutexAutoLock lock(sMutex);
   if (NS_WARN_IF(!sInstance)) {
     aName.Truncate();
     return;
   }
@@ -600,37 +611,39 @@ DeviceStorageStatics::Observe(nsISupport
     while (i > 0) {
       --i;
       mListeners[i]->OnFileWatcherUpdate(data, file);
     }
     return NS_OK;
   }
 
   if (!strcmp(aTopic, kDiskSpaceWatcher)) {
-    // 'disk-space-watcher' notifications are sent when there is a modification
-    // of a file in a specific location while a low device storage situation
-    // exists or after recovery of a low storage situation. For Firefox OS,
-    // these notifications are specific for apps storage.
-    bool lowDiskSpace = false;
-    if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
-      lowDiskSpace = true;
-    } else if (NS_strcmp(aData, MOZ_UTF16("free"))) {
-      return NS_OK;
-    }
-
     StaticMutexAutoLock lock(sMutex);
     if (NS_WARN_IF(!sInstance)) {
       return NS_OK;
     }
 
+    // 'disk-space-watcher' notifications are sent when there is a modification
+    // of a file in a specific location while a low device storage situation
+    // exists or after recovery of a low storage situation. For Firefox OS,
+    // these notifications are specific for apps storage.
+    if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
+      sInstance->mLowDiskSpace = true;
+    } else if (!NS_strcmp(aData, MOZ_UTF16("free"))) {
+      sInstance->mLowDiskSpace = false;
+    } else {
+      return NS_OK;
+    }
+
+
     uint32_t i = mListeners.Length();
-    DS_LOG_INFO("disk space %d (%u)", lowDiskSpace, i);
+    DS_LOG_INFO("disk space %d (%u)", sInstance->mLowDiskSpace, i);
     while (i > 0) {
       --i;
-      mListeners[i]->OnDiskSpaceWatcher(lowDiskSpace);
+      mListeners[i]->OnDiskSpaceWatcher(sInstance->mLowDiskSpace);
     }
     return NS_OK;
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     StaticMutexAutoLock lock(sMutex);
     if (NS_WARN_IF(!sInstance)) {
       return NS_OK;
--- a/dom/devicestorage/DeviceStorageStatics.h
+++ b/dom/devicestorage/DeviceStorageStatics.h
@@ -25,16 +25,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static void Initialize();
   static void InitializeDirs();
   static void AddListener(nsDOMDeviceStorage* aListener);
   static void RemoveListener(nsDOMDeviceStorage* aListener);
 
+  static bool LowDiskSpace();
   static bool IsPromptTesting();
   static void GetWritableName(nsString& aName);
   static void SetWritableName(const nsAString& aName);
 
   static bool HasOverrideRootDir();
   static already_AddRefed<nsIFile> GetAppsDir();
   static already_AddRefed<nsIFile> GetCrashesDir();
   static already_AddRefed<nsIFile> GetPicturesDir();
@@ -87,16 +88,17 @@ private:
     nsCOMPtr<nsIThread> mOwningThread;
   };
 
   nsTArray<nsRefPtr<ListenerWrapper> > mListeners;
   nsCOMPtr<nsIFile> mDirs[TYPE_COUNT];
 
   bool mInitialized;
   bool mPromptTesting;
+  bool mLowDiskSpace;
   nsString mWritableName;
 
   static StaticRefPtr<DeviceStorageStatics> sInstance;
   static StaticMutex sMutex;
 };
 
 } // namespace devicestorage
 } // namespace dom
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3369,16 +3369,22 @@ nsDOMDeviceStorage::CanBeShared()
 }
 
 bool
 nsDOMDeviceStorage::IsRemovable()
 {
   return mIsRemovable;
 }
 
+bool
+nsDOMDeviceStorage::LowDiskSpace()
+{
+  return DeviceStorageStatics::LowDiskSpace();
+}
+
 already_AddRefed<Promise>
 nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
 {
   if (!mFileSystem) {
     mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
     mFileSystem->Init(this);
   }
   return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -479,16 +479,28 @@ const kEventConstructors = {
                                              },
   SVGZoomEvent:                              { create: function (aName, aProps) {
                                                          var e = document.createEvent("svgzoomevent");
                                                          e.initUIEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                        aProps.view, aProps.detail);
                                                          return e;
                                                        },
                                              },
+  TCPSocketErrorEvent:                       { create: function(aName, aProps) {
+                                                         return new TCPSocketErrorEvent(aName, aProps);
+                                                       },
+                                             },
+  TCPSocketEvent:                            { create: function(aName, aProps) {
+                                                         return new TCPSocketEvent(aName, aProps);
+                                                       },
+                                             },
+  TCPServerSocketEvent:                      { create: function(aName, aProps) {
+                                                         return new TCPServerSocketEvent(aName, aProps);
+                                                       },
+                                             },
   TimeEvent:                                 { create: function (aName, aProps) {
                                                          var e = document.createEvent("timeevent");
                                                          e.initTimeEvent(aName, aProps.view, aProps.detail);
                                                          return e;
                                                        },
                                              },
   TouchEvent:                                { create: function (aName, aProps) {
                                                          var e = document.createEvent("touchevent");
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -37,16 +37,17 @@
 #include "InternalRequest.h"
 #include "InternalResponse.h"
 
 #include "nsFormData.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 #include "Workers.h"
+#include "FetchUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
 
 class WorkerFetchResolver final : public FetchDriverObserver
 {
@@ -904,63 +905,16 @@ ExtractByteStreamFromBody(const ArrayBuf
     return ExtractFromURLSearchParams(params, aStream, aContentType);
   }
 
   NS_NOTREACHED("Should never reach here");
   return NS_ERROR_FAILURE;
 }
 
 namespace {
-class StreamDecoder final
-{
-  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
-  nsString mDecoded;
-
-public:
-  StreamDecoder()
-    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
-  {
-    MOZ_ASSERT(mDecoder);
-  }
-
-  nsresult
-  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
-  {
-    int32_t destBufferLen;
-    nsresult rv =
-      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
-    int32_t totalChars = mDecoded.Length();
-
-    int32_t srcLen = (int32_t) aSrcBufferLen;
-    int32_t outLen = destBufferLen;
-    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
-    MOZ_ASSERT(NS_SUCCEEDED(rv));
-
-    totalChars += outLen;
-    mDecoded.SetLength(totalChars);
-
-    return NS_OK;
-  }
-
-  nsString&
-  GetText()
-  {
-    return mDecoded;
-  }
-};
-
 /*
  * Called on successfully reading the complete stream.
  */
 template <class Derived>
 class ContinueConsumeBodyRunnable final : public WorkerRunnable
 {
   // This has been addrefed before this runnable is dispatched,
   // released in WorkerRun().
@@ -1431,138 +1385,91 @@ FetchBody<Derived>::ContinueConsumeBody(
 
   // Finish successfully consuming body according to type.
   MOZ_ASSERT(aResult);
 
   AutoJSAPI jsapi;
   jsapi.Init(DerivedClass()->GetParentObject());
   JSContext* cx = jsapi.cx();
 
+  ErrorResult error;
+
   switch (mConsumeType) {
     case CONSUME_ARRAYBUFFER: {
       JS::Rooted<JSObject*> arrayBuffer(cx);
-      arrayBuffer = JS_NewArrayBufferWithContents(cx, aResultLength, reinterpret_cast<void *>(aResult));
-      if (!arrayBuffer) {
-        JS_ClearPendingException(cx);
-        localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-        NS_WARNING("OUT OF MEMORY");
-        return;
+      FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
+                                    error);
+
+      error.WouldReportJSException();
+      if (!error.Failed()) {
+        JS::Rooted<JS::Value> val(cx);
+        val.setObjectOrNull(arrayBuffer);
+
+        localPromise->MaybeResolve(cx, val);
+        // ArrayBuffer takes over ownership.
+        autoFree.Reset();
       }
-
-      JS::Rooted<JS::Value> val(cx);
-      val.setObjectOrNull(arrayBuffer);
-      localPromise->MaybeResolve(cx, val);
-      // ArrayBuffer takes over ownership.
-      autoFree.Reset();
-      return;
+      break;
     }
     case CONSUME_BLOB: {
-      nsRefPtr<dom::Blob> blob =
-        Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(),
-                               reinterpret_cast<void *>(aResult), aResultLength,
-                               NS_ConvertUTF8toUTF16(mMimeType));
-
-      if (!blob) {
-        localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-        return;
+      nsRefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
+        DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
+        aResultLength, aResult, error);
+      error.WouldReportJSException();
+      if (!error.Failed()) {
+        localPromise->MaybeResolve(blob);
+        // File takes over ownership.
+        autoFree.Reset();
       }
-
-      localPromise->MaybeResolve(blob);
-      // File takes over ownership.
-      autoFree.Reset();
-      return;
+      break;
     }
     case CONSUME_FORMDATA: {
       nsCString data;
       data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
       autoFree.Reset();
 
-      NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
-
-      // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
-      // but disallow multipart/form-datafoobar.
-      bool isValidFormDataMimeType = StringBeginsWith(mMimeType, formDataMimeType);
-
-      if (isValidFormDataMimeType && mMimeType.Length() > formDataMimeType.Length()) {
-        isValidFormDataMimeType = mMimeType[formDataMimeType.Length()] == ';';
-      }
-
-      if (isValidFormDataMimeType) {
-        FormDataParser parser(mMimeType, data, DerivedClass()->GetParentObject());
-        if (!parser.Parse()) {
-          ErrorResult result;
-          result.ThrowTypeError(MSG_BAD_FORMDATA);
-          localPromise->MaybeReject(result);
-          return;
-        }
-
-        nsRefPtr<nsFormData> fd = parser.FormData();
-        MOZ_ASSERT(fd);
+      nsRefPtr<nsFormData> fd = FetchUtil::ConsumeFormData(
+        DerivedClass()->GetParentObject(),
+        mMimeType, data, error);
+      if (!error.Failed()) {
         localPromise->MaybeResolve(fd);
-      } else {
-        NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
-        bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType);
-
-        if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) {
-          isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';';
-        }
-
-        if (isValidUrlEncodedMimeType) {
-          URLParams params;
-          params.ParseInput(data);
-
-          nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
-          FillFormIterator iterator(fd);
-          DebugOnly<bool> status = params.ForEach(iterator);
-          MOZ_ASSERT(status);
-
-          localPromise->MaybeResolve(fd);
-        } else {
-          ErrorResult result;
-          result.ThrowTypeError(MSG_BAD_FORMDATA);
-          localPromise->MaybeReject(result);
-        }
       }
-      return;
+      break;
     }
     case CONSUME_TEXT:
       // fall through handles early exit.
     case CONSUME_JSON: {
-      StreamDecoder decoder;
-      decoder.AppendText(reinterpret_cast<char*>(aResult), aResultLength);
-
-      nsString& decoded = decoder.GetText();
-      if (mConsumeType == CONSUME_TEXT) {
-        localPromise->MaybeResolve(decoded);
-        return;
-      }
+      nsString decoded;
+      if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
+        if (mConsumeType == CONSUME_TEXT) {
+          localPromise->MaybeResolve(decoded);
+        } else {
+          JS::Rooted<JS::Value> json(cx);
+          FetchUtil::ConsumeJson(cx, &json, decoded, error);
+          if (!error.Failed()) {
+            localPromise->MaybeResolve(cx, json);
+          }
+        }
+      };
+      break;
+    }
+    default:
+      NS_NOTREACHED("Unexpected consume body type");
+  }
 
-      AutoForceSetExceptionOnContext forceExn(cx);
-      JS::Rooted<JS::Value> json(cx);
-      if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
-        if (!JS_IsExceptionPending(cx)) {
-          localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
-          return;
-        }
-
-        JS::Rooted<JS::Value> exn(cx);
-        DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
-        MOZ_ASSERT(gotException);
-
-        JS_ClearPendingException(cx);
-        localPromise->MaybeReject(cx, exn);
-        return;
-      }
-
-      localPromise->MaybeResolve(cx, json);
-      return;
+  error.WouldReportJSException();
+  if (error.Failed()) {
+    if (error.IsJSException()) {
+      JS::Rooted<JS::Value> exn(cx);
+      error.StealJSException(cx, &exn);
+      localPromise->MaybeReject(cx, exn);
+    } else {
+      localPromise->MaybeReject(error);
     }
   }
-
-  NS_NOTREACHED("Unexpected consume body type");
 }
 
 template <class Derived>
 already_AddRefed<Promise>
 FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
 {
   mConsumeType = aType;
   if (BodyUsed()) {
@@ -1617,10 +1524,11 @@ FetchBody<Derived>::SetMimeType()
 
 template
 void
 FetchBody<Request>::SetMimeType();
 
 template
 void
 FetchBody<Response>::SetMimeType();
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.cpp
+++ b/dom/fetch/FetchUtil.cpp
@@ -1,15 +1,68 @@
 #include "FetchUtil.h"
+
 #include "nsError.h"
+#include "nsIUnicodeDecoder.h"
 #include "nsString.h"
 
+#include "mozilla/dom/EncodingUtils.h"
+
 namespace mozilla {
 namespace dom {
 
+namespace {
+class StreamDecoder final
+{
+  nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+  nsString mDecoded;
+
+public:
+  StreamDecoder()
+    : mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
+  {
+    MOZ_ASSERT(mDecoder);
+  }
+
+  nsresult
+  AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
+  {
+    int32_t destBufferLen;
+    nsresult rv =
+      mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
+    int32_t totalChars = mDecoded.Length();
+
+    int32_t srcLen = (int32_t) aSrcBufferLen;
+    int32_t outLen = destBufferLen;
+    rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    totalChars += outLen;
+    mDecoded.SetLength(totalChars);
+
+    return NS_OK;
+  }
+
+  nsString&
+  GetText()
+  {
+    return mDecoded;
+  }
+};
+}
+
 // static
 nsresult
 FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
 {
   nsAutoCString upperCaseMethod(aMethod);
   ToUpperCase(upperCaseMethod);
   if (upperCaseMethod.EqualsLiteral("CONNECT") ||
       upperCaseMethod.EqualsLiteral("TRACE") ||
@@ -28,10 +81,138 @@ FetchUtil::GetValidRequestMethod(const n
     outMethod = upperCaseMethod;
   }
   else {
     outMethod = aMethod; // Case unchanged for non-standard methods
   }
   return NS_OK;
 }
 
+// static
+void
+FetchUtil::ConsumeArrayBuffer(JSContext* aCx,
+                              JS::MutableHandle<JSObject*> aValue,
+                              uint32_t aInputLength, uint8_t* aInput,
+                              ErrorResult& aRv)
+{
+  JS::Rooted<JSObject*> arrayBuffer(aCx);
+  arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
+    reinterpret_cast<void *>(aInput));
+  if (!arrayBuffer) {
+    JS_ClearPendingException(aCx);
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aValue.set(arrayBuffer);
+}
+
+// static
+already_AddRefed<Blob>
+FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+                       uint32_t aInputLength, uint8_t* aInput,
+                       ErrorResult& aRv)
+{
+  nsRefPtr<Blob> blob =
+    Blob::CreateMemoryBlob(aParent,
+                           reinterpret_cast<void *>(aInput), aInputLength,
+                           aMimeType);
+
+  if (!blob) {
+    aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+    return nullptr;
+  }
+  return blob.forget();
+}
+
+// static
+already_AddRefed<nsFormData>
+FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                           const nsCString& aStr, ErrorResult& aRv)
+{
+  NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
+
+  // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
+  // but disallow multipart/form-datafoobar.
+  bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
+
+  if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
+    isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
+  }
+
+  if (isValidFormDataMimeType) {
+    FormDataParser parser(aMimeType, aStr, aParent);
+    if (!parser.Parse()) {
+      aRv.ThrowTypeError(MSG_BAD_FORMDATA);
+      return nullptr;
+    }
+
+    nsRefPtr<nsFormData> fd = parser.FormData();
+    MOZ_ASSERT(fd);
+    return fd.forget();
+  }
+
+  NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
+  bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
+
+  if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
+    isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
+  }
+
+  if (isValidUrlEncodedMimeType) {
+    URLParams params;
+    params.ParseInput(aStr);
+
+    nsRefPtr<nsFormData> fd = new nsFormData(aParent);
+    FillFormIterator iterator(fd);
+    DebugOnly<bool> status = params.ForEach(iterator);
+    MOZ_ASSERT(status);
+
+    return fd.forget();
+  }
+
+  aRv.ThrowTypeError(MSG_BAD_FORMDATA);
+  return nullptr;
+}
+
+// static
+nsresult
+FetchUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
+                       nsString& aText)
+{
+  StreamDecoder decoder;
+  nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
+                                   aInputLength);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  aText = decoder.GetText();
+  return NS_OK;
+}
+
+// static
+void
+FetchUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+                       const nsString& aStr, ErrorResult& aRv)
+{
+  aRv.MightThrowJSException();
+
+  AutoForceSetExceptionOnContext forceExn(aCx);
+  JS::Rooted<JS::Value> json(aCx);
+  if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
+    if (!JS_IsExceptionPending(aCx)) {
+      aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
+      return;
+    }
+
+    JS::Rooted<JS::Value> exn(aCx);
+    DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
+    MOZ_ASSERT(gotException);
+
+    JS_ClearPendingException(aCx);
+    aRv.ThrowJSException(aCx, exn);
+    return;
+  }
+
+  aValue.set(json);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/FetchUtil.h
+++ b/dom/fetch/FetchUtil.h
@@ -1,13 +1,17 @@
 #ifndef mozilla_dom_FetchUtil_h
 #define mozilla_dom_FetchUtil_h
 
 #include "nsString.h"
 #include "nsError.h"
+#include "nsFormData.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/File.h"
 
 namespace mozilla {
 namespace dom {
 
 class FetchUtil final
 {
 private:
   FetchUtil() = delete;
@@ -16,13 +20,53 @@ public:
   /**
   * Sets outMethod to a valid HTTP request method string based on an input method.
   * Implements checks and normalization as specified by the Fetch specification.
   * Returns NS_ERROR_DOM_SECURITY_ERR if the method is invalid.
   * Otherwise returns NS_OK and the normalized method via outMethod.
   */
   static nsresult
   GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod);
+
+  /**
+   * Creates an array buffer from an array, assigning the result to |aValue|.
+   * The array buffer takes ownership of |aInput|, which must be allocated
+   * by |malloc|.
+   */
+  static void
+  ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
+                     uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates an in-memory blob from an array. The blob takes ownership of
+   * |aInput|, which must be allocated by |malloc|.
+   */
+  static already_AddRefed<Blob>
+  ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
+              uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
+
+  /**
+   * Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
+   * and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
+   */
+  static already_AddRefed<nsFormData>
+  ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
+                  const nsCString& aStr, ErrorResult& aRv);
+
+  /**
+   * UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
+   * once this method returns.
+   */
+  static nsresult
+  ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
+
+  /**
+   * Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
+   * Sets |aRv| to a syntax error if |aStr| contains invalid data.
+   */
+  static void
+  ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
+              const nsString& aStr, ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 #endif
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -77,16 +77,26 @@ public:
 
   // FIXME(nsm): Return with exclude fragment.
   void
   GetUrl(nsCString& aURL) const
   {
     aURL.Assign(mURL);
   }
 
+  void
+  GetUnfilteredUrl(nsCString& aURL) const
+  {
+    if (mWrappedResponse) {
+      return mWrappedResponse->GetUrl(aURL);
+    }
+
+    return GetUrl(aURL);
+  }
+
   // SetUrl should only be called when the fragment has alredy been stripped
   void
   SetUrl(const nsACString& aURL)
   {
     mURL.Assign(aURL);
   }
 
   uint16_t
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -29,17 +29,17 @@ interface nsIServiceWorkerInfo : nsISupp
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString currentWorkerURL;
 
   readonly attribute DOMString activeCacheName;
   readonly attribute DOMString waitingCacheName;
 };
 
-[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)]
+[scriptable, builtinclass, uuid(471b2d5d-64c3-4dea-bde1-219853dcaac8)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -136,19 +136,20 @@ interface nsIServiceWorkerManager : nsIS
                                   in AString aTitle,
                                   in AString aDir,
                                   in AString aLang,
                                   in AString aBody,
                                   in AString aTag,
                                   in AString aIcon,
                                   in AString aData,
                                   in AString aBehavior);
-  void sendPushEvent(in ACString aOriginAttributes,
-                     in ACString aScope,
-                     in DOMString aData);
+  [optional_argc] void sendPushEvent(in ACString aOriginAttributes,
+                                     in ACString aScope,
+                                     [optional] in uint32_t aDataLength,
+                                     [optional, array, size_is(aDataLength)] in uint8_t aDataBytes);
   void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
                                        in ACString scope);
 
   void updateAllRegistrations();
 };
 
 %{ C++
 #define SERVICEWORKERMANAGER_CONTRACTID "@mozilla.org/serviceworkers/manager;1"
--- a/dom/locales/en-US/chrome/xslt/xslt.properties
+++ b/dom/locales/en-US/chrome/xslt/xslt.properties
@@ -28,11 +28,12 @@ 23 = XPath parse failure: ':' unexpected
 24 = XPath parse failure: '!' unexpected, negation is not():
 25 = XPath parse failure: illegal character found:
 26 = XPath parse failure: binary operator expected:
 27 = An XSLT stylesheet load was blocked for security reasons.
 28 = Evaluating an invalid expression.
 29 = Unbalanced curly brace.
 30 = Creating an element with an invalid QName.
 31 = Variable binding shadows variable binding within the same template.
+32 = Call to the key function not allowed.
 
 LoadingError = Error loading stylesheet: %S
 TransformError = Error during XSLT transformation: %S
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -353,16 +353,24 @@ DOMMediaStream::SetTrackEnabled(TrackID 
 void
 DOMMediaStream::StopTrack(TrackID aTrackID)
 {
   if (mStream && mStream->AsSourceStream()) {
     mStream->AsSourceStream()->EndTrack(aTrackID);
   }
 }
 
+already_AddRefed<Promise>
+DOMMediaStream::ApplyConstraintsToTrack(TrackID aTrackID,
+                                        const MediaTrackConstraints& aConstraints,
+                                        ErrorResult &aRv)
+{
+  return nullptr;
+}
+
 bool
 DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
 {
   bool changed =
     nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
   if (changed) {
     NotifyPrincipalChanged();
   }
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -43,16 +43,17 @@ class HTMLCanvasElement;
 class MediaStreamTrack;
 class AudioStreamTrack;
 class VideoStreamTrack;
 class AudioTrack;
 class VideoTrack;
 class AudioTrackList;
 class VideoTrackList;
 class MediaTrackListListener;
+struct MediaTrackConstraints;
 } // namespace dom
 
 namespace layers {
 class ImageContainer;
 class OverlayImage;
 } // namespace layers
 
 class MediaStreamDirectListener;
@@ -72,16 +73,17 @@ class DOMMediaStream : public DOMEventTa
   typedef dom::VideoStreamTrack VideoStreamTrack;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
   typedef dom::VideoTrackList VideoTrackList;
   typedef dom::MediaTrackListListener MediaTrackListListener;
 
 public:
+  typedef dom::MediaTrackConstraints MediaTrackConstraints;
   typedef uint8_t TrackTypeHints;
 
   DOMMediaStream();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMMediaStream,
                                            DOMEventTargetHelper)
@@ -116,16 +118,21 @@ public:
   /**
    * Overridden in DOMLocalMediaStreams to allow getUserMedia to disable
    * media at the SourceMediaStream.
    */
   virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
 
   virtual void StopTrack(TrackID aTrackID);
 
+  virtual already_AddRefed<dom::Promise>
+  ApplyConstraintsToTrack(TrackID aTrackID,
+                          const MediaTrackConstraints& aConstraints,
+                          ErrorResult &aRv);
+
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() { return nullptr; }
   virtual DOMHwMediaStream* AsDOMHwMediaStream() { return nullptr; }
 
   bool IsFinished();
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -74,18 +74,16 @@
 
 #if defined(XP_MACOSX)
 #include "nsCocoaFeatures.h"
 #endif
 #if defined (XP_WIN)
 #include "mozilla/WindowsVersion.h"
 #endif
 
-#include <map>
-
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
 // XXX Workaround for bug 986974 to maintain the existing broken semantics
 template<>
@@ -114,16 +112,17 @@ GetMediaManagerLog()
   return sLog;
 }
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
 using dom::File;
 using dom::MediaStreamConstraints;
 using dom::MediaTrackConstraintSet;
 using dom::MediaTrackConstraints;
+using dom::MediaStreamTrack;
 using dom::MediaStreamError;
 using dom::GetUserMediaRequest;
 using dom::Sequence;
 using dom::OwningBooleanOrMediaTrackConstraints;
 using media::Pledge;
 using media::NewRunnableFrom;
 using media::NewTaskFrom;
 
@@ -209,16 +208,164 @@ HostHasPermission(nsIURI &docURI)
     }
 
     begin = end + 1;
   } while (end < domainWhiteList.Length());
 
   return false;
 }
 
+// Generic class for running long media operations like Start off the main
+// thread, and then (because nsDOMMediaStreams aren't threadsafe),
+// ProxyReleases mStream since it's cycle collected.
+class MediaOperationTask : public Task
+{
+public:
+  // so we can send Stop without AddRef()ing from the MSG thread
+  MediaOperationTask(MediaOperation aType,
+    GetUserMediaCallbackMediaStreamListener* aListener,
+    DOMMediaStream* aStream,
+    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
+    AudioDevice* aAudioDevice,
+    VideoDevice* aVideoDevice,
+    bool aBool,
+    uint64_t aWindowID,
+    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError,
+    const dom::MediaTrackConstraints& aConstraints = dom::MediaTrackConstraints())
+    : mType(aType)
+    , mStream(aStream)
+    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
+    , mAudioDevice(aAudioDevice)
+    , mVideoDevice(aVideoDevice)
+    , mListener(aListener)
+    , mBool(aBool)
+    , mWindowID(aWindowID)
+    , mOnFailure(aError)
+    , mConstraints(aConstraints)
+  {}
+
+  ~MediaOperationTask()
+  {
+    // MediaStreams can be released on any thread.
+  }
+
+  void
+  ReturnCallbackError(nsresult rv, const char* errorLog);
+
+  void
+  Run()
+  {
+    SourceMediaStream *source = mListener->GetSourceStream();
+    // No locking between these is required as all the callbacks for the
+    // same MediaStream will occur on the same thread.
+    if (!source) // means the stream was never Activated()
+      return;
+
+    switch (mType) {
+      case MEDIA_START:
+        {
+          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
+          nsresult rv;
+
+          if (mAudioDevice) {
+            rv = mAudioDevice->GetSource()->Start(source, kAudioTrack);
+            if (NS_FAILED(rv)) {
+              ReturnCallbackError(rv, "Starting audio failed");
+              return;
+            }
+          }
+          if (mVideoDevice) {
+            rv = mVideoDevice->GetSource()->Start(source, kVideoTrack);
+            if (NS_FAILED(rv)) {
+              ReturnCallbackError(rv, "Starting video failed");
+              return;
+            }
+          }
+          // Start() queued the tracks to be added synchronously to avoid races
+          source->FinishAddTracks();
+
+          source->SetPullEnabled(true);
+          source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
+
+          MM_LOG(("started all sources"));
+          // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
+          // because mOnTracksAvailableCallback needs to be added to mStream
+          // on the main thread.
+          nsIRunnable *event =
+            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
+                                              mStream.forget(),
+                                              mOnTracksAvailableCallback.forget(),
+                                              mAudioDevice != nullptr,
+                                              mVideoDevice != nullptr,
+                                              mWindowID, mOnFailure.forget());
+          // event must always be released on mainthread due to the JS callbacks
+          // in the TracksAvailableCallback
+          NS_DispatchToMainThread(event);
+        }
+        break;
+
+      case MEDIA_STOP:
+      case MEDIA_STOP_TRACK:
+        {
+          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
+          if (mAudioDevice) {
+            mAudioDevice->GetSource()->Stop(source, kAudioTrack);
+            mAudioDevice->GetSource()->Deallocate();
+          }
+          if (mVideoDevice) {
+            mVideoDevice->GetSource()->Stop(source, kVideoTrack);
+            mVideoDevice->GetSource()->Deallocate();
+          }
+          // Do this after stopping all tracks with EndTrack()
+          if (mBool) {
+            source->Finish();
+          }
+
+          nsIRunnable *event =
+            new GetUserMediaNotificationEvent(mListener,
+                                              mType == MEDIA_STOP ?
+                                              GetUserMediaNotificationEvent::STOPPING :
+                                              GetUserMediaNotificationEvent::STOPPED_TRACK,
+                                              mAudioDevice != nullptr,
+                                              mVideoDevice != nullptr,
+                                              mWindowID);
+          // event must always be released on mainthread due to the JS callbacks
+          // in the TracksAvailableCallback
+          NS_DispatchToMainThread(event);
+        }
+        break;
+
+      case MEDIA_DIRECT_LISTENERS:
+        {
+          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
+          if (mVideoDevice) {
+            mVideoDevice->GetSource()->SetDirectListeners(mBool);
+          }
+        }
+        break;
+
+      default:
+        MOZ_ASSERT(false,"invalid MediaManager operation");
+        break;
+    }
+  }
+
+private:
+  MediaOperation mType;
+  nsRefPtr<DOMMediaStream> mStream;
+  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
+  nsRefPtr<AudioDevice> mAudioDevice; // threadsafe
+  nsRefPtr<VideoDevice> mVideoDevice; // threadsafe
+  nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
+  bool mBool;
+  uint64_t mWindowID;
+  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
+  dom::MediaTrackConstraints mConstraints;
+};
+
 /**
  * Send an error back to content.
  * Do this only on the main thread. The onSuccess callback is also passed here
  * so it can be released correctly.
  */
 template<class SuccessCallbackType>
 class ErrorCallbackRunnable : public nsRunnable
 {
@@ -465,43 +612,53 @@ nsresult VideoDevice::Allocate(const dom
   return GetSource()->Allocate(aConstraints, aPrefs, mID);
 }
 
 nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
                                const MediaEnginePrefs &aPrefs) {
   return GetSource()->Allocate(aConstraints, aPrefs, mID);
 }
 
+nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
+                              const MediaEnginePrefs &aPrefs) {
+  return GetSource()->Restart(aConstraints, aPrefs, mID);
+}
+
+nsresult AudioDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
+                              const MediaEnginePrefs &aPrefs) {
+  return GetSource()->Restart(aConstraints, aPrefs, mID);
+}
+
 /**
  * A subclass that we only use to stash internal pointers to MediaStreamGraph objects
  * that need to be cleaned up.
  */
 class nsDOMUserMediaStream : public DOMLocalMediaStream
 {
 public:
   static already_AddRefed<nsDOMUserMediaStream>
   CreateTrackUnionStream(nsIDOMWindow* aWindow,
                          GetUserMediaCallbackMediaStreamListener* aListener,
-                         MediaEngineSource* aAudioSource,
-                         MediaEngineSource* aVideoSource,
+                         AudioDevice* aAudioDevice,
+                         VideoDevice* aVideoDevice,
                          MediaStreamGraph* aMSG)
   {
     nsRefPtr<nsDOMUserMediaStream> stream = new nsDOMUserMediaStream(aListener,
-                                                                     aAudioSource,
-                                                                     aVideoSource);
+                                                                     aAudioDevice,
+                                                                     aVideoDevice);
     stream->InitTrackUnionStream(aWindow, aMSG);
     return stream.forget();
   }
 
   nsDOMUserMediaStream(GetUserMediaCallbackMediaStreamListener* aListener,
-                       MediaEngineSource *aAudioSource,
-                       MediaEngineSource *aVideoSource) :
+                       AudioDevice *aAudioDevice,
+                       VideoDevice *aVideoDevice) :
     mListener(aListener),
-    mAudioSource(aAudioSource),
-    mVideoSource(aVideoSource),
+    mAudioDevice(aAudioDevice),
+    mVideoDevice(aVideoDevice),
     mEchoOn(true),
     mAgcOn(false),
     mNoiseOn(true),
 #ifdef MOZ_WEBRTC
     mEcho(webrtc::kEcDefault),
     mAgc(webrtc::kAgcDefault),
     mNoise(webrtc::kNsDefault),
 #else
@@ -538,23 +695,70 @@ public:
   virtual void StopTrack(TrackID aTrackID) override
   {
     if (mSourceStream) {
       mSourceStream->EndTrack(aTrackID);
       // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's
       // risky to do late in a release since that will affect all track ends, and not
       // just StopTrack()s.
       if (GetDOMTrackFor(aTrackID)) {
-        mListener->StopTrack(aTrackID, !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
+        mListener->StopTrack(aTrackID,
+                             !!GetDOMTrackFor(aTrackID)->AsAudioStreamTrack());
       } else {
-        LOG(("StopTrack(%d) on non-existant track", aTrackID));
+        LOG(("StopTrack(%d) on non-existent track", aTrackID));
       }
     }
   }
 
+  virtual already_AddRefed<Promise>
+  ApplyConstraintsToTrack(TrackID aTrackID,
+                          const MediaTrackConstraints& aConstraints,
+                          ErrorResult &aRv) override
+  {
+    nsPIDOMWindow* window = static_cast<nsPIDOMWindow*>(mWindow.get());
+    nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
+    nsRefPtr<Promise> promise = Promise::Create(go, aRv);
+
+    if (sInShutdown) {
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("AbortError"),
+          NS_LITERAL_STRING("In shutdown"));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+    if (!mSourceStream) {
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("InternalError"),
+          NS_LITERAL_STRING("No stream."));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+
+    nsRefPtr<dom::MediaStreamTrack> track = GetDOMTrackFor(aTrackID);
+    if (!track) {
+      LOG(("ApplyConstraintsToTrack(%d) on non-existent track", aTrackID));
+      nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
+          NS_LITERAL_STRING("InternalError"),
+          NS_LITERAL_STRING("No track."));
+      promise->MaybeReject(error);
+      return promise.forget();
+    }
+
+    typedef media::Pledge<bool, MediaStreamError*> PledgeVoid;
+
+    nsRefPtr<PledgeVoid> p = mListener->ApplyConstraintsToTrack(window,
+        aTrackID, !!track->AsAudioStreamTrack(), aConstraints);
+    p->Then([promise](bool& aDummy) mutable {
+      promise->MaybeResolve(false);
+    }, [promise](MediaStreamError*& reason) mutable {
+      promise->MaybeReject(reason);
+    });
+    return promise.forget();
+  }
+
 #if 0
   virtual void NotifyMediaStreamTrackEnded(dom::MediaStreamTrack* aTrack)
   {
     TrackID trackID = aTrack->GetTrackID();
     // We override this so we can also tell the backend to stop capturing if the track ends
     LOG(("track %d ending, type = %s",
          trackID, aTrack->AsAudioStreamTrack() ? "audio" : "video"));
     MOZ_ASSERT(aTrack->AsVideoStreamTrack() || aTrack->AsAudioStreamTrack());
@@ -593,52 +797,52 @@ public:
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) override
   {
     if (mSourceStream) {
       mSourceStream->RemoveDirectListener(aListener);
     }
   }
 
   // let us intervene for direct listeners when someone does track.enabled = false
-  virtual void SetTrackEnabled(TrackID aID, bool aEnabled) override
+  virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled) override
   {
     // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
     // we can handle the disabling at the SourceMediaStream
 
-    // We need to find the input track ID for output ID aID, so we let the TrackUnion
+    // We need to find the input track ID for output ID aTrackID, so we let the TrackUnion
     // forward the request to the source and translate the ID
-    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
+    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aTrackID, aEnabled);
   }
 
   virtual DOMLocalMediaStream* AsDOMLocalMediaStream() override
   {
     return this;
   }
 
   virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) override
   {
     // MediaEngine supports only one video and on video track now and TrackID is
     // fixed in MediaEngine.
     if (aTrackID == kVideoTrack) {
-      return mVideoSource;
+      return mVideoDevice ? mVideoDevice->GetSource() : nullptr;
     }
     else if (aTrackID == kAudioTrack) {
-      return mAudioSource;
+      return mAudioDevice ? mAudioDevice->GetSource() : nullptr;
     }
 
     return nullptr;
   }
 
   // The actual MediaStream is a TrackUnionStream. But these resources need to be
   // explicitly destroyed too.
   nsRefPtr<SourceMediaStream> mSourceStream;
   nsRefPtr<MediaInputPort> mPort;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
-  nsRefPtr<MediaEngineSource> mAudioSource; // so we can turn on AEC
-  nsRefPtr<MediaEngineSource> mVideoSource;
+  nsRefPtr<AudioDevice> mAudioDevice; // so we can turn on AEC
+  nsRefPtr<VideoDevice> mVideoDevice;
   bool mEchoOn;
   bool mAgcOn;
   bool mNoiseOn;
   uint32_t mEcho;
   uint32_t mAgc;
   uint32_t mNoise;
   uint32_t mPlayoutDelay;
 };
@@ -681,21 +885,21 @@ class GetUserMediaStreamRunnable : publi
 {
 public:
   GetUserMediaStreamRunnable(
     nsCOMPtr<nsIDOMGetUserMediaSuccessCallback>& aOnSuccess,
     nsCOMPtr<nsIDOMGetUserMediaErrorCallback>& aOnFailure,
     uint64_t aWindowID,
     GetUserMediaCallbackMediaStreamListener* aListener,
     const nsCString& aOrigin,
-    MediaEngineSource* aAudioSource,
-    MediaEngineSource* aVideoSource,
+    AudioDevice* aAudioDevice,
+    VideoDevice* aVideoDevice,
     PeerIdentity* aPeerIdentity)
-    : mAudioSource(aAudioSource)
-    , mVideoSource(aVideoSource)
+    : mAudioDevice(aAudioDevice)
+    , mVideoDevice(aVideoDevice)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mOrigin(aOrigin)
     , mPeerIdentity(aPeerIdentity)
     , mManager(MediaManager::GetInstance())
   {
     mOnSuccess.swap(aOnSuccess);
     mOnFailure.swap(aOnFailure);
@@ -782,44 +986,44 @@ public:
         branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on);
         branch->GetIntPref("media.getusermedia.noise", &noise);
         branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay);
       }
     }
 #endif
 
     MediaStreamGraph::GraphDriverType graphDriverType =
-      mAudioSource ? MediaStreamGraph::AUDIO_THREAD_DRIVER
+      mAudioDevice ? MediaStreamGraph::AUDIO_THREAD_DRIVER
                    : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
     MediaStreamGraph* msg =
       MediaStreamGraph::GetInstance(graphDriverType,
                                     dom::AudioChannel::Normal);
 
     nsRefPtr<SourceMediaStream> stream = msg->CreateSourceStream(nullptr);
 
     nsRefPtr<DOMLocalMediaStream> domStream;
     // AudioCapture is a special case, here, in the sense that we're not really
     // using the audio source and the SourceMediaStream, which acts as
     // placeholders. We re-route a number of stream internaly in the MSG and mix
     // them down instead.
-    if (mAudioSource &&
-        mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
+    if (mAudioDevice &&
+        mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
       domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window, msg);
       // It should be possible to pipe the capture stream to anything. CORS is
       // not a problem here, we got explicit user content.
       domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal());
       msg->RegisterCaptureStreamForWindow(
             mWindowID, domStream->GetStream()->AsProcessedStream());
       window->SetAudioCapture(true);
     } else {
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking
       nsRefPtr<nsDOMUserMediaStream> trackunion =
         nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
-                                                     mAudioSource, mVideoSource,
+                                                     mAudioDevice, mVideoDevice,
                                                      msg);
       trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
       nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
         AllocateInputPort(stream);
       trackunion->mSourceStream = stream;
       trackunion->mPort = port.forget();
       // Log the relationship between SourceMediaStream and TrackUnion stream
       // Make sure logger starts before capture
@@ -854,52 +1058,52 @@ public:
       }
       return NS_OK;
     }
 
     // The listener was added at the beginning in an inactive state.
     // Activate our listener. We'll call Start() on the source when get a callback
     // that the MediaStream has started consuming. The listener is freed
     // when the page is invalidated (on navigation or close).
-    mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
+    mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice);
 
     // Note: includes JS callbacks; must be released on MainThread
     TracksAvailableCallback* tracksAvailableCallback =
       new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream);
 
     mListener->AudioConfig(aec_on, (uint32_t) aec,
                            agc_on, (uint32_t) agc,
                            noise_on, (uint32_t) noise,
                            playout_delay);
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of trackunion to the MediaOperationTask
     // to ensure it's kept alive until the MediaOperationTask runs (at least).
-    MediaManager::PostTask(
-      FROM_HERE, new MediaOperationTask(MEDIA_START, mListener, domStream,
-                                        tracksAvailableCallback, mAudioSource,
-                                        mVideoSource, false, mWindowID,
-                                        mOnFailure.forget()));
+    MediaManager::PostTask(FROM_HERE,
+        new MediaOperationTask(MEDIA_START, mListener, domStream,
+                               tracksAvailableCallback,
+                               mAudioDevice, mVideoDevice,
+                               false, mWindowID, mOnFailure.forget()));
     // We won't need mOnFailure now.
     mOnFailure = nullptr;
 
     if (!MediaManager::IsPrivateBrowsing(window)) {
       // Call GetOriginKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       nsRefPtr<Pledge<nsCString>> p = media::GetOriginKey(mOrigin, false, true);
     }
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> mOnSuccess;
   nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-  nsRefPtr<MediaEngineSource> mAudioSource;
-  nsRefPtr<MediaEngineSource> mVideoSource;
+  nsRefPtr<AudioDevice> mAudioDevice;
+  nsRefPtr<VideoDevice> mVideoDevice;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsCString mOrigin;
   nsAutoPtr<PeerIdentity> mPeerIdentity;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 static bool
@@ -944,118 +1148,56 @@ GetSources(MediaEngine *engine, dom::Med
     }
   } else {
     for (auto& source : sources) {
       aResult.AppendElement(new DeviceType(source));
     }
   }
 }
 
-// Apply constrains to a supplied list of sources (removes items from the list)
-
-template<class DeviceType>
-static void
-ApplyConstraints(const MediaTrackConstraints &aConstraints,
-                 nsTArray<nsRefPtr<DeviceType>>& aSources)
-{
-  auto& c = aConstraints;
-
-  // First apply top-level constraints.
-
-  // Stack constraintSets that pass, starting with the required one, because the
-  // whole stack must be re-satisfied each time a capability-set is ruled out
-  // (this avoids storing state or pushing algorithm into the lower-level code).
-  nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
-  aggregateConstraints.AppendElement(&c);
-
-  std::multimap<uint32_t, nsRefPtr<DeviceType>> ordered;
-
-  for (uint32_t i = 0; i < aSources.Length();) {
-    uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
-    if (distance == UINT32_MAX) {
-      aSources.RemoveElementAt(i);
-    } else {
-      ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
-                                                               aSources[i]));
-      ++i;
-    }
-  }
-  // Order devices by shortest distance
-  for (auto& ordinal : ordered) {
-    aSources.RemoveElement(ordinal.second);
-    aSources.AppendElement(ordinal.second);
-  }
-
-  // Then apply advanced constraints.
-
-  if (c.mAdvanced.WasPassed()) {
-    auto &array = c.mAdvanced.Value();
-
-    for (int i = 0; i < int(array.Length()); i++) {
-      aggregateConstraints.AppendElement(&array[i]);
-      nsTArray<nsRefPtr<DeviceType>> rejects;
-      for (uint32_t j = 0; j < aSources.Length();) {
-        if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
-          rejects.AppendElement(aSources[j]);
-          aSources.RemoveElementAt(j);
-        } else {
-          ++j;
-        }
-      }
-      if (!aSources.Length()) {
-        aSources.AppendElements(Move(rejects));
-        aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
-      }
-    }
-  }
-}
-
-static bool
-ApplyConstraints(MediaStreamConstraints &aConstraints,
-                 nsTArray<nsRefPtr<MediaDevice>>& aSources)
+static const char*
+SelectSettings(MediaStreamConstraints &aConstraints,
+               nsTArray<nsRefPtr<MediaDevice>>& aSources)
 {
   // Since the advanced part of the constraints algorithm needs to know when
   // a candidate set is overconstrained (zero members), we must split up the
   // list into videos and audios, and put it back together again at the end.
 
-  bool overconstrained = false;
   nsTArray<nsRefPtr<VideoDevice>> videos;
   nsTArray<nsRefPtr<AudioDevice>> audios;
 
   for (auto& source : aSources) {
     if (source->mIsVideo) {
       nsRefPtr<VideoDevice> video = static_cast<VideoDevice*>(source.get());
       videos.AppendElement(video);
     } else {
       nsRefPtr<AudioDevice> audio = static_cast<AudioDevice*>(source.get());
       audios.AppendElement(audio);
     }
   }
   aSources.Clear();
   MOZ_ASSERT(!aSources.Length());
 
+  const char* badConstraint = nullptr;
+
   if (IsOn(aConstraints.mVideo)) {
-    ApplyConstraints(GetInvariant(aConstraints.mVideo), videos);
-    if (!videos.Length()) {
-      overconstrained = true;
-    }
+    badConstraint = MediaConstraintsHelper::SelectSettings(
+        GetInvariant(aConstraints.mVideo), videos);
     for (auto& video : videos) {
       aSources.AppendElement(video);
     }
   }
-  if (IsOn(aConstraints.mAudio)) {
-    ApplyConstraints(GetInvariant(aConstraints.mAudio), audios);
-    if (!audios.Length()) {
-      overconstrained = true;
-    }
+  if (audios.Length() && IsOn(aConstraints.mAudio)) {
+    badConstraint = MediaConstraintsHelper::SelectSettings(
+        GetInvariant(aConstraints.mAudio), audios);
     for (auto& audio : audios) {
       aSources.AppendElement(audio);
     }
   }
-  return !overconstrained;
+  return badConstraint;
 }
 
 /**
  * Runs on a seperate thread and is responsible for enumerating devices.
  * Depending on whether a picture or stream was asked for, either
  * ProcessGetUserMedia is called, and the results are sent back to the DOM.
  *
  * Do not run this on the main thread. The success and error callbacks *MUST*
@@ -1138,23 +1280,20 @@ public:
         return;
       }
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
 
-    NS_DispatchToMainThread(do_AddRef(new GetUserMediaStreamRunnable(
-      mOnSuccess, mOnFailure, mWindowID, mListener, mOrigin,
-      (mAudioDevice? mAudioDevice->GetSource() : nullptr),
-      (mVideoDevice? mVideoDevice->GetSource() : nullptr),
-      peerIdentity
-    )));
-
+    NS_DispatchToMainThread(do_AddRef(
+        new GetUserMediaStreamRunnable(mOnSuccess, mOnFailure, mWindowID,
+                                       mListener, mOrigin, mAudioDevice,
+                                       mVideoDevice, peerIdentity)));
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
   }
 
   nsresult
   Denied(const nsAString& aName,
          const nsAString& aMessage = EmptyString())
   {
@@ -1692,17 +1831,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
 
   MediaSourceEnum videoType = dom::MediaSourceEnum::Camera;
   MediaSourceEnum audioType = dom::MediaSourceEnum::Microphone;
 
   if (c.mVideo.IsMediaTrackConstraints()) {
     auto& vc = c.mVideo.GetAsMediaTrackConstraints();
     videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
                              vc.mMediaSource,
-                             videoType);
+                             dom::MediaSourceEnum::Other);
     switch (videoType) {
       case dom::MediaSourceEnum::Camera:
         break;
 
       case dom::MediaSourceEnum::Browser:
       case dom::MediaSourceEnum::Screen:
       case dom::MediaSourceEnum::Application:
       case dom::MediaSourceEnum::Window:
@@ -1735,17 +1874,20 @@ MediaManager::GetUserMedia(nsPIDOMWindow
           return NS_OK;
         }
         break;
 
       case dom::MediaSourceEnum::Microphone:
       case dom::MediaSourceEnum::Other:
       default: {
         nsRefPtr<MediaStreamError> error =
-            new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError"));
+            new MediaStreamError(aWindow,
+                                 NS_LITERAL_STRING("OverconstrainedError"),
+                                 NS_LITERAL_STRING(""),
+                                 NS_LITERAL_STRING("mediaSource"));
         onFailure->OnError(error);
         return NS_OK;
       }
     }
 
     if (vc.mAdvanced.WasPassed() && videoType != dom::MediaSourceEnum::Camera) {
       // iterate through advanced, forcing all unset mediaSources to match "root"
       const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
@@ -1780,17 +1922,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
        privileged = false;
     }
   }
 
   if (c.mAudio.IsMediaTrackConstraints()) {
     auto& ac = c.mAudio.GetAsMediaTrackConstraints();
     audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
                              ac.mMediaSource,
-                             audioType);
+                             dom::MediaSourceEnum::Other);
     // Work around WebIDL default since spec uses same dictionary w/audio & video.
     if (audioType == dom::MediaSourceEnum::Camera) {
       audioType = dom::MediaSourceEnum::Microphone;
       ac.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
                                               audioType));
     }
 
     switch (audioType) {
@@ -1807,17 +1949,20 @@ MediaManager::GetUserMedia(nsPIDOMWindow
           onFailure->OnError(error);
           return NS_OK;
         }
         break;
 
       case dom::MediaSourceEnum::Other:
       default: {
         nsRefPtr<MediaStreamError> error =
-            new MediaStreamError(aWindow, NS_LITERAL_STRING("NotFoundError"));
+            new MediaStreamError(aWindow,
+                                 NS_LITERAL_STRING("OverconstrainedError"),
+                                 NS_LITERAL_STRING(""),
+                                 NS_LITERAL_STRING("mediaSource"));
         onFailure->OnError(error);
         return NS_OK;
       }
     }
     if (ac.mAdvanced.WasPassed()) {
       // iterate through advanced, forcing all unset mediaSources to match "root"
       const char *unset = EnumToASCII(dom::MediaSourceEnumValues::strings,
                                       dom::MediaSourceEnum::Camera);
@@ -1900,18 +2045,29 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
     nsRefPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
         (nsGlobalWindow::GetInnerWindowWithId(windowID));
     if (!mgr || !window) {
       return;
     }
 
     // Apply any constraints. This modifies the list.
-
-    if (!ApplyConstraints(c, *devices)) {
+    const char* badConstraint = SelectSettings(c, *devices);
+    if (badConstraint) {
+      nsString constraint;
+      constraint.AssignASCII(badConstraint);
+      nsRefPtr<MediaStreamError> error =
+          new MediaStreamError(window,
+                               NS_LITERAL_STRING("OverconstrainedError"),
+                               NS_LITERAL_STRING(""),
+                               constraint);
+      onFailure->OnError(error);
+      return;
+    }
+    if (!devices->Length()) {
       nsRefPtr<MediaStreamError> error =
           new MediaStreamError(window, NS_LITERAL_STRING("NotFoundError"));
       onFailure->OnError(error);
       return;
     }
 
     nsCOMPtr<nsISupportsArray> devicesCopy; // before we give up devices below
     if (!askPermission) {
@@ -1952,18 +2108,18 @@ MediaManager::GetUserMedia(nsPIDOMWindow
       nsRefPtr<GetUserMediaRequest> req =
           new GetUserMediaRequest(window, callID, c, isHTTPS);
       obs->NotifyObservers(req, "getUserMedia:request", nullptr);
     }
 
 #ifdef MOZ_WEBRTC
     EnableWebRtcLog();
 #endif
-  }, [onFailure](MediaStreamError& reason) mutable {
-    onFailure->OnError(&reason);
+  }, [onFailure](MediaStreamError*& reason) mutable {
+    onFailure->OnError(reason);
   });
   return NS_OK;
 }
 
 /* static */ void
 MediaManager::AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey)
 {
   if (!aOriginKey.IsEmpty()) {
@@ -2133,20 +2289,20 @@ MediaManager::EnumerateDevices(nsPIDOMWi
                                                      dom::MediaSourceEnum::Microphone,
                                                      fake);
   p->Then([onSuccess, windowId, listener](SourceSet*& aDevices) mutable {
     ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
     nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
     mgr->RemoveFromWindowList(windowId, listener);
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
-  }, [onFailure, windowId, listener](MediaStreamError& reason) mutable {
+  }, [onFailure, windowId, listener](MediaStreamError*& reason) mutable {
     nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
     mgr->RemoveFromWindowList(windowId, listener);
-    onFailure->OnError(&reason);
+    onFailure->OnError(reason);
   });
   return NS_OK;
 }
 
 /*
  * GetUserMediaDevices - called by the UI-part of getUserMedia from chrome JS.
  */
 
@@ -2848,20 +3004,20 @@ MediaManager::IsActivelyCapturingOrHasAP
 
 void
 GetUserMediaCallbackMediaStreamListener::AudioConfig(bool aEchoOn,
               uint32_t aEcho,
               bool aAgcOn, uint32_t aAGC,
               bool aNoiseOn, uint32_t aNoise,
               int32_t aPlayoutDelay)
 {
-  if (mAudioSource) {
+  if (mAudioDevice) {
 #ifdef MOZ_WEBRTC
     MediaManager::PostTask(FROM_HERE,
-      NewRunnableMethod(mAudioSource.get(), &MediaEngineSource::Config,
+      NewRunnableMethod(mAudioDevice->GetSource(), &MediaEngineSource::Config,
                         aEchoOn, aEcho, aAgcOn, aAGC, aNoiseOn,
                         aNoise, aPlayoutDelay));
 #endif
   }
 }
 
 // Can be invoked from EITHER MainThread or MSG thread
 void
@@ -2869,68 +3025,162 @@ GetUserMediaCallbackMediaStreamListener:
 {
   // We can't take a chance on blocking here, so proxy this to another
   // thread.
   // Pass a ref to us (which is threadsafe) so it can query us for the
   // source stream info.
   MediaManager::PostTask(FROM_HERE,
     new MediaOperationTask(MEDIA_STOP,
                            this, nullptr, nullptr,
-                           mAudioSource, mVideoSource,
+                           mAudioDevice, mVideoDevice,
                            mFinished, mWindowID, nullptr));
 }
 
 // Doesn't kill audio
 // XXX refactor to combine with Invalidate()?
 void
 GetUserMediaCallbackMediaStreamListener::StopSharing()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  if (mVideoSource && !mStopped &&
-      (mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen ||
-       mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application ||
-       mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window)) {
+  if (mVideoDevice && !mStopped &&
+      (mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen ||
+       mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application ||
+       mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window)) {
     // Stop the whole stream if there's no audio; just the video track if we have both
     MediaManager::PostTask(FROM_HERE,
-      new MediaOperationTask(mAudioSource ? MEDIA_STOP_TRACK : MEDIA_STOP,
+      new MediaOperationTask(mAudioDevice ? MEDIA_STOP_TRACK : MEDIA_STOP,
                              this, nullptr, nullptr,
-                             nullptr, mVideoSource,
+                             nullptr, mVideoDevice,
                              mFinished, mWindowID, nullptr));
-  } else if (mAudioSource &&
-             mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
+  } else if (mAudioDevice &&
+             mAudioDevice->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
     nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
     MOZ_ASSERT(window);
     window->SetAudioCapture(false);
     MediaStreamGraph* graph =
       MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
                                     dom::AudioChannel::Normal);
     graph->UnregisterCaptureStreamForWindow(mWindowID);
     mStream->Destroy();
   }
 }
 
+// ApplyConstraints for track
+
+already_AddRefed<GetUserMediaCallbackMediaStreamListener::PledgeVoid>
+GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
+    nsPIDOMWindow* aWindow,
+    TrackID aTrackID,
+    bool aIsAudio,
+    const MediaTrackConstraints& aConstraints)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsRefPtr<PledgeVoid> p = new PledgeVoid();
+
+  if (!(((aIsAudio && mAudioDevice) ||
+         (!aIsAudio && mVideoDevice)) && !mStopped))
+  {
+    LOG(("gUM track %d applyConstraints, but we don't have type %s",
+         aTrackID, aIsAudio ? "audio" : "video"));
+    p->Resolve(false);
+    return p.forget();
+  }
+
+  // XXX to support multiple tracks of a type in a stream, this should key off
+  // the TrackID and not just the type
+  nsRefPtr<AudioDevice> audioDevice = aIsAudio ? mAudioDevice.get() : nullptr;
+  nsRefPtr<VideoDevice> videoDevice = !aIsAudio ? mVideoDevice.get() : nullptr;
+
+  nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+  uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
+  uint64_t windowId = aWindow->WindowID();
+
+  MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, windowId,
+                                                 audioDevice, videoDevice,
+                                                 aConstraints]() mutable {
+    MOZ_ASSERT(MediaManager::IsInMediaThread());
+    nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
+    const char* badConstraint = nullptr;
+    nsresult rv = NS_OK;
+
+    if (audioDevice) {
+      rv = audioDevice->Restart(aConstraints, mgr->mPrefs);
+      if (rv == NS_ERROR_NOT_AVAILABLE) {
+        nsTArray<nsRefPtr<AudioDevice>> audios;
+        audios.AppendElement(audioDevice);
+        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
+                                                               audios);
+      }
+    } else {
+      rv = videoDevice->Restart(aConstraints, mgr->mPrefs);
+      if (rv == NS_ERROR_NOT_AVAILABLE) {
+        nsTArray<nsRefPtr<VideoDevice>> videos;
+        videos.AppendElement(videoDevice);
+        badConstraint = MediaConstraintsHelper::SelectSettings(aConstraints,
+                                                               videos);
+      }
+    }
+    NS_DispatchToMainThread(do_AddRef(NewRunnableFrom([id, windowId, rv,
+                                                       badConstraint]() mutable {
+      MOZ_ASSERT(NS_IsMainThread());
+      nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
+      if (!mgr) {
+        return NS_OK;
+      }
+      nsRefPtr<PledgeVoid> p = mgr->mOutstandingVoidPledges.Remove(id);
+      if (p) {
+        if (NS_SUCCEEDED(rv)) {
+          p->Resolve(false);
+        } else {
+          nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
+              (nsGlobalWindow::GetInnerWindowWithId(windowId));
+          if (window) {
+            if (rv == NS_ERROR_NOT_AVAILABLE) {
+              nsString constraint;
+              constraint.AssignASCII(badConstraint);
+              nsRefPtr<MediaStreamError> error =
+                  new MediaStreamError(window,
+                                       NS_LITERAL_STRING("OverconstrainedError"),
+                                       NS_LITERAL_STRING(""),
+                                       constraint);
+              p->Reject(error);
+            } else {
+              nsRefPtr<MediaStreamError> error =
+                  new MediaStreamError(window,
+                                       NS_LITERAL_STRING("InternalError"));
+              p->Reject(error);
+            }
+          }
+        }
+      }
+      return NS_OK;
+    })));
+  }));
+  return p.forget();
+}
+
 // Stop backend for track
 
 void
-GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio)
+GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aTrackID, bool aIsAudio)
 {
-  if (((aIsAudio && mAudioSource) ||
-       (!aIsAudio && mVideoSource)) && !mStopped)
+  if (((aIsAudio && mAudioDevice) ||
+       (!aIsAudio && mVideoDevice)) && !mStopped)
   {
     // XXX to support multiple tracks of a type in a stream, this should key off
     // the TrackID and not just the type
     MediaManager::PostTask(FROM_HERE,
       new MediaOperationTask(MEDIA_STOP_TRACK,
                              this, nullptr, nullptr,
-                             aIsAudio  ? mAudioSource.get() : nullptr,
-                             !aIsAudio ? mVideoSource.get() : nullptr,
+                             aIsAudio  ? mAudioDevice.get() : nullptr,
+                             !aIsAudio ? mVideoDevice.get() : nullptr,
                              mFinished, mWindowID, nullptr));
   } else {
     LOG(("gUM track %d ended, but we don't have type %s",
-         aID, aIsAudio ? "audio" : "video"));
+         aTrackID, aIsAudio ? "audio" : "video"));
   }
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
 {
   mFinished = true;
@@ -2941,17 +3191,17 @@ GetUserMediaCallbackMediaStreamListener:
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyDirectListeners(MediaStreamGraph* aGraph,
                                                                bool aHasListeners)
 {
   MediaManager::PostTask(FROM_HERE,
     new MediaOperationTask(MEDIA_DIRECT_LISTENERS,
                            this, nullptr, nullptr,
-                           mAudioSource, mVideoSource,
+                           mAudioDevice, mVideoDevice,
                            aHasListeners, mWindowID, nullptr));
 }
 
 // Called from the MediaStreamGraph thread
 // this can be in response to our own RemoveListener() (via ::Remove()), or
 // because the DOM GC'd the DOMLocalMediaStream/etc we're attached to.
 void
 GetUserMediaCallbackMediaStreamListener::NotifyRemoved(MediaStreamGraph* aGraph)
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -42,22 +42,82 @@
 
 #ifdef MOZ_WIDGET_GONK
 #include "DOMCameraManager.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 struct MediaStreamConstraints;
+struct MediaTrackConstraints;
 struct MediaTrackConstraintSet;
 } // namespace dom
 
 extern PRLogModuleInfo* GetMediaManagerLog();
 #define MM_LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 
+class MediaDevice : public nsIMediaDevice
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIMEDIADEVICE
+
+  void SetId(const nsAString& aID);
+  virtual uint32_t GetBestFitnessDistance(
+      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
+protected:
+  virtual ~MediaDevice() {}
+  explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
+  static uint32_t FitnessDistance(nsString aN,
+    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
+private:
+  static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings,
+                             nsString aN);
+  static uint32_t FitnessDistance(nsString aN,
+      const dom::ConstrainDOMStringParameters& aParams);
+protected:
+  nsString mName;
+  nsString mID;
+  dom::MediaSourceEnum mMediaSource;
+  nsRefPtr<MediaEngineSource> mSource;
+public:
+  dom::MediaSourceEnum GetMediaSource() {
+    return mMediaSource;
+  }
+  bool mIsVideo;
+};
+
+class VideoDevice : public MediaDevice
+{
+public:
+  typedef MediaEngineVideoSource Source;
+
+  explicit VideoDevice(Source* aSource);
+  NS_IMETHOD GetType(nsAString& aType);
+  Source* GetSource();
+  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                    const MediaEnginePrefs &aPrefs);
+  nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
+                   const MediaEnginePrefs &aPrefs);
+};
+
+class AudioDevice : public MediaDevice
+{
+public:
+  typedef MediaEngineAudioSource Source;
+
+  explicit AudioDevice(Source* aSource);
+  NS_IMETHOD GetType(nsAString& aType);
+  Source* GetSource();
+  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
+                    const MediaEnginePrefs &aPrefs);
+  nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
+                   const MediaEnginePrefs &aPrefs);
+};
+
 /**
  * This class is an implementation of MediaStreamListener. This is used
  * to Start() and Stop() the underlying MediaEngineSource when MediaStreams
  * are assigned and deassigned in content.
  */
 class GetUserMediaCallbackMediaStreamListener : public MediaStreamListener
 {
 public:
@@ -74,23 +134,23 @@ public:
   ~GetUserMediaCallbackMediaStreamListener()
   {
     unused << mMediaThread;
     // It's OK to release mStream on any thread; they have thread-safe
     // refcounts.
   }
 
   void Activate(already_AddRefed<SourceMediaStream> aStream,
-    MediaEngineSource* aAudioSource,
-    MediaEngineSource* aVideoSource)
+                AudioDevice* aAudioDevice,
+                VideoDevice* aVideoDevice)
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
     mStream = aStream;
-    mAudioSource = aAudioSource;
-    mVideoSource = aVideoSource;
+    mAudioDevice = aAudioDevice;
+    mVideoDevice = aVideoDevice;
 
     mStream->AddListener(this);
   }
 
   MediaStream *Stream() // Can be used to test if Activate was called
   {
     return mStream;
   }
@@ -102,56 +162,67 @@ public:
     }
     return mStream->AsSourceStream();
   }
 
   void StopSharing();
 
   void StopTrack(TrackID aID, bool aIsAudio);
 
-  // mVideo/AudioSource are set by Activate(), so we assume they're capturing
+  typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
+
+  already_AddRefed<PledgeVoid>
+  ApplyConstraintsToTrack(nsPIDOMWindow* aWindow,
+                          TrackID aID, bool aIsAudio,
+                          const dom::MediaTrackConstraints& aConstraints);
+
+  // mVideo/AudioDevice are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mVideoSource && !mStopped &&
-           mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Camera &&
-           (!mVideoSource->IsFake() ||
+    return mVideoDevice && !mStopped &&
+           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Camera &&
+           (!mVideoDevice->GetSource()->IsFake() ||
             Preferences::GetBool("media.navigator.permission.fake"));
   }
   bool CapturingAudio()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mAudioSource && !mStopped &&
-           (!mAudioSource->IsFake() ||
+    return mAudioDevice && !mStopped &&
+           (!mAudioDevice->GetSource()->IsFake() ||
             Preferences::GetBool("media.navigator.permission.fake"));
   }
   bool CapturingScreen()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
-           mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen;
+    return mVideoDevice && !mStopped &&
+           !mVideoDevice->GetSource()->IsAvailable() &&
+           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Screen;
   }
   bool CapturingWindow()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
-           mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window;
+    return mVideoDevice && !mStopped &&
+           !mVideoDevice->GetSource()->IsAvailable() &&
+           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Window;
   }
   bool CapturingApplication()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mVideoSource && !mStopped && !mVideoSource->IsAvailable() &&
-           mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application;
+    return mVideoDevice && !mStopped &&
+           !mVideoDevice->GetSource()->IsAvailable() &&
+           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Application;
   }
   bool CapturingBrowser()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-    return mVideoSource && !mStopped && mVideoSource->IsAvailable() &&
-           mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Browser;
+    return mVideoDevice && !mStopped &&
+           mVideoDevice->GetSource()->IsAvailable() &&
+           mVideoDevice->GetMediaSource() == dom::MediaSourceEnum::Browser;
   }
 
   void SetStopped()
   {
     mStopped = true;
   }
 
   // implement in .cpp to avoid circular dependency with MediaOperationTask
@@ -182,21 +253,23 @@ public:
   }
 
   // Proxy NotifyPull() to sources
   virtual void
   NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
   {
     // Currently audio sources ignore NotifyPull, but they could
     // watch it especially for fake audio.
-    if (mAudioSource) {
-      mAudioSource->NotifyPull(aGraph, mStream, kAudioTrack, aDesiredTime);
+    if (mAudioDevice) {
+      mAudioDevice->GetSource()->NotifyPull(aGraph, mStream, kAudioTrack,
+                                            aDesiredTime);
     }
-    if (mVideoSource) {
-      mVideoSource->NotifyPull(aGraph, mStream, kVideoTrack, aDesiredTime);
+    if (mVideoDevice) {
+      mVideoDevice->GetSource()->NotifyPull(aGraph, mStream, kVideoTrack,
+                                            aDesiredTime);
     }
   }
 
   virtual void
   NotifyEvent(MediaStreamGraph* aGraph,
               MediaStreamListener::MediaStreamGraphEvent aEvent) override
   {
     switch (aEvent) {
@@ -232,33 +305,33 @@ private:
   uint64_t mWindowID;
 
   bool mStopped; // MainThread only
 
   // Set at Activate on MainThread
 
   // Accessed from MediaStreamGraph thread, MediaManager thread, and MainThread
   // No locking needed as they're only addrefed except on the MediaManager thread
-  nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe refcnt
-  nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe refcnt
+  nsRefPtr<AudioDevice> mAudioDevice; // threadsafe refcnt
+  nsRefPtr<VideoDevice> mVideoDevice; // threadsafe refcnt
   nsRefPtr<SourceMediaStream> mStream; // threadsafe refcnt
   bool mFinished;
 
   // Accessed from MainThread and MSG thread
   Mutex mLock; // protects mRemoved access from MainThread
   bool mRemoved;
 };
 
 class GetUserMediaNotificationEvent: public nsRunnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING,
-      STOPPED_TRACK
+      STOPPED_TRACK,
     };
     GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
                                   GetUserMediaStatus aStatus,
                                   bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
     : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
     , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
@@ -286,17 +359,17 @@ class GetUserMediaNotificationEvent: pub
     uint64_t mWindowID;
     nsRefPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
 };
 
 typedef enum {
   MEDIA_START,
   MEDIA_STOP,
   MEDIA_STOP_TRACK,
-  MEDIA_DIRECT_LISTENERS
+  MEDIA_DIRECT_LISTENERS,
 } MediaOperation;
 
 class MediaManager;
 class GetUserMediaTask;
 
 class ReleaseMediaOperationResource : public nsRunnable
 {
 public:
@@ -305,225 +378,29 @@ public:
     mStream(aStream),
     mOnTracksAvailableCallback(aOnTracksAvailableCallback) {}
   NS_IMETHOD Run() override {return NS_OK;}
 private:
   nsRefPtr<DOMMediaStream> mStream;
   nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
 };
 
-// Generic class for running long media operations like Start off the main
-// thread, and then (because nsDOMMediaStreams aren't threadsafe),
-// ProxyReleases mStream since it's cycle collected.
-class MediaOperationTask : public Task
-{
-public:
-  // so we can send Stop without AddRef()ing from the MSG thread
-  MediaOperationTask(MediaOperation aType,
-    GetUserMediaCallbackMediaStreamListener* aListener,
-    DOMMediaStream* aStream,
-    DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
-    MediaEngineSource* aAudioSource,
-    MediaEngineSource* aVideoSource,
-    bool aBool,
-    uint64_t aWindowID,
-    already_AddRefed<nsIDOMGetUserMediaErrorCallback> aError)
-    : mType(aType)
-    , mStream(aStream)
-    , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
-    , mAudioSource(aAudioSource)
-    , mVideoSource(aVideoSource)
-    , mListener(aListener)
-    , mBool(aBool)
-    , mWindowID(aWindowID)
-    , mOnFailure(aError)
-  {}
-
-  ~MediaOperationTask()
-  {
-    // MediaStreams can be released on any thread.
-  }
-
-  void
-  ReturnCallbackError(nsresult rv, const char* errorLog);
-
-  void
-  Run()
-  {
-    SourceMediaStream *source = mListener->GetSourceStream();
-    // No locking between these is required as all the callbacks for the
-    // same MediaStream will occur on the same thread.
-    if (!source) // means the stream was never Activated()
-      return;
-
-    switch (mType) {
-      case MEDIA_START:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          nsresult rv;
-
-          if (mAudioSource) {
-            rv = mAudioSource->Start(source, kAudioTrack);
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting audio failed");
-              return;
-            }
-          }
-          if (mVideoSource) {
-            rv = mVideoSource->Start(source, kVideoTrack);
-            if (NS_FAILED(rv)) {
-              ReturnCallbackError(rv, "Starting video failed");
-              return;
-            }
-          }
-          // Start() queued the tracks to be added synchronously to avoid races
-          source->FinishAddTracks();
-
-          source->SetPullEnabled(true);
-          source->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-
-          MM_LOG(("started all sources"));
-          // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
-          // because mOnTracksAvailableCallback needs to be added to mStream
-          // on the main thread.
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
-                                              mStream.forget(),
-                                              mOnTracksAvailableCallback.forget(),
-                                              mAudioSource != nullptr,
-                                              mVideoSource != nullptr,
-                                              mWindowID, mOnFailure.forget());
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_STOP:
-      case MEDIA_STOP_TRACK:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mAudioSource) {
-            mAudioSource->Stop(source, kAudioTrack);
-            mAudioSource->Deallocate();
-          }
-          if (mVideoSource) {
-            mVideoSource->Stop(source, kVideoTrack);
-            mVideoSource->Deallocate();
-          }
-          // Do this after stopping all tracks with EndTrack()
-          if (mBool) {
-            source->Finish();
-          }
-
-          nsIRunnable *event =
-            new GetUserMediaNotificationEvent(mListener,
-                                              mType == MEDIA_STOP ?
-                                              GetUserMediaNotificationEvent::STOPPING :
-                                              GetUserMediaNotificationEvent::STOPPED_TRACK,
-                                              mAudioSource != nullptr,
-                                              mVideoSource != nullptr,
-                                              mWindowID);
-          // event must always be released on mainthread due to the JS callbacks
-          // in the TracksAvailableCallback
-          NS_DispatchToMainThread(event);
-        }
-        break;
-
-      case MEDIA_DIRECT_LISTENERS:
-        {
-          NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
-          if (mVideoSource) {
-            mVideoSource->SetDirectListeners(mBool);
-          }
-        }
-        break;
-
-      default:
-        MOZ_ASSERT(false,"invalid MediaManager operation");
-        break;
-    }
-  }
-
-private:
-  MediaOperation mType;
-  nsRefPtr<DOMMediaStream> mStream;
-  nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
-  nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
-  nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
-  nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
-  bool mBool;
-  uint64_t mWindowID;
-  nsCOMPtr<nsIDOMGetUserMediaErrorCallback> mOnFailure;
-};
-
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
-class MediaDevice : public nsIMediaDevice
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIMEDIADEVICE
-
-  void SetId(const nsAString& aID);
-  virtual uint32_t GetBestFitnessDistance(
-      const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
-protected:
-  virtual ~MediaDevice() {}
-  explicit MediaDevice(MediaEngineSource* aSource, bool aIsVideo);
-  static uint32_t FitnessDistance(nsString aN,
-    const dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters& aConstraint);
-private:
-  static bool StringsContain(const dom::OwningStringOrStringSequence& aStrings,
-                             nsString aN);
-  static uint32_t FitnessDistance(nsString aN,
-      const dom::ConstrainDOMStringParameters& aParams);
-protected:
-  nsString mName;
-  nsString mID;
-  dom::MediaSourceEnum mMediaSource;
-  nsRefPtr<MediaEngineSource> mSource;
-public:
-  bool mIsVideo;
-};
-
-class VideoDevice : public MediaDevice
-{
-public:
-  typedef MediaEngineVideoSource Source;
-
-  explicit VideoDevice(Source* aSource);
-  NS_IMETHOD GetType(nsAString& aType);
-  Source* GetSource();
-  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                    const MediaEnginePrefs &aPrefs);
-};
-
-class AudioDevice : public MediaDevice
-{
-public:
-  typedef MediaEngineAudioSource Source;
-
-  explicit AudioDevice(Source* aSource);
-  NS_IMETHOD GetType(nsAString& aType);
-  Source* GetSource();
-  nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
-                    const MediaEnginePrefs &aPrefs);
-};
-
 // we could add MediaManager if needed
 typedef void (*WindowListenerCallback)(MediaManager *aThis,
                                        uint64_t aWindowID,
                                        StreamListeners *aListeners,
                                        void *aData);
 
 class MediaManager final : public nsIMediaManagerService,
                            public nsIObserver
 {
+  friend GetUserMediaCallbackMediaStreamListener;
 public:
   static already_AddRefed<MediaManager> GetInstance();
 
   // NOTE: never Dispatch(....,NS_DISPATCH_SYNC) to the MediaManager
   // thread from the MainThread, as we NS_DISPATCH_SYNC to MainThread
   // from MediaManager thread.
   static MediaManager* Get();
   static MediaManager* GetIfExists();
@@ -581,17 +458,17 @@ public:
   void OnNavigation(uint64_t aWindowID);
   bool IsActivelyCapturingOrHasAPermission(uint64_t aWindowId);
 
   MediaEnginePrefs mPrefs;
 
   typedef nsTArray<nsRefPtr<MediaDevice>> SourceSet;
   static bool IsPrivateBrowsing(nsPIDOMWindow *window);
 private:
-  typedef media::Pledge<SourceSet*, dom::MediaStreamError> PledgeSourceSet;
+  typedef media::Pledge<SourceSet*, dom::MediaStreamError*> PledgeSourceSet;
 
   static bool IsPrivileged();
   static bool IsLoop(nsIURI* aDocURI);
   static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
   static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
@@ -641,16 +518,17 @@ private:
 
   Mutex mMutex;
   // protected with mMutex:
   RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
   media::CoatCheck<PledgeSourceSet> mOutstandingPledges;
+  media::CoatCheck<GetUserMediaCallbackMediaStreamListener::PledgeVoid> mOutstandingVoidPledges;
 #if defined(MOZ_B2G_CAMERA) && defined(MOZ_WIDGET_GONK)
   nsRefPtr<nsDOMCameraManager> mCameraManager;
 #endif
 public:
   media::CoatCheck<media::Pledge<nsCString>> mGetOriginKeyPledges;
   ScopedDeletePtr<media::Parent<media::NonE10s>> mNonE10sParent;
 };
 
--- a/dom/media/MediaStreamError.cpp
+++ b/dom/media/MediaStreamError.cpp
@@ -7,49 +7,50 @@
 #include "MediaStreamError.h"
 #include "mozilla/dom/MediaStreamErrorBinding.h"
 #include "nsContentUtils.h"
 
 namespace mozilla {
 
 BaseMediaMgrError::BaseMediaMgrError(const nsAString& aName,
                                      const nsAString& aMessage,
-                                     const nsAString& aConstraintName)
+                                     const nsAString& aConstraint)
   : mName(aName)
   , mMessage(aMessage)
-  , mConstraintName(aConstraintName)
+  , mConstraint(aConstraint)
 {
   if (mMessage.IsEmpty()) {
     if (mName.EqualsLiteral("NotFoundError")) {
       mMessage.AssignLiteral("The object can not be found here.");
     } else if (mName.EqualsLiteral("PermissionDeniedError")) {
       mMessage.AssignLiteral("The user did not grant permission for the operation.");
     } else if (mName.EqualsLiteral("SourceUnavailableError")) {
       mMessage.AssignLiteral("The source of the MediaStream could not be "
           "accessed due to a hardware error (e.g. lock from another process).");
     } else if (mName.EqualsLiteral("InternalError")) {
       mMessage.AssignLiteral("Internal error.");
     } else if (mName.EqualsLiteral("NotSupportedError")) {
-      mMessage.AssignLiteral("Constraints with no audio or video in it are not "
-          "supported");
+      mMessage.AssignLiteral("The operation is not supported.");
+    } else if (mName.EqualsLiteral("OverconstrainedError")) {
+      mMessage.AssignLiteral("Constraints could be not satisfied.");
     }
   }
 }
 
 
 NS_IMPL_ISUPPORTS0(MediaMgrError)
 
 namespace dom {
 
 MediaStreamError::MediaStreamError(
     nsPIDOMWindow* aParent,
     const nsAString& aName,
     const nsAString& aMessage,
-    const nsAString& aConstraintName)
-  : BaseMediaMgrError(aName, aMessage, aConstraintName)
+    const nsAString& aConstraint)
+  : BaseMediaMgrError(aName, aMessage, aConstraint)
   , mParent(aParent) {}
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaStreamError, mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamError)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamError)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamError)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
@@ -70,15 +71,15 @@ MediaStreamError::GetName(nsAString& aNa
 
 void
 MediaStreamError::GetMessage(nsAString& aMessage) const
 {
   aMessage = mMessage;
 }
 
 void
-MediaStreamError::GetConstraintName(nsAString& aConstraintName) const
+MediaStreamError::GetConstraint(nsAString& aConstraint) const
 {
-  aConstraintName = mConstraintName;
+  aConstraint = mConstraint;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/MediaStreamError.h
+++ b/dom/media/MediaStreamError.h
@@ -29,66 +29,66 @@ class MediaStreamError;
 } // namespace dom
 
 class BaseMediaMgrError
 {
   friend class dom::MediaStreamError;
 protected:
   BaseMediaMgrError(const nsAString& aName,
                     const nsAString& aMessage,
-                    const nsAString& aConstraintName);
+                    const nsAString& aConstraint);
   const nsString mName;
   nsString mMessage;
-  const nsString mConstraintName;
+  const nsString mConstraint;
 };
 
 class MediaMgrError final : public nsISupports,
                             public BaseMediaMgrError
 {
 public:
   explicit MediaMgrError(const nsAString& aName,
                          const nsAString& aMessage =  EmptyString(),
-                         const nsAString& aConstraintName =  EmptyString())
-  : BaseMediaMgrError(aName, aMessage, aConstraintName) {}
+                         const nsAString& aConstraint =  EmptyString())
+  : BaseMediaMgrError(aName, aMessage, aConstraint) {}
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
 private:
   ~MediaMgrError() {}
 };
 
 namespace dom {
 class MediaStreamError final : public nsISupports,
                                public BaseMediaMgrError,
                                public nsWrapperCache
 {
 public:
   MediaStreamError(nsPIDOMWindow* aParent,
                    const nsAString& aName,
                    const nsAString& aMessage = EmptyString(),
-                   const nsAString& aConstraintName =  EmptyString());
+                   const nsAString& aConstraint =  EmptyString());
 
   MediaStreamError(nsPIDOMWindow* aParent,
                    const BaseMediaMgrError& aOther)
-  : BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraintName)
+  : BaseMediaMgrError(aOther.mName, aOther.mMessage, aOther.mConstraint)
   , mParent(aParent) {}
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaStreamError)
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIASTREAMERROR_IMPLEMENTATION_IID)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsPIDOMWindow* GetParentObject() const
   {
     return mParent;
   }
   void GetName(nsAString& aName) const;
   void GetMessage(nsAString& aMessage) const;
-  void GetConstraintName(nsAString& aConstraintName) const;
+  void GetConstraint(nsAString& aConstraint) const;
 
 private:
   virtual ~MediaStreamError() {}
 
   nsRefPtr<nsPIDOMWindow> mParent;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(MediaStreamError,
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -57,10 +57,17 @@ MediaStreamTrack::SetEnabled(bool aEnabl
 }
 
 void
 MediaStreamTrack::Stop()
 {
   mStream->StopTrack(mTrackID);
 }
 
+already_AddRefed<Promise>
+MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
+                                   ErrorResult &aRv)
+{
+  return mStream->ApplyConstraintsToTrack(mTrackID, aConstraints, aRv);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -4,16 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MEDIASTREAMTRACK_H_
 #define MEDIASTREAMTRACK_H_
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsID.h"
 #include "StreamBuffer.h"
+#include "MediaTrackConstraints.h"
 
 namespace mozilla {
 
 class DOMMediaStream;
 
 namespace dom {
 
 class AudioStreamTrack;
@@ -44,16 +45,18 @@ public:
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID) const;
   void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
+  already_AddRefed<Promise>
+  ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
 
   // Notifications from the MediaStreamGraph
   void NotifyEnded() { mEnded = true; }
 
   // Webrtc allows the remote side to name tracks whatever it wants, and we
   // need to surface this to content.
   void AssignId(const nsAString& aID) { mID = aID; }
 
--- a/dom/media/systemservices/MediaUtils.h
+++ b/dom/media/systemservices/MediaUtils.h
@@ -58,17 +58,17 @@ class Pledge : public PledgeBase
   public:
     FunctorsBase() {}
     virtual void Succeed(ValueType& result) = 0;
     virtual void Fail(ErrorType& error) = 0;
     virtual ~FunctorsBase() {};
   };
 
 public:
-  explicit Pledge() : mDone(false), mError(nullptr) {}
+  explicit Pledge() : mDone(false), mRejected(false) {}
   Pledge(const Pledge& aOther) = delete;
   Pledge& operator = (const Pledge&) = delete;
 
   template<typename OnSuccessType>
   void Then(OnSuccessType aOnSuccess)
   {
     Then(aOnSuccess, [](ErrorType&){});
   }
@@ -92,150 +92,59 @@ public:
       };
 
       OnSuccessType mOnSuccess;
       OnFailureType mOnFailure;
     };
     mFunctors = new Functors(aOnSuccess, aOnFailure);
 
     if (mDone) {
-      if (!mError) {
-        mFunctors->Succeed(mValue);
-      } else {
-        mFunctors->Fail(*mError);
-      }
-    }
-  }
-
-  void Resolve(const ValueType& aValue)
-  {
-    mValue = aValue;
-    Resolve();
-  }
-protected:
-  void Resolve()
-  {
-    if (!mDone) {
-      mDone = true;
-      MOZ_ASSERT(!mError);
-      if (mFunctors) {
-        mFunctors->Succeed(mValue);
-      }
-    }
-  }
-
-  void Reject(ErrorType rv)
-  {
-    if (!mDone) {
-      mDone = true;
-      mError = rv;
-      if (mFunctors) {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
-  ValueType mValue;
-private:
-  ~Pledge() {};
-  bool mDone;
-  nsRefPtr<ErrorType> mError;
-  ScopedDeletePtr<FunctorsBase> mFunctors;
-};
-
-template<typename ValueType>
-class Pledge<ValueType, nsresult>  : public PledgeBase
-{
-  // TODO: Remove workaround once mozilla allows std::function from <functional>
-  // wo/std::function support, do template + virtual trick to accept lambdas
-  class FunctorsBase
-  {
-  public:
-    FunctorsBase() {}
-    virtual void Succeed(ValueType& result) = 0;
-    virtual void Fail(nsresult error) = 0;
-    virtual ~FunctorsBase() {};
-  };
-
-public:
-  explicit Pledge() : mDone(false), mError(NS_OK) {}
-  Pledge(const Pledge& aOther) = delete;
-  Pledge& operator = (const Pledge&) = delete;
-
-  template<typename OnSuccessType>
-  void Then(OnSuccessType aOnSuccess)
-  {
-    Then(aOnSuccess, [](nsresult){});
-  }
-
-  template<typename OnSuccessType, typename OnFailureType>
-  void Then(OnSuccessType aOnSuccess, OnFailureType aOnFailure)
-  {
-    class Functors : public FunctorsBase
-    {
-    public:
-      Functors(OnSuccessType& aOnSuccess, OnFailureType& aOnFailure)
-        : mOnSuccess(aOnSuccess), mOnFailure(aOnFailure) {}
-
-      void Succeed(ValueType& result)
-      {
-        mOnSuccess(result);
-      }
-      void Fail(nsresult rv)
-      {
-        mOnFailure(rv);
-      };
-
-      OnSuccessType mOnSuccess;
-      OnFailureType mOnFailure;
-    };
-    mFunctors = new Functors(aOnSuccess, aOnFailure);
-
-    if (mDone) {
-      if (mError == NS_OK) {
+      if (!mRejected) {
         mFunctors->Succeed(mValue);
       } else {
         mFunctors->Fail(mError);
       }
     }
   }
 
   void Resolve(const ValueType& aValue)
   {
     mValue = aValue;
     Resolve();
   }
+
+  void Reject(ErrorType rv)
+  {
+    if (!mDone) {
+      mDone = mRejected = true;
+      mError = rv;
+      if (mFunctors) {
+        mFunctors->Fail(mError);
+      }
+    }
+  }
+
 protected:
   void Resolve()
   {
     if (!mDone) {
       mDone = true;
-      MOZ_ASSERT(mError == NS_OK);
+      MOZ_ASSERT(!mRejected);
       if (mFunctors) {
         mFunctors->Succeed(mValue);
       }
     }
   }
 
-  void Reject(nsresult error)
-  {
-    if (!mDone) {
-      mDone = true;
-      mError = error;
-      if (mFunctors) {
-        mFunctors->Fail(mError);
-      }
-    }
-  }
-
   ValueType mValue;
 private:
   ~Pledge() {};
   bool mDone;
-  nsresult mError;
+  bool mRejected;
+  ErrorType mError;
   ScopedDeletePtr<FunctorsBase> mFunctors;
 };
 
 /* media::NewRunnableFrom() - Create an nsRunnable from a lambda.
  * media::NewTaskFrom()     - Create a Task from a lambda.
  *
  * Passing variables (closures) to an async function is clunky with nsRunnable:
  *
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -229,17 +229,16 @@ function setupEnvironment() {
     return;
   }
 
   // Running as a Mochitest.
   SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
   window.finish = () => SimpleTest.finish();
   SpecialPowers.pushPrefEnv({
     'set': [
-      ['canvas.capturestream.enabled', true],
       ['media.peerconnection.enabled', true],
       ['media.peerconnection.identity.enabled', true],
       ['media.peerconnection.identity.timeout', 120000],
       ['media.peerconnection.ice.stun_client_maximum_transmits', 14],
       ['media.peerconnection.ice.trickle_grace_period', 30000],
       ['media.navigator.permission.disabled', true],
       ['media.navigator.streams.fake', FAKE_ENABLED],
       ['media.getusermedia.screensharing.enabled', true],
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -11,19 +11,23 @@ createHTML({ title: "Run enumerateDevice
   Tests covering enumerateDevices API and deviceId constraint. Exercise code.
 */
 
 function mustSucceed(msg, f) {
   return f().then(() => ok(true, msg + " must succeed"),
                   e => is(e.name, null, msg + " must succeed: " + e.message));
 }
 
-function mustFailWith(msg, reason, f) {
-  return f().then(() => ok(false, msg + " must fail"),
-                  e => is(e.name, reason, msg + " must fail: " + e.message));
+function mustFailWith(msg, reason, constraint, f) {
+  return f().then(() => ok(false, msg + " must fail"), e => {
+      is(e.name, reason, msg + " must fail: " + e.message);
+      if (constraint) {
+        is(e.constraint, constraint, msg + " must fail w/correct constraint.");
+      }
+    });
 }
 
 var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
 
 runTest(() =>
   pushPrefs(["media.navigator.streams.fake", true])
   .then(() => navigator.mediaDevices.enumerateDevices())
   .then(devices => {
@@ -44,23 +48,25 @@ runTest(() =>
     video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
     fake: true,
   })))
   .then(() => mustSucceed("unknown plain deviceId on audio",
                           () => navigator.mediaDevices.getUserMedia({
     audio: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
     fake: true,
   })))
-  .then(() => mustFailWith("unknown exact deviceId on video", "NotFoundError",
-                       () => navigator.mediaDevices.getUserMedia({
+  .then(() => mustFailWith("unknown exact deviceId on video",
+                           "OverconstrainedError", "deviceId",
+                           () => navigator.mediaDevices.getUserMedia({
     video: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
     fake: true,
   })))
-  .then(() => mustFailWith("unknown exact deviceId on audio", "NotFoundError",
-                       () => navigator.mediaDevices.getUserMedia({
+  .then(() => mustFailWith("unknown exact deviceId on audio",
+                           "OverconstrainedError", "deviceId",
+                           () => navigator.mediaDevices.getUserMedia({
     audio: { deviceId: { exact: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" } },
     fake: true,
   })))
   // Check the special case of no devices found (these prefs override fake).
   .then(() => pushPrefs(["media.audio_loopback_dev", "none"],
                         ["media.video_loopback_dev", "none"]))
   .then(() => navigator.mediaDevices.enumerateDevices())
   .then(devices => ok(devices.length === 0, "No devices found")));
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html
@@ -34,19 +34,24 @@ var tests = [
     constraints: { video: { mediaSource: 'application' } },
     error: "PermissionDeniedError" },
   { message: "window screensharing requires permission",
     constraints: { video: { mediaSource: 'window' } },
     error: "PermissionDeniedError" },
   { message: "browser screensharing requires permission",
     constraints: { video: { mediaSource: 'browser' } },
     error: "PermissionDeniedError" },
-  { message: "unknown mediaSource fails",
+  { message: "unknown mediaSource in video fails",
     constraints: { video: { mediaSource: 'uncle' } },
-    error: "NotFoundError" },
+    error: "OverconstrainedError",
+    constraint: "mediaSource" },
+  { message: "unknown mediaSource in audio fails",
+    constraints: { audio: { mediaSource: 'uncle' } },
+    error: "OverconstrainedError",
+    constraint: "mediaSource" },
   { message: "emtpy constraint fails",
     constraints: { },
     error: "NotSupportedError" },
   { message: "Success-path: optional video facingMode + audio ignoring facingMode",
     constraints: { fake: true,
                    audio: { mediaSource: 'microphone',
                             facingMode: 'left',
                             foo: 0,
@@ -101,16 +106,26 @@ runTest(function() {
 
   var p = new Promise(resolve => SpecialPowers.pushPrefEnv({
       set : [ ['media.getusermedia.browser.enabled', false],
               ['media.getusermedia.screensharing.enabled', false] ]
     }, resolve));
 
   return tests.reduce((p, test) =>
     p.then(() => navigator.mediaDevices.getUserMedia(test.constraints))
-    .then(() => is(null, test.error, test.message),
-          e => is(e.name, test.error, test.message + ": " + e.message)), p);
+    .then(() => is(null, test.error, test.message), e => {
+      is(e.name, test.error, test.message + ": " + e.message);
+      if (test.constraint) {
+        is(e.constraint, test.constraint,
+           test.message + " w/correct constraint.");
+      }
+    }), p)
+    .then(() => navigator.mediaDevices.getUserMedia({video: true, audio: true}))
+    .then(stream => stream.getVideoTracks()[0].applyConstraints({ width: 320 })
+      .then(() => stream.getAudioTracks()[0].applyConstraints({ })))
+    .then(() => ok(true, "applyConstraints code exercised"))
+    // TODO: Test outcome once fake devices support constraints (Bug 1088621)
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -106,16 +106,21 @@ public:
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) = 0;
 
   /* Stop the device and release the corresponding MediaStream */
   virtual nsresult Stop(SourceMediaStream *aSource, TrackID aID) = 0;
 
+  /* Restart with new capability */
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) = 0;
+
   /* Change device configuration.  */
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) = 0;
 
   /* Returns true if a source represents a fake capture device and
    * false otherwise
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -196,16 +196,24 @@ MediaEngineDefaultVideoSource::Stop(Sour
       aSource->EndTrack(kTrackCount + i);
     }
   }
 
   mState = kStopped;
   return NS_OK;
 }
 
+nsresult
+MediaEngineDefaultVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                       const MediaEnginePrefs &aPrefs,
+                                       const nsString& aDeviceId)
+{
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 MediaEngineDefaultVideoSource::Notify(nsITimer* aTimer)
 {
   // Update the target color
   if (mCr <= 16) {
     if (mCb < 240) {
       mCb++;
     } else {
@@ -465,16 +473,24 @@ MediaEngineDefaultAudioSource::Stop(Sour
       aSource->EndTrack(kTrackCount + kFakeVideoTrackCount+i);
     }
   }
 
   mState = kStopped;
   return NS_OK;
 }
 
+nsresult
+MediaEngineDefaultAudioSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                       const MediaEnginePrefs &aPrefs,
+                                       const nsString& aDeviceId)
+{
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 MediaEngineDefaultAudioSource::Notify(nsITimer* aTimer)
 {
   AudioSegment segment;
   nsRefPtr<SharedBuffer> buffer = SharedBuffer::Create(AUDIO_FRAME_LENGTH * sizeof(int16_t));
   int16_t* dest = static_cast<int16_t*>(buffer->Data());
 
   mSineGenerator->generate(dest, AUDIO_FRAME_LENGTH);
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -44,16 +44,19 @@ public:
   virtual void GetUUID(nsACString&) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override { return NS_OK; };
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
@@ -114,16 +117,19 @@ public:
   virtual void GetUUID(nsACString&) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override { return NS_OK; };
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream *aSource,
                           TrackID aId,
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.cpp
@@ -323,16 +323,24 @@ MediaEngineGonkVideoSource::Stop(SourceM
   }
 
   NS_DispatchToMainThread(WrapRunnable(nsRefPtr<MediaEngineGonkVideoSource>(this),
                                        &MediaEngineGonkVideoSource::StopImpl));
 
   return NS_OK;
 }
 
+nsresult
+MediaEngineGonkVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                    const MediaEnginePrefs& aPrefs,
+                                    const nsString& aDeviceId)
+{
+  return NS_OK;
+}
+
 /**
 * Initialization and Shutdown functions for the video source, called by the
 * constructor and destructor respectively.
 */
 
 void
 MediaEngineGonkVideoSource::Init()
 {
--- a/dom/media/webrtc/MediaEngineGonkVideoSource.h
+++ b/dom/media/webrtc/MediaEngineGonkVideoSource.h
@@ -61,16 +61,19 @@ public:
     }
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
                             const MediaEnginePrefs &aPrefs,
                             const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) override;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) override;
   virtual const dom::MediaSourceEnum GetMediaSource() override {
     return dom::MediaSourceEnum::Camera;
   }
 
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -207,16 +207,41 @@ MediaEngineRemoteVideoSource::Stop(mozil
     mImage = nullptr;
   }
 
   mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
 
   return NS_OK;
 }
 
+nsresult
+MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                      const MediaEnginePrefs& aPrefs,
+                                      const nsString& aDeviceId)
+{
+  if (!mInitDone) {
+    LOG(("Init not done"));
+    return NS_ERROR_FAILURE;
+  }
+  if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (mState != kStarted) {
+    return NS_OK;
+  }
+
+  mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
+  if (mozilla::camera::StartCapture(mCapEngine,
+                                    mCaptureIndex, mCapability, this)) {
+    LOG(("StartCapture failed"));
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 void
 MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
                                          SourceMediaStream* aSource,
                                          TrackID aID, StreamTime aDesiredTime)
 {
   VideoSegment segment;
 
   MonitorAutoLock lock(mMonitor);
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -66,16 +66,19 @@ public:
                                const char* aMonitorName = "RemoteVideo.Monitor");
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                             const MediaEnginePrefs& aPrefs,
                             const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;;
   virtual nsresult Start(SourceMediaStream*, TrackID) override;
   virtual nsresult Stop(SourceMediaStream*, TrackID) override;
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) override;
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
                           TrackID aId,
                           StreamTime aDesiredTime) override;
   virtual const dom::MediaSourceEnum GetMediaSource() override {
     return mMediaSource;
   }
 
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -287,16 +287,25 @@ MediaEngineTabVideoSource::Stop(mozilla:
   if (!mWindow)
     return NS_OK;
 
   NS_DispatchToMainThread(new StopRunnable(this));
   return NS_OK;
 }
 
 nsresult
+MediaEngineTabVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                   const mozilla::MediaEnginePrefs& aPrefs,
+                                   const nsString& aDeviceId)
+{
+  // TODO
+  return NS_OK;
+}
+
+nsresult
 MediaEngineTabVideoSource::Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t)
 {
   return NS_OK;
 }
 
 bool
 MediaEngineTabVideoSource::IsFake()
 {
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -24,16 +24,19 @@ class MediaEngineTabVideoSource : public
     virtual nsresult Allocate(const dom::MediaTrackConstraints &,
                               const mozilla::MediaEnginePrefs&,
                               const nsString& aDeviceId) override;
     virtual nsresult Deallocate() override;
     virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override;
     virtual void SetDirectListeners(bool aHasDirectListeners) override {};
     virtual void NotifyPull(mozilla::MediaStreamGraph*, mozilla::SourceMediaStream*, mozilla::TrackID, mozilla::StreamTime) override;
     virtual nsresult Stop(mozilla::SourceMediaStream*, mozilla::TrackID) override;
+    virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                             const mozilla::MediaEnginePrefs& aPrefs,
+                             const nsString& aDeviceId) override;
     virtual nsresult Config(bool, uint32_t, bool, uint32_t, bool, uint32_t, int32_t) override;
     virtual bool IsFake() override;
     virtual const dom::MediaSourceEnum GetMediaSource() override {
       return dom::MediaSourceEnum::Browser;
     }
     virtual uint32_t GetBestFitnessDistance(
       const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) override
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -79,16 +79,19 @@ public:
     return NS_OK;
   }
   void Shutdown() override
   {
     // Nothing to do here, everything is managed in MediaManager.cpp
   }
   nsresult Start(SourceMediaStream* aMediaStream, TrackID aId) override;
   nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
+  nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                   const MediaEnginePrefs &aPrefs,
+                   const nsString& aDeviceId) override;
   void SetDirectListeners(bool aDirect) override
   {}
   nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn,
                   uint32_t aAGC, bool aNoiseOn, uint32_t aNoise,
                   int32_t aPlayoutDelay) override
   {
     return NS_OK;
   }
@@ -150,16 +153,19 @@ public:
   virtual void GetUUID(nsACString& aUUID) override;
 
   virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                             const MediaEnginePrefs& aPrefs,
                             const nsString& aDeviceId) override;
   virtual nsresult Deallocate() override;
   virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
   virtual nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;
+  virtual nsresult Restart(const dom::MediaTrackConstraints& aConstraints,
+                           const MediaEnginePrefs &aPrefs,
+                           const nsString& aDeviceId) override;
   virtual void SetDirectListeners(bool aHasDirectListeners) override {};
   virtual nsresult Config(bool aEchoOn, uint32_t aEcho,
                           bool aAgcOn, uint32_t aAGC,
                           bool aNoiseOn, uint32_t aNoise,
                           int32_t aPlayoutDelay) override;
 
   virtual void NotifyPull(MediaStreamGraph* aGraph,
                           SourceMediaStream* aSource,
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -417,16 +417,24 @@ MediaEngineWebRTCMicrophoneSource::Stop(
     return NS_ERROR_FAILURE;
   }
   if (mVoEBase->StopReceive(mChannel)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
+nsresult
+MediaEngineWebRTCMicrophoneSource::Restart(const dom::MediaTrackConstraints& aConstraints,
+                                           const MediaEnginePrefs &aPrefs,
+                                           const nsString& aDeviceId)
+{
+  return NS_OK;
+}
+
 void
 MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph,
                                               SourceMediaStream *aSource,
                                               TrackID aID,
                                               StreamTime aDesiredTime)
 {
   // Ignore - we push audio data
   LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime));
@@ -659,16 +667,25 @@ MediaEngineWebRTCAudioCaptureSource::Sta
 nsresult
 MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
                                           TrackID aId)
 {
   aMediaStream->EndAllTrackAndFinish();
   return NS_OK;
 }
 
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Restart(
+    const dom::MediaTrackConstraints& aConstraints,
+    const MediaEnginePrefs &aPrefs,
+    const nsString& aDeviceId)
+{
+  return NS_OK;
+}
+
 uint32_t
 MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
     const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   // There is only one way of capturing audio for now, and it's always adequate.
   return 0;
 }
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -7,16 +7,18 @@
 #ifndef MEDIATRACKCONSTRAINTS_H_
 #define MEDIATRACKCONSTRAINTS_H_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/MediaTrackConstraintSetBinding.h"
 #include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
 
+#include <map>
+
 namespace mozilla {
 
 template<class EnumValuesStrings, class Enum>
 static const char* EnumToASCII(const EnumValuesStrings& aStrings, Enum aValue) {
   return aStrings[uint32_t(aValue)].value;
 }
 
 template<class EnumValuesStrings, class Enum>
@@ -99,13 +101,135 @@ protected:
     bool aAdvanced);
   static uint32_t FitnessDistance(nsString aN,
       const dom::ConstrainDOMStringParameters& aParams);
 
   static uint32_t
   GetMinimumFitnessDistance(const dom::MediaTrackConstraintSet &aConstraints,
                             bool aAdvanced,
                             const nsString& aDeviceId);
+
+  template<class DeviceType>
+  static bool
+  AreUnfitSettings(const dom::MediaTrackConstraints &aConstraints,
+                   nsTArray<nsRefPtr<DeviceType>>& aSources)
+  {
+    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
+    aggregateConstraints.AppendElement(&aConstraints);
+
+    for (auto& source : aSources) {
+      if (source->GetBestFitnessDistance(aggregateConstraints) != UINT32_MAX) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+public:
+  // Apply constrains to a supplied list of sources (removes items from the list)
+
+  template<class DeviceType>
+  static const char*
+  SelectSettings(const dom::MediaTrackConstraints &aConstraints,
+                 nsTArray<nsRefPtr<DeviceType>>& aSources)
+  {
+    auto& c = aConstraints;
+
+    // First apply top-level constraints.
+
+    // Stack constraintSets that pass, starting with the required one, because the
+    // whole stack must be re-satisfied each time a capability-set is ruled out
+    // (this avoids storing state or pushing algorithm into the lower-level code).
+    nsTArray<nsRefPtr<DeviceType>> unsatisfactory;
+    nsTArray<const dom::MediaTrackConstraintSet*> aggregateConstraints;
+    aggregateConstraints.AppendElement(&c);
+
+    std::multimap<uint32_t, nsRefPtr<DeviceType>> ordered;
+
+    for (uint32_t i = 0; i < aSources.Length();) {
+      uint32_t distance = aSources[i]->GetBestFitnessDistance(aggregateConstraints);
+      if (distance == UINT32_MAX) {
+        unsatisfactory.AppendElement(aSources[i]);
+        aSources.RemoveElementAt(i);
+      } else {
+        ordered.insert(std::pair<uint32_t, nsRefPtr<DeviceType>>(distance,
+                                                                 aSources[i]));
+        ++i;
+      }
+    }
+    if (!aSources.Length()) {
+      // None selected. The spec says to report a constraint that satisfies NONE
+      // of the sources. Unfortunately, this is a bit laborious to find out, and
+      // requires updating as new constraints are added!
+
+      if (c.mDeviceId.IsConstrainDOMStringParameters()) {
+        dom::MediaTrackConstraints fresh;
+        fresh.mDeviceId = c.mDeviceId;
+        if (AreUnfitSettings(fresh, unsatisfactory)) {
+          return "deviceId";
+        }
+      }
+      if (c.mWidth.IsConstrainLongRange()) {
+        dom::MediaTrackConstraints fresh;
+        fresh.mWidth = c.mWidth;
+        if (AreUnfitSettings(fresh, unsatisfactory)) {
+          return "width";
+        }
+      }
+      if (c.mHeight.IsConstrainLongRange()) {
+        dom::MediaTrackConstraints fresh;
+        fresh.mHeight = c.mHeight;
+        if (AreUnfitSettings(fresh, unsatisfactory)) {
+          return "height";
+        }
+      }
+      if (c.mFrameRate.IsConstrainDoubleRange()) {
+        dom::MediaTrackConstraints fresh;
+        fresh.mFrameRate = c.mFrameRate;
+        if (AreUnfitSettings(fresh, unsatisfactory)) {
+          return "frameRate";
+        }
+      }
+      if (c.mFacingMode.IsConstrainDOMStringParameters()) {
+        dom::MediaTrackConstraints fresh;
+        fresh.mFacingMode = c.mFacingMode;
+        if (AreUnfitSettings(fresh, unsatisfactory)) {
+          return "facingMode";
+        }
+      }
+      return "";
+    }
+
+    // Order devices by shortest distance
+    for (auto& ordinal : ordered) {
+      aSources.RemoveElement(ordinal.second);
+      aSources.AppendElement(ordinal.second);
+    }
+
+    // Then apply advanced constraints.
+
+    if (c.mAdvanced.WasPassed()) {
+      auto &array = c.mAdvanced.Value();
+
+      for (int i = 0; i < int(array.Length()); i++) {
+        aggregateConstraints.AppendElement(&array[i]);
+        nsTArray<nsRefPtr<DeviceType>> rejects;
+        for (uint32_t j = 0; j < aSources.Length();) {
+          if (aSources[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
+            rejects.AppendElement(aSources[j]);
+            aSources.RemoveElementAt(j);
+          } else {
+            ++j;
+          }
+        }
+        if (!aSources.Length()) {
+          aSources.AppendElements(Move(rejects));
+          aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
+        }
+      }
+    }
+    return nullptr;
+  }
 };
 
 } // namespace mozilla
 
 #endif /* MEDIATRACKCONSTRAINTS_H_ */
--- a/dom/network/PTCPServerSocket.ipdl
+++ b/dom/network/PTCPServerSocket.ipdl
@@ -19,16 +19,14 @@ protocol PTCPServerSocket
   manager PNecko;
 
 parent:
   Close();
   RequestDelete();
 
 child:
   CallbackAccept(PTCPSocket socket);
-  CallbackError(nsString message, nsString filename,
-                uint32_t lineNumber, uint32_t columnNumber);
   __delete__();
 };
 
 } // namespace net
 } // namespace mozilla
 
--- a/dom/network/PTCPSocket.ipdl
+++ b/dom/network/PTCPSocket.ipdl
@@ -8,21 +8,22 @@
 include protocol PNecko;
 
 include "mozilla/net/NeckoMessageUtils.h";
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 struct TCPError {
   nsString name;
+  nsString message;
 };
 
 union SendableData {
   uint8_t[];
-  nsString;
+  nsCString;
 };
 
 union CallbackData {
   void_t;
   SendableData;
   TCPError;
 };
 
@@ -33,17 +34,23 @@ namespace net {
 protocol PTCPSocket
 {
   manager PNecko;
 
 parent:
   // Forward calling to child's open() method to parent, expect TCPOptions
   // is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
   // |binaryType| (from TCPOption.binaryType).
-  Open(nsString host, uint16_t port, bool useSSL, nsString binaryType);
+  Open(nsString host, uint16_t port, bool useSSL, bool useArrayBuffers);
+
+  // Ask parent to open a socket and bind the newly-opened socket to a local
+  // address specified in |localAddr| and |localPort|.
+  OpenBind(nsCString host, uint16_t port,
+           nsCString localAddr, uint16_t localPort,
+           bool useSSL, bool aUseArrayBuffers);
 
   // When child's send() is called, this message requrests parent to send
   // data and update it's trackingNumber.
   Data(SendableData data, uint32_t trackingNumber);
 
   // Forward calling to child's upgradeToSecure() method to parent.
   StartTLS();
 
@@ -53,17 +60,17 @@ parent:
   // Forward calling to child's resume() method to parent.
   Resume();
 
   // Forward calling to child's close() method to parent.
   Close();
 
 child:
   // Forward events that are dispatched by parent.
-  Callback(nsString type, CallbackData data, nsString readyState);
+  Callback(nsString type, CallbackData data, uint32_t readyState);
 
   // Update child's bufferedAmount when parent's bufferedAmount is updated.
   // trackingNumber is also passed back to child to ensure the bufferedAmount
   // is corresponding the last call to send().
   UpdateBufferedAmount(uint32_t bufferedAmount, uint32_t trackingNumber);
 
 both:
   RequestDelete();
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPServerSocket.cpp
@@ -0,0 +1,196 @@
+/* -*- 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 "mozilla/dom/TCPServerSocketBinding.h"
+#include "mozilla/dom/TCPServerSocketEvent.h"
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "TCPServerSocketParent.h"
+#include "TCPServerSocketChild.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/ErrorResult.h"
+#include "TCPServerSocket.h"
+#include "TCPSocket.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPServerSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPServerSocket,
+                                               DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPServerSocket,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerSocket)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeChild)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPServerSocket,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerSocket)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeChild)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPServerSocket)
+  NS_INTERFACE_MAP_ENTRY(nsIServerSocketListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPServerSocket::TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
+                                 bool aUseArrayBuffers, uint16_t aBacklog)
+  : DOMEventTargetHelper(aGlobal)
+  , mPort(aPort)
+  , mBacklog(aBacklog)
+  , mUseArrayBuffers(aUseArrayBuffers)
+{
+}
+
+TCPServerSocket::~TCPServerSocket()
+{
+}
+
+nsresult
+TCPServerSocket::Init()
+{
+  if (mServerSocket || mServerBridgeChild) {
+    NS_WARNING("Child TCPServerSocket is already listening.");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    mServerBridgeChild = new TCPServerSocketChild(this, mPort, mBacklog, mUseArrayBuffers);
+    return NS_OK;
+  }
+
+  nsresult rv;
+  mServerSocket = do_CreateInstance("@mozilla.org/network/server-socket;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->Init(mPort, false, mBacklog);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->GetPort(&mPort);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mServerSocket->AsyncListen(this);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+already_AddRefed<TCPServerSocket>
+TCPServerSocket::Constructor(const GlobalObject& aGlobal,
+                             uint16_t aPort,
+                             const ServerSocketOptions& aOptions,
+                             uint16_t aBacklog,
+                             mozilla::ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!global) {
+    aRv = NS_ERROR_FAILURE;
+    return nullptr;
+  }
+  bool useArrayBuffers = aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer;
+  nsRefPtr<TCPServerSocket> socket = new TCPServerSocket(global, aPort, useArrayBuffers, aBacklog);
+  nsresult rv = socket->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv = NS_ERROR_FAILURE;
+    return nullptr;
+  }
+  return socket.forget();
+}
+
+uint16_t
+TCPServerSocket::LocalPort()
+{
+  return mPort;
+}
+
+void
+TCPServerSocket::Close()
+{
+  if (mServerBridgeChild) {
+    mServerBridgeChild->Close();
+  }
+  if (mServerSocket) {
+    mServerSocket->Close();
+  }
+}
+
+void
+TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket)
+{
+  AutoJSAPI api;
+  api.Init(GetOwner());
+
+  TCPServerSocketEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mSocket = aSocket;
+
+  nsRefPtr<TCPServerSocketEvent> event =
+      TCPServerSocketEvent::Constructor(this, aType, init);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+
+  if (mServerBridgeParent) {
+    mServerBridgeParent->OnConnect(event);
+  }
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer, nsISocketTransport* aTransport)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
+  if (mServerBridgeParent) {
+    socket->SetAppIdAndBrowser(mServerBridgeParent->GetAppId(),
+                               mServerBridgeParent->GetInBrowser());
+  }
+  FireEvent(NS_LITERAL_STRING("connect"), socket);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnStopListening(nsIServerSocket* aServer, nsresult aStatus)
+{
+  if (aStatus != NS_BINDING_ABORTED) {
+    nsRefPtr<Event> event = new Event(GetOwner());
+    nsresult rv = event->InitEvent(NS_LITERAL_STRING("error"), false, false);
+    NS_ENSURE_SUCCESS(rv, rv);
+    event->SetTrusted(true);
+    bool dummy;
+    DispatchEvent(event, &dummy);
+
+    NS_WARNING("Server socket was closed by unexpected reason.");
+    return NS_ERROR_FAILURE;
+  }
+  mServerSocket = nullptr;
+  return NS_OK;
+}
+
+nsresult
+TCPServerSocket::AcceptChildSocket(TCPSocketChild* aSocketChild)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+  nsRefPtr<TCPSocket> socket = TCPSocket::CreateAcceptedSocket(global, aSocketChild, mUseArrayBuffers);
+  NS_ENSURE_TRUE(socket, NS_ERROR_FAILURE);
+  FireEvent(NS_LITERAL_STRING("connect"), socket);
+  return NS_OK;
+}
+
+void
+TCPServerSocket::SetServerBridgeParent(TCPServerSocketParent* aBridgeParent)
+{
+  mServerBridgeParent = aBridgeParent;
+}
+
+JSObject*
+TCPServerSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TCPServerSocketBinding::Wrap(aCx, this, aGivenProto);
+}
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPServerSocket.h
@@ -0,0 +1,83 @@
+/* -*- 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 mozilla_dom_TCPServerSocket_h
+#define mozilla_dom_TCPServerSocket_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIServerSocket.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+struct ServerSocketOptions;
+class GlobalObject;
+class TCPSocket;
+class TCPSocketChild;
+class TCPServerSocketChild;
+class TCPServerSocketParent;
+
+class TCPServerSocket final : public DOMEventTargetHelper
+                            , public nsIServerSocketListener
+{
+public:
+  TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort, bool aUseArrayBuffers,
+                  uint16_t aBacklog);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+  NS_DECL_NSISERVERSOCKETLISTENER
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  nsresult Init();
+
+  uint16_t LocalPort();
+  void Close();
+
+  static already_AddRefed<TCPServerSocket>
+  Constructor(const GlobalObject& aGlobal,
+              uint16_t aPort,
+              const ServerSocketOptions& aOptions,
+              uint16_t aBacklog,
+              mozilla::ErrorResult& aRv);
+
+  IMPL_EVENT_HANDLER(connect);
+  IMPL_EVENT_HANDLER(error);
+
+  // Relay an accepted socket notification from the parent process and
+  // initialize this object with an existing child actor for the new socket.
+  nsresult AcceptChildSocket(TCPSocketChild* aSocketChild);
+  // Associate this object with an IPC actor in the parent process to relay
+  // notifications to content processes.
+  void SetServerBridgeParent(TCPServerSocketParent* aBridgeParent);
+
+private:
+  ~TCPServerSocket();
+  // Dispatch a TCPServerSocketEvent event of a given type at this object.
+  void FireEvent(const nsAString& aType, TCPSocket* aSocket);
+
+  // The server socket associated with this object.
+  nsCOMPtr<nsIServerSocket> mServerSocket;
+  // The IPC actor in the content process.
+  nsRefPtr<TCPServerSocketChild> mServerBridgeChild;
+  // The IPC actor in the parent process.
+  nsRefPtr<TCPServerSocketParent> mServerBridgeParent;
+  int32_t mPort;
+  uint16_t mBacklog;
+  // True if any accepted sockets should use array buffers for received messages.
+  bool mUseArrayBuffers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPServerSocket_h
deleted file mode 100644
--- a/dom/network/TCPServerSocket.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-const CC = Components.Constructor;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const ServerSocket = CC(
-        '@mozilla.org/network/server-socket;1', 'nsIServerSocket', 'init'),
-      TCPSocketInternal = Cc[
-        '@mozilla.org/tcp-socket;1'].createInstance(Ci.nsITCPSocketInternal);
-
-/*
- * Debug logging function
- */
-
-var debug = true;
-function LOG(msg) {
-  if (debug) {
-    dump("TCPServerSocket: " + msg + "\n");
-  }
-}
-
-/*
- * nsIDOMTCPServerSocket object
- */
-
-function TCPServerSocket() {
-  this._localPort = 0;
-  this._binaryType = null;
-
-  this._onconnect = null;
-  this._onerror = null;
-
-  this._inChild = false;
-  this._neckoTCPServerSocket = null;
-  this._serverBridge = null;
-  this.useWin = null;
-}
-
-// When this API moves to WebIDL and these __exposedProps__ go away, remove
-// this call here and remove the API from XPConnect.
-Cu.skipCOWCallableChecks();
-
-TCPServerSocket.prototype = {
-  __exposedProps__: {
-    localPort: 'r',
-    onconnect: 'rw',
-    onerror: 'rw'
-  },
-  get localPort() {
-    return this._localPort;
-  },
-  get onconnect() {
-    return this._onconnect;
-  },
-  set onconnect(f) {
-    this._onconnect = f;
-  },
-  get onerror() {
-    return this._onerror;
-  },
-  set onerror(f) {
-    this._onerror = f;
-  },
-
-  _callListenerAcceptCommon: function tss_callListenerAcceptCommon(socket) {
-    if (this._onconnect) {
-      try {
-        this["onconnect"].call(null, socket);
-      } catch (e) {
-        socket.close();
-      }
-    }
-    else {
-      socket.close();
-      dump("Received unexpected connection!");
-    }
-  },
-  init: function tss_init(aWindowObj) {
-    this.useWin = aWindowObj;
-  },
-
-  /* nsITCPServerSocketInternal method */
-  listen: function tss_listen(localPort, options, backlog) {
-    this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
-                       .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-    this._binaryType = options.binaryType;
-
-    if (this._inChild) {
-      if (this._serverBridge == null) {
-        this._serverBridge = Cc["@mozilla.org/tcp-server-socket-child;1"]
-                                .createInstance(Ci.nsITCPServerSocketChild);
-        this._serverBridge.listen(this, localPort, backlog, options.binaryType);
-      }
-      else {
-        throw new Error("Child TCPServerSocket has already listening. \n");
-      }
-    }
-    else {
-      if (this._neckoTCPServerSocket == null) {
-        this._neckoTCPServerSocket = new ServerSocket(localPort, false, backlog);
-        this._localPort = this._neckoTCPServerSocket.port;
-        this._neckoTCPServerSocket.asyncListen(this);
-      }
-      else {
-        throw new Error("Parent TCPServerSocket has already listening. \n");
-      }
-    }
-  },
-
-  callListenerAccept: function tss_callListenerSocket(socketChild) {
-    // this method is called at child process when the socket is accepted at parent process.
-    let socket = TCPSocketInternal.createAcceptedChild(socketChild, this._binaryType, this.useWin);
-    this._callListenerAcceptCommon(socket);
-  },
-
-  callListenerError: function tss_callListenerError(message, filename, lineNumber, columnNumber) {
-    if (this._onerror) {
-      var type = "error";
-      var error = new Error(message, filename, lineNumber, columnNumber);
-
-      this["onerror"].call(null, new TCPSocketEvent(type, this, error));
-    }
-  },
-  /* end nsITCPServerSocketInternal method */
-
-  close: function tss_close() {
-    if (this._inChild) {
-      this._serverBridge.close();
-      return;
-    }
-
-    /* Close ServerSocket */
-    if (this._neckoTCPServerSocket) {
-      this._neckoTCPServerSocket.close();
-    }
-  },
-
-  // nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
-  onSocketAccepted: function tss_onSocketAccepted(server, trans) {
-    // precondition: this._inChild == false
-    try {
-      let that = TCPSocketInternal.createAcceptedParent(trans, this._binaryType,
-                                                        this.useWin);
-      this._callListenerAcceptCommon(that);
-    }
-    catch(e) {
-      trans.close(Cr.NS_BINDING_ABORTED);
-    }
-  },
-
-  // nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
-  onStopListening: function tss_onStopListening(server, status) {
-    if (status != Cr.NS_BINDING_ABORTED) {
-      throw new Error("Server socket was closed by unexpected reason.");
-    }
-    this._neckoTCPServerSocket = null;
-  },
-
-  classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
-
-  classInfo: XPCOMUtils.generateCI({
-    classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
-    classDescription: "Server TCP Socket",
-    interfaces: [
-      Ci.nsIDOMTCPServerSocket,
-      Ci.nsISupportsWeakReference
-    ],
-    flags: Ci.nsIClassInfo.DOM_OBJECT,
-  }),
-
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIDOMTCPServerSocket,
-    Ci.nsITCPServerSocketInternal,
-    Ci.nsISupportsWeakReference
-  ])
-}
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPServerSocket]);
--- a/dom/network/TCPServerSocketChild.cpp
+++ b/dom/network/TCPServerSocketChild.cpp
@@ -1,34 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TCPServerSocketChild.h"
 #include "TCPSocketChild.h"
+#include "TCPServerSocket.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/TabChild.h"
-#include "nsIDOMTCPSocket.h"
 #include "nsJSUtils.h"
 #include "jsfriendapi.h"
 
 using mozilla::net::gNeckoChild;
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION(TCPServerSocketChildBase, mServerSocket)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketChildBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase)
-  NS_INTERFACE_MAP_ENTRY(nsITCPServerSocketChild)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 TCPServerSocketChildBase::TCPServerSocketChildBase()
 : mIPCOpen(false)
 {
 }
 
@@ -41,28 +40,22 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
   nsrefcnt refcnt = TCPServerSocketChildBase::Release();
   if (refcnt == 1 && mIPCOpen) {
     PTCPServerSocketChild::SendRequestDelete();
     return 1;
   }
   return refcnt;
 }
 
-TCPServerSocketChild::TCPServerSocketChild()
-{
-}
-
-NS_IMETHODIMP
-TCPServerSocketChild::Listen(nsITCPServerSocketInternal* aServerSocket, uint16_t aLocalPort,
-                             uint16_t aBacklog, const nsAString & aBinaryType, JSContext* aCx)
+TCPServerSocketChild::TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
+                                           uint16_t aBacklog, bool aUseArrayBuffers)
 {
   mServerSocket = aServerSocket;
   AddIPDLReference();
-  gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, nsString(aBinaryType));
-  return NS_OK;
+  gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, aUseArrayBuffers);
 }
 
 void
 TCPServerSocketChildBase::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
   this->Release();
@@ -78,40 +71,22 @@ TCPServerSocketChildBase::AddIPDLReferen
 
 TCPServerSocketChild::~TCPServerSocketChild()
 {
 }
 
 bool
 TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket)
 {
-  TCPSocketChild* socket = static_cast<TCPSocketChild*>(psocket);
-
-  nsresult rv = mServerSocket->CallListenerAccept(static_cast<nsITCPSocketChild*>(socket));
-  if (NS_FAILED(rv)) {
-    NS_WARNING("CallListenerAccept threw exception.");
-  }
+  nsRefPtr<TCPSocketChild> socket = static_cast<TCPSocketChild*>(psocket);
+  nsresult rv = mServerSocket->AcceptChildSocket(socket);
+  NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
-bool
-TCPServerSocketChild::RecvCallbackError(const nsString& aMessage,
-                                        const nsString& aFilename,
-                                        const uint32_t& aLineNumber,
-                                        const uint32_t& aColumnNumber)
-{
-  nsresult rv = mServerSocket->CallListenerError(aMessage, aFilename,
-                                                 aLineNumber, aColumnNumber);
-  if (NS_FAILED(rv)) {
-    NS_WARNING("CallListenerError threw exception.");
-  }
-  return true;
-}
-
-NS_IMETHODIMP
+void
 TCPServerSocketChild::Close()
 {
   SendClose();
-  return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/network/TCPServerSocketChild.h
+++ b/dom/network/TCPServerSocketChild.h
@@ -1,54 +1,58 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifndef mozilla_dom_TCPServerSocketChild_h
+#define mozilla_dom_TCPServerSocketChild_h
+
 #include "mozilla/net/PTCPServerSocketChild.h"
-#include "nsITCPServerSocketChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 
 #define TCPSERVERSOCKETCHILD_CID \
   { 0x41a77ec8, 0xfd86, 0x409e, { 0xae, 0xa9, 0xaf, 0x2c, 0xa4, 0x07, 0xef, 0x8e } }
 
 class nsITCPServerSocketInternal;
 
 namespace mozilla {
 namespace dom {
 
-class TCPServerSocketChildBase : public nsITCPServerSocketChild {
+class TCPServerSocket;
+
+class TCPServerSocketChildBase : public nsISupports {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
 protected:
   TCPServerSocketChildBase();
   virtual ~TCPServerSocketChildBase();
 
-  nsCOMPtr<nsITCPServerSocketInternal> mServerSocket;
+  nsRefPtr<TCPServerSocket> mServerSocket;
   bool mIPCOpen;
 };
 
 class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild
                            , public TCPServerSocketChildBase
 {
 public:
-  NS_DECL_NSITCPSERVERSOCKETCHILD
   NS_IMETHOD_(MozExternalRefCountType) Release() override;
 
-  TCPServerSocketChild();
+  TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
+                       uint16_t aBacklog, bool aUseArrayBuffers);
   ~TCPServerSocketChild();
 
+  void Close();
+
   virtual bool RecvCallbackAccept(PTCPSocketChild *socket)  override;
-  virtual bool RecvCallbackError(const nsString& aMessage,
-                                 const nsString& aFilename,
-                                 const uint32_t& aLineNumber,
-                                 const uint32_t& aColumnNumber) override;
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif // mozilla_dom_TCPServerSocketChild_h
--- a/dom/network/TCPServerSocketParent.cpp
+++ b/dom/network/TCPServerSocketParent.cpp
@@ -1,41 +1,32 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIScriptSecurityManager.h"
+#include "TCPServerSocket.h"
 #include "TCPServerSocketParent.h"
 #include "nsJSUtils.h"
 #include "TCPSocketParent.h"
 #include "mozilla/unused.h"
 #include "mozilla/AppProcessChecker.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabParent.h"
 
 namespace mozilla {
 namespace dom {
 
-static void
-FireInteralError(mozilla::net::PTCPServerSocketParent* aActor,
-                 uint32_t aLineNo)
-{
-  mozilla::unused <<
-      aActor->SendCallbackError(NS_LITERAL_STRING("Internal error"),
-                          NS_LITERAL_STRING(__FILE__), aLineNo, 0);
-}
-
-NS_IMPL_CYCLE_COLLECTION(TCPServerSocketParent, mServerSocket, mIntermediary)
+NS_IMPL_CYCLE_COLLECTION(TCPServerSocketParent, mServerSocket)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketParent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketParent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketParent)
-  NS_INTERFACE_MAP_ENTRY(nsITCPServerSocketParent)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 void
 TCPServerSocketParent::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
@@ -45,36 +36,35 @@ TCPServerSocketParent::ReleaseIPDLRefere
 void
 TCPServerSocketParent::AddIPDLReference()
 {
   MOZ_ASSERT(!mIPCOpen);
   mIPCOpen = true;
   this->AddRef();
 }
 
-bool
-TCPServerSocketParent::Init(PNeckoParent* neckoParent, const uint16_t& aLocalPort,
-                            const uint16_t& aBacklog, const nsString& aBinaryType)
+TCPServerSocketParent::TCPServerSocketParent(PNeckoParent* neckoParent,
+                                             uint16_t aLocalPort,
+                                             uint16_t aBacklog,
+                                             bool aUseArrayBuffers)
+: mNeckoParent(neckoParent)
+, mIPCOpen(false)
 {
-  mNeckoParent = neckoParent;
+  mServerSocket = new TCPServerSocket(nullptr, aLocalPort, aUseArrayBuffers, aBacklog);
+  mServerSocket->SetServerBridgeParent(this);
+}
 
-  nsresult rv;
-  mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
-  if (NS_FAILED(rv)) {
-    FireInteralError(this, __LINE__);
-    return true;
-  }
+TCPServerSocketParent::~TCPServerSocketParent()
+{
+}
 
-  rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, GetAppId(),
-                             GetInBrowser(), getter_AddRefs(mServerSocket));
-  if (NS_FAILED(rv) || !mServerSocket) {
-    FireInteralError(this, __LINE__);
-    return true;
-  }
-  return true;
+void
+TCPServerSocketParent::Init()
+{
+  NS_ENSURE_SUCCESS_VOID(mServerSocket->Init());
 }
 
 uint32_t
 TCPServerSocketParent::GetAppId()
 {
   uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
   const PContentParent *content = Manager()->Manager();
   const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
@@ -93,23 +83,20 @@ TCPServerSocketParent::GetInBrowser()
   const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
   if (browsers.Length() > 0) {
     TabParent *tab = TabParent::GetFrom(browsers[0]);
     inBrowser = tab->IsBrowserElement();
   }
   return inBrowser;
 }
 
-NS_IMETHODIMP
-TCPServerSocketParent::SendCallbackAccept(nsITCPSocketParent *socket)
+nsresult
+TCPServerSocketParent::SendCallbackAccept(TCPSocketParent *socket)
 {
-  TCPSocketParent* _socket = static_cast<TCPSocketParent*>(socket);
-  PTCPSocketParent* _psocket = static_cast<PTCPSocketParent*>(_socket);
-
-  _socket->AddIPDLReference();
+  socket->AddIPDLReference();
 
   nsresult rv;
 
   nsString host;
   rv = socket->GetHost(host);
   if (NS_FAILED(rv)) {
     NS_ERROR("Failed to get host from nsITCPSocketParent");
     return NS_ERROR_FAILURE;
@@ -118,61 +105,62 @@ TCPServerSocketParent::SendCallbackAccep
   uint16_t port;
   rv = socket->GetPort(&port);
   if (NS_FAILED(rv)) {
     NS_ERROR("Failed to get port from nsITCPSocketParent");
     return NS_ERROR_FAILURE;
   }
 
   if (mNeckoParent) {
-    if (mNeckoParent->SendPTCPSocketConstructor(_psocket, host, port)) {
-      mozilla::unused << PTCPServerSocketParent::SendCallbackAccept(_psocket);
+    if (mNeckoParent->SendPTCPSocketConstructor(socket, host, port)) {
+      mozilla::unused << PTCPServerSocketParent::SendCallbackAccept(socket);
     }
     else {
       NS_ERROR("Sending data from PTCPSocketParent was failed.");
     };
   }
   else {
     NS_ERROR("The member value for NeckoParent is wrong.");
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP
-TCPServerSocketParent::SendCallbackError(const nsAString& message,
-                                         const nsAString& filename,
-                                         uint32_t lineNumber,
-                                         uint32_t columnNumber)
-{
-  mozilla::unused <<
-    PTCPServerSocketParent::SendCallbackError(nsString(message), nsString(filename),
-                                              lineNumber, columnNumber);
-  return NS_OK;
-}
-
 bool
 TCPServerSocketParent::RecvClose()
 {
   NS_ENSURE_TRUE(mServerSocket, true);
   mServerSocket->Close();
   return true;
 }
 
 void
 TCPServerSocketParent::ActorDestroy(ActorDestroyReason why)
 {
   if (mServerSocket) {
     mServerSocket->Close();
     mServerSocket = nullptr;
   }
   mNeckoParent = nullptr;
-  mIntermediary = nullptr;
 }
 
 bool
 TCPServerSocketParent::RecvRequestDelete()
 {
   mozilla::unused << Send__delete__(this);
   return true;
 }
 
+void
+TCPServerSocketParent::OnConnect(TCPServerSocketEvent* event)
+{
+  nsRefPtr<TCPSocket> socket = event->Socket();
+  socket->SetAppIdAndBrowser(GetAppId(), GetInBrowser());
+
+  nsRefPtr<TCPSocketParent> socketParent = new TCPSocketParent();
+  socketParent->SetSocket(socket);
+
+  socket->SetSocketBridgeParent(socketParent);
+
+  SendCallbackAccept(socketParent);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/network/TCPServerSocketParent.h
+++ b/dom/network/TCPServerSocketParent.h
@@ -1,52 +1,60 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifndef mozilla_dom_TCPServerSocketParent_h
+#define mozilla_dom_TCPServerSocketParent_h
+
 #include "mozilla/net/PNeckoParent.h"
 #include "mozilla/net/PTCPServerSocketParent.h"
-#include "nsITCPSocketParent.h"
-#include "nsITCPServerSocketParent.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
-#include "nsIDOMTCPSocket.h"
 
 namespace mozilla {
 namespace dom {
 
+class TCPServerSocket;
+class TCPServerSocketEvent;
+class TCPSocketParent;
+
 class TCPServerSocketParent : public mozilla::net::PTCPServerSocketParent
-                            , public nsITCPServerSocketParent
+                            , public nsISupports
 {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketParent)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_NSITCPSERVERSOCKETPARENT
 
-  TCPServerSocketParent() : mNeckoParent(nullptr), mIPCOpen(false) {}
+  TCPServerSocketParent(PNeckoParent* neckoParent, uint16_t aLocalPort,
+                        uint16_t aBacklog, bool aUseArrayBuffers);
 
-  bool Init(PNeckoParent* neckoParent, const uint16_t& aLocalPort, const uint16_t& aBacklog,
-            const nsString& aBinaryType);
+  void Init();
 
   virtual bool RecvClose() override;
   virtual bool RecvRequestDelete() override;
 
   uint32_t GetAppId();
   bool GetInBrowser();
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
+  void OnConnect(TCPServerSocketEvent* event);
+
 private:
-  ~TCPServerSocketParent() {}
+  ~TCPServerSocketParent();
+
+  nsresult SendCallbackAccept(TCPSocketParent *socket);
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   PNeckoParent* mNeckoParent;
-  nsCOMPtr<nsITCPSocketIntermediary> mIntermediary;
-  nsCOMPtr<nsIDOMTCPServerSocket> mServerSocket;
+  nsRefPtr<TCPServerSocket> mServerSocket;
   bool mIPCOpen;
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif // mozilla_dom_TCPServerSocketParent_h
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPSocket.cpp
@@ -0,0 +1,1191 @@
+/* -*- 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 "mozilla/ErrorResult.h"
+#include "TCPSocket.h"
+#include "TCPServerSocket.h"
+#include "TCPSocketChild.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/dom/TCPSocketErrorEvent.h"
+#include "mozilla/dom/TCPSocketErrorEventBinding.h"
+#include "mozilla/dom/TCPSocketEvent.h"
+#include "mozilla/dom/TCPSocketEventBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIArrayBufferInputStream.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIInputStream.h"
+#include "nsIBinaryInputStream.h"
+#include "nsIScriptableInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransport.h"
+#include "nsIOutputStream.h"
+#include "nsINSSErrorsService.h"
+#include "nsISSLSocketControl.h"
+#include "nsStringStream.h"
+#include "secerr.h"
+#include "sslerr.h"
+#ifdef MOZ_WIDGET_GONK
+#include "nsINetworkStatsServiceProxy.h"
+#include "nsINetworkManager.h"
+#include "nsINetworkInterface.h"
+#endif
+
+#define BUFFER_SIZE 65536
+#define NETWORK_STATS_THRESHOLD 65536
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(LegacyMozTCPSocket, mGlobal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyMozTCPSocket)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyMozTCPSocket)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyMozTCPSocket)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+LegacyMozTCPSocket::LegacyMozTCPSocket(nsPIDOMWindow* aWindow)
+: mGlobal(do_QueryInterface(aWindow))
+{
+}
+
+LegacyMozTCPSocket::~LegacyMozTCPSocket()
+{
+}
+
+already_AddRefed<TCPSocket>
+LegacyMozTCPSocket::Open(const nsAString& aHost,
+                         uint16_t aPort,
+                         const SocketOptions& aOptions,
+                         mozilla::ErrorResult& aRv)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(mGlobal))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+  GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
+  return TCPSocket::Constructor(globalObj, aHost, aPort, aOptions, aRv);
+}
+
+already_AddRefed<TCPServerSocket>
+LegacyMozTCPSocket::Listen(uint16_t aPort,
+                           const ServerSocketOptions& aOptions,
+                           uint16_t aBacklog,
+                           mozilla::ErrorResult& aRv)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(mGlobal))) {
+    return nullptr;
+  }
+  GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
+  return TCPServerSocket::Constructor(globalObj, aPort, aOptions, aBacklog, aRv);
+}
+
+bool
+LegacyMozTCPSocket::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto,
+                               JS::MutableHandle<JSObject*> aReflector)
+{
+  return LegacyMozTCPSocketBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPSocket,
+                                               DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPSocket,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransport)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketInputStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketOutputStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamPump)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamScriptable)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamBinary)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMultiplexStream)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMultiplexStreamCopier)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingDataAfterStartTLS)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeChild)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPSocket,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransport)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketInputStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketOutputStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamPump)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamScriptable)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamBinary)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMultiplexStream)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMultiplexStreamCopier)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingDataAfterStartTLS)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeChild)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TCPSocket)
+  NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY(nsITCPSocketCallback)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
+                     bool aSsl, bool aUseArrayBuffers)
+  : DOMEventTargetHelper(aGlobal)
+  , mReadyState(TCPReadyState::Closed)
+  , mUseArrayBuffers(aUseArrayBuffers)
+  , mHost(aHost)
+  , mPort(aPort)
+  , mSsl(aSsl)
+  , mAsyncCopierActive(false)
+  , mWaitingForDrain(false)
+  , mInnerWindowID(0)
+  , mBufferedAmount(0)
+  , mSuspendCount(0)
+  , mTrackingNumber(0)
+  , mWaitingForStartTLS(false)
+#ifdef MOZ_WIDGET_GONK
+  , mTxBytes(0)
+  , mRxBytes(0)
+  , mAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
+  , mInBrowser(false)
+#endif
+{
+  if (aGlobal) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+    if (window && window->IsOuterWindow()) {
+      window = window->GetCurrentInnerWindow();
+    }
+    if (window) {
+      mInnerWindowID = window->WindowID();
+    }
+  }
+}
+
+TCPSocket::~TCPSocket()
+{
+}
+
+nsresult
+TCPSocket::CreateStream()
+{
+  nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If the other side is not listening, we will
+  // get an onInputStreamReady callback where available
+  // raises to indicate the connection was refused.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mSocketInputStream);
+  NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+
+  rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mUseArrayBuffers) {
+    mInputStreamBinary = do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mInputStreamScriptable->Init(mSocketInputStream);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsISocketTransportService> sts =
+      do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(sts);
+  rv = mMultiplexStreamCopier->Init(mMultiplexStream,
+                                    mSocketOutputStream,
+                                    target,
+                                    true, /* source buffered */
+                                    false, /* sink buffered */
+                                    BUFFER_SIZE,
+                                    false, /* close source */
+                                    false); /* close sink */
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+nsresult
+TCPSocket::InitWithUnconnectedTransport(nsISocketTransport* aTransport)
+{
+  mReadyState = TCPReadyState::Connecting;
+  mTransport = aTransport;
+
+  MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
+
+  nsCOMPtr<nsIThread> mainThread;
+  NS_GetMainThread(getter_AddRefs(mainThread));
+  mTransport->SetEventSink(this, mainThread);
+
+  nsresult rv = CreateStream();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+TCPSocket::Init()
+{
+  nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
+  if (obs) {
+    obs->AddObserver(this, "inner-window-destroyed", true);
+  }
+
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    mReadyState = TCPReadyState::Connecting;
+    mSocketBridgeChild = new TCPSocketChild(mHost, mPort);
+    mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers);
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsISocketTransportService> sts =
+    do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+  const char* socketTypes[1];
+  if (mSsl) {
+    socketTypes[0] = "ssl";
+  } else {
+    socketTypes[0] = "starttls";
+  }
+  nsCOMPtr<nsISocketTransport> transport;
+  nsresult rv = sts->CreateTransport(socketTypes, 1, NS_ConvertUTF16toUTF8(mHost), mPort,
+                                     nullptr, getter_AddRefs(transport));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return InitWithUnconnectedTransport(transport);
+}
+
+void
+TCPSocket::InitWithSocketChild(TCPSocketChild* aSocketBridge)
+{
+  mSocketBridgeChild = aSocketBridge;
+  mReadyState = TCPReadyState::Open;
+  mSocketBridgeChild->SetSocket(this);
+  mSocketBridgeChild->GetHost(mHost);
+  mSocketBridgeChild->GetPort(&mPort);
+}
+
+nsresult
+TCPSocket::InitWithTransport(nsISocketTransport* aTransport)
+{
+  mTransport = aTransport;
+  nsresult rv = CreateStream();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mReadyState = TCPReadyState::Open;
+  rv = CreateInputStreamPump();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString host;
+  mTransport->GetHost(host);
+  mHost = NS_ConvertUTF8toUTF16(host);
+  int32_t port;
+  mTransport->GetPort(&port);
+  mPort = port;
+
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsINetworkManager> networkManager = do_GetService("@mozilla.org/network/manager;1");
+  if (networkManager) {
+    networkManager->GetActiveNetworkInfo(getter_AddRefs(mActiveNetworkInfo));
+  }
+#endif
+
+  return NS_OK;
+}
+
+void
+TCPSocket::UpgradeToSecure(mozilla::ErrorResult& aRv)
+{
+  if (mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (mSsl) {
+    return;
+  }
+
+  mSsl = true;
+
+  if (mSocketBridgeChild) {
+    mSocketBridgeChild->SendStartTLS();
+    return;
+  }
+
+  uint32_t count = 0;
+  mMultiplexStream->GetCount(&count);
+  if (!count) {
+    ActivateTLS();
+  } else {
+    mWaitingForStartTLS = true;
+  }
+}
+
+namespace {
+class CopierCallbacks final : public nsIRequestObserver
+{
+  nsRefPtr<TCPSocket> mOwner;
+public:
+  explicit CopierCallbacks(TCPSocket* aSocket) : mOwner(aSocket) {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+private:
+  ~CopierCallbacks() {}
+};
+
+NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
+
+NS_IMETHODIMP
+CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
+{
+  mOwner->NotifyCopyComplete(aStatus);
+  return NS_OK;
+}
+} // unnamed namespace
+
+nsresult
+TCPSocket::EnsureCopying()
+{
+  if (mAsyncCopierActive) {
+    return NS_OK;
+  }
+
+  mAsyncCopierActive = true;
+  nsRefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
+  return mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr);
+}
+
+void
+TCPSocket::NotifyCopyComplete(nsresult aStatus)
+{
+  mAsyncCopierActive = false;
+  mMultiplexStream->RemoveStream(0);
+  if (mSocketBridgeParent) {
+    mozilla::unused << mSocketBridgeParent->SendUpdateBufferedAmount(BufferedAmount(),
+                                                                     mTrackingNumber);
+  }
+
+  if (NS_FAILED(aStatus)) {
+    MaybeReportErrorAndCloseIfOpen(aStatus);
+    return;
+  }
+
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  if (count) {
+    EnsureCopying();
+    return;
+  }
+
+  // If we are waiting for initiating starttls, we can begin to
+  // activate tls now.
+  if (mWaitingForStartTLS && mReadyState == TCPReadyState::Open) {
+    ActivateTLS();
+    mWaitingForStartTLS = false;
+    // If we have pending data, we should send them, or fire
+    // a drain event if we are waiting for it.
+    if (!mPendingDataAfterStartTLS.IsEmpty()) {
+      while (!mPendingDataAfterStartTLS.IsEmpty()) {
+        nsCOMPtr<nsIInputStream> stream = mPendingDataAfterStartTLS[0];
+        mMultiplexStream->AppendStream(stream);
+        mPendingDataAfterStartTLS.RemoveElementAt(0);
+      }
+      EnsureCopying();
+      return;
+    }
+  }
+
+  // If we have a connected child, we let the child decide whether
+  // ondrain should be dispatched.
+  if (mWaitingForDrain && !mSocketBridgeParent) {
+    mWaitingForDrain = false;
+    FireEvent(NS_LITERAL_STRING("drain"));
+  }
+
+  if (mReadyState == TCPReadyState::Closing) {
+    mSocketOutputStream->Close();
+    mReadyState = TCPReadyState::Closed;
+    FireEvent(NS_LITERAL_STRING("close"));
+  }
+}
+
+void
+TCPSocket::ActivateTLS()
+{
+  nsCOMPtr<nsISupports> securityInfo;
+  mTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
+  nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(securityInfo);
+  if (socketControl) {
+    socketControl->StartTLS();
+  }
+}
+
+NS_IMETHODIMP
+TCPSocket::FireErrorEvent(const nsAString& aName, const nsAString& aType)
+{
+  if (mSocketBridgeParent) {
+    mSocketBridgeParent->FireErrorEvent(aName, aType, mReadyState);
+    return NS_OK;
+  }
+
+  TCPSocketErrorEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mName = aName;
+  init.mMessage = aType;
+
+  nsRefPtr<TCPSocketErrorEvent> event =
+    TCPSocketErrorEvent::Constructor(this, NS_LITERAL_STRING("error"), init);
+  MOZ_ASSERT(event);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::FireEvent(const nsAString& aType)
+{
+  if (mSocketBridgeParent) {
+    mSocketBridgeParent->FireEvent(aType, mReadyState);
+    return NS_OK;
+  }
+
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+    return NS_ERROR_FAILURE;
+  }
+  JS::Rooted<JS::Value> val(api.cx());
+  return FireDataEvent(api.cx(), aType, val);
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataArrayEvent(const nsAString& aType,
+                              const InfallibleTArray<uint8_t>& buffer)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+    return NS_ERROR_FAILURE;
+  }
+  JSContext* cx = api.cx();
+  JS::Rooted<JS::Value> val(cx);
+
+  bool ok = IPC::DeserializeArrayBuffer(cx, buffer, &val);
+  if (ok) {
+    return FireDataEvent(cx, aType, val);
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataStringEvent(const nsAString& aType,
+                               const nsACString& aString)
+{
+  AutoJSAPI api;
+  if (NS_WARN_IF(!api.Init(GetOwner()))) {
+    return NS_ERROR_FAILURE;
+  }
+  JSContext* cx = api.cx();
+  JS::Rooted<JS::Value> val(cx);
+
+  bool ok = ToJSValue(cx, NS_ConvertASCIItoUTF16(aString), &val);
+  if (ok) {
+    return FireDataEvent(cx, aType, val);
+  }
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataEvent(JSContext* aCx, const nsAString& aType, JS::Handle<JS::Value> aData)
+{
+  MOZ_ASSERT(!mSocketBridgeParent);
+
+  RootedDictionary<TCPSocketEventInit> init(aCx);
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mData = aData;
+
+  nsRefPtr<TCPSocketEvent> event =
+    TCPSocketEvent::Constructor(this, aType, init);
+  event->SetTrusted(true);
+  bool dummy;
+  DispatchEvent(event, &dummy);
+  return NS_OK;
+}
+
+JSObject*
+TCPSocket::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TCPSocketBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+TCPSocket::GetHost(nsAString& aHost)
+{
+  aHost.Assign(mHost);
+}
+
+uint32_t
+TCPSocket::Port()
+{
+  return mPort;
+}
+
+bool
+TCPSocket::Ssl()
+{
+  return mSsl;
+}
+
+uint64_t
+TCPSocket::BufferedAmount()
+{
+  if (mSocketBridgeChild) {
+    return mBufferedAmount;
+  }
+  if (mMultiplexStream) {
+    uint64_t available = 0;
+    mMultiplexStream->Available(&available);
+    return available;
+  }
+  return 0;
+}
+
+void
+TCPSocket::Suspend()
+{
+  if (mSocketBridgeChild) {
+    mSocketBridgeChild->SendSuspend();
+    return;
+  }
+  if (mInputStreamPump) {
+    mInputStreamPump->Suspend();
+  }
+  mSuspendCount++;
+}
+
+void
+TCPSocket::Resume(mozilla::ErrorResult& aRv)
+{
+  if (mSocketBridgeChild) {
+    mSocketBridgeChild->SendResume();
+    return;
+  }
+  if (!mSuspendCount) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  if (mInputStreamPump) {
+    mInputStreamPump->Resume();
+  }
+  mSuspendCount--;
+}
+
+nsresult
+TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) {
+#ifdef MOZ_WIDGET_GONK
+  // Save network statistics once the connection is closed.
+  // For now this function is Gonk-specific.
+  SaveNetworkStats(true);
+#endif
+
+  // If we're closed, we've already reported the error or just don't need to
+  // report the error.
+  if (mReadyState == TCPReadyState::Closed) {
+    return NS_OK;
+  }
+  mReadyState = TCPReadyState::Closed;
+
+  if (NS_FAILED(status)) {
+    // Convert the status code to an appropriate error message.
+
+    nsString errorType, errName;
+
+    // security module? (and this is an error)
+    if ((static_cast<uint32_t>(status) & 0xFF0000) == 0x5a0000) {
+      nsCOMPtr<nsINSSErrorsService> errSvc = do_GetService("@mozilla.org/nss_errors_service;1");
+      // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+      // somehow not in the set of covered errors.
+      uint32_t errorClass;
+      nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+      if (NS_FAILED(rv)) {
+        errorType.AssignLiteral("SecurityProtocol");
+      } else {
+        switch (errorClass) {
+          case nsINSSErrorsService::ERROR_CLASS_BAD_CERT:
+            errorType.AssignLiteral("SecurityCertificate");
+            break;
+          default:
+            errorType.AssignLiteral("SecurityProtocol");
+            break;
+        }
+      }
+
+      // NSS_SEC errors (happen below the base value because of negative vals)
+      if ((static_cast<int32_t>(status) & 0xFFFF) < abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) {
+        switch (static_cast<SECErrorCodes>(status)) {
+          case SEC_ERROR_EXPIRED_CERTIFICATE:
+            errName.AssignLiteral("SecurityExpiredCertificateError");
+            break;
+          case SEC_ERROR_REVOKED_CERTIFICATE:
+            errName.AssignLiteral("SecurityRevokedCertificateError");
+            break;
+            // per bsmith, we will be unable to tell these errors apart very soon,
+            // so it makes sense to just folder them all together already.
+          case SEC_ERROR_UNKNOWN_ISSUER:
+          case SEC_ERROR_UNTRUSTED_ISSUER:
+          case SEC_ERROR_UNTRUSTED_CERT:
+          case SEC_ERROR_CA_CERT_INVALID:
+            errName.AssignLiteral("SecurityUntrustedCertificateIssuerError");
+            break;
+          case SEC_ERROR_INADEQUATE_KEY_USAGE:
+            errName.AssignLiteral("SecurityInadequateKeyUsageError");
+            break;
+          case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+            errName.AssignLiteral("SecurityCertificateSignatureAlgorithmDisabledError");
+            break;
+          default:
+            errName.AssignLiteral("SecurityError");
+            break;
+        }
+      } else {
+        // NSS_SSL errors
+        switch (static_cast<SSLErrorCodes>(status)) {
+          case SSL_ERROR_NO_CERTIFICATE:
+            errName.AssignLiteral("SecurityNoCertificateError");
+            break;
+          case SSL_ERROR_BAD_CERTIFICATE:
+            errName.AssignLiteral("SecurityBadCertificateError");
+            break;
+          case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:
+            errName.AssignLiteral("SecurityUnsupportedCertificateTypeError");
+            break;
+          case SSL_ERROR_UNSUPPORTED_VERSION:
+            errName.AssignLiteral("SecurityUnsupportedTLSVersionError");
+            break;
+          case SSL_ERROR_BAD_CERT_DOMAIN:
+            errName.AssignLiteral("SecurityCertificateDomainMismatchError");
+            break;
+          default:
+            errName.AssignLiteral("SecurityError");
+            break;
+        }
+      }
+    } else {
+      // must be network
+      errorType.AssignLiteral("Network");
+
+      switch (status) {
+        // connect to host:port failed
+        case NS_ERROR_CONNECTION_REFUSED:
+          errName.AssignLiteral("ConnectionRefusedError");
+          break;
+          // network timeout error
+        case NS_ERROR_NET_TIMEOUT:
+          errName.AssignLiteral("NetworkTimeoutError");
+          break;
+          // hostname lookup failed
+        case NS_ERROR_UNKNOWN_HOST:
+          errName.AssignLiteral("DomainNotFoundError");
+          break;
+        case NS_ERROR_NET_INTERRUPT:
+          errName.AssignLiteral("NetworkInterruptError");
+          break;
+        default:
+          errName.AssignLiteral("NetworkError");
+          break;
+      }
+    }
+
+    NS_WARN_IF(NS_FAILED(FireErrorEvent(errName, errorType)));
+  }
+
+  return FireEvent(NS_LITERAL_STRING("close"));
+}
+
+void
+TCPSocket::Close()
+{
+  if (mReadyState == TCPReadyState::Closed || mReadyState == TCPReadyState::Closing) {
+    return;
+  }
+
+  mReadyState = TCPReadyState::Closing;
+
+  if (mSocketBridgeChild) {
+    mSocketBridgeChild->SendClose();
+    return;
+  }
+
+  uint32_t count = 0;
+  mMultiplexStream->GetCount(&count);
+  if (!count) {
+    mSocketOutputStream->Close();
+  }
+  mSocketInputStream->Close();
+}
+
+void
+TCPSocket::SendWithTrackingNumber(const nsACString& aData,
+                                  const uint32_t& aTrackingNumber,
+                                  mozilla::ErrorResult& aRv)
+{
+  MOZ_ASSERT(mSocketBridgeParent);
+  mTrackingNumber = aTrackingNumber;
+  // The JSContext isn't necessary for string values; it's a codegen limitation.
+  Send(nullptr, aData, aRv);
+}
+
+bool
+TCPSocket::Send(JSContext* aCx, const nsACString& aData, mozilla::ErrorResult& aRv)
+{
+  if (mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return false;
+  }
+
+  uint64_t byteLength;
+  nsCOMPtr<nsIInputStream> stream;
+  if (mSocketBridgeChild) {
+    mSocketBridgeChild->SendSend(aData, ++mTrackingNumber);
+    byteLength = aData.Length();
+  } else {
+    nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), aData);
+    if (NS_FAILED(rv)) {
+      aRv.Throw(rv);
+      return false;
+    }
+    rv = stream->Available(&byteLength);
+    if (NS_FAILED(rv)) {
+      aRv.Throw(rv);
+      return false;
+    }
+  }
+  return Send(stream, byteLength);
+}
+
+void
+TCPSocket::SendWithTrackingNumber(JSContext* aCx,
+                                  const ArrayBuffer& aData,
+                                  uint32_t aByteOffset,
+                                  const Optional<uint32_t>& aByteLength,
+                                  const uint32_t& aTrackingNumber,
+                                  mozilla::ErrorResult& aRv)
+{
+  MOZ_ASSERT(mSocketBridgeParent);
+  mTrackingNumber = aTrackingNumber;
+  Send(aCx, aData, aByteOffset, aByteLength, aRv);
+}
+
+bool
+TCPSocket::Send(JSContext* aCx,
+                const ArrayBuffer& aData,
+                uint32_t aByteOffset,
+                const Optional<uint32_t>& aByteLength,
+                mozilla::ErrorResult& aRv)
+{
+  if (mReadyState != TCPReadyState::Open) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return false;
+  }
+
+  nsCOMPtr<nsIArrayBufferInputStream> stream;
+
+  aData.ComputeLengthAndData();
+  uint32_t byteLength = aByteLength.WasPassed() ? aByteLength.Value() : aData.Length();
+
+  if (mSocketBridgeChild) {
+    nsresult rv = mSocketBridgeChild->SendSend(aData, aByteOffset, byteLength, ++mTrackingNumber);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.Throw(rv);
+      return false;
+    }
+  } else {
+    JS::Rooted<JSObject*> obj(aCx, aData.Obj());
+    JSAutoCompartment ac(aCx, obj);
+    JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*obj));
+
+    stream = do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1");
+    nsresult rv = stream->SetData(value, aByteOffset, byteLength, aCx);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      aRv.Throw(rv);
+      return false;
+    }
+  }
+  return Send(stream, byteLength);
+}
+
+bool
+TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength)
+{
+  uint64_t newBufferedAmount = BufferedAmount() + aByteLength;
+  bool bufferFull = newBufferedAmount > BUFFER_SIZE;
+  if (bufferFull) {
+    // If we buffered more than some arbitrary amount of data,
+    // (65535 right now) we should tell the caller so they can
+    // wait until ondrain is called if they so desire. Once all the
+    // buffered data has been written to the socket, ondrain is
+    // called.
+    mWaitingForDrain = true;
+  }
+
+  if (mSocketBridgeChild) {
+    // In the child, we just add the buffer length to our bufferedAmount and let
+    // the parent update our bufferedAmount when the data have been sent.
+    mBufferedAmount = newBufferedAmount;
+    return !bufferFull;
+  }
+
+  if (mWaitingForStartTLS) {
+    // When we are waiting for starttls, newStream is added to pendingData
+    // and will be appended to multiplexStream after tls had been set up.
+    mPendingDataAfterStartTLS.AppendElement(aStream);
+  } else {
+    mMultiplexStream->AppendStream(aStream);
+  }
+
+  EnsureCopying();
+
+#ifdef MOZ_WIDGET_GONK
+  // Collect transmitted amount for network statistics.
+  mTxBytes += aByteLength;
+  SaveNetworkStats(false);
+#endif
+
+  return !bufferFull;
+}
+
+TCPReadyState
+TCPSocket::ReadyState()
+{
+  return mReadyState;
+}
+
+TCPSocketBinaryType
+TCPSocket::BinaryType()
+{
+  if (mUseArrayBuffers) {
+    return TCPSocketBinaryType::Arraybuffer;
+  } else {
+    return TCPSocketBinaryType::String;
+  }
+}
+
+already_AddRefed<TCPSocket>
+TCPSocket::CreateAcceptedSocket(nsIGlobalObject* aGlobal,
+                                nsISocketTransport* aTransport,
+                                bool aUseArrayBuffers)
+{
+  nsRefPtr<TCPSocket> socket = new TCPSocket(aGlobal, EmptyString(), 0, false, aUseArrayBuffers);
+  nsresult rv = socket->InitWithTransport(aTransport);
+  NS_ENSURE_SUCCESS(rv, nullptr);
+  return socket.forget();
+}
+
+already_AddRefed<TCPSocket>
+TCPSocket::CreateAcceptedSocket(nsIGlobalObject* aGlobal,
+                                TCPSocketChild* aBridge,
+                                bool aUseArrayBuffers)
+{
+  nsRefPtr<TCPSocket> socket = new TCPSocket(aGlobal, EmptyString(), 0, false, aUseArrayBuffers);
+  socket->InitWithSocketChild(aBridge);
+  return socket.forget();
+}
+
+already_AddRefed<TCPSocket>
+TCPSocket::Constructor(const GlobalObject& aGlobal,
+                       const nsAString& aHost,
+                       uint16_t aPort,
+                       const SocketOptions& aOptions,
+                       mozilla::ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<TCPSocket> socket =
+    new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
+                  aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
+  nsresult rv = socket->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return socket.forget();
+}
+
+nsresult
+TCPSocket::CreateInputStreamPump()
+{
+  nsresult rv;
+  mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint64_t suspendCount = mSuspendCount;
+  while (suspendCount--) {
+    mInputStreamPump->Suspend();
+  }
+
+  rv = mInputStreamPump->AsyncRead(this, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+                             int64_t aProgress, int64_t aProgressMax)
+{
+  if (static_cast<uint32_t>(aStatus) != nsISocketTransport::STATUS_CONNECTED_TO) {
+    return NS_OK;
+  }
+
+  mReadyState = TCPReadyState::Open;
+  FireEvent(NS_LITERAL_STRING("open"));
+
+  nsresult rv = CreateInputStreamPump();
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+  // Only used for detecting if the connection was refused.
+
+  uint64_t dummy;
+  nsresult rv = aStream->Available(&dummy);
+  if (NS_FAILED(rv)) {
+    MaybeReportErrorAndCloseIfOpen(NS_ERROR_CONNECTION_REFUSED);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
+                           uint64_t aOffset, uint32_t aCount)
+{
+#ifdef MOZ_WIDGET_GONK
+  // Collect received amount for network statistics.
+  mRxBytes += aCount;
+  SaveNetworkStats(false);
+#endif
+
+  if (mUseArrayBuffers) {
+    nsTArray<uint8_t> buffer;
+    buffer.SetCapacity(aCount);
+    uint32_t actual;
+    nsresult rv = aStream->Read(reinterpret_cast<char*>(buffer.Elements()), aCount, &actual);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(actual == aCount);
+    buffer.SetLength(actual);
+
+    if (mSocketBridgeParent) {
+      mSocketBridgeParent->FireArrayBufferDataEvent(buffer, mReadyState);
+      return NS_OK;
+    }
+
+    AutoJSAPI api;
+    if (!api.Init(GetOwner())) {
+      return NS_ERROR_FAILURE;
+    }
+    JSContext* cx = api.cx();
+
+    JS::Rooted<JS::Value> value(cx);
+    if (!ToJSValue(cx, TypedArrayCreator<Uint8Array>(buffer), &value)) {
+      return NS_ERROR_FAILURE;
+    }
+    FireDataEvent(cx, NS_LITERAL_STRING("data"), value);
+    return NS_OK;
+  }
+
+  nsCString data;
+  nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (mSocketBridgeParent) {
+    mSocketBridgeParent->FireStringDataEvent(data, mReadyState);
+    return NS_OK;
+  }
+
+  AutoJSAPI api;
+  if (!api.Init(GetOwner())) {
+    return NS_ERROR_FAILURE;
+  }
+  JSContext* cx = api.cx();
+
+  JS::Rooted<JS::Value> value(cx);
+  if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) {
+    return NS_ERROR_FAILURE;
+  }
+  FireDataEvent(cx, NS_LITERAL_STRING("data"), value);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
+{
+  uint32_t count;
+  nsresult rv = mMultiplexStream->GetCount(&count);
+  NS_ENSURE_SUCCESS(rv, rv);
+  bool bufferedOutput = count != 0;
+
+  mInputStreamPump = nullptr;
+
+  if (bufferedOutput && NS_SUCCEEDED(aStatus)) {
+    // If we have some buffered output still, and status is not an
+    // error, the other side has done a half-close, but we don't
+    // want to be in the close state until we are done sending
+    // everything that was buffered. We also don't want to call onclose
+    // yet.
+    return NS_OK;
+  }
+
+  // We call this even if there is no error.
+  MaybeReportErrorAndCloseIfOpen(aStatus);
+  return NS_OK;
+}
+
+void
+TCPSocket::SetSocketBridgeParent(TCPSocketParent* aBridgeParent)
+{
+  mSocketBridgeParent = aBridgeParent;
+}
+
+void
+TCPSocket::SetAppIdAndBrowser(uint32_t aAppId, bool aInBrowser)
+{
+#ifdef MOZ_WIDGET_GONK
+  mAppId = aAppId;
+  mInBrowser = aInBrowser;
+#endif
+}
+
+NS_IMETHODIMP
+TCPSocket::UpdateReadyState(uint32_t aReadyState)
+{
+  MOZ_ASSERT(mSocketBridgeChild);
+  mReadyState = static_cast<TCPReadyState>(aReadyState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::UpdateBufferedAmount(uint32_t aBufferedAmount, uint32_t aTrackingNumber)
+{
+  if (aTrackingNumber != mTrackingNumber) {
+    return NS_OK;
+  }
+  mBufferedAmount = aBufferedAmount;
+  if (!mBufferedAmount) {
+    if (mWaitingForDrain) {
+      mWaitingForDrain = false;
+      return FireEvent(NS_LITERAL_STRING("drain"));
+    }
+  }
+  return NS_OK;
+}
+
+#ifdef MOZ_WIDGET_GONK
+void
+TCPSocket::SaveNetworkStats(bool aEnforce)
+{
+  if (!mTxBytes && !mRxBytes) {
+    // There is no traffic at all. No need to save statistics.
+    return;
+  }
+
+  // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy
+  // only when the total amount exceeds the predefined threshold value.
+  // The purpose is to avoid too much overhead for collecting statistics.
+  uint32_t totalBytes = mTxBytes + mRxBytes;
+  if (!aEnforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+    return;
+  }
+
+  nsCOMPtr<nsINetworkStatsServiceProxy> nssProxy =
+    do_GetService("@mozilla.org/networkstatsServiceProxy;1");
+  if (!nssProxy) {
+    return;
+  }
+
+  nssProxy->SaveAppStats(mAppId, mInBrowser, mActiveNetworkInfo, PR_Now(),
+                         mRxBytes, mTxBytes, false, nullptr);
+
+  // Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
+  mTxBytes = mRxBytes = 0;
+}
+#endif
+
+NS_IMETHODIMP
+TCPSocket::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  if (!strcmp(aTopic, "inner-window-destroyed")) {
+    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+    uint64_t innerID;
+    nsresult rv = wrapper->GetData(&innerID);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (innerID == mInnerWindowID) {
+      nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
+      if (obs) {
+        obs->RemoveObserver(this, "inner-window-destroyed");
+      }
+
+      Close();
+    }
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/network/TCPSocket.h
@@ -0,0 +1,258 @@
+/* -*- 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 mozilla_dom_TCPSocket_h
+#define mozilla_dom_TCPSocket_h
+
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsITransport.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupportsImpl.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITCPSocketCallback.h"
+#include "js/RootingAPI.h"
+
+class nsISocketTransport;
+class nsIInputStreamPump;
+class nsIScriptableInputStream;
+class nsIBinaryInputStream;
+class nsIMultiplexInputStream;
+class nsIAsyncStreamCopier;
+class nsIInputStream;
+class nsINetworkInfo;
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+class DOMError;
+struct ServerSocketOptions;
+class TCPServerSocket;
+class TCPSocketChild;
+class TCPSocketParent;
+
+// This interface is only used for legacy navigator.mozTCPSocket API compatibility.
+class LegacyMozTCPSocket : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(LegacyMozTCPSocket)
+
+  explicit LegacyMozTCPSocket(nsPIDOMWindow* aWindow);
+
+  already_AddRefed<TCPServerSocket>
+  Listen(uint16_t aPort,
+         const ServerSocketOptions& aOptions,
+         uint16_t aBacklog,
+         ErrorResult& aRv);
+
+  already_AddRefed<TCPSocket>
+  Open(const nsAString& aHost,
+       uint16_t aPort,
+       const SocketOptions& aOptions,
+       ErrorResult& aRv);
+
+  bool WrapObject(JSContext* aCx,
+                  JS::Handle<JSObject*> aGivenProto,
+                  JS::MutableHandle<JSObject*> aReflector);
+
+private:
+  virtual ~LegacyMozTCPSocket();
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+class TCPSocket final : public DOMEventTargetHelper
+                      , public nsIStreamListener
+                      , public nsITransportEventSink
+                      , public nsIInputStreamCallback
+                      , public nsIObserver
+                      , public nsSupportsWeakReference
+                      , public nsITCPSocketCallback
+{
+public:
+  TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
+            bool aSsl, bool aUseArrayBuffers);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPSocket, DOMEventTargetHelper)
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITRANSPORTEVENTSINK
+  NS_DECL_NSIINPUTSTREAMCALLBACK
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITCPSOCKETCALLBACK
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void GetHost(nsAString& aHost);
+  uint32_t Port();
+  bool Ssl();
+  uint64_t BufferedAmount();
+  void Suspend();
+  void Resume(ErrorResult& aRv);
+  void Close();
+  bool Send(JSContext* aCx, const nsACString& aData, ErrorResult& aRv);
+  bool Send(JSContext* aCx,
+            const ArrayBuffer& aData,
+            uint32_t aByteOffset,
+            const Optional<uint32_t>& aByteLength,
+            ErrorResult& aRv);
+  TCPReadyState ReadyState();
+  TCPSocketBinaryType BinaryType();
+  void UpgradeToSecure(ErrorResult& aRv);
+
+  static already_AddRefed<TCPSocket>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aHost,
+              uint16_t aPort,
+              const SocketOptions& aOptions,
+              ErrorResult& aRv);
+
+  // Perform a send operation that's asssociated with a sequence number. Used in
+  // IPC scenarios to track the number of bytes buffered at any given time.
+  void SendWithTrackingNumber(const nsACString& aData,
+                              const uint32_t& aTrackingNumber,
+                              ErrorResult& aRv);
+  void SendWithTrackingNumber(JSContext* aCx,
+                              const ArrayBuffer& aData,
+                              uint32_t aByteOffset,
+                              const Optional<uint32_t>& aByteLength,
+                              const uint32_t& aTrackingNumber,
+                              ErrorResult& aRv);
+  // Create a TCPSocket object from an existing low-level socket connection.
+  // Used by the TCPServerSocket implementation when a new connection is accepted.
+  static already_AddRefed<TCPSocket>
+  CreateAcceptedSocket(nsIGlobalObject* aGlobal, nsISocketTransport* aTransport, bool aUseArrayBuffers);
+  // Create a TCPSocket object from an existing child-side IPC actor.
+  // Used by the TCPServerSocketChild implementation when a new connection is accepted.
+  static already_AddRefed<TCPSocket>
+  CreateAcceptedSocket(nsIGlobalObject* aGlobal, TCPSocketChild* aSocketBridge, bool aUseArrayBuffers);
+
+  // Initialize this socket's associated app and browser information.
+  void SetAppIdAndBrowser(uint32_t aAppId, bool aInBrowser);
+  // Initialize this socket's associated IPC actor in the parent process.
+  void SetSocketBridgeParent(TCPSocketParent* aBridgeParent);
+
+  static bool SocketEnabled();
+
+  IMPL_EVENT_HANDLER(open);
+  IMPL_EVENT_HANDLER(drain);
+  IMPL_EVENT_HANDLER(data);
+  IMPL_EVENT_HANDLER(error);
+  IMPL_EVENT_HANDLER(close);
+
+  nsresult Init();
+
+  // Inform this socket that a buffered send() has completed sending.
+  void NotifyCopyComplete(nsresult aStatus);
+
+  // Initialize this socket from a low-level connection that hasn't connected yet
+  // (called from RecvOpenBind() in TCPSocketParent).
+  nsresult InitWithUnconnectedTransport(nsISocketTransport* aTransport);
+
+private:
+  ~TCPSocket();
+
+  // Initialize this socket with an existing IPC actor.
+  void InitWithSocketChild(TCPSocketChild* aBridge);
+  // Initialize this socket from an existing low-level connection.
+  nsresult InitWithTransport(nsISocketTransport* aTransport);
+  // Initialize the input/output streams for this socket object.
+  nsresult CreateStream();
+  // Initialize the asynchronous read operation from this socket's input stream.
+  nsresult CreateInputStreamPump();
+  // Send the contents of the provided input stream, which is assumed to be the given length
+  // for reporting and buffering purposes.
+  bool Send(nsIInputStream* aStream, uint32_t aByteLength);
+  // Begin an asynchronous copy operation if one is not already in progress.
+  nsresult EnsureCopying();
+  // Enable TLS on this socket.
+  void ActivateTLS();
+  // Dispatch an error event if necessary, then dispatch a "close" event.
+  nsresult MaybeReportErrorAndCloseIfOpen(nsresult status);
+#ifdef MOZ_WIDGET_GONK
+  // Store and reset any saved network stats for this socket.
+  void SaveNetworkStats(bool aEnforce);
+#endif
+
+  TCPReadyState mReadyState;
+  // Whether to use strings or array buffers for the "data" event.
+  bool mUseArrayBuffers;
+  nsString mHost;
+  uint16_t mPort;
+  // Whether this socket is using a secure transport.
+  bool mSsl;
+
+  // The associated IPC actor in a child process.
+  nsRefPtr<TCPSocketChild> mSocketBridgeChild;
+  // The associated IPC actor in a parent process.
+  nsRefPtr<TCPSocketParent> mSocketBridgeParent;
+
+  // Raw socket streams
+  nsCOMPtr<nsISocketTransport> mTransport;
+  nsCOMPtr<nsIInputStream> mSocketInputStream;
+  nsCOMPtr<nsIOutputStream> mSocketOutputStream;
+
+  // Input stream machinery
+  nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
+  nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;
+  nsCOMPtr<nsIBinaryInputStream> mInputStreamBinary;
+
+  // Output stream machinery
+  nsCOMPtr<nsIMultiplexInputStream> mMultiplexStream;
+  nsCOMPtr<nsIAsyncStreamCopier> mMultiplexStreamCopier;
+
+  // Is there an async copy operation in progress?
+  bool mAsyncCopierActive;
+  // True if the buffer is full and a "drain" event is expected by the client.
+  bool mWaitingForDrain;
+
+  // The id of the window that created this socket.
+  uint64_t mInnerWindowID;
+
+  // The current number of buffered bytes. Only used in content processes when IPC is enabled.
+  uint64_t mBufferedAmount;
+
+  // The number of times this socket has had `Suspend` called without a corresponding `Resume`.
+  uint32_t mSuspendCount;
+
+  // The current sequence number (ie. number of send operations) that have been processed.
+  // This is used in the IPC scenario by the child process to filter out outdated notifications
+  // about the amount of buffered data present in the parent process.
+  uint32_t mTrackingNumber;
+
+  // True if this socket has been upgraded to secure after the initial connection,
+  // but the actual upgrade is waiting for an in-progress copy operation to complete.
+  bool mWaitingForStartTLS;
+  // The buffered data awaiting the TLS upgrade to finish.
+  nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
+
+#ifdef MOZ_WIDGET_GONK
+  // Number of bytes sent.
+  uint32_t mTxBytes;
+  // Number of bytes received.
+  uint32_t mRxBytes;
+  // The app that owns this socket.
+  uint32_t mAppId;
+  // Was this socket created inside of a mozbrowser frame?
+  bool mInBrowser;
+  // The name of the active network used by this socket.
+  nsCOMPtr<nsINetworkInfo> mActiveNetworkInfo;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPSocket_h
deleted file mode 100644
--- a/dom/network/TCPSocket.js
+++ /dev/null
@@ -1,1005 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-const CC = Components.Constructor;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const InputStreamPump = CC(
-        "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"),
-      AsyncStreamCopier = CC(
-        "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
-      ScriptableInputStream = CC(
-        "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
-      BinaryInputStream = CC(
-        "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
-      StringInputStream = CC(
-        '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
-      ArrayBufferInputStream = CC(
-        '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'),
-      MultiplexInputStream = CC(
-        '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
-const TCPServerSocket = CC(
-        "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init");
-
-const kCONNECTING = 'connecting';
-const kOPEN = 'open';
-const kCLOSING = 'closing';
-const kCLOSED = 'closed';
-const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.';
-
-const BUFFER_SIZE = 65536;
-const NETWORK_STATS_THRESHOLD = 65536;
-
-// XXX we have no TCPError implementation right now because it's really hard to
-// do on b2g18.  On mozilla-central we want a proper TCPError that ideally
-// sub-classes DOMError.  Bug 867872 has been filed to implement this and
-// contains a documented TCPError.webidl that maps all the error codes we use in
-// this file to slightly more readable explanations.
-function createTCPError(aWindow, aErrorName, aErrorType) {
-  return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName);
-}
-
-
-/*
- * Debug logging function
- */
-
-var debug = false;
-function LOG(msg) {
-  if (debug)
-    dump("TCPSocket: " + msg + "\n");
-}
-
-/*
- * nsITCPSocketEvent object
- */
-
-function TCPSocketEvent(type, sock, data) {
-  this._type = type;
-  this._target = sock;
-  this._data = data;
-}
-
-// When this API moves to WebIDL and these __exposedProps__ go away, remove
-// this call here and remove the API from XPConnect.
-Cu.skipCOWCallableChecks();
-
-TCPSocketEvent.prototype = {
-  __exposedProps__: {
-    type: 'r',
-    target: 'r',
-    data: 'r',
-    // Promise::ResolveInternal tries to check if the thing being resolved is
-    // itself a promise through the presence of "then".  Accordingly, we list
-    // it as an exposed property, although we return undefined for it.
-    // Bug 882123 covers making TCPSocket be a proper event target with proper
-    // events.
-    then: 'r'
-  },
-  get type() {
-    return this._type;
-  },
-  get target() {
-    return this._target;
-  },
-  get data() {
-    return this._data;
-  },
-  get then() {
-    return undefined;
-  }
-}
-
-/*
- * nsIDOMTCPSocket object
- */
-
-function TCPSocket() {
-  this._readyState = kCLOSED;
-
-  this._onopen = null;
-  this._ondrain = null;
-  this._ondata = null;
-  this._onerror = null;
-  this._onclose = null;
-
-  this._binaryType = "string";
-
-  this._host = "";
-  this._port = 0;
-  this._ssl = false;
-
-  this.useWin = null;
-}
-
-TCPSocket.prototype = {
-  __exposedProps__: {
-    open: 'r',
-    host: 'r',
-    port: 'r',
-    ssl: 'r',
-    bufferedAmount: 'r',
-    suspend: 'r',
-    resume: 'r',
-    close: 'r',
-    send: 'r',
-    readyState: 'r',
-    binaryType: 'r',
-    listen: 'r',
-    onopen: 'rw',
-    ondrain: 'rw',
-    ondata: 'rw',
-    onerror: 'rw',
-    onclose: 'rw'
-  },
-  // The binary type, "string" or "arraybuffer"
-  _binaryType: null,
-
-  // Internal
-  _hasPrivileges: null,
-
-  // Raw socket streams
-  _transport: null,
-  _socketInputStream: null,
-  _socketOutputStream: null,
-
-  // Input stream machinery
-  _inputStreamPump: null,
-  _inputStreamScriptable: null,
-  _inputStreamBinary: null,
-
-  // Output stream machinery
-  _multiplexStream: null,
-  _multiplexStreamCopier: null,
-
-  _asyncCopierActive: false,
-  _waitingForDrain: false,
-  _suspendCount: 0,
-
-  // Reported parent process buffer
-  _bufferedAmount: 0,
-
-  // IPC socket actor
-  _socketBridge: null,
-
-  // StartTLS
-  _waitingForStartTLS: false,
-  _pendingDataAfterStartTLS: [],
-
-  // Used to notify when update bufferedAmount is updated.
-  _onUpdateBufferedAmount: null,
-  _trackingNumber: 0,
-
-#ifdef MOZ_WIDGET_GONK
-  // Network statistics (Gonk-specific feature)
-  _txBytes: 0,
-  _rxBytes: 0,
-  _appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
-  _inBrowser: false,
-  _activeNetworkInfo: null,
-#endif
-
-  // Public accessors.
-  get readyState() {
-    return this._readyState;
-  },
-  get binaryType() {
-    return this._binaryType;
-  },
-  get host() {
-    return this._host;
-  },
-  get port() {
-    return this._port;
-  },
-  get ssl() {
-    return this._ssl;
-  },
-  get bufferedAmount() {
-    if (this._inChild) {
-      return this._bufferedAmount;
-    }
-    return this._multiplexStream.available();
-  },
-  get onopen() {
-    return this._onopen;
-  },
-  set onopen(f) {
-    this._onopen = f;
-  },
-  get ondrain() {
-    return this._ondrain;
-  },
-  set ondrain(f) {
-    this._ondrain = f;
-  },
-  get ondata() {
-    return this._ondata;
-  },
-  set ondata(f) {
-    this._ondata = f;
-  },
-  get onerror() {
-    return this._onerror;
-  },
-  set onerror(f) {
-    this._onerror = f;
-  },
-  get onclose() {
-    return this._onclose;
-  },
-  set onclose(f) {
-    this._onclose = f;
-  },
-
-  _activateTLS: function() {
-    let securityInfo = this._transport.securityInfo
-          .QueryInterface(Ci.nsISSLSocketControl);
-    securityInfo.StartTLS();
-  },
-
-  // Helper methods.
-  _createTransport: function ts_createTransport(host, port, sslMode) {
-    let options;
-    if (sslMode === 'ssl') {
-      options = ['ssl'];
-    } else {
-      options = ['starttls'];
-    }
-    return Cc["@mozilla.org/network/socket-transport-service;1"]
-             .getService(Ci.nsISocketTransportService)
-             .createTransport(options, 1, host, port, null);
-  },
-
-  _sendBufferedAmount: function ts_sendBufferedAmount() {
-    if (this._onUpdateBufferedAmount) {
-      this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber);
-    }
-  },
-
-  _ensureCopying: function ts_ensureCopying() {
-    let self = this;
-    if (this._asyncCopierActive) {
-      return;
-    }
-    this._asyncCopierActive = true;
-    this._multiplexStreamCopier.asyncCopy({
-      onStartRequest: function ts_output_onStartRequest() {
-      },
-      onStopRequest: function ts_output_onStopRequest(request, context, status) {
-        self._asyncCopierActive = false;
-        self._multiplexStream.removeStream(0);
-        self._sendBufferedAmount();
-
-        if (!Components.isSuccessCode(status)) {
-          // Note that we can/will get an error here as well as in the
-          // onStopRequest for inbound data.
-          self._maybeReportErrorAndCloseIfOpen(status);
-          return;
-        }
-
-        if (self._multiplexStream.count) {
-          self._ensureCopying();
-        } else {
-          // If we are waiting for initiating starttls, we can begin to
-          // activate tls now.
-          if (self._waitingForStartTLS && self._readyState == kOPEN) {
-            self._activateTLS();
-            self._waitingForStartTLS = false;
-            // If we have pending data, we should send them, or fire
-            // a drain event if we are waiting for it.
-            if (self._pendingDataAfterStartTLS.length > 0) {
-              while (self._pendingDataAfterStartTLS.length)
-                self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
-              self._ensureCopying();
-              return;
-            }
-          }
-
-          // If we have a callback to update bufferedAmount, we let child to
-          // decide whether ondrain should be dispatched.
-          if (self._waitingForDrain && !self._onUpdateBufferedAmount) {
-            self._waitingForDrain = false;
-            self.callListener("drain");
-          }
-          if (self._readyState === kCLOSING) {
-            self._socketOutputStream.close();
-            self._readyState = kCLOSED;
-            self.callListener("close");
-          }
-        }
-      }
-    }, null);
-  },
-
-  _initStream: function ts_initStream(binaryType) {
-    this._binaryType = binaryType;
-    this._socketInputStream = this._transport.openInputStream(0, 0, 0);
-    this._socketOutputStream = this._transport.openOutputStream(
-      Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
-
-    // If the other side is not listening, we will
-    // get an onInputStreamReady callback where available
-    // raises to indicate the connection was refused.
-    this._socketInputStream.asyncWait(
-      this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
-
-    if (this._binaryType === "arraybuffer") {
-      this._inputStreamBinary = new BinaryInputStream(this._socketInputStream);
-    } else {
-      this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream);
-    }
-
-    this._multiplexStream = new MultiplexInputStream();
-
-    this._multiplexStreamCopier = new AsyncStreamCopier(
-      this._multiplexStream,
-      this._socketOutputStream,
-      // (nsSocketTransport uses gSocketTransportService)
-      Cc["@mozilla.org/network/socket-transport-service;1"]
-        .getService(Ci.nsIEventTarget),
-      /* source buffered */ true, /* sink buffered */ false,
-      BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
-  },
-
-#ifdef MOZ_WIDGET_GONK
-  // Helper method for collecting network statistics.
-  // Note this method is Gonk-specific.
-  _saveNetworkStats: function ts_saveNetworkStats(enforce) {
-    if (this._txBytes <= 0 && this._rxBytes <= 0) {
-      // There is no traffic at all. No need to save statistics.
-      return;
-    }
-
-    // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy
-    // only when the total amount exceeds the predefined threshold value.
-    // The purpose is to avoid too much overhead for collecting statistics.
-    let totalBytes = this._txBytes + this._rxBytes;
-    if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
-      return;
-    }
-
-    let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"]
-                     .getService(Ci.nsINetworkStatsServiceProxy);
-    if (!nssProxy) {
-      LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available.");
-      return;
-    }
-    nssProxy.saveAppStats(this._appId, this._inBrowser, this._activeNetworkInfo,
-                          Date.now(), this._rxBytes, this._txBytes, false);
-
-    // Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
-    this._txBytes = this._rxBytes = 0;
-  },
-  // End of helper method for network statistics.
-#endif
-
-  callListener: function ts_callListener(type, data) {
-    if (!this["on" + type])
-      return;
-
-    this["on" + type].call(null, new TCPSocketEvent(type, this, data || ""));
-  },
-
-  /* nsITCPSocketInternal methods */
-  callListenerError: function ts_callListenerError(type, name) {
-    // XXX we're not really using TCPError at this time, so there's only a name
-    // attribute to pass.
-    this.callListener(type, createTCPError(this.useWin, name));
-  },
-
-  callListenerData: function ts_callListenerString(type, data) {
-    this.callListener(type, data);
-  },
-
-  callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) {
-    this.callListener(type, data);
-  },
-
-  callListenerVoid: function ts_callListenerVoid(type) {
-    this.callListener(type);
-  },
-
-  /**
-   * This method is expected to be called by TCPSocketChild to update child's
-   * readyState.
-   */
-  updateReadyState: function ts_updateReadyState(readyState) {
-    if (!this._inChild) {
-      LOG("Calling updateReadyState in parent, which should only be called " +
-          "in child");
-      return;
-    }
-    this._readyState = readyState;
-  },
-
-  updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) {
-    if (trackingNumber != this._trackingNumber) {
-      LOG("updateBufferedAmount is called but trackingNumber is not matched " +
-          "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " +
-          this._trackingNumber);
-      return;
-    }
-    this._bufferedAmount = bufferedAmount;
-    if (bufferedAmount == 0) {
-      if (this._waitingForDrain) {
-        this._waitingForDrain = false;
-        this.callListener("drain");
-      }
-    } else {
-      LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " +
-          bufferedAmount);
-    }
-  },
-
-  createAcceptedParent: function ts_createAcceptedParent(transport, binaryType, windowObject) {
-    let that = new TCPSocket();
-    that._transport = transport;
-    that._initStream(binaryType);
-
-    // ReadyState is kOpen since accepted transport stream has already been connected
-    that._readyState = kOPEN;
-    that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false);
-    that._inputStreamPump.asyncRead(that, null);
-
-    // Grab host/port from SocketTransport.
-    that._host = transport.host;
-    that._port = transport.port;
-    that.useWin = windowObject;
-
-    return that;
-  },
-
-  createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) {
-    let that = new TCPSocket();
-
-    that._binaryType = binaryType;
-    that._inChild = true;
-    that._readyState = kOPEN;
-    socketChild.setSocketAndWindow(that, windowObject);
-    that._socketBridge = socketChild;
-    that._host = socketChild.host;
-    that._port = socketChild.port;
-    that.useWin = windowObject;
-
-    return that;
-  },
-
-  setAppId: function ts_setAppId(appId) {
-#ifdef MOZ_WIDGET_GONK
-    this._appId = appId;
-#else
-    // Do nothing because _appId only exists on Gonk-specific platform.
-#endif
-  },
-
-  setInBrowser: function ts_setInBrowser(inBrowser) {
-#ifdef MOZ_WIDGET_GONK
-    this._inBrowser = inBrowser;
-#else
-    // Do nothing.
-#endif
-  },
-
-  setOnUpdateBufferedAmountHandler: function(aFunction) {
-    if (typeof(aFunction) == 'function') {
-      this._onUpdateBufferedAmount = aFunction;
-    } else {
-      throw new Error("only function can be passed to " +
-                      "setOnUpdateBufferedAmountHandler");
-    }
-  },
-
-  /**
-   * Handle the requst of sending data and update trackingNumber from
-   * child.
-   * This function is expected to be called by TCPSocketChild.
-   */
-  onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) {
-    this._trackingNumber = trackingNumber;
-    this.send(data, byteOffset, byteLength);
-  },
-
-  /* end nsITCPSocketInternal methods */
-
-  initWindowless: function ts_initWindowless() {
-    try {
-      return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
-    } catch (e) {
-      // no pref means return false
-      return false;
-    }
-  },
-
-  init: function ts_init(aWindow) {
-    if (!this.initWindowless())
-      return null;
-
-    let principal = aWindow.document.nodePrincipal;
-    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                   .getService(Ci.nsIScriptSecurityManager);
-
-    let perm = principal == secMan.getSystemPrincipal()
-                 ? Ci.nsIPermissionManager.ALLOW_ACTION
-                 : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket");
-
-    this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
-
-    let util = aWindow.QueryInterface(
-      Ci.nsIInterfaceRequestor
-    ).getInterface(Ci.nsIDOMWindowUtils);
-
-    this.useWin = XPCNativeWrapper.unwrap(aWindow);
-    this.innerWindowID = util.currentInnerWindowID;
-    LOG("window init: " + this.innerWindowID);
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    if (aTopic == "inner-window-destroyed") {
-      let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
-      if (wId == this.innerWindowID) {
-        LOG("inner-window-destroyed: " + this.innerWindowID);
-
-        // This window is now dead, so we want to clear the callbacks
-        // so that we don't get a "can't access dead object" when the
-        // underlying stream goes to tell us that we are closed
-        this.onopen = null;
-        this.ondrain = null;
-        this.ondata = null;
-        this.onerror = null;
-        this.onclose = null;
-
-        this.useWin = null;
-
-        // Clean up our socket
-        this.close();
-      }
-    }
-  },
-
-  // nsIDOMTCPSocket
-  open: function ts_open(host, port, options) {
-    this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
-                       .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-    LOG("content process: " + (this._inChild ? "true" : "false"));
-
-    // in the testing case, init won't be called and
-    // hasPrivileges will be null. We want to proceed to test.
-    if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
-      throw new Error("TCPSocket does not have permission in this context.\n");
-    }
-    let that = new TCPSocket();
-
-    that.useWin = this.useWin;
-    that.innerWindowID = this.innerWindowID;
-    that._inChild = this._inChild;
-
-    LOG("window init: " + that.innerWindowID);
-    Services.obs.addObserver(that, "inner-window-destroyed", true);
-
-    LOG("startup called");
-    LOG("Host info: " + host + ":" + port);
-
-    that._readyState = kCONNECTING;
-    that._host = host;
-    that._port = port;
-    if (options !== undefined) {
-      if (options.useSecureTransport) {
-          that._ssl = 'ssl';
-      } else {
-          that._ssl = false;
-      }
-      that._binaryType = options.binaryType || that._binaryType;
-    }
-
-    LOG("SSL: " + that.ssl);
-
-    if (this._inChild) {
-      that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"]
-                             .createInstance(Ci.nsITCPSocketChild);
-      that._socketBridge.sendOpen(that, host, port, !!that._ssl,
-                                  that._binaryType, this.useWin, this.useWin || this);
-      return that;
-    }
-
-    let transport = that._transport = this._createTransport(host, port, that._ssl);
-    transport.setEventSink(that, Services.tm.currentThread);
-    that._initStream(that._binaryType);
-
-#ifdef MOZ_WIDGET_GONK
-    // Set _activeNetworkInfo, which is only required for network statistics.
-    // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is
-    // Gonk-specific.
-    let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
-    if (networkManager) {
-      that._activeNetworkInfo = networkManager.activeNetworkInfo;
-    }
-#endif
-
-    return that;
-  },
-
-  upgradeToSecure: function ts_upgradeToSecure() {
-    if (this._readyState !== kOPEN) {
-      throw new Error("Socket not open.");
-    }
-    if (this._ssl == 'ssl') {
-      // Already SSL
-      return;
-    }
-
-    this._ssl = 'ssl';
-
-    if (this._inChild) {
-      this._socketBridge.sendStartTLS();
-      return;
-    }
-
-    if (this._multiplexStream.count == 0) {
-      this._activateTLS();
-    } else {
-      this._waitingForStartTLS = true;
-    }
-  },
-
-  listen: function ts_listen(localPort, options, backlog) {
-    // in the testing case, init won't be called and
-    // hasPrivileges will be null. We want to proceed to test.
-    if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
-      throw new Error("TCPSocket does not have permission in this context.\n");
-    }
-    let that = new TCPServerSocket(this.useWin);
-
-    options = options || { binaryType : this.binaryType };
-    backlog = backlog || -1;
-    that.listen(localPort, options, backlog);
-    return that;
-  },
-
-  close: function ts_close() {
-    if (this._readyState === kCLOSED || this._readyState === kCLOSING)
-      return;
-
-    LOG("close called");
-    this._readyState = kCLOSING;
-
-    if (this._inChild) {
-      this._socketBridge.sendClose();
-      return;
-    }
-
-    if (!this._multiplexStream.count) {
-      this._socketOutputStream.close();
-    }
-    this._socketInputStream.close();
-  },
-
-  send: function ts_send(data, byteOffset, byteLength) {
-    if (this._readyState !== kOPEN) {
-      throw new Error("Socket not open.");
-    }
-
-    if (this._binaryType === "arraybuffer") {
-      byteLength = byteLength || data.byteLength;
-    } else {
-      data = data.toString();
-      byteLength = data.length;
-    }
-
-    if (this._inChild) {
-      this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber);
-    }
-
-    let newBufferedAmount = this.bufferedAmount + byteLength;
-    let bufferFull = newBufferedAmount >= BUFFER_SIZE;
-
-    if (bufferFull) {
-      // If we buffered more than some arbitrary amount of data,
-      // (65535 right now) we should tell the caller so they can
-      // wait until ondrain is called if they so desire. Once all the
-      // buffered data has been written to the socket, ondrain is
-      // called.
-      this._waitingForDrain = true;
-    }
-
-    if (this._inChild) {
-      // In child, we just add buffer length to our bufferedAmount and let
-      // parent to update our bufferedAmount when data have been sent.
-      this._bufferedAmount = newBufferedAmount;
-      return !bufferFull;
-    }
-
-    let new_stream;
-    if (this._binaryType === "arraybuffer") {
-      new_stream = new ArrayBufferInputStream();
-      new_stream.setData(data, byteOffset, byteLength);
-    } else {
-      new_stream = new StringInputStream();
-      new_stream.setData(data, byteLength);
-    }
-
-    if (this._waitingForStartTLS) {
-      // When we are waiting for starttls, new_stream is added to pendingData
-      // and will be appended to multiplexStream after tls had been set up.
-      this._pendingDataAfterStartTLS.push(new_stream);
-    } else {
-      this._multiplexStream.appendStream(new_stream);
-    }
-
-    this._ensureCopying();
-
-#ifdef MOZ_WIDGET_GONK
-    // Collect transmitted amount for network statistics.
-    this._txBytes += byteLength;
-    this._saveNetworkStats(false);
-#endif
-
-    return !bufferFull;
-  },
-
-  suspend: function ts_suspend() {
-    if (this._inChild) {
-      this._socketBridge.sendSuspend();
-      return;
-    }
-
-    if (this._inputStreamPump) {
-      this._inputStreamPump.suspend();
-    } else {
-      ++this._suspendCount;
-    }
-  },
-
-  resume: function ts_resume() {
-    if (this._inChild) {
-      this._socketBridge.sendResume();
-      return;
-    }
-
-    if (this._inputStreamPump) {
-      this._inputStreamPump.resume();
-    } else if (this._suspendCount < 1) {
-      throw new Error(kRESUME_ERROR);
-    } else {
-      --this._suspendCount;
-    }
-  },
-
-  _maybeReportErrorAndCloseIfOpen: function(status) {
-#ifdef MOZ_WIDGET_GONK
-    // Save network statistics once the connection is closed.
-    // For now this function is Gonk-specific.
-    this._saveNetworkStats(true);
-#endif
-
-    // If we're closed, we've already reported the error or just don't need to
-    // report the error.
-    if (this._readyState === kCLOSED)
-      return;
-    this._readyState = kCLOSED;
-
-    if (!Components.isSuccessCode(status)) {
-      // Convert the status code to an appropriate error message.  Raw constants
-      // are used inline in all cases for consistency.  Some error codes are
-      // available in Components.results, some aren't.  Network error codes are
-      // effectively stable, NSS error codes are officially not, but we have no
-      // symbolic way to dynamically resolve them anyways (other than an ability
-      // to determine the error class.)
-      let errName, errType;
-      // security module? (and this is an error)
-      if ((status & 0xff0000) === 0x5a0000) {
-        const nsINSSErrorsService = Ci.nsINSSErrorsService;
-        let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
-                                 .getService(nsINSSErrorsService);
-        let errorClass;
-        // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
-        // somehow not in the set of covered errors.
-        try {
-          errorClass = nssErrorsService.getErrorClass(status);
-        }
-        catch (ex) {
-          errorClass = 'SecurityProtocol';
-        }
-        switch (errorClass) {
-          case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL:
-            errType = 'SecurityProtocol';
-            break;
-          case nsINSSErrorsService.ERROR_CLASS_BAD_CERT:
-            errType = 'SecurityCertificate';
-            break;
-          // no default is required; the platform impl automatically defaults to
-          // ERROR_CLASS_SSL_PROTOCOL.
-        }
-
-        // NSS_SEC errors (happen below the base value because of negative vals)
-        if ((status & 0xffff) <
-            Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
-          // The bases are actually negative, so in our positive numeric space, we
-          // need to subtract the base off our value.
-          let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) -
-                         (status & 0xffff);
-          switch (nssErr) {
-            case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
-              errName = 'SecurityExpiredCertificateError';
-              break;
-            case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
-              errName = 'SecurityRevokedCertificateError';
-              break;
-            // per bsmith, we will be unable to tell these errors apart very soon,
-            // so it makes sense to just folder them all together already.
-            case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
-            case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
-            case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
-            case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
-              errName = 'SecurityUntrustedCertificateIssuerError';
-              break;
-            case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
-              errName = 'SecurityInadequateKeyUsageError';
-              break;
-            case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
-              errName = 'SecurityCertificateSignatureAlgorithmDisabledError';
-              break;
-            default:
-              errName = 'SecurityError';
-              break;
-          }
-        }
-        // NSS_SSL errors
-        else {
-          let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) -
-                         (status & 0xffff);
-          switch (sslErr) {
-            case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
-              errName = 'SecurityNoCertificateError';
-              break;
-            case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
-              errName = 'SecurityBadCertificateError';
-              break;
-            case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
-              errName = 'SecurityUnsupportedCertificateTypeError';
-              break;
-            case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
-              errName = 'SecurityUnsupportedTLSVersionError';
-              break;
-            case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
-              errName = 'SecurityCertificateDomainMismatchError';
-              break;
-            default:
-              errName = 'SecurityError';
-              break;
-          }
-        }
-      }
-      // must be network
-      else {
-        errType = 'Network';
-        switch (status) {
-          // connect to host:port failed
-          case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
-            errName = 'ConnectionRefusedError';
-            break;
-          // network timeout error
-          case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
-            errName = 'NetworkTimeoutError';
-            break;
-          // hostname lookup failed
-          case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
-            errName = 'DomainNotFoundError';
-            break;
-          case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
-            errName = 'NetworkInterruptError';
-            break;
-          default:
-            errName = 'NetworkError';
-            break;
-        }
-      }
-      let err = createTCPError(this.useWin, errName, errType);
-      this.callListener("error", err);
-    }
-    this.callListener("close");
-  },
-
-  // nsITransportEventSink (Triggered by transport.setEventSink)
-  onTransportStatus: function ts_onTransportStatus(
-    transport, status, progress, max) {
-    if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
-      this._readyState = kOPEN;
-      this.callListener("open");
-
-      this._inputStreamPump = new InputStreamPump(
-        this._socketInputStream, -1, -1, 0, 0, false
-      );
-
-      while (this._suspendCount--) {
-        this._inputStreamPump.suspend();
-      }
-
-      this._inputStreamPump.asyncRead(this, null);
-    }
-  },
-
-  // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait)
-  // Only used for detecting connection refused
-  onInputStreamReady: function ts_onInputStreamReady(input) {
-    try {
-      input.available();
-    } catch (e) {
-      // NS_ERROR_CONNECTION_REFUSED
-      this._maybeReportErrorAndCloseIfOpen(0x804B000C);
-    }
-  },
-
-  // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
-  onStartRequest: function ts_onStartRequest(request, context) {
-  },
-
-  // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
-  onStopRequest: function ts_onStopRequest(request, context, status) {
-    let buffered_output = this._multiplexStream.count !== 0;
-
-    this._inputStreamPump = null;
-
-    let statusIsError = !Components.isSuccessCode(status);
-
-    if (buffered_output && !statusIsError) {
-      // If we have some buffered output still, and status is not an
-      // error, the other side has done a half-close, but we don't
-      // want to be in the close state until we are done sending
-      // everything that was buffered. We also don't want to call onclose
-      // yet.
-      return;
-    }
-
-    // We call this even if there is no error.
-    this._maybeReportErrorAndCloseIfOpen(status);
-  },
-
-  // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
-  onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
-    if (this._binaryType === "arraybuffer") {
-      let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count);
-      this._inputStreamBinary.readArrayBuffer(count, buffer);
-      this.callListener("data", buffer);
-    } else {
-      this.callListener("data", this._inputStreamScriptable.read(count));
-    }
-
-#ifdef MOZ_WIDGET_GONK
-    // Collect received amount for network statistics.
-    this._rxBytes += count;
-    this._saveNetworkStats(false);
-#endif
-  },
-
-  classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
-
-  classInfo: XPCOMUtils.generateCI({
-    classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
-    contractID: "@mozilla.org/tcp-socket;1",
-    classDescription: "Client TCP Socket",
-    interfaces: [
-      Ci.nsIDOMTCPSocket,
-    ],
-    flags: Ci.nsIClassInfo.DOM_OBJECT,
-  }),
-
-  QueryInterface: XPCOMUtils.generateQI([
-    Ci.nsIDOMTCPSocket,
-    Ci.nsITCPSocketInternal,
-    Ci.nsIDOMGlobalPropertyInitializer,
-    Ci.nsIObserver,
-    Ci.nsISupportsWeakReference
-  ])
-}
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);
deleted file mode 100644
--- a/dom/network/TCPSocket.manifest
+++ /dev/null
@@ -1,12 +0,0 @@
-# TCPSocket.js
-component {cda91b22-6472-11e1-aa11-834fec09cd0a} TCPSocket.js
-contract @mozilla.org/tcp-socket;1 {cda91b22-6472-11e1-aa11-834fec09cd0a}
-category JavaScript-navigator-property mozTCPSocket @mozilla.org/tcp-socket;1
-
-# TCPSocketParentIntermediary.js
-component {afa42841-a6cb-4a91-912f-93099f6a3d18} TCPSocketParentIntermediary.js
-contract @mozilla.org/tcp-socket-intermediary;1 {afa42841-a6cb-4a91-912f-93099f6a3d18}
-
-# TCPServerSocket.js
-component {73065eae-27dc-11e2-895a-000c29987aa2} TCPServerSocket.js
-contract @mozilla.org/tcp-server-socket;1 {73065eae-27dc-11e2-895a-000c29987aa2}
--- a/dom/network/TCPSocketChild.cpp
+++ b/dom/network/TCPSocketChild.cpp
@@ -6,35 +6,31 @@
 
 #include <algorithm>
 #include "TCPSocketChild.h"
 #include "mozilla/unused.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/TabChild.h"
-#include "nsIDOMTCPSocket.h"
-#include "nsJSUtils.h"
+#include "nsITCPSocketCallback.h"
+#include "TCPSocket.h"
 #include "nsContentUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
-#include "jswrapper.h"
 
 using mozilla::net::gNeckoChild;
 
 namespace IPC {
 
 bool
-DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
+DeserializeArrayBuffer(JSContext* cx,
                        const InfallibleTArray<uint8_t>& aBuffer,
                        JS::MutableHandle<JS::Value> aVal)
 {
-  mozilla::AutoSafeJSContext cx;
-  JSAutoCompartment ac(cx, aObj);
-
   mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(js_pod_malloc<uint8_t>(aBuffer.Length()));
   if (!data)
       return false;
   memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
 
   JSObject* obj = JS_NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
   if (!obj)
       return false;
@@ -52,29 +48,26 @@ namespace dom {
 NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketChildBase)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketChildBase)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketChildBase)
-  tmp->mWindowObj = nullptr;
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketChildBase)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mWindowObj)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketChildBase)
-  NS_INTERFACE_MAP_ENTRY(nsITCPSocketChild)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 TCPSocketChildBase::TCPSocketChildBase()
 : mIPCOpen(false)
 {
   mozilla::HoldJSObjects(this);
 }
@@ -89,46 +82,46 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
   nsrefcnt refcnt = TCPSocketChildBase::Release();
   if (refcnt == 1 && mIPCOpen) {
     PTCPSocketChild::SendRequestDelete();
     return 1;
   }
   return refcnt;
 }
 
-TCPSocketChild::TCPSocketChild()
-: mHost()
-, mPort(0)
+TCPSocketChild::TCPSocketChild(const nsAString& aHost, const uint16_t& aPort)
+: mHost(aHost)
+, mPort(aPort)
 {
 }
 
-void TCPSocketChild::Init(const nsString& aHost, const uint16_t& aPort) {
-  mHost = aHost;
-  mPort = aPort;
-}
-
-NS_IMETHODIMP
-TCPSocketChild::SendOpen(nsITCPSocketInternal* aSocket,
-                         const nsAString& aHost, uint16_t aPort,
-                         bool aUseSSL, const nsAString& aBinaryType,
-                         nsIDOMWindow* aWindow, JS::Handle<JS::Value> aWindowObj,
-                         JSContext* aCx)
+void
+TCPSocketChild::SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseArrayBuffers)
 {
   mSocket = aSocket;
 
-  MOZ_ASSERT(aWindowObj.isObject());
-  mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
-  if (!mWindowObj) {
-    return NS_ERROR_FAILURE;
-  }
   AddIPDLReference();
-  gNeckoChild->SendPTCPSocketConstructor(this, nsString(aHost), aPort);
-  PTCPSocketChild::SendOpen(nsString(aHost), aPort,
-                            aUseSSL, nsString(aBinaryType));
-  return NS_OK;
+  gNeckoChild->SendPTCPSocketConstructor(this, mHost, mPort);
+  PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers);
+}
+
+void
+TCPSocketChild::SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
+                                       const nsACString& aRemoteHost, uint16_t aRemotePort,
+                                       const nsACString& aLocalHost, uint16_t aLocalPort,
+                                       bool aUseSSL)
+{
+  mSocket = aSocket;
+  AddIPDLReference();
+  gNeckoChild->SendPTCPSocketConstructor(this,
+                                         NS_ConvertUTF8toUTF16(aRemoteHost),
+                                         aRemotePort);
+  PTCPSocketChild::SendOpenBind(nsCString(aRemoteHost), aRemotePort,
+                                nsCString(aLocalHost), aLocalPort,
+                                aUseSSL, true);
 }
 
 void
 TCPSocketChildBase::ReleaseIPDLReference()
 {
   MOZ_ASSERT(mIPCOpen);
   mIPCOpen = false;
   this->Release();
@@ -145,157 +138,99 @@ TCPSocketChildBase::AddIPDLReference()
 TCPSocketChild::~TCPSocketChild()
 {
 }
 
 bool
 TCPSocketChild::RecvUpdateBufferedAmount(const uint32_t& aBuffered,
                                          const uint32_t& aTrackingNumber)
 {
-  if (NS_FAILED(mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber))) {
-    NS_ERROR("Shouldn't fail!");
-  }
+  mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber);
   return true;
 }
 
 bool
 TCPSocketChild::RecvCallback(const nsString& aType,
                              const CallbackData& aData,
-                             const nsString& aReadyState)
+                             const uint32_t& aReadyState)
 {
-  if (NS_FAILED(mSocket->UpdateReadyState(aReadyState)))
-    NS_ERROR("Shouldn't fail!");
+  mSocket->UpdateReadyState(aReadyState);
 
-  nsresult rv = NS_ERROR_FAILURE;
   if (aData.type() == CallbackData::Tvoid_t) {
-    rv = mSocket->CallListenerVoid(aType);
+    mSocket->FireEvent(aType);
 
   } else if (aData.type() == CallbackData::TTCPError) {
     const TCPError& err(aData.get_TCPError());
-    rv = mSocket->CallListenerError(aType, err.name());
+    mSocket->FireErrorEvent(err.name(), err.message());
 
   } else if (aData.type() == CallbackData::TSendableData) {
     const SendableData& data = aData.get_SendableData();
 
     if (data.type() == SendableData::TArrayOfuint8_t) {
-      JSContext* cx = nsContentUtils::GetSafeJSContext();
-      JSAutoRequest ar(cx);
-      JS::Rooted<JS::Value> val(cx);
-      JS::Rooted<JSObject*> window(cx, mWindowObj);
-      bool ok = IPC::DeserializeArrayBuffer(window, data.get_ArrayOfuint8_t(), &val);
-      NS_ENSURE_TRUE(ok, true);
-      rv = mSocket->CallListenerArrayBuffer(aType, val);
-
-    } else if (data.type() == SendableData::TnsString) {
-      rv = mSocket->CallListenerData(aType, data.get_nsString());
-
+      mSocket->FireDataArrayEvent(aType, data.get_ArrayOfuint8_t());
+    } else if (data.type() == SendableData::TnsCString) {
+      mSocket->FireDataStringEvent(aType, data.get_nsCString());
     } else {
       MOZ_CRASH("Invalid callback data type!");
     }
-
   } else {
     MOZ_CRASH("Invalid callback type!");
   }
-  NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
-NS_IMETHODIMP
-TCPSocketChild::SendStartTLS()
+void
+TCPSocketChild::SendSend(const nsACString& aData, uint32_t aTrackingNumber)
 {
-  PTCPSocketChild::SendStartTLS();
-  return NS_OK;
+  SendData(nsCString(aData), aTrackingNumber);
 }
 
-NS_IMETHODIMP
-TCPSocketChild::SendSuspend()
+nsresult
+TCPSocketChild::SendSend(const ArrayBuffer& aData,
+                         uint32_t aByteOffset,
+                         uint32_t aByteLength,
+                         uint32_t aTrackingNumber)
 {
-  PTCPSocketChild::SendSuspend();
-  return NS_OK;
-}
+  uint32_t buflen = aData.Length();
+  uint32_t offset = std::min(buflen, aByteOffset);
+  uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
+  FallibleTArray<uint8_t> fallibleArr;
+  if (!fallibleArr.InsertElementsAt(0, aData.Data() + offset, nbytes, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
 
-NS_IMETHODIMP
-TCPSocketChild::SendResume()
-{
-  PTCPSocketChild::SendResume();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-TCPSocketChild::SendClose()
-{
-  PTCPSocketChild::SendClose();
+  InfallibleTArray<uint8_t> arr;
+  arr.SwapElements(fallibleArr);
+  SendData(arr, aTrackingNumber);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::SendSend(JS::Handle<JS::Value> aData,
-                         uint32_t aByteOffset,
-                         uint32_t aByteLength,
-                         uint32_t aTrackingNumber,
-                         JSContext* aCx)
+TCPSocketChild::SendSendArray(nsTArray<uint8_t>& aArray, uint32_t aTrackingNumber)
 {
-  if (aData.isString()) {
-    JSString* jsstr = aData.toString();
-    nsAutoJSString str;
-    bool ok = str.init(aCx, jsstr);
-    NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-    SendData(str, aTrackingNumber);
-  } else {
-    NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
-    JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
-    NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj), NS_ERROR_FAILURE);
-    uint32_t buflen = JS_GetArrayBufferByteLength(obj);
-    aByteOffset = std::min(buflen, aByteOffset);
-    uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
-    FallibleTArray<uint8_t> fallibleArr;
-    {
-        JS::AutoCheckCannotGC nogc;
-        uint8_t* data = JS_GetArrayBufferData(obj, nogc);
-        if (!data) {
-            return NS_ERROR_OUT_OF_MEMORY;
-        }
-        if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes,
-                                          fallible)) {
-            return NS_ERROR_OUT_OF_MEMORY;
-        }
-    }
-    InfallibleTArray<uint8_t> arr;
-    arr.SwapElements(fallibleArr);
-    SendData(arr, aTrackingNumber);
-  }
+  SendData(aArray, aTrackingNumber);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-TCPSocketChild::SetSocketAndWindow(nsITCPSocketInternal *aSocket,
-                                   JS::Handle<JS::Value> aWindowObj,
-                                   JSContext* aCx)
+void
+TCPSocketChild::SetSocket(TCPSocket* aSocket)
 {
   mSocket = aSocket;
-  MOZ_ASSERT(aWindowObj.isObject());
-  mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
-  if (!mWindowObj) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 TCPSocketChild::GetHost(nsAString& aHost)
 {
   aHost = mHost;
-  return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 TCPSocketChild::GetPort(uint16_t* aPort)
 {
   *aPort = mPort;
-  return NS_OK;
 }
 
 bool
 TCPSocketChild::RecvRequestDelete()
 {
   mozilla::unused << Send__delete__(this);
   return true;
 }
--- a/dom/network/TCPSocketChild.h
+++ b/dom/network/TCPSocketChild.h
@@ -3,61 +3,81 @@
 /* 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_TCPSocketChild_h
 #define mozilla_dom_TCPSocketChild_h
 
 #include "mozilla/net/PTCPSocketChild.h"
-#include "nsITCPSocketChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 #include "js/TypeDecls.h"
 
-#define TCPSOCKETCHILD_CID \
-  { 0xa589d96f, 0x7e09, 0x4edf, { 0xa0, 0x1a, 0xeb, 0x49, 0x51, 0xf4, 0x2f, 0x37 } }
+class nsITCPSocketCallback;
 
-class nsITCPSocketInternal;
+namespace IPC {
+bool
+DeserializeArrayBuffer(JSContext* cx,
+                       const InfallibleTArray<uint8_t>& aBuffer,
+                       JS::MutableHandle<JS::Value> aVal);
+}
 
 namespace mozilla {
 namespace dom {
 
-class TCPSocketChildBase : public nsITCPSocketChild {
+class TCPSocket;
+
+class TCPSocketChildBase : public nsISupports {
 public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TCPSocketChildBase)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
 protected:
   TCPSocketChildBase();
   virtual ~TCPSocketChildBase();
 
-  nsCOMPtr<nsITCPSocketInternal> mSocket;
-  JS::Heap<JSObject*> mWindowObj;
+  nsCOMPtr<nsITCPSocketCallback> mSocket;
   bool mIPCOpen;
 };
 
 class TCPSocketChild : public mozilla::net::PTCPSocketChild
                      , public TCPSocketChildBase
 {
 public:
-  NS_DECL_NSITCPSOCKETCHILD
   NS_IMETHOD_(MozExternalRefCountType) Release() override;
 
-  TCPSocketChild();
+  TCPSocketChild(const nsAString& aHost, const uint16_t& aPort);
   ~TCPSocketChild();
 
-  void Init(const nsString& aHost, const uint16_t& aPort);
+  void SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseArrayBuffers);
+  void SendWindowlessOpenBind(nsITCPSocketCallback* aSocket,
+                              const nsACString& aRemoteHost, uint16_t aRemotePort,
+                              const nsACString& aLocalHost, uint16_t aLocalPort,
+                              bool aUseSSL);
+  NS_IMETHOD SendSendArray(nsTArray<uint8_t>& aArray,
+                           uint32_t aTrackingNumber);
+  void SendSend(const nsACString& aData, uint32_t aTrackingNumber);
+  nsresult SendSend(const ArrayBuffer& aData,
+                    uint32_t aByteOffset,
+                    uint32_t aByteLength,
+                    uint32_t aTrackingNumber);
+  void SendSendArray(nsTArray<uint8_t>* arr,
+                     uint32_t trackingNumber);
+  void SetSocket(TCPSocket* aSocket);
+
+  void GetHost(nsAString& aHost);
+  void GetPort(uint16_t* aPort);
 
   virtual bool RecvCallback(const nsString& aType,
                             const CallbackData& aData,
-                            const nsString& aReadyState) override;
+                            const uint32_t& aReadyState) override;
   virtual bool RecvRequestDelete() override;
   virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred,
                                         const uint32_t& aTrackingNumber) override;
 private:
   nsString mHost;
   uint16_t mPort;
 };
 
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -3,89 +3,70 @@
 /* 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 "TCPSocketParent.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "nsJSUtils.h"
-#include "nsIDOMTCPSocket.h"
 #include "mozilla/unused.h"
 #include "mozilla/AppProcessChecker.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/PNeckoParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/HoldDropJSObjects.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsNetUtil.h"
 
 namespace IPC {
 
 //Defined in TCPSocketChild.cpp
 extern bool
-DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
+DeserializeArrayBuffer(JSContext* aCx,
                        const InfallibleTArray<uint8_t>& aBuffer,
                        JS::MutableHandle<JS::Value> aVal);
 
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
 
 static void
 FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
 {
   mozilla::unused <<
       aActor->SendCallback(NS_LITERAL_STRING("onerror"),
-                           TCPError(NS_LITERAL_STRING("InvalidStateError")),
-                           NS_LITERAL_STRING("connecting"));
+                           TCPError(NS_LITERAL_STRING("InvalidStateError"), NS_LITERAL_STRING("Internal error")),
+                           static_cast<uint32_t>(TCPReadyState::Connecting));
 }
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketParentBase)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntermediary)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketParentBase)
-  tmp->mIntermediaryObj = nullptr;
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntermediary)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketParentBase)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIntermediaryObj)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
-  NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+NS_IMPL_CYCLE_COLLECTION(TCPSocketParentBase, mSocket)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
 
 TCPSocketParentBase::TCPSocketParentBase()
 : mIPCOpen(false)
 {
   mObserver = new mozilla::net::OfflineObserver(this);
-  mozilla::HoldJSObjects(this);
 }
 
 TCPSocketParentBase::~TCPSocketParentBase()
 {
   if (mObserver) {
     mObserver->RemoveObserver();
   }
-  mozilla::DropJSObjects(this);
 }
 
 uint32_t
 TCPSocketParent::GetAppId()
 {
   uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
   const PContentParent *content = Manager()->Manager();
   const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
@@ -125,18 +106,16 @@ TCPSocketParent::OfflineNotification(nsI
   if (appId != targetAppId) {
     return NS_OK;
   }
 
   // If the app is offline, close the socket
   if (mSocket && NS_IsAppOffline(appId)) {
     mSocket->Close();
     mSocket = nullptr;
-    mIntermediaryObj = nullptr;
-    mIntermediary = nullptr;
   }
 
   return NS_OK;
 }
 
 
 void
 TCPSocketParentBase::ReleaseIPDLReference()
@@ -161,17 +140,17 @@ NS_IMETHODIMP_(MozExternalRefCountType) 
     mozilla::unused << PTCPSocketParent::SendRequestDelete();
     return 1;
   }
   return refcnt;
 }
 
 bool
 TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bool& aUseSSL,
-                          const nsString& aBinaryType)
+                          const bool& aUseArrayBuffers)
 {
   // We don't have browser actors in xpcshell, and hence can't run automated
   // tests without this loophole.
   if (net::UsingNeckoIPCSecurity() &&
       !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
     FireInteralError(this, __LINE__);
     return true;
   }
@@ -181,230 +160,228 @@ TCPSocketParent::RecvOpen(const nsString
   bool     inBrowser = GetInBrowser();
 
   if (NS_IsAppOffline(appId)) {
     NS_ERROR("Can't open socket because app is offline");
     FireInteralError(this, __LINE__);
     return true;
   }
 
+  mSocket = new TCPSocket(nullptr, aHost, aPort, aUseSSL, aUseArrayBuffers);
+  mSocket->SetAppIdAndBrowser(appId, inBrowser);
+  mSocket->SetSocketBridgeParent(this);
+  NS_ENSURE_SUCCESS(mSocket->Init(), true);
+  return true;
+}
+
+bool
+TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost,
+                              const uint16_t& aRemotePort,
+                              const nsCString& aLocalAddr,
+                              const uint16_t& aLocalPort,
+                              const bool&     aUseSSL,
+                              const bool&     aUseArrayBuffers)
+{
+  if (net::UsingNeckoIPCSecurity() &&
+      !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
+    FireInteralError(this, __LINE__);
+    return true;
+  }
+
   nsresult rv;
-  mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
+  nsCOMPtr<nsISocketTransportService> sts =
+    do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
   if (NS_FAILED(rv)) {
     FireInteralError(this, __LINE__);
     return true;