Bug 932183: Part 2: Update child's bufferedAmount more frequently. r=honzeb
authorPatrick Wang <kk1fff@patrickz.net>
Thu, 14 Nov 2013 16:00:28 +0800
changeset 154798 c189e4487461856c3e4b23d417273d782c8db3e8
parent 154797 c0ea791b69f52907c969ea6cb5f4bfbd284062a0
child 154799 1b62846428cea82bc4cf7cb36acf457b7f7fda48
push id36168
push userryanvm@gmail.com
push dateFri, 15 Nov 2013 01:59:01 +0000
treeherdermozilla-inbound@1e1b514a0f4b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzeb
bugs932183
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 932183: Part 2: Update child's bufferedAmount more frequently. r=honzeb
dom/network/interfaces/nsIDOMTCPSocket.idl
dom/network/interfaces/nsITCPSocketParent.idl
dom/network/src/PTCPSocket.ipdl
dom/network/src/TCPSocket.js
dom/network/src/TCPSocketChild.cpp
dom/network/src/TCPSocketChild.h
dom/network/src/TCPSocketParent.cpp
dom/network/src/TCPSocketParent.h
dom/network/src/TCPSocketParentIntermediary.js
dom/network/tests/unit/test_tcpsocket.js
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -206,36 +206,49 @@ interface nsIDOMTCPSocket : nsISupports
    *
    * If onerror was not called before onclose, then either side cleanly
    * closed the connection.
    */
   attribute jsval onclose;
 };
 
 /*
- * Internal interfaces for use in cross-process socket implementation.
+ * This interface is implemented in TCPSocket.js as an internal interfaces
+ * for use in cross-process socket implementation.
  * Needed to account for multiple possible types that can be provided to
  * the socket callbacks as arguments.
  */
-[scriptable, uuid(234c664c-3d6c-4859-b45c-4e9a98cb5bdc)]
+[scriptable, uuid(017f130f-2477-4215-8783-57eada957699)]
 interface nsITCPSocketInternal : nsISupports {
   // Trigger the callback for |type| and provide a DOMError() object with the given data
   void callListenerError(in DOMString type, in DOMString name);
 
   // Trigger the callback for |type| and provide a string argument
   void callListenerData(in DOMString type, in DOMString data);
 
   // Trigger the callback for |type| and provide an ArrayBuffer argument
   void callListenerArrayBuffer(in DOMString type, in jsval data);
 
   // Trigger the callback for |type| with no argument
   void callListenerVoid(in DOMString type);
 
-  // Update the DOM object's readyState and bufferedAmount values with the provided data
-  void updateReadyStateAndBuffered(in DOMString readyState, in uint32_t bufferedAmount);
+  // Update the DOM object's readyState.
+  // @param readyState
+  //        new ready state to be set to TCPSocket.
+  void updateReadyState(in DOMString readyState);
+
+  // Update the DOM object's bufferedAmount value with a tracking number to
+  // ensure the update request is sent after child's send() invocation.
+  // @param bufferedAmount
+  //        TCPSocket parent's bufferedAmount.
+  // @param trackingNumber
+  //        A number to ensure the bufferedAmount is updated after data
+  //        from child are sent to parent.
+  void updateBufferedAmount(in uint32_t bufferedAmount,
+                            in uint32_t trackingNumber);
 
   // Create a socket object on the parent side.
   // This is called in accepting any open request on the parent side.
   // 
   // @param transport
   //        The accepted socket transport.
   // @param binaryType
   //        "arraybuffer" to use ArrayBuffer instances 
@@ -254,16 +267,28 @@ interface nsITCPSocketInternal : nsISupp
   // @param window
   //        An object to create ArrayBuffer for this window. See Bug 831107.
   nsIDOMTCPSocket createAcceptedChild(in nsITCPSocketChild socketChild,
                                       in DOMString binaryType,
                                       in nsIDOMWindow window);
 
   // Set App ID.
   void setAppId(in unsigned long appId);
+
+  // Set a callback that handles the request from a TCP socket parent when that
+  // socket parent wants to notify that its bufferedAmount is updated.
+  void setOnUpdateBufferedAmountHandler(in jsval handler);
+
+  // Providing child process with ability to pass more arguments to parent's
+  // send() function.
+  // @param trackingNumber
+  //        To ensure the request to update bufferedAmount in child is after
+  //        lastest send() invocation from child.
+  void onRecvSendFromChild(in jsval data, in unsigned long byteOffset,
+                           in unsigned long byteLength, in unsigned long trackingNumber);
 };
 
 /**
  * nsITCPSocketEvent is the event object which is passed as the
  * first argument to all the event handler callbacks. It contains
  * the socket that was associated with the event, the type of event,
  * and the data associated with the event (if any).
  */
--- a/dom/network/interfaces/nsITCPSocketParent.idl
+++ b/dom/network/interfaces/nsITCPSocketParent.idl
@@ -25,29 +25,41 @@ interface nsITCPSocketParent : nsISuppor
   //        Event type: 'onopen', 'ondata', 'onerror' or 'onclose'. 'odrain' is
   //        controlled by child.
   // @param data
   //        Serialized data that is passed to event handler.
   // @param readyState
   //        Current ready state.
   [implicit_jscontext] void sendEvent(in DOMString type,
                                       in jsval data,
-                                      in DOMString readyState,
-                                      in uint32_t bufferedAmount);
+                                      in DOMString readyState);
 
   // Initialize a parent socket object. It is called from the parent side socket,
   // which is generated in accepting any open request on the parent side.
   // The socket after being initialized will be established.
   //
   // @param socket
   //        The socket on the parent side.
   // @param intermediary
   //        Intermediate class object. See nsITCPSocketIntermediary.
   [implicit_jscontext] void setSocketAndIntermediary(in nsIDOMTCPSocket socket,
                                                      in nsITCPSocketIntermediary intermediary);
+
+  // When parent's buffered amount is updated and it wants to inform child to
+  // update the bufferedAmount as well.
+  //
+  // @param bufferedAmount
+  //        The new value of bufferedAmount that is going to be set to child's
+  //        bufferedAmount.
+  // @param trackingNumber
+  //        Parent's current tracking number, reflecting the number of calls to
+  //        send() on the child process. This number is sent back to the child
+  //        to make sure the bufferedAmount updated on the child will correspond
+  //        to the latest call of send().
+  void sendUpdateBufferedAmount(in uint32_t bufferedAmount, in uint32_t trackingNumber);
 };
 
 // Intermediate class to handle sending multiple possible data types
 // and kicking off the chrome process socket object's connection.
 // This interface is the bridge of TCPSocketParent, which is written in C++,
 // and TCPSocket, which is written in Javascript. TCPSocketParentIntermediary
 // implements nsITCPSocketIntermediary in Javascript.
 [scriptable, uuid(c434224a-dbb7-4869-8b2b-e49cee990e85)]
@@ -59,13 +71,13 @@ interface nsITCPSocketIntermediary : nsI
                        in unsigned long appId);
 
   // Listen on a port
   nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
                                in unsigned short port, in unsigned short backlog,
                                in DOMString binaryType);
 
   // Called when received a child request to send a string.
-  void onRecvSendString(in DOMString data);
+  void onRecvSendString(in DOMString data, in uint32_t trackingNumber);
 
   // Called when received a child request to send an array buffer.
-  void onRecvSendArrayBuffer(in jsval data);
+  void onRecvSendArrayBuffer(in jsval data, in uint32_t trackingNumber);
 };
--- a/dom/network/src/PTCPSocket.ipdl
+++ b/dom/network/src/PTCPSocket.ipdl
@@ -35,34 +35,40 @@ 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);
 
-  Data(SendableData data);
+  // 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();
 
   // Forward calling to child's send() method to parent.
   Suspend();
 
   // 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, uint32_t bufferedAmount);
+  Callback(nsString type, CallbackData data, nsString 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();
   __delete__();
 };
 
 
 } // namespace net
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/src/TCPSocket.js
@@ -157,16 +157,20 @@ TCPSocket.prototype = {
 
   // 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,
   _activeNetwork: null,
 #endif
 
@@ -237,28 +241,35 @@ TCPSocket.prototype = {
     } 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;
         }
 
@@ -275,17 +286,19 @@ TCPSocket.prototype = {
             if (self._pendingDataAfterStartTLS.length > 0) {
               while (self._pendingDataAfterStartTLS.length)
                 self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
               self._ensureCopying();
               return;
             }
           }
 
-          if (self._waitingForDrain) {
+          // 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");
           }
@@ -377,19 +390,46 @@ TCPSocket.prototype = {
   callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) {
     this.callListener(type, data);
   },
 
   callListenerVoid: function ts_callListenerVoid(type) {
     this.callListener(type);
   },
 
-  updateReadyStateAndBuffered: function ts_setReadyState(readyState, bufferedAmount) {
+  /**
+   * 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) {
     let that = new TCPSocket();
     that._transport = transport;
     that._initStream(binaryType);
 
     // ReadyState is kOpen since accepted transport stream has already been connected
@@ -415,16 +455,35 @@ TCPSocket.prototype = {
   setAppId: function ts_setAppId(appId) {
 #ifdef MOZ_WIDGET_GONK
     this._appId = appId;
 #else
     // Do nothing because _appId only exists on Gonk-specific platform.
 #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;
@@ -600,25 +659,37 @@ TCPSocket.prototype = {
       throw new Error("Socket not open.");
     }
 
     if (this._binaryType === "arraybuffer") {
       byteLength = byteLength || data.byteLength;
     }
 
     if (this._inChild) {
-      this._socketBridge.sendSend(data, byteOffset, byteLength);
+      this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber);
     }
 
     let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+    let newBufferedAmount = this.bufferedAmount + length;
+    let bufferFull = newBufferedAmount >= BUFFER_SIZE;
 
-    var newBufferedAmount = this.bufferedAmount + length;
-    var bufferNotFull = 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) {
-      return bufferNotFull;
+      // 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();
@@ -628,34 +699,25 @@ TCPSocket.prototype = {
     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);
     }
 
-    if (newBufferedAmount >= BUFFER_SIZE) {
-      // 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;
-    }
-
     this._ensureCopying();
 
 #ifdef MOZ_WIDGET_GONK
     // Collect transmitted amount for network statistics.
     this._txBytes += length;
     this._saveNetworkStats(false);
 #endif
 
-    return bufferNotFull;
+    return !bufferFull;
   },
 
   suspend: function ts_suspend() {
     if (this._inChild) {
       this._socketBridge.sendSuspend();
       return;
     }
 
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -113,22 +113,31 @@ TCPSocketChildBase::AddIPDLReference()
   this->AddRef();
 }
 
 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!");
+  }
+  return true;
+}
+
+bool
 TCPSocketChild::RecvCallback(const nsString& aType,
                              const CallbackData& aData,
-                             const nsString& aReadyState,
-                             const uint32_t& aBuffered)
+                             const nsString& aReadyState)
 {
-  if (NS_FAILED(mSocket->UpdateReadyStateAndBuffered(aReadyState, aBuffered)))
+  if (NS_FAILED(mSocket->UpdateReadyState(aReadyState)))
     NS_ERROR("Shouldn't fail!");
 
   nsresult rv = NS_ERROR_FAILURE;
   if (aData.type() == CallbackData::Tvoid_t) {
     rv = mSocket->CallListenerVoid(aType);
 
   } else if (aData.type() == CallbackData::TTCPError) {
     const TCPError& err(aData.get_TCPError());
@@ -187,25 +196,25 @@ TCPSocketChild::SendClose()
   PTCPSocketChild::SendClose();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketChild::SendSend(const JS::Value& aData,
                          uint32_t aByteOffset,
                          uint32_t aByteLength,
+                         uint32_t aTrackingNumber,
                          JSContext* aCx)
 {
   if (aData.isString()) {
     JSString* jsstr = aData.toString();
     nsDependentJSString str;
     bool ok = str.init(aCx, jsstr);
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
-    SendData(str);
-
+    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);
     uint8_t* data = JS_GetArrayBufferData(obj);
@@ -213,17 +222,17 @@ TCPSocketChild::SendSend(const JS::Value
       return NS_ERROR_OUT_OF_MEMORY;
     }
     FallibleTArray<uint8_t> fallibleArr;
     if (!fallibleArr.InsertElementsAt(0, data, nbytes)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     InfallibleTArray<uint8_t> arr;
     arr.SwapElements(fallibleArr);
-    SendData(arr);
+    SendData(arr, aTrackingNumber);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketChild::SetSocketAndWindow(nsITCPSocketInternal *aSocket,
                                    const JS::Value& aWindowObj,
                                    JSContext* aCx)
--- a/dom/network/src/TCPSocketChild.h
+++ b/dom/network/src/TCPSocketChild.h
@@ -39,17 +39,18 @@ public:
   NS_DECL_NSITCPSOCKETCHILD
   NS_IMETHOD_(nsrefcnt) Release() MOZ_OVERRIDE;
 
   TCPSocketChild();
   ~TCPSocketChild();
 
   virtual bool RecvCallback(const nsString& aType,
                             const CallbackData& aData,
-                            const nsString& aReadyState,
-                            const uint32_t& aBuffered) MOZ_OVERRIDE;
+                            const nsString& aReadyState) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
+  virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred,
+                                        const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
 private:
   JSObject* mWindowObj;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -30,17 +30,17 @@ 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"), 0);
+                           NS_LITERAL_STRING("connecting"));
 }
 
 NS_IMPL_CYCLE_COLLECTION_2(TCPSocketParentBase, mSocket, mIntermediary)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
   NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
@@ -151,35 +151,36 @@ TCPSocketParent::RecvResume()
 {
   NS_ENSURE_TRUE(mSocket, true);
   nsresult rv = mSocket->Resume();
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 bool
-TCPSocketParent::RecvData(const SendableData& aData)
+TCPSocketParent::RecvData(const SendableData& aData,
+                          const uint32_t& aTrackingNumber)
 {
   NS_ENSURE_TRUE(mIntermediary, true);
 
   nsresult rv;
   switch (aData.type()) {
     case SendableData::TArrayOfuint8_t: {
       AutoSafeJSContext cx;
       JSAutoRequest ar(cx);
       JS::Rooted<JS::Value> val(cx);
       JS::Rooted<JSObject*> obj(cx, mIntermediaryObj);
       IPC::DeserializeArrayBuffer(obj, aData.get_ArrayOfuint8_t(), &val);
-      rv = mIntermediary->OnRecvSendArrayBuffer(val);
+      rv = mIntermediary->OnRecvSendArrayBuffer(val, aTrackingNumber);
       NS_ENSURE_SUCCESS(rv, true);
       break;
     }
 
     case SendableData::TnsString:
-      rv = mIntermediary->OnRecvSendString(aData.get_nsString());
+      rv = mIntermediary->OnRecvSendString(aData.get_nsString(), aTrackingNumber);
       NS_ENSURE_SUCCESS(rv, true);
       break;
 
     default:
       MOZ_CRASH("unexpected SendableData type");
   }
   return true;
 }
@@ -249,30 +250,39 @@ TCPSocketParent::SendEvent(const nsAStri
     }
   } else {
     NS_ERROR("Unexpected JS value encountered");
     FireInteralError(this, __LINE__);
     return NS_ERROR_FAILURE;
   }
   mozilla::unused <<
       PTCPSocketParent::SendCallback(nsString(aType), data,
-                                     nsString(aReadyState), aBuffered);
+                                     nsString(aReadyState));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketParent::SetSocketAndIntermediary(nsIDOMTCPSocket *socket,
                                           nsITCPSocketIntermediary *intermediary,
                                           JSContext* cx)
 {
   mSocket = socket;
   mIntermediary = intermediary;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TCPSocketParent::SendUpdateBufferedAmount(uint32_t aBufferedAmount,
+                                          uint32_t aTrackingNumber)
+{
+  mozilla::unused << PTCPSocketParent::SendUpdateBufferedAmount(aBufferedAmount,
+                                                                aTrackingNumber);
+  return NS_OK;
+}
+
 void
 TCPSocketParent::ActorDestroy(ActorDestroyReason why)
 {
   if (mSocket) {
     mSocket->Close();
   }
   mSocket = nullptr;
   mIntermediaryObj = nullptr;
--- a/dom/network/src/TCPSocketParent.h
+++ b/dom/network/src/TCPSocketParent.h
@@ -46,17 +46,18 @@ public:
 
   virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
                         const bool& useSSL, const nsString& aBinaryType);
 
   virtual bool RecvStartTLS() MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
-  virtual bool RecvData(const SendableData& aData) MOZ_OVERRIDE;
+  virtual bool RecvData(const SendableData& aData,
+                        const uint32_t& aTrackingNumber) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
 private:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   JSObject* mIntermediaryObj;
 };
 
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -15,37 +15,47 @@ function TCPSocketParentIntermediary() {
 
 TCPSocketParentIntermediary.prototype = {
   _setCallbacks: function(aParentSide, socket) {
     aParentSide.initJS(this);
     this._socket = socket;
 
     // Create handlers for every possible callback that attempt to trigger
     // corresponding callbacks on the child object.
-    ["open", "drain", "data", "error", "close"].forEach(
+    // ondrain event is not forwarded, since the decision of firing ondrain
+    // is made in child.
+    ["open", "data", "error", "close"].forEach(
       function(p) {
         socket["on" + p] = function(data) {
           aParentSide.sendEvent(p, data.data, socket.readyState,
                                 socket.bufferedAmount);
         };
       }
     );
- },
+  },
+
+  _onUpdateBufferedAmountHandler: function(aParentSide, aBufferedAmount, aTrackingNumber) {
+    aParentSide.sendUpdateBufferedAmount(aBufferedAmount, aTrackingNumber);
+  },
 
   open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType, aAppId) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
     if (!socket)
       return null;
 
     let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
     if (socketInternal) {
       socketInternal.setAppId(aAppId);
     }
 
+    // Handle parent's request to update buffered amount.
+    socketInternal.setOnUpdateBufferedAmountHandler(
+      this._onUpdateBufferedAmountHandler.bind(this, aParentSide));
+
     // Handlers are set to the JS-implemented socket object on the parent side.
     this._setCallbacks(aParentSide, socket);
     return socket;
   },
 
   listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
     let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
@@ -74,22 +84,25 @@ TCPSocketParentIntermediary.prototype = 
 
         aTCPServerSocketParent.sendCallbackError(error.message, error.filename,
                                                  error.lineNumber, error.columnNumber);
     };
 
     return serverSocket;
   },
 
-  onRecvSendString: function(aData) {
-    return this._socket.send(aData);
+  onRecvSendString: function(aData, aTrackingNumber) {
+    let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+    return socketInternal.onRecvSendFromChild(aData, 0, 0, aTrackingNumber);
   },
 
-  onRecvSendArrayBuffer: function(aData) {
-    return this._socket.send(aData, 0, aData.byteLength);
+  onRecvSendArrayBuffer: function(aData, aTrackingNumber) {
+    let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+    return socketInternal.onRecvSendFromChild(aData, 0, aData.byteLength,
+                                              aTrackingNumber);
   },
 
   classID: Components.ID("{afa42841-a6cb-4a91-912f-93099f6a3d18}"),
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsITCPSocketIntermediary
   ])
 };
 
--- a/dom/network/tests/unit/test_tcpsocket.js
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -413,37 +413,55 @@ function badConnect() {
  * and buffering again causes ondrain to be fired again.
  */
 
 function drainTwice() {
   let yays = makeJointSuccess(
     ['ondrain', 'ondrain2',
     'ondata', 'ondata2',
     'serverclose', 'clientclose']);
+  let ondrainCalled = false,
+      ondataCalled = false;
 
-  function serverSideCallback() {
-    yays.ondata();
+  function maybeSendNextData() {
+    if (!ondrainCalled || !ondataCalled) {
+      // make sure server got data and client got ondrain.
+      return;
+    }
+
     server.ondata = makeExpectData(
       "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
 
     sock.ondrain = yays.ondrain2;
 
     if (sock.send(BIG_ARRAY_BUFFER_2)) {
       do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
     }
 
     sock.close();
   }
 
+  function clientOndrain() {
+    yays.ondrain();
+    ondrainCalled = true;
+    maybeSendNextData();
+  }
+
+  function serverSideCallback() {
+    yays.ondata();
+    ondataCalled = true;
+    maybeSendNextData();
+  }
+
   server.onclose = yays.serverclose;
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
 
   sock.onclose = yays.clientclose;
-  sock.ondrain = yays.ondrain;
+  sock.ondrain = clientOndrain;
 
   if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
 }
 
 function cleanup() {
   do_print("Cleaning up");