Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 19 Apr 2013 07:46:11 -0400
changeset 129322 39615ca8ab61c0ef1d23a0666492a4884eb72b11
parent 129321 f5ba9622f687c9d52b9adffe5fc7b1cc3d81ac82 (current diff)
parent 129250 fd264d55113047884e1f3f7d440d7968e0916755 (diff)
child 129323 4fb6cb5453268a3db3f97015f87e9f378cf70c64
push idunknown
push userunknown
push dateunknown
milestone23.0a1
Merge m-c to inbound.
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -388,22 +388,16 @@ BluetoothHfpManager::Reset()
   ResetCallArray();
 }
 
 bool
 BluetoothHfpManager::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mSocket = new BluetoothSocket(this,
-                                BluetoothSocketType::RFCOMM,
-                                true,
-                                true);
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
-
   sHfpObserver = new BluetoothHfpManagerObserver();
   if (!sHfpObserver->Init()) {
     NS_WARNING("Cannot set up Hfp Observers!");
   }
 
   mListener = new BluetoothTelephonyListener();
   if (!mListener->StartListening()) {
     NS_WARNING("Failed to start listening RIL");
@@ -417,16 +411,18 @@ BluetoothHfpManager::Init()
   nsCOMPtr<nsISettingsServiceLock> settingsLock;
   nsresult rv = settings->CreateLock(getter_AddRefs(settingsLock));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
+  Listen();
+
   return true;
 }
 
 BluetoothHfpManager::~BluetoothHfpManager()
 {
   Cleanup();
 }
 
@@ -471,18 +467,17 @@ void
 BluetoothHfpManager::NotifySettings()
 {
   nsString type, name;
   BluetoothValue v;
   InfallibleTArray<BluetoothNamedValue> parameters;
   type.AssignLiteral("bluetooth-hfp-status-changed");
 
   name.AssignLiteral("connected");
-  v = (mSocket->GetConnectionStatus() ==
-       SocketConnectionStatus::SOCKET_CONNECTED);
+  v = IsConnected();
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("address");
   v = mDevicePath;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to broadcast system message to settings");
@@ -568,17 +563,17 @@ BluetoothHfpManager::HandleVolumeChanged
 
   // Adjust volume by headset and we don't have to send volume back to headset
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return NS_OK;
   }
 
   // Only send volume back when there's a connected headset
-  if (mSocket->GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED) {
+  if (IsConnected()) {
     SendCommand("+VGS: ", mCurrentVgs);
   }
 
   return NS_OK;
 }
 
 nsresult
 BluetoothHfpManager::HandleVoiceConnectionChanged()
@@ -671,16 +666,17 @@ BluetoothHfpManager::HandleShutdown()
 }
 
 // Virtual function of class SocketConsumer
 void
 BluetoothHfpManager::ReceiveSocketData(BluetoothSocket* aSocket,
                                        nsAutoPtr<UnixSocketRawData>& aMessage)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSocket);
 
   nsAutoCString msg((const char*)aMessage->mData.get(), aMessage->mSize);
   msg.StripWhitespace();
 
   nsTArray<nsCString> atCommandValues;
 
   // For more information, please refer to 4.34.1 "Bluetooth Defined AT
   // Capabilities" in Bluetooth hands-free profile 1.6
@@ -886,33 +882,56 @@ BluetoothHfpManager::ReceiveSocketData(B
 
     if (atCommandValues.IsEmpty()) {
       NS_WARNING("Could't get the value of command [AT+CCWA=]");
       goto respond_with_ok;
     }
 
     mCCWA = atCommandValues[0].EqualsLiteral("1");
   } else if (msg.Find("AT+CKPD") != -1) {
-    // For Headset Profile (HSP)
-    switch (mCurrentCallArray[mCurrentCallIndex].mState) {
-      case nsITelephonyProvider::CALL_STATE_INCOMING:
-        NotifyDialer(NS_LITERAL_STRING("ATA"));
-        break;
-      case nsITelephonyProvider::CALL_STATE_CONNECTED:
-      case nsITelephonyProvider::CALL_STATE_DIALING:
-      case nsITelephonyProvider::CALL_STATE_ALERTING:
-        NotifyDialer(NS_LITERAL_STRING("CHUP"));
-        break;
-      case nsITelephonyProvider::CALL_STATE_DISCONNECTED:
-        NotifyDialer(NS_LITERAL_STRING("BLDN"));
-        break;
-      default:
-        NS_WARNING("Not handling state changed");
-        break;
+    BluetoothScoManager* sco = BluetoothScoManager::Get();
+    if (!sco) {
+      NS_WARNING("Couldn't get BluetoothScoManager instance");
+      goto respond_with_ok;
     }
+
+    if (!sStopSendingRingFlag) {
+      // Bluetooth HSP spec 4.2.2
+      // There is an incoming call, notify Dialer to pick up the phone call
+      // and SCO will be established after we get the CallStateChanged event
+      // indicating the call is answered successfully.
+      NotifyDialer(NS_LITERAL_STRING("ATA"));
+    } else {
+      if (!sco->IsConnected()) {
+        // Bluetooth HSP spec 4.3
+        // If there's no SCO, set up a SCO link.
+        nsAutoString address;
+        mSocket->GetAddress(address);
+        sco->Connect(address);
+      } else if (!mFirstCKPD) {
+        // Bluetooth HSP spec 4.5
+        // There are two ways to release SCO: sending CHUP to dialer or closing
+        // SCO socket directly. We notify dialer only if there is at least one
+        // active call.
+        if (mCurrentCallArray.Length() > 1) {
+          NotifyDialer(NS_LITERAL_STRING("CHUP"));
+        } else {
+          sco->Disconnect();
+        }
+      } else {
+        // Three conditions have to be matched to come in here:
+        // (1) Not sending RING indicator
+        // (2) A SCO link exists
+        // (3) This is the very first AT+CKPD=200 of this session
+        // It is the case of Figure 4.3, Bluetooth HSP spec. Do nothing.
+        NS_WARNING("AT+CKPD=200: Do nothing");
+      }
+    }
+
+    mFirstCKPD = false;
   } else if (msg.Find("AT+CNUM") != -1) {
     if (!mMsisdn.IsEmpty()) {
       nsAutoCString message("+CNUM: ,\"");
       message.Append(NS_ConvertUTF16toUTF8(mMsisdn).get());
       message.AppendLiteral("\",");
       message.AppendInt(TOA_UNKNOWN);
       message.AppendLiteral(",,4");
       SendLine(message.get());
@@ -940,37 +959,45 @@ BluetoothHfpManager::Connect(const nsASt
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
     NS_WARNING("Connect called while in shutdown!");
     return false;
   }
 
-  SocketConnectionStatus s = mSocket->GetConnectionStatus();
-
-  if (s == SocketConnectionStatus::SOCKET_CONNECTED ||
-      s == SocketConnectionStatus::SOCKET_CONNECTING) {
+  if (mSocket) {
     NS_WARNING("BluetoothHfpManager has been already connected");
     return false;
   }
 
-  mSocket->Disconnect();
+  // Stop listening because currently we only support one connection at a time.
+  if (mHandsfreeSocket) {
+    mHandsfreeSocket->Disconnect();
+    mHandsfreeSocket = nullptr;
+  }
+
+  if (mHeadsetSocket) {
+    mHeadsetSocket->Disconnect();
+    mHeadsetSocket = nullptr;
+  }
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, false);
 
   nsString uuid;
   if (aIsHandsfree) {
     BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
   } else {
     BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, uuid);
   }
 
   mRunnable = aRunnable;
+  mSocket =
+    new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
 
   nsresult rv = bs->GetSocketViaService(aDevicePath,
                                         uuid,
                                         BluetoothSocketType::RFCOMM,
                                         true,
                                         true,
                                         mSocket,
                                         mRunnable);
@@ -983,55 +1010,78 @@ BluetoothHfpManager::Listen()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
     MOZ_ASSERT(false, "Listen called while in shutdown!");
     return false;
   }
 
-  if (mSocket->GetConnectionStatus() ==
-      SocketConnectionStatus::SOCKET_LISTENING) {
-    NS_WARNING("BluetoothHfpManager has been already listening");
-    return true;
-  }
-
-  mSocket->Disconnect();
-
-  if (!mSocket->Listen(BluetoothReservedChannels::CHANNEL_HANDSFREE_AG)) {
-    NS_WARNING("[HFP] Can't listen on socket!");
+  if (mSocket) {
+    NS_WARNING("mSocket exists. Failed to listen.");
     return false;
   }
 
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
+  if (!mHandsfreeSocket) {
+    mHandsfreeSocket =
+      new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
+
+    if (!mHandsfreeSocket->Listen(
+          BluetoothReservedChannels::CHANNEL_HANDSFREE_AG)) {
+      NS_WARNING("[HFP] Can't listen on RFCOMM socket!");
+      mHandsfreeSocket = nullptr;
+      return false;
+    }
+  }
+
+  if (!mHeadsetSocket) {
+    mHeadsetSocket =
+      new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
+
+    if (!mHeadsetSocket->Listen(
+          BluetoothReservedChannels::CHANNEL_HEADSET_AG)) {
+      NS_WARNING("[HSP] Can't listen on RFCOMM socket!");
+      mHandsfreeSocket->Disconnect();
+      mHandsfreeSocket = nullptr;
+      mHeadsetSocket = nullptr;
+      return false;
+    }
+  }
+
   return true;
 }
 
 void
 BluetoothHfpManager::Disconnect()
 {
-  mSocket->Disconnect();
+  if (mSocket) {
+    mSocket->Disconnect();
+    mSocket = nullptr;
+  }
 }
 
 bool
 BluetoothHfpManager::SendLine(const char* aMessage)
 {
+  MOZ_ASSERT(mSocket);
+
   nsAutoCString msg;
 
   msg.AppendLiteral(kHfpCrlf);
   msg.Append(aMessage);
   msg.AppendLiteral(kHfpCrlf);
 
   return mSocket->SendSocketData(msg);
 }
 
 bool
 BluetoothHfpManager::SendCommand(const char* aCommand, uint8_t aValue)
 {
-  if (mPrevSocketStatus != SocketConnectionStatus::SOCKET_CONNECTED) {
+  if (!IsConnected()) {
+    NS_WARNING("Trying to SendCommand() without a SLC");
     return false;
   }
 
   nsAutoCString message;
   int value = aValue;
   message += aCommand;
 
   if (!strcmp(aCommand, "+CIEV: ")) {
@@ -1136,18 +1186,18 @@ BluetoothHfpManager::UpdateCIND(uint8_t 
 }
 
 void
 BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex,
                                             uint16_t aCallState,
                                             const nsAString& aNumber,
                                             bool aSend)
 {
-  if (mSocket->GetConnectionStatus() !=
-      SocketConnectionStatus::SOCKET_CONNECTED) {
+  if (!IsConnected()) {
+    // Normal case. No need to print out warnings.
     return;
   }
 
   while (aCallIndex >= mCurrentCallArray.Length()) {
     Call call;
     mCurrentCallArray.AppendElement(call);
   }
 
@@ -1187,18 +1237,20 @@ BluetoothHfpManager::HandleCallStateChan
         sStopSendingRingFlag = false;
         UpdateCIND(CINDType::CALLSETUP, CallSetupState::INCOMING, aSend);
 
         nsAutoString number(aNumber);
         if (!mCLIP) {
           number.AssignLiteral("");
         }
 
-        MessageLoop::current()->PostDelayedTask(FROM_HERE,
-          new SendRingIndicatorTask(number, mCurrentCallArray[aCallIndex].mType),
+        MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          new SendRingIndicatorTask(number,
+                                    mCurrentCallArray[aCallIndex].mType),
           sRingInterval);
       }
       break;
     case nsITelephonyProvider::CALL_STATE_DIALING:
       mCurrentCallArray[aCallIndex].mDirection = false;
       UpdateCIND(CINDType::CALLSETUP, CallSetupState::OUTGOING, aSend);
 
       mSocket->GetAddress(address);
@@ -1261,17 +1313,18 @@ BluetoothHfpManager::HandleCallStateChan
           sStopSendingRingFlag = true;
         case nsITelephonyProvider::CALL_STATE_DIALING:
         case nsITelephonyProvider::CALL_STATE_ALERTING:
           // Outgoing call
           UpdateCIND(CINDType::CALLSETUP, CallSetupState::NO_CALLSETUP, aSend);
           break;
         case nsITelephonyProvider::CALL_STATE_CONNECTED:
           // No call is ongoing
-          if (sCINDItems[CINDType::CALLHELD].value == CallHeldState::NO_CALLHELD) {
+          if (sCINDItems[CINDType::CALLHELD].value ==
+              CallHeldState::NO_CALLHELD) {
             UpdateCIND(CINDType::CALL, CallState::NO_CALL, aSend);
           }
           break;
         case nsITelephonyProvider::CALL_STATE_HELD:
           UpdateCIND(CINDType::CALLHELD, CallHeldState::NO_CALLHELD, aSend);
           break;
         default:
           NS_WARNING("Not handling state changed");
@@ -1302,75 +1355,103 @@ BluetoothHfpManager::HandleCallStateChan
       sCINDItems[CINDType::CALLSETUP].value = CallSetupState::NO_CALLSETUP;
       sCINDItems[CINDType::CALLHELD].value = CallHeldState::NO_CALLHELD;
   }
 }
 
 void
 BluetoothHfpManager::OnConnectSuccess(BluetoothSocket* aSocket)
 {
-  MOZ_ASSERT(aSocket == mSocket);
+  MOZ_ASSERT(aSocket);
+
+  /**
+   * If the created connection is an inbound connection, close another server
+   * socket because currently only one SLC is allowed. After that, we need to
+   * make sure that both server socket would be nulled out. As for outbound
+   * connections, we do nothing since sockets have been already handled in
+   * function Connect().
+   */
+  if (aSocket == mHandsfreeSocket) {
+    MOZ_ASSERT(!mSocket);
+    mHandsfreeSocket.swap(mSocket);
+
+    mHeadsetSocket->Disconnect();
+    mHeadsetSocket = nullptr;
+  } else if (aSocket == mHeadsetSocket) {
+    MOZ_ASSERT(!mSocket);
+    mHeadsetSocket.swap(mSocket);
+
+    mHandsfreeSocket->Disconnect();
+    mHandsfreeSocket = nullptr;
+  }
 
   nsCOMPtr<nsITelephonyProvider> provider =
     do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
   NS_ENSURE_TRUE_VOID(provider);
   provider->EnumerateCalls(mListener->GetListener());
 
   // For active connection request, we need to reply the DOMRequest
   if (mRunnable) {
     BluetoothValue v = true;
     nsString errorStr;
     DispatchBluetoothReply(mRunnable, v, errorStr);
 
     mRunnable.forget();
   }
 
+  mFirstCKPD = true;
+
   // Cache device path for NotifySettings() since we can't get socket address
   // when a headset disconnect with us
   mSocket->GetAddress(mDevicePath);
-  mPrevSocketStatus = mSocket->GetConnectionStatus();
 
   NotifySettings();
 }
 
 void
 BluetoothHfpManager::OnConnectError(BluetoothSocket* aSocket)
 {
-  MOZ_ASSERT(aSocket == mSocket);
-
   // For active connection request, we need to reply the DOMRequest
   if (mRunnable) {
     BluetoothValue v;
     nsString errorStr;
     errorStr.AssignLiteral("Failed to connect with a bluetooth headset!");
     DispatchBluetoothReply(mRunnable, v, errorStr);
 
     mRunnable.forget();
   }
 
+  mSocket = nullptr;
+  mHandsfreeSocket = nullptr;
+  mHeadsetSocket = nullptr;
+
   // If connecting for some reason didn't work, restart listening
   Listen();
 }
 
 void
 BluetoothHfpManager::OnDisconnect(BluetoothSocket* aSocket)
 {
-  MOZ_ASSERT(aSocket == mSocket);
+  MOZ_ASSERT(aSocket);
 
-  // When we close a connected socket, then restart listening again and
-  // notify Settings app.
-  if (mPrevSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
-    Listen();
-    NotifySettings();
-  } else if (mPrevSocketStatus == SocketConnectionStatus::SOCKET_CONNECTING) {
-    NS_WARNING("BluetoothHfpManager got unexpected socket status!");
+  if (aSocket != mSocket) {
+    // Do nothing when a listening server socket is closed.
+    return;
   }
 
+  mSocket = nullptr;
   CloseScoSocket();
+
+  Listen();
+  NotifySettings();
   Reset();
 }
 
 bool
 BluetoothHfpManager::IsConnected()
 {
-  return mSocket->GetConnectionStatus() ==
-         SocketConnectionStatus::SOCKET_CONNECTED;
+  if (mSocket) {
+    return mSocket->GetConnectionStatus() ==
+           SocketConnectionStatus::SOCKET_CONNECTED;
+  }
+
+  return false;
 }
--- a/dom/bluetooth/BluetoothHfpManager.h
+++ b/dom/bluetooth/BluetoothHfpManager.h
@@ -104,24 +104,35 @@ private:
 
   int mCurrentVgs;
   int mCurrentVgm;
   uint32_t mCurrentCallIndex;
   bool mCCWA;
   bool mCLIP;
   bool mCMEE;
   bool mCMER;
+  bool mFirstCKPD;
   int mNetworkSelectionMode;
   bool mReceiveVgsFlag;
   nsString mDevicePath;
   nsString mMsisdn;
   nsString mOperatorName;
-  SocketConnectionStatus mPrevSocketStatus;
 
   nsTArray<Call> mCurrentCallArray;
   nsAutoPtr<BluetoothTelephonyListener> mListener;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  // If a connection has been established, mSocket will be the socket
+  // communicating with the remote socket. We maintain the invariant that if
+  // mSocket is non-null, mHandsfreeSocket and mHeadsetSocket must be null (and
+  // vice versa).
   nsRefPtr<BluetoothSocket> mSocket;
+
+  // Server sockets. Once an inbound connection is established, it will hand
+  // over the ownership to mSocket, and get a new server socket while Listen()
+  // is called.
+  nsRefPtr<BluetoothSocket> mHandsfreeSocket;
+  nsRefPtr<BluetoothSocket> mHeadsetSocket;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/BluetoothScoManager.cpp
+++ b/dom/bluetooth/BluetoothScoManager.cpp
@@ -69,30 +69,24 @@ void
 BluetoothScoManager::NotifyAudioManager(const nsAString& aAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obs =
     do_GetService("@mozilla.org/observer-service;1");
   NS_ENSURE_TRUE_VOID(obs);
 
-  nsCOMPtr<nsIAudioManager> am =
-    do_GetService("@mozilla.org/telephony/audiomanager;1");
-  NS_ENSURE_TRUE_VOID(am);
+  const PRUnichar* addr =
+    aAddress.IsEmpty() ? nullptr : aAddress.BeginReading();
 
-  if (aAddress.IsEmpty()) {
-    if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, nullptr))) {
-      NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
-      return;
-    }
-  } else {
-    if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, aAddress.BeginReading()))) {
-      NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
-      return;
-    }
+  if (NS_FAILED(obs->NotifyObservers(nullptr,
+                                     BLUETOOTH_SCO_STATUS_CHANGED,
+                                     addr))) {
+    NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
+    return;
   }
 }
 
 NS_IMPL_ISUPPORTS1(BluetoothScoManagerObserver, nsIObserver)
 
 namespace {
 StaticAutoPtr<BluetoothScoManager> gBluetoothScoManager;
 StaticRefPtr<BluetoothScoManagerObserver> sScoObserver;
@@ -100,17 +94,17 @@ bool gInShutdown = false;
 } // anonymous namespace
 
 NS_IMETHODIMP
 BluetoothScoManagerObserver::Observe(nsISupports* aSubject,
                                      const char* aTopic,
                                      const PRUnichar* aData)
 {
   MOZ_ASSERT(gBluetoothScoManager);
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {    
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     return gBluetoothScoManager->HandleShutdown();
   }
 
   MOZ_ASSERT(false, "BluetoothScoManager got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
 BluetoothScoManager::BluetoothScoManager()
@@ -194,29 +188,27 @@ BluetoothScoManager::Connect(const nsASt
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gInShutdown) {
     MOZ_ASSERT(false, "Connect called while in shutdown!");
     return false;
   }
 
-  if (mSocket->GetConnectionStatus() ==
-      SocketConnectionStatus::SOCKET_CONNECTED) {
-    NS_WARNING("Sco socket has been connected");
+  SocketConnectionStatus status = mSocket->GetConnectionStatus();
+  if (status == SocketConnectionStatus::SOCKET_CONNECTED ||
+      status == SocketConnectionStatus::SOCKET_CONNECTING) {
+    NS_WARNING("SCO connection exists or is being established");
     return false;
   }
 
   mSocket->Disconnect();
 
   BluetoothService* bs = BluetoothService::Get();
-  if (!bs) {
-    NS_WARNING("BluetoothService not available!");
-    return false;
-  }
+  NS_ENSURE_TRUE(bs, false);
 
   nsresult rv = bs->GetScoSocket(aDeviceAddress,
                                  true,
                                  false,
                                  mSocket);
 
   return NS_FAILED(rv) ? false : true;
 }
@@ -283,8 +275,19 @@ BluetoothScoManager::OnDisconnect(Blueto
 
   if (mPrevSocketStatus == SocketConnectionStatus::SOCKET_CONNECTED) {
     Listen();
 
     nsString address = NS_LITERAL_STRING("");
     NotifyAudioManager(address);
   }
 }
+
+bool
+BluetoothScoManager::IsConnected()
+{
+  if (mSocket) {
+    return mSocket->GetConnectionStatus() ==
+           SocketConnectionStatus::SOCKET_CONNECTED;
+  }
+
+  return false;
+}
--- a/dom/bluetooth/BluetoothScoManager.h
+++ b/dom/bluetooth/BluetoothScoManager.h
@@ -28,16 +28,17 @@ public:
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
   virtual void OnConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
 
   bool Connect(const nsAString& aDeviceObjectPath);
   void Disconnect();
   bool Listen();
+  bool IsConnected();
 
 private:
   friend class BluetoothScoManagerObserver;
   BluetoothScoManager();
   bool Init();
   void Cleanup();
   nsresult HandleShutdown();
   void NotifyAudioManager(const nsAString& aAddress);
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -373,31 +373,40 @@ BluetoothService::UnregisterBluetoothSig
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aHandler);
 
   BT_LOG("[S] %s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(aNodeName).get());
 
   BluetoothSignalObserverList* ol;
   if (mBluetoothSignalObserverTable.Get(aNodeName, &ol)) {
     ol->RemoveObserver(aHandler);
+    // We shouldn't have duplicate instances in the ObserverList, but there's
+    // no appropriate way to do duplication check while registering, so
+    // assertions are added here.
+    MOZ_ASSERT(!ol->RemoveObserver(aHandler));
     if (ol->Length() == 0) {
       mBluetoothSignalObserverTable.Remove(aNodeName);
     }
   }
   else {
     NS_WARNING("Node was never registered!");
   }
 }
 
 PLDHashOperator
 RemoveAllSignalHandlers(const nsAString& aKey,
                         nsAutoPtr<BluetoothSignalObserverList>& aData,
                         void* aUserArg)
 {
-  aData->RemoveObserver(static_cast<BluetoothSignalObserver*>(aUserArg));
+  BluetoothSignalObserver* handler = static_cast<BluetoothSignalObserver*>(aUserArg);
+  aData->RemoveObserver(handler);
+  // We shouldn't have duplicate instances in the ObserverList, but there's
+  // no appropriate way to do duplication check while registering, so
+  // assertions are added here.
+  MOZ_ASSERT(!aData->RemoveObserver(handler));
   return aData->Length() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
 }
 
 void
 BluetoothService::UnregisterAllSignalHandlers(BluetoothSignalObserver* aHandler)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aHandler);
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -72,31 +72,31 @@ BluetoothServiceChildProcess::NoteDeadAc
   gBluetoothChild = nullptr;
 }
 
 void
 BluetoothServiceChildProcess::RegisterBluetoothSignalHandler(
                                               const nsAString& aNodeName,
                                               BluetoothSignalObserver* aHandler)
 {
-  if (gBluetoothChild) {
+  if (gBluetoothChild && !IsSignalRegistered(aNodeName)) {
     gBluetoothChild->SendRegisterSignalHandler(nsString(aNodeName));
   }
   BluetoothService::RegisterBluetoothSignalHandler(aNodeName, aHandler);
 }
 
 void
 BluetoothServiceChildProcess::UnregisterBluetoothSignalHandler(
                                               const nsAString& aNodeName,
                                               BluetoothSignalObserver* aHandler)
 {
-  if (gBluetoothChild) {
+  BluetoothService::UnregisterBluetoothSignalHandler(aNodeName, aHandler);
+  if (gBluetoothChild && !IsSignalRegistered(aNodeName)) {
     gBluetoothChild->SendUnregisterSignalHandler(nsString(aNodeName));
   }
-  BluetoothService::UnregisterBluetoothSignalHandler(aNodeName, aHandler);
 }
 
 nsresult
 BluetoothServiceChildProcess::GetDefaultAdapterPathInternal(
                                               BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, DefaultAdapterPathRequest());
   return NS_OK;
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -169,13 +169,18 @@ private:
 
   // Should never be called from the child
   virtual nsresult
   GetDevicePropertiesInternal(const BluetoothSignal& aSignal) MOZ_OVERRIDE;
 
   // This method should never be called from the child.
   virtual nsresult
   PrepareAdapterInternal() MOZ_OVERRIDE;
+
+  bool
+  IsSignalRegistered(const nsAString& aNodeName) {
+    return !!mBluetoothSignalObserverTable.Get(aNodeName);
+  }
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothservicechildprocess_h__
--- a/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/ril/MobileMessageDatabaseService.js
@@ -16,17 +16,17 @@ const RIL_MOBILEMESSAGEDATABASESERVICE_C
   Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}");
 const RIL_GETMESSAGESCURSOR_CID =
   Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DB_NAME = "sms";
-const DB_VERSION = 9;
+const DB_VERSION = 10;
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
 const DELIVERY_RECEIVED = "received";
@@ -200,16 +200,20 @@ MobileMessageDatabaseService.prototype =
           case 7:
             if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores.");
             self.upgradeSchema7(db, event.target.transaction);
             break;
           case 8:
             if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS.");
             self.upgradeSchema8(event.target.transaction);
             break;
+          case 9:
+            if (DEBUG) debug("Upgrade to version 10. Upgrade type if it's not existing.");
+            self.upgradeSchema9(event.target.transaction);
+            break;
           default:
             event.target.transaction.abort();
             callback("Old database version: " + event.oldVersion, null);
             break;
         }
         currentVersion++;
       }
     };
@@ -592,16 +596,35 @@ MobileMessageDatabaseService.prototype =
         messageRecord.transactionIdIndex =
           messageRecord.headers["x-mms-transaction-id"];
         cursor.update(messageRecord);
       }
       cursor.continue();
     };
   },
 
+  upgradeSchema9: function upgradeSchema9(transaction) {
+    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
+
+    // Update type attributes.
+    messageStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        return;
+      }
+
+      let messageRecord = cursor.value;
+      if (messageRecord.type == undefined) {
+        messageRecord.type = "sms";
+        cursor.update(messageRecord);
+      }
+      cursor.continue();
+    };
+  },
+
   createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) {
     if (DEBUG) {
       debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord));
     }
     if (aMessageRecord.type == "sms") {
       return gMobileMessageService.createSmsMessage(aMessageRecord.id,
                                                     aMessageRecord.threadId,
                                                     aMessageRecord.delivery,
@@ -889,16 +912,17 @@ MobileMessageDatabaseService.prototype =
 
         let timestamp = aMessageRecord.timestamp;
         if (threadRecord) {
           let needsUpdate = false;
 
           if (threadRecord.lastTimestamp <= timestamp) {
             threadRecord.lastTimestamp = timestamp;
             threadRecord.subject = aMessageRecord.body;
+            threadRecord.lastMessageId = aMessageRecord.id;
             needsUpdate = true;
           }
 
           if (!aMessageRecord.read) {
             threadRecord.unreadCount++;
             needsUpdate = true;
           }
 
@@ -1255,20 +1279,22 @@ MobileMessageDatabaseService.prototype =
         }
         aRequest.notifyGetMessageFailed(aRv, null);
       }
     };
     this.getMessageRecordById(aMessageId, notifyCallback);
   },
 
   deleteMessage: function deleteMessage(messageId, aRequest) {
+    if (DEBUG) debug("deleteMessage: message id " + messageId);
     let deleted = false;
     let self = this;
     this.newTxn(READ_WRITE, function (error, txn, stores) {
       if (error) {
+        if (DEBUG) debug("deleteMessage: failed to open transaction");
         aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       txn.onerror = function onerror(event) {
         if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
         //TODO look at event.target.errorCode, pick appropriate error constant
         aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
       };
@@ -1285,24 +1311,26 @@ MobileMessageDatabaseService.prototype =
 
       messageStore.get(messageId).onsuccess = function(event) {
         let messageRecord = event.target.result;
         if (messageRecord) {
           if (DEBUG) debug("Deleting message id " + messageId);
 
           // First actually delete the message.
           messageStore.delete(messageId).onsuccess = function(event) {
+            if (DEBUG) debug("Message id " + messageId + " deleted");
             deleted = true;
 
             // Then update unread count and most recent message.
             let threadId = messageRecord.threadId;
 
             threadStore.get(threadId).onsuccess = function(event) {
               // This must exist.
               let threadRecord = event.target.result;
+              if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord));
 
               if (!messageRecord.read) {
                 threadRecord.unreadCount--;
               }
 
               if (threadRecord.lastMessageId == messageId) {
                 // Check most recent sender/receiver.
                 let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]);
@@ -2052,25 +2080,25 @@ GetThreadsCursor.prototype = {
   classID: RIL_GETTHREADSCURSOR_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
 
   service: null,
   callback: null,
   collector: null,
 
   getThreadTxn: function getThreadTxn(threadStore, threadId) {
-    if (DEBUG) debug ("Fetching message " + threadId);
+    if (DEBUG) debug ("Fetching thread " + threadId);
 
     let getRequest = threadStore.get(threadId);
     let self = this;
     getRequest.onsuccess = function onsuccess(event) {
+      let threadRecord = event.target.result;
       if (DEBUG) {
-        debug("notifyCursorResult - threadId: " + threadId);
+        debug("notifyCursorResult: " + JSON.stringify(threadRecord));
       }
-      let threadRecord = event.target.result;
       let thread =
         gMobileMessageService.createThread(threadRecord.id,
                                            threadRecord.participantAddresses,
                                            threadRecord.lastTimestamp,
                                            threadRecord.subject,
                                            threadRecord.unreadCount);
       self.callback.notifyCursorResult(thread);
     };
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -25,8 +25,9 @@ qemu = true
 [test_filter_mixed.js]
 [test_segment_info.js]
 [test_mark_msg_read.js]
 [test_mark_msg_read_error.js]
 [test_bug814761.js]
 [test_strict_7bit_encoding.js]
 [test_incoming_max_segments.js]
 [test_outgoing_max_segments.js]
+[test_update_thread_record_in_delete.js]
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
@@ -0,0 +1,232 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+
+const FROM = "09123456789";
+const PREFIX = "msg_";
+const MSGS = 3;
+
+SpecialPowers.addPermission("sms", true, document);
+SpecialPowers.setBoolPref("dom.sms.enabled", true);
+
+let mozSms = window.navigator.mozSms;
+ok(mozSms instanceof MozSmsManager);
+
+let pendingEmulatorCmdCount = 0;
+function sendSmsToEmulator(from, text) {
+  ++pendingEmulatorCmdCount;
+
+  let cmd = "sms send " + from + " " + text;
+  runEmulatorCmd(cmd, function (result) {
+    --pendingEmulatorCmdCount;
+
+    is(result[0], "OK", "Emulator response");
+  });
+}
+
+let tasks = {
+  // List of test fuctions. Each of them should call |tasks.next()| when
+  // completed or |tasks.finish()| to jump to the last one.
+  _tasks: [],
+  _nextTaskIndex: 0,
+
+  push: function push(func) {
+    this._tasks.push(func);
+  },
+
+  next: function next() {
+    let index = this._nextTaskIndex++;
+    let task = this._tasks[index];
+    try {
+      task();
+    } catch (ex) {
+      ok(false, "test task[" + index + "] throws: " + ex);
+      // Run last task as clean up if possible.
+      if (index != this._tasks.length - 1) {
+        this.finish();
+      }
+    }
+  },
+
+  finish: function finish() {
+    this._tasks[this._tasks.length - 1]();
+  },
+
+  run: function run() {
+    this.next();
+  }
+};
+
+function getAllMessages(callback, filter, reverse) {
+  if (!filter) {
+    filter = new MozSmsFilter;
+  }
+  let messages = [];
+  let cursor = mozSms.getMessages(filter, reverse || false);
+  cursor.onsuccess = function(event) {
+    if (!this.done) {
+      messages.push(this.result);
+      this.continue();
+      return;
+    }
+
+    window.setTimeout(callback.bind(null, messages), 0);
+  }
+}
+
+function getAllThreads(callback) {
+  let threads = [];
+  let cursor = mozSms.getThreads();
+  cursor.onsuccess = function(event) {
+    if (!this.done) {
+      threads.push(this.result);
+      this.continue();
+      return;
+    }
+
+    window.setTimeout(callback.bind(null, threads), 0);
+  }
+}
+
+function deleteAllMessages() {
+  getAllMessages(function deleteAll(messages) {
+    let message = messages.shift();
+    if (!message) {
+      ok(true, "all messages deleted");
+      tasks.next();
+      return;
+    }
+
+    let request = mozSms.delete(message.id);
+    request.onsuccess = deleteAll.bind(null, messages);
+    request.onerror = function (event) {
+      ok(false, "failed to delete all messages");
+      tasks.finish();
+    }
+  });
+}
+
+function checkThread(thread, id, body, unreadCount, timestamp)
+{
+  is(thread.id, id, "Thread ID is set");
+  is(thread.body, body, "Thread subject is set to last message body");
+  is(thread.unreadCount, unreadCount, "Thread unread count");
+  is(JSON.stringify(thread.participants), JSON.stringify([FROM]), "Thread participants");
+  is(thread.timestamp.getTime(), timestamp.getTime(), "Thread timestamp is set");
+}
+
+tasks.push(deleteAllMessages);
+
+tasks.push(getAllThreads.bind(null, function (threads) {
+  is(threads.length, 0, "Threads are cleared");
+  tasks.next();
+}));
+
+let gotMessagesCount = 0;
+tasks.push(function () {
+  mozSms.onreceived = function () {
+    ++gotMessagesCount;
+  };
+  tasks.next();
+});
+
+tasks.push(function () {
+  for (let i = 1; i <= MSGS; i++) {
+    sendSmsToEmulator(FROM, PREFIX + i);
+  }
+  tasks.next();
+});
+
+let allMessages;
+tasks.push(function waitAllMessageReceived() {
+  if (gotMessagesCount != MSGS) {
+    window.setTimeout(waitAllMessageReceived, 100);
+    return;
+  }
+
+  getAllMessages(function (messages) {
+    allMessages = messages;
+    tasks.next();
+  });
+});
+
+
+let originalThread;
+tasks.push(getAllThreads.bind(null, function (threads) {
+  is(threads.length, 1, "Should have only one thread");
+
+  let lastMessage = allMessages[allMessages.length - 1];
+
+  originalThread = threads[0];
+  checkThread(originalThread,
+              originalThread.id,
+              lastMessage.body,
+              allMessages.length,
+              lastMessage.timestamp);
+
+  tasks.next();
+}));
+
+tasks.push(function DeleteFirstMessage() {
+  let request = mozSms.delete(allMessages[0].id);
+  request.onsuccess = function () {
+    getAllThreads(function (threads) {
+      is(threads.length, 1, "Should have only one thread");
+
+      allMessages = allMessages.slice(1);
+      let lastMessage = allMessages[allMessages.length - 1];
+
+      checkThread(threads[0],
+                  originalThread.id,
+                  lastMessage.body,
+                  allMessages.length,
+                  lastMessage.timestamp);
+
+      tasks.next();
+    });
+  };
+});
+
+tasks.push(function DeleteLastMessage() {
+  let request = mozSms.delete(allMessages[allMessages.length - 1].id);
+  request.onsuccess = function () {
+    getAllThreads(function (threads) {
+      is(threads.length, 1, "Should have only one thread");
+
+      allMessages = allMessages.slice(0, -1);
+      let lastMessage = allMessages[allMessages.length - 1];
+
+      checkThread(threads[0],
+                  originalThread.id,
+                  lastMessage.body,
+                  allMessages.length,
+                  lastMessage.timestamp);
+
+      tasks.next();
+    });
+  };
+});
+
+tasks.push(deleteAllMessages);
+
+tasks.push(getAllThreads.bind(null, function (threads) {
+  is(threads.length, 0, "Should have deleted all threads");
+  tasks.next();
+}));
+
+// WARNING: All tasks should be pushed before this!!!
+tasks.push(function cleanUp() {
+  if (pendingEmulatorCmdCount) {
+    window.setTimeout(cleanUp, 100);
+    return;
+  }
+
+  mozSms.onreceived = null;
+
+  SpecialPowers.removePermission("sms", document);
+  SpecialPowers.clearUserPref("dom.sms.enabled");
+  finish();
+});
+
+tasks.run();
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -34,17 +34,17 @@ interface nsIDOMTCPSocket : nsISupports
    *
    * @param host The hostname of the server to connect to.
    * @param port The port to connect to.
    * @param options An object specifying one or more parameters which
    *                determine the details of the socket.
    *
    *        useSSL: true to create an SSL socket. Defaults to false.
    *
-   *        binaryType: "arraybuffer" to use UInt8 array
+   *        binaryType: "arraybuffer" to use ArrayBuffer
    *          instances in the ondata callback and as the argument
    *          to send. Defaults to "string", to use JavaScript strings.
    *
    * @return The new TCPSocket instance.
    */
   nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
 
   /**
@@ -84,33 +84,37 @@ interface nsIDOMTCPSocket : nsISupports
    */
   void close();
 
   /**
    * Write data to the socket.
    *
    * @param data The data to write to the socket. If
    *             binaryType: "arraybuffer" was passed in the options
-   *             object, then this object should be an Uint8Array instance.
+   *             object, then this object should be an ArrayBuffer instance.
    *             If binaryType: "string" was passed, or if no binaryType
    *             option was specified, then this object should be an
    *             ordinary JavaScript string.
+   * @param byteOffset The offset within the data from which to begin writing.
+   *                   Has no effect on non-ArrayBuffer data.
+   * @param byteLength The number of bytes to write. Has no effect on
+   *                   non-ArrayBuffer data.
    *
    * @return Send returns true or false as a hint to the caller that
    *         they may either continue sending more data immediately, or
    *         may want to wait until the other side has read some of the
    *         data which has already been written to the socket before
    *         buffering more. If send returns true, then less than 64k
    *         has been buffered and it's safe to immediately write more.
    *         If send returns false, then more than 64k has been buffered,
    *         and the caller may wish to wait until the ondrain event
    *         handler has been called before buffering more data by more
    *         calls to send.
    */
-  boolean send(in jsval data);
+  boolean send(in jsval data, [optional] in unsigned long byteOffset, [optional] in unsigned long byteLength);
 
   /**
    * The readyState attribute indicates which state the socket is currently
    * in. The state will be either "connecting", "open", "closing", or "closed".
    */
   readonly attribute DOMString readyState;
 
   /**
@@ -138,17 +142,17 @@ interface nsIDOMTCPSocket : nsISupports
    * to the network, at which point the client can resume calling send again.
    */
   attribute jsval ondrain;
 
   /**
    * The ondata handler will be called repeatedly and asynchronously after
    * onopen has been called, every time some data was available from the server
    * and was read. If binaryType: "arraybuffer" was passed to open, the data
-   * attribute of the event object will be an Uint8Array. If not, it will be a
+   * attribute of the event object will be an ArrayBuffer. If not, it will be a
    * normal JavaScript string.
    *
    * At any time, the client may choose to pause reading and receiving ondata
    * callbacks, by calling the socket's suspend() method. Further invocations
    * of ondata will be paused until resume() is called.
    */
   attribute jsval ondata;
 
@@ -183,17 +187,17 @@ interface nsIDOMTCPSocket : nsISupports
 interface nsITCPSocketInternal : nsISupports {
   // Trigger the callback for |type| and provide an Error() object with the given data
   void callListenerError(in DOMString type, in DOMString message, in DOMString filename,
                          in uint32_t lineNumber, in uint32_t columnNumber);
 
   // 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 a Uint8Array argument
+  // 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);
 };
@@ -221,17 +225,17 @@ interface nsITCPSocketEvent : nsISupport
    * drain
    * close
    */
   readonly attribute DOMString type;
 
   /**
    * The data related to this event, if any. In the ondata callback,
    * data will be the bytes read from the network; if the binaryType
-   * of the socket was "arraybuffer", this value will be of type Uint8Array;
+   * of the socket was "arraybuffer", this value will be of type ArrayBuffer;
    * otherwise, it will be a normal JavaScript string.
    *
    * In the onerror callback, data will be a string with a description
    * of the error.
    *
    * In the other callbacks, data will be an empty string.
    */
   readonly attribute jsval data;
--- a/dom/network/interfaces/nsITCPSocketChild.idl
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -13,13 +13,13 @@ interface nsITCPSocketChild : nsISupport
 {
   // Tell the chrome process to open a corresponding connection with the given parameters
   [implicit_jscontext]
   void open(in nsITCPSocketInternal socket, in DOMString host,
             in unsigned short port, in boolean ssl, in DOMString binaryType,
             in nsIDOMWindow window, in jsval socketVal);
 
   // Tell the chrome process to perform equivalent operations to all following methods
-  [implicit_jscontext] void send(in jsval data);
+  [implicit_jscontext] void send(in jsval data, in unsigned long byteOffset, in unsigned long byteLength);
   void resume();
   void suspend();
   void close();
 };
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/src/TCPSocket.js
@@ -18,16 +18,18 @@ const InputStreamPump = CC(
       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 kCONNECTING = 'connecting';
 const kOPEN = 'open';
 const kCLOSING = 'closing';
 const kCLOSED = 'closed';
 
@@ -84,23 +86,16 @@ function TCPSocket() {
   this._onclose = null;
 
   this._binaryType = "string";
 
   this._host = "";
   this._port = 0;
   this._ssl = false;
 
-  // As a workaround for bug https://bugzilla.mozilla.org/show_bug.cgi?id=786639
-  // we want to create any Uint8Array's off of the owning window so that there
-  // is no need for a wrapper to exist around the typed array from the
-  // perspective of content.  (The wrapper is bad because it only lets content
-  // see length, and forbids access to the array indices unless we excplicitly
-  // list them all.)  We will then access the array through a wrapper, but
-  // since we are chrome-privileged, this does not pose a problem.
   this.useWin = null;
 }
 
 TCPSocket.prototype = {
   __exposedProps__: {
     open: 'r',
     host: 'r',
     port: 'r',
@@ -431,53 +426,45 @@ TCPSocket.prototype = {
     }
 
     if (!this._multiplexStream.count) {
       this._socketOutputStream.close();
     }
     this._socketInputStream.close();
   },
 
-  send: function ts_send(data) {
+  send: function ts_send(data, byteOffset, byteLength) {
     if (this._readyState !== kOPEN) {
       throw new Error("Socket not open.");
     }
 
-    if (this._inChild) {
-      this._socketBridge.send(data);
+    if (this._binaryType === "arraybuffer") {
+      byteLength = byteLength || data.byteLength;
     }
 
-    let new_stream = new StringInputStream();
-    if (this._binaryType === "arraybuffer") {
-      // It would be really nice if there were an interface
-      // that took an ArrayBuffer like StringInputStream takes
-      // a string. There is one, but only in C++ and not exposed
-      // to js as far as I can tell
-      var dataLen = data.length;
-      var offset = 0;
-      var result = "";
-      while (dataLen) {
-        var fragmentLen = dataLen;
-        if (fragmentLen > 32768)
-          fragmentLen = 32768;
-        dataLen -= fragmentLen;
+    if (this._inChild) {
+      this._socketBridge.send(data, byteOffset, byteLength);
+    }
 
-        var fragment = data.subarray(offset, offset + fragmentLen);
-        offset += fragmentLen;
-        result += String.fromCharCode.apply(null, fragment);
-      }
-      data = result;
-    }
-    var newBufferedAmount = this.bufferedAmount + data.length;
+    let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+
+    var newBufferedAmount = this.bufferedAmount + length;
     var bufferNotFull = newBufferedAmount < BUFFER_SIZE;
     if (this._inChild) {
       return bufferNotFull;
     }
 
-    new_stream.setData(data, data.length);
+    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, length);
+    }
     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.
@@ -571,20 +558,17 @@ TCPSocket.prototype = {
     }
 
     this.callListener("close");
   },
 
   // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
   onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
     if (this._binaryType === "arraybuffer") {
-      let ua = this.useWin ? new this.useWin.Uint8Array(count)
-                           : new Uint8Array(count);
-      ua.set(this._inputStreamBinary.readByteArray(count));
-      this.callListener("data", ua);
+      this.callListener("data", this._inputStreamBinary.readArrayBuffer(count));
     } else {
       this.callListener("data", this._inputStreamScriptable.read(count));
     }
   },
 
   classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
 
   classInfo: XPCOMUtils.generateCI({
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -12,35 +12,32 @@
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 using mozilla::net::gNeckoChild;
 
 namespace IPC {
 
 bool
-DeserializeUint8Array(JSRawObject aObj,
-                      const InfallibleTArray<uint8_t>& aBuffer,
-                      JS::Value* aVal)
+DeserializeArrayBuffer(JSRawObject aObj,
+                       const InfallibleTArray<uint8_t>& aBuffer,
+                       JS::Value* aVal)
 {
   JSContext* cx = nsContentUtils::GetSafeJSContext();
   JSAutoRequest ar(cx);
   JSAutoCompartment ac(cx, aObj);
 
   JSObject* obj = JS_NewArrayBuffer(cx, aBuffer.Length());
   if (!obj)
     return false;
   uint8_t* data = JS_GetArrayBufferData(obj);
   if (!data)
     return false;
   memcpy(data, aBuffer.Elements(), aBuffer.Length());
-  JSObject* arr = JS_NewUint8ArrayWithBuffer(cx, obj, 0, aBuffer.Length());
-  if (!arr)
-    return false;
-  *aVal = OBJECT_TO_JSVAL(arr);
+  *aVal = OBJECT_TO_JSVAL(obj);
   return true;
 }
 
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
 
@@ -131,17 +128,17 @@ TCPSocketChild::RecvCallback(const nsStr
     rv = mSocket->CallListenerError(aType, err.message(), err.filename(),
                                    err.lineNumber(), err.columnNumber());
 
   } else if (aData.type() == CallbackData::TSendableData) {
     const SendableData& data = aData.get_SendableData();
 
     if (data.type() == SendableData::TArrayOfuint8_t) {
       JS::Value val;
-      IPC::DeserializeUint8Array(mSocketObj, data.get_ArrayOfuint8_t(), &val);
+      IPC::DeserializeArrayBuffer(mSocketObj, data.get_ArrayOfuint8_t(), &val);
       rv = mSocket->CallListenerArrayBuffer(aType, val);
 
     } else if (data.type() == SendableData::TnsString) {
       rv = mSocket->CallListenerData(aType, data.get_nsString());
 
     } else {
       MOZ_NOT_REACHED("Invalid callback data type!");
     }
@@ -170,32 +167,36 @@ TCPSocketChild::Resume()
 NS_IMETHODIMP
 TCPSocketChild::Close()
 {
   SendClose();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Send(const JS::Value& aData, JSContext* aCx)
+TCPSocketChild::Send(const JS::Value& aData,
+                     uint32_t aByteOffset,
+                     uint32_t aByteLength,
+                     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);
 
   } else {
     NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
     JSObject* obj = &aData.toObject();
-    NS_ENSURE_TRUE(JS_IsTypedArrayObject(obj), NS_ERROR_FAILURE);
-    NS_ENSURE_TRUE(JS_IsUint8Array(obj), NS_ERROR_FAILURE);
-    uint32_t nbytes = JS_GetTypedArrayByteLength(obj);
-    uint8_t* data = JS_GetUint8ArrayData(obj);
+    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);
     if (!data) {
       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;
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -9,19 +9,19 @@
 #include "nsIDOMTCPSocket.h"
 #include "mozilla/unused.h"
 #include "mozilla/AppProcessChecker.h"
 
 namespace IPC {
 
 //Defined in TCPSocketChild.cpp
 extern bool
-DeserializeUint8Array(JSRawObject aObj,
-                      const InfallibleTArray<uint8_t>& aBuffer,
-                      JS::Value* aVal);
+DeserializeArrayBuffer(JSRawObject aObj,
+                       const InfallibleTArray<uint8_t>& aBuffer,
+                       JS::Value* aVal);
 
 }
 
 namespace mozilla {
 namespace dom {
 
 static void
 FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
@@ -92,17 +92,17 @@ bool
 TCPSocketParent::RecvData(const SendableData& aData)
 {
   NS_ENSURE_TRUE(mIntermediary, true);
 
   nsresult rv;
   switch (aData.type()) {
     case SendableData::TArrayOfuint8_t: {
       JS::Value val;
-      IPC::DeserializeUint8Array(mIntermediaryObj, aData.get_ArrayOfuint8_t(), &val);
+      IPC::DeserializeArrayBuffer(mIntermediaryObj, aData.get_ArrayOfuint8_t(), &val);
       rv = mIntermediary->SendArrayBuffer(val);
       NS_ENSURE_SUCCESS(rv, true);
       break;
     }
 
     case SendableData::TnsString:
       rv = mIntermediary->SendString(aData.get_nsString());
       NS_ENSURE_SUCCESS(rv, true);
@@ -144,20 +144,19 @@ TCPSocketParent::SendCallback(const nsAS
     }
     data = str;
 
   } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
     data = mozilla::void_t();
 
   } else if (aDataVal.isObject()) {
     JSObject* obj = &aDataVal.toObject();
-    if (JS_IsTypedArrayObject(obj)) {
-      NS_ENSURE_TRUE(JS_IsUint8Array(obj), NS_ERROR_FAILURE);
-      uint32_t nbytes = JS_GetTypedArrayByteLength(obj);
-      uint8_t* buffer = JS_GetUint8ArrayData(obj);
+    if (JS_IsArrayBufferObject(obj)) {
+      uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
+      uint8_t* buffer = JS_GetArrayBufferData(obj);
       if (!buffer) {
         FireInteralError(this, __LINE__);
         return NS_ERROR_OUT_OF_MEMORY;
       }
       FallibleTArray<uint8_t> fallibleArr;
       if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
         FireInteralError(this, __LINE__);
         return NS_ERROR_OUT_OF_MEMORY;
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -38,17 +38,17 @@ TCPSocketParentIntermediary.prototype = 
     return socket;
   },
 
   sendString: function(aData) {
     return this._socket.send(aData);
   },
 
   sendArrayBuffer: function(aData) {
-    return this._socket.send(aData);
+    return this._socket.send(aData, 0, aData.byteLength);
   },
 
   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
@@ -23,29 +23,36 @@ const CC = Components.Constructor;
 /**
  *
  * Constants
  *
  */
 
 // Some binary data to send.
 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
-      TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY),
+      DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
+      TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
       HELLO_WORLD = "hlo wrld. ",
       BIG_ARRAY = new Array(65539),
       BIG_ARRAY_2 = new Array(65539);
 
+TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
+
 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
   BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
   BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
 }
 
-const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY),
-      BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_2);
-      
+const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length),
+      BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length);
+const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER),
+      BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2);
+BIG_TYPED_ARRAY.set(BIG_ARRAY);
+BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2);
+
 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
                         "nsIServerSocket",
                         "init"),
       InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
                            "nsIInputStreamPump",
                            "init"),
       BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                              "nsIBinaryInputStream",
@@ -171,57 +178,67 @@ function makeJointSuccess(names) {
   return funcs;
 }
 
 function makeFailureCase(name) {
   return function() {
     let argstr;
     if (arguments.length) {
       argstr = '(args: ' +
-        Array.map(arguments, function(x) { return x + ""; }).join(" ") + ')';
+        Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')';
     }
     else {
       argstr = '(no arguments)';
     }
     do_throw('got unexpected: ' + name + ' ' + argstr);
   };
 }
 
 function makeExpectData(name, expectedData, fromEvent, callback) {
   let dataBuffer = fromEvent ? null : [], done = false;
+  let dataBufferView = null;
   return function(receivedData) {
+    if (receivedData.data) {
+      receivedData = receivedData.data;
+    }
+    let recvLength = receivedData.byteLength !== undefined ?
+        receivedData.byteLength : receivedData.length;
+
     if (fromEvent) {
-      receivedData = receivedData.data;
       if (dataBuffer) {
-        let newBuffer = new Uint8Array(dataBuffer.length + receivedData.length);
-        newBuffer.set(dataBuffer, 0);
-        newBuffer.set(receivedData, dataBuffer.length);
+        let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
+        let newBufferView = new Uint8Array(newBuffer);
+        newBufferView.set(dataBufferView, 0);
+        newBufferView.set(receivedData, dataBuffer.byteLength);
         dataBuffer = newBuffer;
+        dataBufferView = newBufferView;
       }
       else {
         dataBuffer = receivedData;
+        dataBufferView = new Uint8Array(dataBuffer);
       }
     }
     else {
       dataBuffer = dataBuffer.concat(receivedData);
     }
-    do_print(name + ' received ' + receivedData.length + ' bytes');
+    do_print(name + ' received ' + recvLength + ' bytes');
 
     if (done)
       do_throw(name + ' Received data event when already done!');
 
-    if (dataBuffer.length >= expectedData.length) {
+    let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
+    if (dataView.length >= expectedData.length) {
       // check the bytes are equivalent
       for (let i = 0; i < expectedData.length; i++) {
-        if (dataBuffer[i] !== expectedData[i]) {
+        if (dataView[i] !== expectedData[i]) {
           do_throw(name + ' Received mismatched character at position ' + i);
         }
       }
-      if (dataBuffer.length > expectedData.length)
-        do_throw(name + ' Received ' + dataBuffer.length + ' bytes but only expected ' +
+      if (dataView.length > expectedData.length)
+        do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
                  expectedData.length + ' bytes.');
 
       done = true;
       if (callback) {
         callback();
       } else {
         run_next_test();
       }
@@ -264,17 +281,17 @@ function connectSock() {
 
 /**
  * Test that sending a small amount of data works, and that buffering
  * does not take place for this small amount of data.
  */
 
 function sendData() {
   server.ondata = makeExpectData('serverdata', DATA_ARRAY);
-  if (!sock.send(TYPED_DATA_ARRAY)) {
+  if (!sock.send(DATA_ARRAY_BUFFER)) {
     do_throw("send should not have buffered such a small amount of data");
   }
 }
 
 /**
  * Test that sending a large amount of data works, that buffering
  * takes place (send returns true), and that ondrain is called once
  * the data has been sent.
@@ -291,17 +308,17 @@ function sendBig() {
     }
   };
   sock.ondrain = function(evt) {
     if (sock.bufferedAmount) {
       do_throw("sock.bufferedAmount was > 0 in ondrain");
     }
     yays.clientdrain(evt);
   }
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  if (sock.send(BIG_ARRAY_BUFFER)) {
     do_throw("expected sock.send to return false on large buffer send");
   }
 }
 
 /**
  * Test that data sent from the server correctly fires the ondata
  * callback on the client side.
  */
@@ -349,17 +366,17 @@ function clientCloses() {
  */
 
 function bufferedClose() {
   var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
   server.onclose = yays.serverclose;
   sock.onclose = yays.clientclose;
-  sock.send(BIG_TYPED_ARRAY);
+  sock.send(BIG_ARRAY_BUFFER);
   sock.close();
 }
 
 /**
  * Connect to a port we know is not listening so an error is assured,
  * and make sure that onerror and onclose are fired on the client side.
  */
  
@@ -394,31 +411,31 @@ function drainTwice() {
 
   function serverSideCallback() {
     yays.ondata();
     server.ondata = makeExpectData(
       "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
 
     sock.ondrain = yays.ondrain2;
 
-    if (sock.send(BIG_TYPED_ARRAY_2)) {
+    if (sock.send(BIG_ARRAY_BUFFER_2)) {
       do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
     }
 
     sock.close();
   }
 
   server.onclose = yays.serverclose;
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
 
   sock.onclose = yays.clientclose;
   sock.ondrain = yays.ondrain;
 
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  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");
   sock.close();
   if (!gInChild)
@@ -442,20 +459,20 @@ function bufferTwice() {
   server.onclose = yays.serverclose;
   sock.onclose = yays.clientclose;
 
   sock.ondrain = function () {
     sock.close();
     yays.ondrain();
   }
 
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
-  if (sock.send(BIG_TYPED_ARRAY_2)) {
+  if (sock.send(BIG_ARRAY_BUFFER_2)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
   }
 }
 
 // - connect, data and events work both ways
 add_test(connectSock);
 add_test(sendData);
 add_test(sendBig);
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -6,16 +6,17 @@
 
 XPIDL_SOURCES += [
     'mozIThirdPartyUtil.idl',
     'nsIApplicationCache.idl',
     'nsIApplicationCacheChannel.idl',
     'nsIApplicationCacheContainer.idl',
     'nsIApplicationCacheService.idl',
     'nsIAsyncStreamCopier.idl',
+    'nsIArrayBufferInputStream.idl',
     'nsIAsyncVerifyRedirectCallback.idl',
     'nsIAuthInformation.idl',
     'nsIAuthModule.idl',
     'nsIAuthPrompt.idl',
     'nsIAuthPrompt2.idl',
     'nsIAuthPromptAdapterFactory.idl',
     'nsIAuthPromptCallback.idl',
     'nsIAuthPromptProvider.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIArrayBufferInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- 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 "nsIInputStream.idl"
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+    /**
+     * SetData - assign an ArrayBuffer to the input stream.
+     *
+     * @param buffer    - stream data
+     * @param byteOffset - stream data offset
+     * @param byteLen - stream data length
+     */
+    [implicit_jscontext]
+    void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen);
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ArrayBufferInputStream.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; 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 <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "jsfriendapi.h"
+
+NS_IMPL_ISUPPORTS2(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
+
+ArrayBufferInputStream::ArrayBufferInputStream()
+: mBuffer(nullptr)
+, mBufferLength(0)
+, mOffset(0)
+, mClosed(false)
+{
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetData(const JS::Value& aBuffer,
+                                uint32_t aByteOffset,
+                                uint32_t aLength,
+                                JSContext* aCx)
+{
+  if (!aBuffer.isObject()) {
+    return NS_ERROR_FAILURE;
+  }
+  mArrayBuffer.construct(aCx, &aBuffer.toObject());
+  if (!JS_IsArrayBufferObject(mArrayBuffer.ref())) {
+    return NS_ERROR_FAILURE;
+  }
+  uint32_t buflen = JS_GetArrayBufferByteLength(mArrayBuffer.ref());
+  mOffset = std::min(buflen, aByteOffset);
+  mBufferLength = std::min(buflen - mOffset, aLength);
+  mBuffer = JS_GetArrayBufferData(mArrayBuffer.ref());
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close()
+{
+  mClosed = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount)
+{
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+  *aCount = mBufferLength - mOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
+{
+  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+                                     uint32_t aCount, uint32_t *result)
+{
+  NS_ASSERTION(result, "null ptr");
+  NS_ASSERTION(mBufferLength >= mOffset, "bad stream state");
+
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  uint32_t remaining = mBufferLength - mOffset;
+  if (!remaining) {
+    *result = 0;
+    return NS_OK;
+  }
+
+  if (aCount > remaining) {
+    aCount = remaining;
+  }
+  nsresult rv = writer(this, closure, reinterpret_cast<char*>(mBuffer + mOffset),
+                       0, aCount, result);
+  if (NS_SUCCEEDED(rv)) {
+    NS_ASSERTION(*result <= aCount,
+                 "writer should not write more than we asked it to write");
+    mOffset += *result;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+  *aNonBlocking = true;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ArrayBufferInputStream.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; 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 ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "mozilla/Util.h"
+#include "jsapi.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID                \
+{ /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */       \
+    0x3014dde6,                                      \
+    0xaa1c,                                          \
+    0x41db,                                          \
+    {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \
+}
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+public:
+  ArrayBufferInputStream();
+  virtual ~ArrayBufferInputStream() {}
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+  NS_DECL_NSIINPUTSTREAM
+
+private:
+  mozilla::Maybe<JS::RootedObject> mArrayBuffer;
+  uint8_t* mBuffer;
+  uint32_t mBufferLength;
+  uint32_t mOffset;
+  bool mClosed;
+};
+
+#endif // ArrayBufferInputStream_h
--- a/netwerk/base/src/Makefile.in
+++ b/netwerk/base/src/Makefile.in
@@ -11,16 +11,17 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 LIBRARY_NAME	= neckobase_s
 LIBXUL_LIBRARY  = 1
 FAIL_ON_WARNINGS := 1
 
 CPPSRCS		= \
++		ArrayBufferInputStream.cpp \
 		BackgroundFileSaver.cpp \
 		nsTransportUtils.cpp \
 		nsAsyncStreamCopier.cpp \
 		nsAsyncRedirectVerifyHelper.cpp \
 		nsAuthInformationHolder.cpp \
 		nsBaseChannel.cpp \
 		nsBaseContentStream.cpp \
 		nsBufferedStreams.cpp \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -104,16 +104,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Back
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSyncStreamListener, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileStream)
 
 NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsLoadGroup, Init)
 
+#include "ArrayBufferInputStream.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(ArrayBufferInputStream)
+
 #include "nsEffectiveTLDService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEffectiveTLDService, Init)
 
 #include "nsSerializationHelper.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper)
 
 #include "RedirectChannelRegistrar.h"
 typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar;
@@ -703,16 +706,17 @@ NS_DEFINE_NAMED_CID(NS_PARTIALLOCALFILEI
 NS_DEFINE_NAMED_CID(NS_SAFELOCALFILEOUTPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_LOCALFILESTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_URICHECKER_CID);
 NS_DEFINE_NAMED_CID(NS_INCREMENTALDOWNLOAD_CID);
 NS_DEFINE_NAMED_CID(NS_STDURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_NOAUTHURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_AUTHURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_STANDARDURL_CID);
+NS_DEFINE_NAMED_CID(NS_ARRAYBUFFERINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_DASHBOARD_CID);
 #ifdef BUILD_APPLEFILE_DECODER
 NS_DEFINE_NAMED_CID(NS_APPLEFILEDECODER_CID);
@@ -841,16 +845,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_SAFELOCALFILEOUTPUTSTREAM_CID, false, NULL, nsSafeFileOutputStreamConstructor },
     { &kNS_LOCALFILESTREAM_CID, false, NULL, nsFileStreamConstructor },
     { &kNS_URICHECKER_CID, false, NULL, nsURICheckerConstructor },
     { &kNS_INCREMENTALDOWNLOAD_CID, false, NULL, net_NewIncrementalDownload },
     { &kNS_STDURLPARSER_CID, false, NULL, nsStdURLParserConstructor },
     { &kNS_NOAUTHURLPARSER_CID, false, NULL, nsNoAuthURLParserConstructor },
     { &kNS_AUTHURLPARSER_CID, false, NULL, nsAuthURLParserConstructor },
     { &kNS_STANDARDURL_CID, false, NULL, nsStandardURLConstructor },
+    { &kNS_ARRAYBUFFERINPUTSTREAM_CID, false, NULL, ArrayBufferInputStreamConstructor },
     { &kNS_BUFFEREDINPUTSTREAM_CID, false, NULL, nsBufferedInputStream::Create },
     { &kNS_BUFFEREDOUTPUTSTREAM_CID, false, NULL, nsBufferedOutputStream::Create },
     { &kNS_MIMEINPUTSTREAM_CID, false, NULL, nsMIMEInputStreamConstructor },
     { &kNS_PROTOCOLPROXYSERVICE_CID, true, NULL, nsProtocolProxyServiceConstructor },
     { &kNS_STREAMCONVERTERSERVICE_CID, false, NULL, CreateNewStreamConvServiceFactory },
     { &kNS_DASHBOARD_CID, false, NULL, mozilla::net::DashboardConstructor },
 #ifdef BUILD_APPLEFILE_DECODER
     { &kNS_APPLEFILEDECODER_CID, false, NULL, nsAppleFileDecoderConstructor },
@@ -981,16 +986,17 @@ static const mozilla::Module::ContractID
     { NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_SAFELOCALFILEOUTPUTSTREAM_CID },
     { NS_LOCALFILESTREAM_CONTRACTID, &kNS_LOCALFILESTREAM_CID },
     { NS_URICHECKER_CONTRACT_ID, &kNS_URICHECKER_CID },
     { NS_INCREMENTALDOWNLOAD_CONTRACTID, &kNS_INCREMENTALDOWNLOAD_CID },
     { NS_STDURLPARSER_CONTRACTID, &kNS_STDURLPARSER_CID },
     { NS_NOAUTHURLPARSER_CONTRACTID, &kNS_NOAUTHURLPARSER_CID },
     { NS_AUTHURLPARSER_CONTRACTID, &kNS_AUTHURLPARSER_CID },
     { NS_STANDARDURL_CONTRACTID, &kNS_STANDARDURL_CID },
+    { NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID, &kNS_ARRAYBUFFERINPUTSTREAM_CID },
     { NS_BUFFEREDINPUTSTREAM_CONTRACTID, &kNS_BUFFEREDINPUTSTREAM_CID },
     { NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &kNS_BUFFEREDOUTPUTSTREAM_CID },
     { NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
     { NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
     { NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
     { NS_DASHBOARD_CONTRACTID, &kNS_DASHBOARD_CID },
 #ifdef BUILD_APPLEFILE_DECODER
     { NS_IAPPLEFILEDECODER_CONTRACTID, &kNS_APPLEFILEDECODER_CID },
--- a/xpcom/io/nsBinaryStream.cpp
+++ b/xpcom/io/nsBinaryStream.cpp
@@ -23,16 +23,19 @@
 #include "nsIStreamBufferAccess.h"
 #include "prlong.h"
 #include "nsString.h"
 #include "nsISerializable.h"
 #include "nsIClassInfo.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIURI.h" // for NS_IURI_IID
 
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
 NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream)
 
 NS_IMETHODIMP
 nsBinaryOutputStream::Flush() 
 { 
     NS_ENSURE_STATE(mOutputStream);
     return mOutputStream->Flush(); 
 }
@@ -711,16 +714,40 @@ nsBinaryInputStream::ReadBytes(uint32_t 
 
 NS_IMETHODIMP
 nsBinaryInputStream::ReadByteArray(uint32_t aLength, uint8_t* *_rval)
 {
     return ReadBytes(aLength, reinterpret_cast<char **>(_rval));
 }
 
 NS_IMETHODIMP
+nsBinaryInputStream::ReadArrayBuffer(uint32_t aLength, JSContext* cx, JS::Value* _rval)
+{
+    JSAutoRequest ar(cx);
+    JS::RootedObject buffer(cx, JS_NewArrayBuffer(cx, aLength));
+    if (!buffer) {
+        return NS_ERROR_FAILURE;
+    }
+    uint8_t* data = JS_GetArrayBufferData(buffer);
+    if (!data) {
+        return NS_ERROR_FAILURE;
+    }
+
+    uint32_t bytesRead;
+    nsresult rv = Read(reinterpret_cast<char*>(data), aLength, &bytesRead);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (bytesRead != aLength) {
+        return NS_ERROR_FAILURE;
+    }
+
+    *_rval = JS::ObjectValue(*buffer);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports* *aObject)
 {
     nsCID cid;
     nsIID iid;
     nsresult rv = ReadID(&cid);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = ReadID(&iid);
--- a/xpcom/io/nsIBinaryInputStream.idl
+++ b/xpcom/io/nsIBinaryInputStream.idl
@@ -63,16 +63,26 @@ interface nsIBinaryInputStream : nsIInpu
      * as an array of PRUint8s.
      *
      * @param aLength the number of bytes that must be read.
      *
      * @throws NS_ERROR_FAILURE if it can't read aLength bytes
      */
     void readByteArray(in uint32_t aLength,
                        [array, size_is(aLength), retval] out uint8_t aBytes);
+
+    /**
+     * Read opaque bytes from the stream, storing the results in an ArrayBuffer.
+     *
+     * @param aLength the number of bytes that must be read
+     *
+     * @throws NS_ERROR_FAILURE if it can't read aLength bytes
+     */
+    [implicit_jscontext]
+    jsval readArrayBuffer(in uint32_t aLength);
 };
 
 %{C++
 
 #ifdef MOZILLA_INTERNAL_API
 #include "nsString.h"
 
 inline nsresult