Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 26 Apr 2013 16:46:46 -0400
changeset 130110 0e45f1b9521f016bea4531379fa5c650c31c35ea
parent 130109 6255ed636db10e67ced1e6043781e1c5a650b682 (current diff)
parent 129950 34fabb5ccebfe170f84aaa84ee08fae96f40e068 (diff)
child 130111 e01f456f8a47ce980f7043d777669e270338cfd7
child 130115 45d40d37b1df7d139536a1292e716d0dfb3ac138
child 130170 a063b169750883a3416f8f0169cd8d34c07ccb70
push id24596
push userryanvm@gmail.com
push dateSat, 27 Apr 2013 01:20:57 +0000
treeherdermozilla-central@0e45f1b9521f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
0e45f1b9521f / 23.0a1 / 20130427030919 / files
nightly linux64
0e45f1b9521f / 23.0a1 / 20130427030919 / files
nightly mac
0e45f1b9521f / 23.0a1 / 20130427030919 / files
nightly win32
0e45f1b9521f / 23.0a1 / 20130427030919 / files
nightly win64
0e45f1b9521f / 23.0a1 / 20130427030919 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
b2g/app/b2g.js
b2g/chrome/content/forms.js
b2g/confvars.sh
configure.in
content/canvas/src/CanvasImageCache.cpp
dom/activities/src/Activity.h
dom/base/nsDOMClassInfo.cpp
dom/ipc/preload.js
mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
mobile/android/base/resources/layout/browser_toolbar.xml
modules/libpref/src/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -23,16 +23,17 @@ pref("browser.cache.disk.smart_size.enab
 pref("browser.cache.disk.smart_size.first_run", false);
 
 pref("browser.cache.memory.enable", true);
 pref("browser.cache.memory.capacity", 1024); // kilobytes
 
 /* image cache prefs */
 pref("image.cache.size", 1048576); // bytes
 pref("image.high_quality_downscaling.enabled", false);
+pref("canvas.image.cache.limit", 10485760); // 10 MB
 
 /* offline cache prefs */
 pref("browser.offline-apps.notify", false);
 pref("browser.cache.offline.enable", true);
 pref("offline-apps.allow_by_default", true);
 
 /* protocol warning prefs */
 pref("network.protocol-handler.warn-external.tel", false);
--- a/b2g/chrome/content/forms.js
+++ b/b2g/chrome/content/forms.js
@@ -346,17 +346,17 @@ let FormAssistant = {
       return;
     }
 
     let json = msg.json;
     switch (msg.name) {
       case "Forms:Input:Value": {
         target.value = json.value;
 
-        let event = content.document.createEvent('HTMLEvents');
+        let event = target.ownerDocument.createEvent('HTMLEvents');
         event.initEvent('input', true, false);
         target.dispatchEvent(event);
         break;
       }
 
       case "Forms:Select:Choice":
         let options = target.options;
         let valueChanged = false;
@@ -372,17 +372,17 @@ let FormAssistant = {
               options.item(i).selected = newValue;
               valueChanged = true;
             }
           }
         }
 
         // only fire onchange event if any selected option is changed
         if (valueChanged) {
-          let event = content.document.createEvent('HTMLEvents');
+          let event = target.ownerDocument.createEvent('HTMLEvents');
           event.initEvent('change', true, true);
           target.dispatchEvent(event);
         }
         break;
 
       case "Forms:Select:Blur": {
         this.setFocusedElement(null);
         break;
--- a/content/canvas/src/CanvasImageCache.cpp
+++ b/content/canvas/src/CanvasImageCache.cpp
@@ -8,16 +8,17 @@
 #include "nsExpirationTracker.h"
 #include "imgIRequest.h"
 #include "gfxASurface.h"
 #include "gfxPoint.h"
 #include "mozilla/dom/Element.h"
 #include "nsTHashtable.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsContentUtils.h"
+#include "mozilla/Preferences.h"
 
 namespace mozilla {
 
 using namespace dom;
 
 struct ImageCacheKey {
   ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas)
     : mImage(aImage), mCanvas(aCanvas) {}
@@ -37,16 +38,18 @@ struct ImageCacheEntryData {
   ImageCacheEntryData(const ImageCacheKey& aKey)
     : mImage(aKey.mImage)
     , mILC(nullptr)
     , mCanvas(aKey.mCanvas)
   {}
 
   nsExpirationState* GetExpirationState() { return &mState; }
 
+  size_t SizeInBytes() { return mSize.width * mSize.height * 4; }
+
   // Key
   nsRefPtr<Element> mImage;
   nsIImageLoadingContent* mILC;
   nsRefPtr<HTMLCanvasElement> mCanvas;
   // Value
   nsCOMPtr<imgIRequest> mRequest;
   nsRefPtr<gfxASurface> mSurface;
   gfxIntSize mSize;
@@ -74,37 +77,47 @@ public:
   {
     return HashGeneric(key->mImage, key->mCanvas);
   }
   enum { ALLOW_MEMMOVE = true };
 
   nsAutoPtr<ImageCacheEntryData> mData;
 };
 
+static bool sPrefsInitialized = false;
+static int32_t sCanvasImageCacheLimit = 0;
+
 class ImageCache MOZ_FINAL : public nsExpirationTracker<ImageCacheEntryData,4> {
 public:
   // We use 3 generations of 1 second each to get a 2-3 seconds timeout.
   enum { GENERATION_MS = 1000 };
   ImageCache()
     : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS)
+    , mTotal(0)
   {
+    if (!sPrefsInitialized) {
+      sPrefsInitialized = true;
+      Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0);
+    }
     mCache.Init();
   }
   ~ImageCache() {
     AgeAllGenerations();
   }
 
   virtual void NotifyExpired(ImageCacheEntryData* aObject)
   {
+    mTotal -= aObject->SizeInBytes();
     RemoveObject(aObject);
     // Deleting the entry will delete aObject since the entry owns aObject
     mCache.RemoveEntry(ImageCacheKey(aObject->mImage, aObject->mCanvas));
   }
 
   nsTHashtable<ImageCacheEntry> mCache;
+  size_t mTotal;
 };
 
 static ImageCache* gImageCache = nullptr;
 
 class CanvasImageCacheShutdownObserver MOZ_FINAL : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
@@ -122,29 +135,39 @@ CanvasImageCache::NotifyDrawImage(Elemen
     gImageCache = new ImageCache();
     nsContentUtils::RegisterShutdownObserver(new CanvasImageCacheShutdownObserver());
   }
 
   ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas));
   if (entry) {
     if (entry->mData->mSurface) {
       // We are overwriting an existing entry.
+      gImageCache->mTotal -= entry->mData->SizeInBytes();
       gImageCache->RemoveObject(entry->mData);
     }
     gImageCache->AddObject(entry->mData);
 
     nsCOMPtr<nsIImageLoadingContent> ilc = do_QueryInterface(aImage);
     if (ilc) {
       ilc->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                       getter_AddRefs(entry->mData->mRequest));
     }
     entry->mData->mILC = ilc;
     entry->mData->mSurface = aSurface;
     entry->mData->mSize = aSize;
+
+    gImageCache->mTotal += entry->mData->SizeInBytes();
   }
+
+  if (!sCanvasImageCacheLimit)
+    return;
+
+  // Expire the image cache early if its larger than we want it to be.
+  while (gImageCache->mTotal > size_t(sCanvasImageCacheLimit))
+    gImageCache->AgeOneGeneration();
 }
 
 gfxASurface*
 CanvasImageCache::Lookup(Element* aImage,
                          HTMLCanvasElement* aCanvas,
                          gfxIntSize* aSize)
 {
   if (!gImageCache)
--- a/dom/bluetooth/BluetoothAdapter.cpp
+++ b/dom/bluetooth/BluetoothAdapter.cpp
@@ -49,20 +49,20 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothAdapter)
   NS_INTERFACE_MAP_ENTRY(nsIDOMBluetoothAdapter)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(BluetoothAdapter)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothAdapter, nsDOMEventTargetHelper)
 
-class GetPairedDevicesTask : public BluetoothReplyRunnable
+class GetDevicesTask : public BluetoothReplyRunnable
 {
 public:
-  GetPairedDevicesTask(BluetoothAdapter* aAdapterPtr,
+  GetDevicesTask(BluetoothAdapter* aAdapterPtr,
                        nsIDOMDOMRequest* aReq) :
     BluetoothReplyRunnable(aReq),
     mAdapterPtr(aAdapterPtr)
   {
     MOZ_ASSERT(aReq && aAdapterPtr);
   }
 
   virtual bool ParseSuccessfulReply(JS::Value* aValue)
@@ -232,16 +232,22 @@ BluetoothAdapter::SetPropertyByValue(con
                                     mUuids,
                                     &mJsUuids))) {
       NS_WARNING("Cannot set JS UUIDs object!");
       return;
     }
     Root();
   } else if (name.EqualsLiteral("Devices")) {
     mDeviceAddresses = value.get_ArrayOfnsString();
+
+    uint32_t length = mDeviceAddresses.Length();
+    for (int i = 0; i < length; i++) {
+      mDeviceAddresses[i] = GetAddressFromObjectPath(mDeviceAddresses[i]);
+    }
+
     nsresult rv;
     nsIScriptContext* sc = GetContextForEventHandlers(&rv);
     NS_ENSURE_SUCCESS_VOID(rv);
 
     if (NS_FAILED(nsTArrayToJSArray(sc->GetNativeContext(),
                                     mDeviceAddresses,
                                     &mJsDeviceAddresses))) {
       NS_WARNING("Cannot set JS Devices object!");
@@ -448,46 +454,62 @@ BluetoothAdapter::SetDiscoverableTimeout
     return FirePropertyAlreadySet(GetOwner(), aRequest);
   }
   BluetoothValue value(aDiscoverableTimeout);
   BluetoothNamedValue property(NS_LITERAL_STRING("DiscoverableTimeout"), value);
   return SetProperty(GetOwner(), property, aRequest);
 }
 
 NS_IMETHODIMP
-BluetoothAdapter::GetPairedDevices(nsIDOMDOMRequest** aRequest)
+BluetoothAdapter::GetConnectedDevices(uint16_t aProfileId,
+                                      nsIDOMDOMRequest** aRequest)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothReplyRunnable> results =
-    new GetPairedDevicesTask(this, req);
+    new GetDevicesTask(this, req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
-  if (NS_FAILED(bs->GetPairedDevicePropertiesInternal(mDeviceAddresses,
-                                                      results))) {
-    NS_WARNING("GetPairedDevices failed!");
-    return NS_ERROR_FAILURE;
-  }
+  rv = bs->GetConnectedDevicePropertiesInternal(aProfileId, results);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BluetoothAdapter::GetPairedDevices(nsIDOMDOMRequest** aRequest)
+{
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  nsRefPtr<BluetoothReplyRunnable> results =
+    new GetDevicesTask(this, req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  rv = bs->GetPairedDevicePropertiesInternal(mDeviceAddresses, results);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   req.forget(aRequest);
   return NS_OK;
 }
 
 nsresult
 BluetoothAdapter::PairUnpair(bool aPair,
                              nsIDOMBluetoothDevice* aDevice,
                              nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   nsAutoString addr;
   aDevice->GetAddress(addr);
 
@@ -525,18 +547,17 @@ BluetoothAdapter::Unpair(nsIDOMBluetooth
 }
 
 nsresult
 BluetoothAdapter::SetPinCode(const nsAString& aDeviceAddress,
                              const nsAString& aPinCode,
                              nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   if(!bs->SetPinCodeInternal(aDeviceAddress, aPinCode, results)) {
@@ -548,18 +569,17 @@ BluetoothAdapter::SetPinCode(const nsASt
   return NS_OK;
 }
 
 nsresult
 BluetoothAdapter::SetPasskey(const nsAString& aDeviceAddress, uint32_t aPasskey,
                              nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   if(bs->SetPasskeyInternal(aDeviceAddress, aPasskey, results)) {
@@ -572,18 +592,17 @@ BluetoothAdapter::SetPasskey(const nsASt
 }
 
 nsresult
 BluetoothAdapter::SetPairingConfirmation(const nsAString& aDeviceAddress,
                                          bool aConfirmation,
                                          nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   if(!bs->SetPairingConfirmationInternal(aDeviceAddress,
@@ -597,18 +616,17 @@ BluetoothAdapter::SetPairingConfirmation
   return NS_OK;
 }
 
 nsresult
 BluetoothAdapter::SetAuthorization(const nsAString& aDeviceAddress, bool aAllow,
                                    nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   if(!bs->SetAuthorizationInternal(aDeviceAddress, aAllow, results)) {
@@ -621,18 +639,17 @@ BluetoothAdapter::SetAuthorization(const
 }
 
 NS_IMETHODIMP
 BluetoothAdapter::Connect(const nsAString& aDeviceAddress,
                           uint16_t aProfileId,
                           nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
   bs->Connect(aDeviceAddress, aProfileId, results);
@@ -641,18 +658,17 @@ BluetoothAdapter::Connect(const nsAStrin
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BluetoothAdapter::Disconnect(uint16_t aProfileId,
                              nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   bs->Disconnect(aProfileId, results);
@@ -662,18 +678,17 @@ BluetoothAdapter::Disconnect(uint16_t aP
 }
 
 NS_IMETHODIMP
 BluetoothAdapter::SendFile(const nsAString& aDeviceAddress,
                            nsIDOMBlob* aBlob,
                            nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BlobChild* actor =
     mozilla::dom::ContentChild::GetSingleton()->GetOrCreateActorForBlob(aBlob);
   if (!actor) {
@@ -689,18 +704,17 @@ BluetoothAdapter::SendFile(const nsAStri
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BluetoothAdapter::StopSendingFile(const nsAString& aDeviceAddress,
                                   nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   bs->StopSendingFile(aDeviceAddress, results);
@@ -710,18 +724,17 @@ BluetoothAdapter::StopSendingFile(const 
 }
 
 nsresult
 BluetoothAdapter::ConfirmReceivingFile(const nsAString& aDeviceAddress,
                                        bool aConfirmation,
                                        nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv;
-  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  nsresult rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
   nsRefPtr<BluetoothVoidReplyRunnable> results =
     new BluetoothVoidReplyRunnable(req);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   bs->ConfirmReceivingFile(aDeviceAddress, aConfirmation, results);
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -1450,8 +1450,14 @@ BluetoothHfpManager::IsConnected()
 {
   if (mSocket) {
     return mSocket->GetConnectionStatus() ==
            SocketConnectionStatus::SOCKET_CONNECTED;
   }
 
   return false;
 }
+
+void
+BluetoothHfpManager::GetAddress(nsAString& aDeviceAddress)
+{
+  return mSocket->GetAddress(aDeviceAddress);
+}
--- a/dom/bluetooth/BluetoothHfpManager.h
+++ b/dom/bluetooth/BluetoothHfpManager.h
@@ -70,16 +70,17 @@ public:
   bool Listen();
 
   /**
    * @param aSend A boolean indicates whether we need to notify headset or not
    */
   void HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
                               const nsAString& aNumber, bool aSend);
   bool IsConnected();
+  void GetAddress(nsAString& aDeviceAddress);
 
 private:
   class GetVolumeTask;
   class SendRingIndicatorTask;
 
   friend class GetVolumeTask;
   friend class SendRingIndicatorTask;
   friend class BluetoothHfpManagerObserver;
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -225,16 +225,17 @@ BluetoothOppManager::BluetoothOppManager
                                            , mBodySegmentLength(0)
                                            , mReceivedDataBufferOffset(0)
                                            , mAbortFlag(false)
                                            , mNewFileFlag(false)
                                            , mPutFinalFlag(false)
                                            , mSendTransferCompleteFlag(false)
                                            , mSuccessFlag(false)
                                            , mWaitingForConfirmationFlag(false)
+                                           , mCurrentBlobIndex(-1)
 {
   mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
   Listen();
 }
 
 BluetoothOppManager::~BluetoothOppManager()
 {
 }
@@ -270,20 +271,17 @@ BluetoothOppManager::Connect(const nsASt
   }
 
   if (mL2capSocket) {
     mL2capSocket->Disconnect();
     mL2capSocket = nullptr;
   }
 
   BluetoothService* bs = BluetoothService::Get();
-  if (!bs) {
-    NS_WARNING("BluetoothService not available!");
-    return false;
-  }
+  NS_ENSURE_TRUE(bs, false);
 
   nsString uuid;
   BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
 
   mRunnable = aRunnable;
   mSocket =
     new BluetoothSocket(this, BluetoothSocketType::RFCOMM, true, true);
 
@@ -349,104 +347,81 @@ BluetoothOppManager::Listen()
       mL2capSocket = nullptr;
       return false;
     }
   }
 
   return true;
 }
 
-bool
-BluetoothOppManager::SendFile(BlobParent* aActor)
+void
+BluetoothOppManager::StartSendingNextFile()
 {
-  if (mBlob) {
-    // Means there's a sending process. Reply error.
-    return false;
-  }
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!IsTransferring());
+  MOZ_ASSERT(mBlobs.Length() > mCurrentBlobIndex + 1);
+
+  mBlob = mBlobs[++mCurrentBlobIndex];
 
-  /*
-   * Process of sending a file:
-   *  - Keep blob because OPP connection has not been established yet.
-   *  - Try to retrieve file name from the blob or assign one if failed to get.
-   *  - Create an OPP connection by SendConnectRequest()
-   *  - After receiving the response, start to read file and send.
-   */
-  mBlob = aActor->GetBlob();
+  // Before sending content, we have to send a header including
+  // information such as file name, file length and content type.
+  ExtractBlobHeaders();
+  StartFileTransfer();
 
-  sFileName.Truncate();
-
-  nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
-  if (file) {
-    file->GetName(sFileName);
+  if (mCurrentBlobIndex == 0) {
+    // We may have more than one file waiting for transferring, but only one
+    // CONNECT request would be sent. Therefore check if this is the very first
+    // file at the head of queue.
+    SendConnectRequest();
+  } else {
+    SendPutHeaderRequest(sFileName, sFileLength);
+    AfterFirstPut();
   }
 
-  /**
-   * We try our best to get the file extention to avoid interoperability issues.
-   * However, once we found that we are unable to get suitable extension or
-   * information about the content type, sending a pre-defined file name without
-   * extension would be fine.
-   */
-  if (sFileName.IsEmpty()) {
-    sFileName.AssignLiteral("Unknown");
-  }
+  mIsServer = false;
+}
 
-  int32_t offset = sFileName.RFindChar('/');
-  if (offset != kNotFound) {
-    sFileName = Substring(sFileName, offset + 1);
+bool
+BluetoothOppManager::SendFile(const nsAString& aDeviceAddress,
+                              BlobParent* aActor)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mCurrentBlobIndex >= 0) {
+    if (mConnectedDeviceAddress != aDeviceAddress) {
+      return false;
+    }
+
+    mBlobs.AppendElement(aActor->GetBlob().get());
+    return true;
   }
 
-  offset = sFileName.RFindChar('.');
-  if (offset == kNotFound) {
-    nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
-
-    if (mimeSvc) {
-      nsString mimeType;
-      mBlob->GetType(mimeType);
-
-      nsCString extension;
-      nsresult rv =
-        mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
-                                     EmptyCString(),
-                                     extension);
-      if (NS_SUCCEEDED(rv)) {
-        sFileName.AppendLiteral(".");
-        AppendUTF8toUTF16(extension, sFileName);
-      }
-    }
-  }
-
-  SendConnectRequest();
-  mTransferMode = false;
-  StartFileTransfer();
-
+  mBlobs.AppendElement(aActor->GetBlob().get());
+  StartSendingNextFile();
   return true;
 }
 
 bool
 BluetoothOppManager::StopSendingFile()
 {
   mAbortFlag = true;
 
   return true;
 }
 
 bool
 BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
 {
-  if (!mConnected) return false;
+  NS_ENSURE_TRUE(mConnected, false);
+  NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
 
-  if (!mWaitingForConfirmationFlag) {
-    NS_WARNING("We are not waiting for a confirmation now.");
-    return false;
-  }
+  MOZ_ASSERT(mPacketLeftLength == 0);
+
   mWaitingForConfirmationFlag = false;
 
-  NS_ASSERTION(mPacketLeftLength == 0,
-               "Should not be in the middle of receiving a PUT packet.");
-
   // For the first packet of first file
   bool success = false;
   if (aConfirm) {
     StartFileTransfer();
     if (CreateFile()) {
       success = WriteToFile(mBodySegment.get(), mBodySegmentLength);
     }
   }
@@ -485,19 +460,20 @@ BluetoothOppManager::AfterOppConnected()
 
 void
 BluetoothOppManager::AfterOppDisconnected()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mConnected = false;
   mLastCommand = 0;
-  mBlob = nullptr;
   mPacketLeftLength = 0;
 
+  ClearQueue();
+
   // We can't reset mSuccessFlag here since this function may be called
   // before we send system message of transfer complete
   // mSuccessFlag = false;
 
   if (mInputStream) {
     mInputStream->Close();
     mInputStream = nullptr;
   }
@@ -529,24 +505,56 @@ BluetoothOppManager::DeleteReceivedFile(
   if (mOutputStream) {
     mOutputStream->Close();
     mOutputStream = nullptr;
   }
 
   f->Remove(false);
 }
 
+DeviceStorageFile*
+BluetoothOppManager::CreateDeviceStorageFile(nsIFile* aFile)
+{
+  nsString fullFilePath;
+  aFile->GetPath(fullFilePath);
+
+  MOZ_ASSERT(StringBeginsWith(fullFilePath, NS_LITERAL_STRING(TARGET_ROOT)));
+
+  nsDependentSubstring storagePath =
+    Substring(fullFilePath, strlen(TARGET_ROOT));
+
+  nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(mimeSvc, nullptr);
+
+  nsCString mimeType;
+  nsresult rv = mimeSvc->GetTypeFromFile(aFile, mimeType);
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("image/"))) {
+    return new DeviceStorageFile(NS_LITERAL_STRING("pictures"), storagePath);
+  } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("video/"))) {
+    return new DeviceStorageFile(NS_LITERAL_STRING("movies"), storagePath);
+  } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("audio/"))) {
+    return new DeviceStorageFile(NS_LITERAL_STRING("music"), storagePath);
+  } else {
+    NS_WARNING("Couldn't recognize the mimetype of received file.");
+    return nullptr;
+  }
+}
+
 bool
 BluetoothOppManager::CreateFile()
 {
+  MOZ_ASSERT(mPacketLeftLength == 0);
+
   nsString path;
   path.AssignLiteral(TARGET_FOLDER);
 
-  MOZ_ASSERT(mPacketLeftLength == 0);
-
   nsCOMPtr<nsIFile> f;
   nsresult rv;
   rv = NS_NewLocalFile(path + sFileName, false, getter_AddRefs(f));
   if (NS_FAILED(rv)) {
     NS_WARNING("Couldn't new a local file");
     return false;
   }
 
@@ -558,64 +566,32 @@ BluetoothOppManager::CreateFile()
 
   /*
    * The function CreateUnique() may create a file with a different file
    * name from the original sFileName. Therefore we have to retrieve
    * the file name again.
    */
   f->GetLeafName(sFileName);
 
-  nsString fullFileName;
-  f->GetPath(fullFileName);
-  MOZ_ASSERT(StringBeginsWith(fullFileName, NS_LITERAL_STRING(TARGET_ROOT)));
-  nsDependentSubstring storagePath = Substring(fullFileName, strlen(TARGET_ROOT));
-
-  mDsFile = nullptr;
-
-  nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
-  if (mimeSvc) {
-    nsCString mimeType;
-    nsresult rv = mimeSvc->GetTypeFromFile(f, mimeType);
-
-    if (NS_SUCCEEDED(rv)) {
-      if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("image/"))) {
-        mDsFile = new DeviceStorageFile(NS_LITERAL_STRING("pictures"), storagePath);
-      } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("video/"))) {
-        mDsFile = new DeviceStorageFile(NS_LITERAL_STRING("movies"), storagePath);
-      } else if (StringBeginsWith(mimeType, NS_LITERAL_CSTRING("audio/"))) {
-        mDsFile = new DeviceStorageFile(NS_LITERAL_STRING("music"), storagePath);
-      } else {
-        NS_WARNING("Couldn't recognize the mimetype of received file.");
-      }
-    }
-  }
+  mDsFile = CreateDeviceStorageFile(f);
 
   NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), f);
-  if (!mOutputStream) {
-    NS_WARNING("Couldn't new an output stream");
-    return false;
-  }
+  NS_ENSURE_TRUE(mOutputStream, false);
 
   return true;
 }
 
 bool
 BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
 {
-  if (!mOutputStream) {
-    NS_WARNING("No available output stream");
-    return false;
-  }
+  NS_ENSURE_TRUE(mOutputStream, false);
 
   uint32_t wrote = 0;
   mOutputStream->Write((const char*)aData, aDataLength, &wrote);
-  if (aDataLength != wrote) {
-    NS_WARNING("Writing to the file failed");
-    return false;
-  }
+  NS_ENSURE_TRUE(aDataLength == wrote, false);
 
   return true;
 }
 
 // Virtual function of class SocketConsumer
 void
 BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader)
 {
@@ -639,16 +615,18 @@ BluetoothOppManager::ExtractPacketHeader
 
     aHeader.GetBodyLength(&mBodySegmentLength);
   }
 }
 
 bool
 BluetoothOppManager::ExtractBlobHeaders()
 {
+  RetrieveSentFileName();
+
   nsresult rv = mBlob->GetType(sContentType);
   if (NS_FAILED(rv)) {
     NS_WARNING("Can't get content type");
     SendDisconnectRequest();
     return false;
   }
 
   uint64_t fileLength;
@@ -676,16 +654,62 @@ BluetoothOppManager::ExtractBlobHeaders(
     NS_WARNING("Can't create thread");
     SendDisconnectRequest();
     return false;
   }
 
   return true;
 }
 
+void
+BluetoothOppManager::RetrieveSentFileName()
+{
+  sFileName.Truncate();
+
+  nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
+  if (file) {
+    file->GetName(sFileName);
+  }
+
+  /**
+   * We try our best to get the file extention to avoid interoperability issues.
+   * However, once we found that we are unable to get suitable extension or
+   * information about the content type, sending a pre-defined file name without
+   * extension would be fine.
+   */
+  if (sFileName.IsEmpty()) {
+    sFileName.AssignLiteral("Unknown");
+  }
+
+  int32_t offset = sFileName.RFindChar('/');
+  if (offset != kNotFound) {
+    sFileName = Substring(sFileName, offset + 1);
+  }
+
+  offset = sFileName.RFindChar('.');
+  if (offset == kNotFound) {
+    nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
+
+    if (mimeSvc) {
+      nsString mimeType;
+      mBlob->GetType(mimeType);
+
+      nsCString extension;
+      nsresult rv =
+        mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
+                                     EmptyCString(),
+                                     extension);
+      if (NS_SUCCEEDED(rv)) {
+        sFileName.AppendLiteral(".");
+        AppendUTF8toUTF16(extension, sFileName);
+      }
+    }
+  }
+}
+
 bool
 BluetoothOppManager::IsReservedChar(PRUnichar c)
 {
   return (c < 0x0020 ||
           c == PRUnichar('?') || c == PRUnichar('|') || c == PRUnichar('<') ||
           c == PRUnichar('>') || c == PRUnichar('"') || c == PRUnichar(':') ||
           c == PRUnichar('/') || c == PRUnichar('*') || c == PRUnichar('\\'));
 }
@@ -732,17 +756,17 @@ BluetoothOppManager::ServerDataHandler(U
     // Section 3.3.1 "Connect", IrOBEX 1.2
     // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
     // [Headers:var]
     ParseHeaders(&aMessage->mData[7],
                  receivedLength - 7,
                  &pktHeaders);
     ReplyToConnect();
     AfterOppConnected();
-    mTransferMode = true;
+    mIsServer = true;
   } else if (opCode == ObexRequestCode::Disconnect ||
              opCode == ObexRequestCode::Abort) {
     // Section 3.3.2 "Disconnect", IrOBEX 1.2
     // Section 3.3.5 "Abort", IrOBEX 1.2
     // [opcode:1][length:2][Headers:var]
     ParseHeaders(&aMessage->mData[3],
                 receivedLength - 3,
                 &pktHeaders);
@@ -834,23 +858,35 @@ BluetoothOppManager::ServerDataHandler(U
       UpdateProgress();
       mUpdateProgressCounter = sSentFileLength / kUpdateProgressBase + 1;
     }
 
     // Success to receive a file and notify completion
     if (mPutFinalFlag) {
       mSuccessFlag = true;
       FileTransferComplete();
+      NotifyAboutFileChange();
     }
   } else {
     NS_WARNING("Unhandled ObexRequestCode");
   }
 }
 
 void
+BluetoothOppManager::ClearQueue()
+{
+  mCurrentBlobIndex = -1;
+  mBlob = nullptr;
+
+  while (!mBlobs.IsEmpty()) {
+    mBlobs.RemoveElement(mBlobs[0]);
+  }
+}
+
+void
 BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
 {
   uint8_t opCode;
   int packetLength;
 
   if (mPacketLeftLength > 0) {
     opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
     packetLength = mPacketLeftLength;
@@ -879,17 +915,27 @@ BluetoothOppManager::ClientDataHandler(U
     NS_WARNING(str.get());
     FileTransferComplete();
     return;
   }
 
   if (mLastCommand == ObexRequestCode::PutFinal) {
     mSuccessFlag = true;
     FileTransferComplete();
-    SendDisconnectRequest();
+
+    if (mInputStream) {
+      mInputStream->Close();
+      mInputStream = nullptr;
+    }
+
+    if (mCurrentBlobIndex + 1 == mBlobs.Length()) {
+      SendDisconnectRequest();
+    } else {
+      StartSendingNextFile();
+    }
   } else if (mLastCommand == ObexRequestCode::Abort) {
     SendDisconnectRequest();
     FileTransferComplete();
   } else if (mLastCommand == ObexRequestCode::Disconnect) {
     AfterOppDisconnected();
     // Most devices will directly terminate connection after receiving
     // Disconnect request, so we make a delay here. If the socket hasn't been
     // disconnected, we will close it.
@@ -904,26 +950,18 @@ BluetoothOppManager::ClientDataHandler(U
     AfterOppConnected();
 
     // Keep remote information
     mRemoteObexVersion = aMessage->mData[3];
     mRemoteConnectionFlags = aMessage->mData[4];
     mRemoteMaxPacketLength =
       (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
 
-    /*
-     * Before sending content, we have to send a header including
-     * information such as file name, file length and content type.
-     */
-    if (ExtractBlobHeaders()) {
-      sInstance->SendPutHeaderRequest(sFileName, sFileLength);
-    }
+    sInstance->SendPutHeaderRequest(sFileName, sFileLength);
   } else if (mLastCommand == ObexRequestCode::Put) {
-
-    // Send PutFinal packet when we get response
     if (sWaitingToSendPutFinal) {
       SendPutFinalRequest();
       return;
     }
 
     if (mAbortFlag) {
       SendAbortRequest();
       return;
@@ -992,16 +1030,18 @@ BluetoothOppManager::SendConnectRequest(
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
                                           int aFileSize)
 {
+  if (!mConnected) return;
+
   uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
 
   int len = aFileName.Length();
   uint8_t* fileName = new uint8_t[(len + 1) * 2];
   const PRUnichar* fileNamePtr = aFileName.BeginReading();
 
   for (int i = 0; i < len; i++) {
     fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
@@ -1081,32 +1121,36 @@ BluetoothOppManager::SendPutFinalRequest
   sWaitingToSendPutFinal = false;
 
   delete [] req;
 }
 
 void
 BluetoothOppManager::SendDisconnectRequest()
 {
+  if (!mConnected) return;
+
   // Section 3.3.2 "Disconnect", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexRequestCode::Disconnect, index);
   mLastCommand = ObexRequestCode::Disconnect;
 
   UnixSocketRawData* s = new UnixSocketRawData(index);
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::SendAbortRequest()
 {
+  if (!mConnected) return;
+
   // Section 3.3.5 "Abort", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexRequestCode::Abort, index);
   mLastCommand = ObexRequestCode::Abort;
 
@@ -1117,20 +1161,25 @@ BluetoothOppManager::SendAbortRequest()
 
 bool
 BluetoothOppManager::IsTransferring()
 {
   return (mConnected && !mSendTransferCompleteFlag);
 }
 
 void
+BluetoothOppManager::GetAddress(nsAString& aDeviceAddress)
+{
+  return mSocket->GetAddress(aDeviceAddress);
+}
+
+void
 BluetoothOppManager::ReplyToConnect()
 {
   if (mConnected) return;
-  mConnected = true;
 
   // Section 3.3.1 "Connect", IrOBEX 1.2
   // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
   // [Headers:var]
   uint8_t req[255];
   int index = 7;
 
   req[3] = 0x10; // version=1.0
@@ -1144,17 +1193,16 @@ BluetoothOppManager::ReplyToConnect()
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::ReplyToDisconnect()
 {
   if (!mConnected) return;
-  mConnected = false;
 
   // Section 3.3.2 "Disconnect", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexResponseCode::Success, index);
 
@@ -1210,17 +1258,17 @@ BluetoothOppManager::FileTransferComplet
   v = mConnectedDeviceAddress;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("success");
   v = mSuccessFlag;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("received");
-  v = mTransferMode;
+  v = mIsServer;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("fileName");
   v = sFileName;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("fileLength");
   v = sSentFileLength;
@@ -1246,17 +1294,17 @@ BluetoothOppManager::StartFileTransfer()
   InfallibleTArray<BluetoothNamedValue> parameters;
   type.AssignLiteral("bluetooth-opp-transfer-start");
 
   name.AssignLiteral("address");
   v = mConnectedDeviceAddress;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("received");
-  v = mTransferMode;
+  v = mIsServer;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("fileName");
   v = sFileName;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("fileLength");
   v = sFileLength;
@@ -1280,17 +1328,17 @@ BluetoothOppManager::UpdateProgress()
   InfallibleTArray<BluetoothNamedValue> parameters;
   type.AssignLiteral("bluetooth-opp-update-progress");
 
   name.AssignLiteral("address");
   v = mConnectedDeviceAddress;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("received");
-  v = mTransferMode;
+  v = mIsServer;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("processedLength");
   v = sSentFileLength;
   parameters.AppendElement(BluetoothNamedValue(name, v));
 
   name.AssignLiteral("fileLength");
   v = sFileLength;
@@ -1328,16 +1376,28 @@ BluetoothOppManager::ReceivingFileConfir
 
   if (!BroadcastSystemMessage(type, parameters)) {
     NS_WARNING("Failed to send [bluetooth-opp-receiving-file-confirmation]");
     return;
   }
 }
 
 void
+BluetoothOppManager::NotifyAboutFileChange()
+{
+  NS_NAMED_LITERAL_STRING(data, "modified");
+
+  nsCOMPtr<nsIObserverService> obs =
+    mozilla::services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+
+  obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get());
+}
+
+void
 BluetoothOppManager::OnConnectSuccess(BluetoothSocket* aSocket)
 {
   MOZ_ASSERT(aSocket);
 
   /**
    * If the created connection is an inbound connection, close another server
    * socket because currently only one file-transfer session is allowed. After
    * that, we need to make sure that both server socket would be nulled out.
@@ -1405,32 +1465,21 @@ BluetoothOppManager::OnDisconnect(Blueto
 
   /**
    * It is valid for a bluetooth device which is transfering file via OPP
    * closing socket without sending OBEX disconnect request first. So we
    * delete the broken file when we failed to receive a file from the remote,
    * and notify the transfer has been completed (but failed). We also call
    * AfterOppDisconnected here to ensure all variables will be cleaned.
    */
-
   if (!mSuccessFlag) {
-    if (mTransferMode) {
+    if (mIsServer) {
       DeleteReceivedFile();
     }
     FileTransferComplete();
-  } else if (mTransferMode && mDsFile) {
-    NS_NAMED_LITERAL_STRING(data, "modified");
-
-    nsCOMPtr<nsIObserverService> obs =
-      mozilla::services::GetObserverService();
-    if (obs) {
-      obs->NotifyObservers(mDsFile, "file-watcher-notify", data.get());
-    } else {
-      NS_WARNING("Couldn't get ObserverService");
-    }
   }
 
   AfterOppDisconnected();
   mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
   mSuccessFlag = false;
 
   mSocket = nullptr;
   Listen();
--- a/dom/bluetooth/BluetoothOppManager.h
+++ b/dom/bluetooth/BluetoothOppManager.h
@@ -4,19 +4,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothoppmanager_h__
 #define mozilla_dom_bluetooth_bluetoothoppmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothSocketObserver.h"
+#include "DeviceStorage.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/ipc/UnixSocket.h"
-#include "DeviceStorage.h"
+#include "nsCOMArray.h"
 
 class nsIOutputStream;
 class nsIInputStream;
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothReplyRunnable;
 class BluetoothSocket;
@@ -49,17 +50,17 @@ public:
    * either call Disconnect() to close RFCOMM connection or start another
    * file-sending thread via calling SendFile() again.
    */
   bool Connect(const nsAString& aDeviceObjectPath,
                BluetoothReplyRunnable* aRunnable);
   void Disconnect();
   bool Listen();
 
-  bool SendFile(BlobParent* aBlob);
+  bool SendFile(const nsAString& aDeviceAddress, BlobParent* aBlob);
   bool StopSendingFile();
   bool ConfirmReceivingFile(bool aConfirm);
 
   void SendConnectRequest();
   void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
   void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
   void SendPutFinalRequest();
   void SendDisconnectRequest();
@@ -67,46 +68,52 @@ public:
 
   void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
   bool ExtractBlobHeaders();
   nsresult HandleShutdown();
 
   // Return true if there is an ongoing file-transfer session, please see
   // Bug 827267 for more information.
   bool IsTransferring();
+  void GetAddress(nsAString& aDeviceAddress);
 
   // Implement interface BluetoothSocketObserver
   void ReceiveSocketData(
     BluetoothSocket* aSocket,
     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;
   void OnConnectSuccess() MOZ_OVERRIDE;
   void OnConnectError() MOZ_OVERRIDE;
   void OnDisconnect() MOZ_OVERRIDE;
 
 private:
   BluetoothOppManager();
   void StartFileTransfer();
+  void StartSendingNextFile();
   void FileTransferComplete();
   void UpdateProgress();
   void ReceivingFileConfirmation();
   bool CreateFile();
   bool WriteToFile(const uint8_t* aData, int aDataLength);
   void DeleteReceivedFile();
   void ReplyToConnect();
   void ReplyToDisconnect();
   void ReplyToPut(bool aFinal, bool aContinue);
   void AfterOppConnected();
   void AfterFirstPut();
   void AfterOppDisconnected();
   void ValidateFileName();
   bool IsReservedChar(PRUnichar c);
+  void ClearQueue();
+  void RetrieveSentFileName();
+  DeviceStorageFile* CreateDeviceStorageFile(nsIFile* aFile);
+  void NotifyAboutFileChange();
 
   /**
    * OBEX session status.
    * Set when OBEX session is established.
    */
   bool mConnected;
   nsString mConnectedDeviceAddress;
 
@@ -154,28 +161,30 @@ private:
    * Set when a transfer is successfully completed.
    */
   bool mSuccessFlag;
 
   /**
    * True: Receive file (Server)
    * False: Send file (Client)
    */
-  bool mTransferMode;
+  bool mIsServer;
 
   /**
    * Set when receiving the first PUT packet and wait for
    * ConfirmReceivingFile() to be called.
    */
   bool mWaitingForConfirmationFlag;
 
   nsAutoArrayPtr<uint8_t> mBodySegment;
   nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
 
+  int mCurrentBlobIndex;
   nsCOMPtr<nsIDOMBlob> mBlob;
+  nsCOMArray<nsIDOMBlob> mBlobs;
 
   /**
    * A seperate member thread is required because our read calls can block
    * execution, which is not allowed to happen on the IOThread.
    * 
    */
   nsCOMPtr<nsIThread> mReadFileThread;
   nsCOMPtr<nsIOutputStream> mOutputStream;
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -126,17 +126,27 @@ public:
    * specific method.
    *
    * @return NS_OK on success, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable) = 0;
 
-  /** 
+  /**
+   * Returns the properties of connected devices regarding to specific profile,
+   * implemented via a platform specific methood.
+   *
+   * @return NS_OK on success, NS_ERROR_FAILURE otherwise
+   */
+  virtual nsresult
+  GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
+                                       BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
    * Stop device discovery (platform specific implementation)
    *
    * @return NS_OK if discovery stopped correctly, false otherwise
    */
   virtual nsresult
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0;
 
   /** 
--- a/dom/bluetooth/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/ipc/BluetoothParent.cpp
@@ -192,18 +192,20 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TStartDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StartDiscoveryRequest());
     case Request::TStopDiscoveryRequest:
       return actor->DoRequest(aRequest.get_StopDiscoveryRequest());
     case Request::TPairRequest:
       return actor->DoRequest(aRequest.get_PairRequest());
     case Request::TUnpairRequest:
       return actor->DoRequest(aRequest.get_UnpairRequest());
-    case Request::TDevicePropertiesRequest:
-      return actor->DoRequest(aRequest.get_DevicePropertiesRequest());
+    case Request::TPairedDevicePropertiesRequest:
+      return actor->DoRequest(aRequest.get_PairedDevicePropertiesRequest());
+    case Request::TConnectedDevicePropertiesRequest:
+      return actor->DoRequest(aRequest.get_ConnectedDevicePropertiesRequest());
     case Request::TSetPinCodeRequest:
       return actor->DoRequest(aRequest.get_SetPinCodeRequest());
     case Request::TSetPasskeyRequest:
       return actor->DoRequest(aRequest.get_SetPasskeyRequest());
     case Request::TConfirmPairingConfirmationRequest:
       return actor->DoRequest(aRequest.get_ConfirmPairingConfirmationRequest());
     case Request::TConfirmAuthorizationRequest:
       return actor->DoRequest(aRequest.get_ConfirmAuthorizationRequest());
@@ -367,25 +369,36 @@ BluetoothRequestParent::DoRequest(const 
     mService->RemoveDeviceInternal(aRequest.address(),
                                    mReplyRunnable.get());
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 bool
-BluetoothRequestParent::DoRequest(const DevicePropertiesRequest& aRequest)
+BluetoothRequestParent::DoRequest(const PairedDevicePropertiesRequest& aRequest)
 {
   MOZ_ASSERT(mService);
-  MOZ_ASSERT(mRequestType == Request::TDevicePropertiesRequest);
+  MOZ_ASSERT(mRequestType == Request::TPairedDevicePropertiesRequest);
 
   nsresult rv =
     mService->GetPairedDevicePropertiesInternal(aRequest.addresses(),
                                                 mReplyRunnable.get());
   NS_ENSURE_SUCCESS(rv, false);
+  return true;
+}
+bool
+BluetoothRequestParent::DoRequest(const ConnectedDevicePropertiesRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TConnectedDevicePropertiesRequest);
+  nsresult rv =
+    mService->GetConnectedDevicePropertiesInternal(aRequest.profileId(),
+                                                mReplyRunnable.get());
+  NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
 bool
 BluetoothRequestParent::DoRequest(const SetPinCodeRequest& aRequest)
 {
   MOZ_ASSERT(mService);
--- a/dom/bluetooth/ipc/BluetoothParent.h
+++ b/dom/bluetooth/ipc/BluetoothParent.h
@@ -141,17 +141,19 @@ protected:
 
   bool
   DoRequest(const PairRequest& aRequest);
 
   bool
   DoRequest(const UnpairRequest& aRequest);
 
   bool
-  DoRequest(const DevicePropertiesRequest& aRequest);
+  DoRequest(const PairedDevicePropertiesRequest& aRequest);
+  bool
+  DoRequest(const ConnectedDevicePropertiesRequest& aRequest);
 
   bool
   DoRequest(const SetPinCodeRequest& aRequest);
 
   bool
   DoRequest(const SetPasskeyRequest& aRequest);
 
   bool
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -106,21 +106,29 @@ nsresult
 BluetoothServiceChildProcess::GetDevicePropertiesInternal(
                                                  const BluetoothSignal& aSignal)
 {
   MOZ_NOT_REACHED("Should never be called from child");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult
+BluetoothServiceChildProcess::GetConnectedDevicePropertiesInternal(
+                                              uint16_t aProfileId,
+                                              BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable, ConnectedDevicePropertiesRequest(aProfileId));
+  return NS_OK;
+}
+nsresult
 BluetoothServiceChildProcess::GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable)
 {
-  DevicePropertiesRequest request;
+  PairedDevicePropertiesRequest request;
   request.addresses().AppendElements(aDeviceAddresses);
 
   SendRequest(aRunnable, request);
   return NS_OK;
 }
 
 nsresult
 BluetoothServiceChildProcess::StopDiscoveryInternal(
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -47,16 +47,20 @@ public:
   GetDefaultAdapterPathInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
                                     BluetoothReplyRunnable* aRunnable)
                                     MOZ_OVERRIDE;
 
   virtual nsresult
+  GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
+                                       BluetoothReplyRunnable* aRunnable)
+                                       MOZ_OVERRIDE;
+  virtual nsresult
   StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual nsresult
   SetProperty(BluetoothObjectType aType,
               const BluetoothNamedValue& aValue,
--- a/dom/bluetooth/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/ipc/PBluetooth.ipdl
@@ -83,20 +83,24 @@ struct ConfirmAuthorizationRequest
   nsString path;
 };
 
 struct DenyAuthorizationRequest
 {
   nsString path;
 };
 
-struct DevicePropertiesRequest
+struct PairedDevicePropertiesRequest
 {
   nsString[] addresses;
 };
+struct ConnectedDevicePropertiesRequest
+{
+  uint16_t profileId;
+};
 
 struct ConnectRequest
 {
   nsString address;
   uint16_t profileId;
 };
 
 struct DisconnectRequest
@@ -135,17 +139,18 @@ union Request
   PairRequest;
   UnpairRequest;
   SetPinCodeRequest;
   SetPasskeyRequest;
   ConfirmPairingConfirmationRequest;
   DenyPairingConfirmationRequest;
   ConfirmAuthorizationRequest;
   DenyAuthorizationRequest;
-  DevicePropertiesRequest;
+  ConnectedDevicePropertiesRequest;
+  PairedDevicePropertiesRequest;
   ConnectRequest;
   DisconnectRequest;
   SendFileRequest;
   StopSendingFileRequest;
   ConfirmReceivingFileRequest;
   DenyReceivingFileRequest;
 };
 
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -142,16 +142,17 @@ static const char* sBluetoothDBusSignals
  */
 static nsAutoPtr<RawDBusConnection> gThreadConnection;
 static nsDataHashtable<nsStringHashKey, DBusMessage* > sPairingReqTable;
 static nsDataHashtable<nsStringHashKey, DBusMessage* > sAuthorizeReqTable;
 static int32_t sIsPairing = 0;
 static nsString sAdapterPath;
 
 typedef void (*UnpackFunc)(DBusMessage*, DBusError*, BluetoothValue&, nsAString&);
+typedef bool (*FilterFunc)(const BluetoothValue&);
 
 class RemoveDeviceTask : public nsRunnable {
 public:
   RemoveDeviceTask(const nsACString& aDeviceObjectPath,
                    BluetoothReplyRunnable* aRunnable)
     : mDeviceObjectPath(aDeviceObjectPath)
     , mRunnable(aRunnable)
   {
@@ -186,16 +187,44 @@ public:
     return NS_OK;
   }
 
 private:
   nsCString mDeviceObjectPath;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
 };
 
+static bool
+GetConnectedDevicesFilter(const BluetoothValue& aValue)
+{
+  // We don't have to filter device here
+  return true;
+}
+
+static bool
+GetPairedDevicesFilter(const BluetoothValue& aValue)
+{
+  // Check property 'Paired' and only paired device will be returned
+  if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
+    NS_WARNING("Not a BluetoothNamedValue array!");
+    return false;
+  }
+
+  const InfallibleTArray<BluetoothNamedValue>& deviceProperties =
+    aValue.get_ArrayOfBluetoothNamedValue();
+  uint32_t length = deviceProperties.Length();
+  for (uint32_t p = 0; p < length; ++p) {
+    if (deviceProperties[p].name().EqualsLiteral("Paired")) {
+      return deviceProperties[p].value().get_bool();
+    }
+  }
+
+  return false;
+}
+
 class SendDiscoveryTask : public nsRunnable {
 public:
   SendDiscoveryTask(const char* aMessageName,
                     BluetoothReplyRunnable* aRunnable)
     : mMessageName(aMessageName)
     , mRunnable(aRunnable)
   {
     MOZ_ASSERT(aMessageName);
@@ -1906,80 +1935,70 @@ public:
 
     return NS_OK;
   }
 
 private:
   BluetoothSignal mSignal;
 };
 
-class BluetoothPairedDevicePropertiesRunnable : public nsRunnable
+class BluetoothArrayOfDevicePropertiesRunnable : public nsRunnable
 {
 public:
-  BluetoothPairedDevicePropertiesRunnable(
+  BluetoothArrayOfDevicePropertiesRunnable(
+                                     const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable,
-                                     const nsTArray<nsString>& aDeviceAddresses)
-    : mRunnable(dont_AddRef(aRunnable)),
-      mDeviceAddresses(aDeviceAddresses)
+                                     FilterFunc aFilterFunc)
+    : mDeviceAddresses(aDeviceAddresses)
+    , mRunnable(dont_AddRef(aRunnable))
+    , mFilterFunc(aFilterFunc)
   {
   }
 
   nsresult
   Run()
   {
     MOZ_ASSERT(!NS_IsMainThread());
     DBusError err;
     dbus_error_init(&err);
 
     BluetoothValue values = InfallibleTArray<BluetoothNamedValue>();
+    nsAutoString errorStr;
 
     for (uint32_t i = 0; i < mDeviceAddresses.Length(); i++) {
       BluetoothValue v;
-      if (!GetPropertiesInternal(mDeviceAddresses[i], DBUS_DEVICE_IFACE, v)) {
-        nsAutoString errorStr;
+      nsString objectPath = GetObjectPathFromAddress(sAdapterPath, mDeviceAddresses[i]);
+
+      if (!GetPropertiesInternal(objectPath, DBUS_DEVICE_IFACE, v)) {
         errorStr.AssignLiteral("Getting properties failed!");
-        NS_WARNING(NS_ConvertUTF16toUTF8(errorStr).get());
-        mRunnable->SetReply(new BluetoothReply(BluetoothReplyError(errorStr)));
-        if (NS_FAILED(NS_DispatchToMainThread(mRunnable))) {
-          NS_WARNING("Failed to dispatch to main thread!");
-        }
+        DispatchBluetoothReply(mRunnable, values, errorStr);
         return NS_OK;
       }
+
+      // We have to manually attach the path to the rest of the elements
       v.get_ArrayOfBluetoothNamedValue().AppendElement(
-        BluetoothNamedValue(NS_LITERAL_STRING("Path"), mDeviceAddresses[i])
+        BluetoothNamedValue(NS_LITERAL_STRING("Path"), objectPath)
       );
 
-      InfallibleTArray<BluetoothNamedValue>& deviceProperties =
-        v.get_ArrayOfBluetoothNamedValue();
-      for (uint32_t p = 0;
-           p < v.get_ArrayOfBluetoothNamedValue().Length(); ++p) {
-        BluetoothNamedValue& property = v.get_ArrayOfBluetoothNamedValue()[p];
-        // Only paired devices will be return back to main thread
-        if (property.name().EqualsLiteral("Paired")) {
-          bool paired = property.value();
-          if (paired) {
-            values.get_ArrayOfBluetoothNamedValue().AppendElement(
-              BluetoothNamedValue(mDeviceAddresses[i], deviceProperties)
-            );
-          }
-          break;
-        }
+      if (mFilterFunc(v)) {
+        values.get_ArrayOfBluetoothNamedValue().AppendElement(
+          BluetoothNamedValue(mDeviceAddresses[i],
+                              v.get_ArrayOfBluetoothNamedValue())
+        );
       }
     }
 
-    mRunnable->SetReply(new BluetoothReply(BluetoothReplySuccess(values)));
-    if (NS_FAILED(NS_DispatchToMainThread(mRunnable))) {
-      NS_WARNING("Failed to dispatch to main thread!");
-    }
+    DispatchBluetoothReply(mRunnable, values, errorStr);
     return NS_OK;
   }
 
 private:
+  nsTArray<nsString> mDeviceAddresses;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
-  nsTArray<nsString> mDeviceAddresses;
+  FilterFunc mFilterFunc;
 };
 
 nsresult
 BluetoothDBusService::GetDevicePropertiesInternal(const BluetoothSignal& aSignal)
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called from main thread!");
 
   if (!mConnection || !gThreadConnection) {
@@ -1992,31 +2011,82 @@ BluetoothDBusService::GetDevicePropertie
     NS_WARNING("Cannot dispatch task!");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
+BluetoothDBusService::GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
+                                              BluetoothReplyRunnable* aRunnable)
+{
+  nsAutoString errorStr;
+  BluetoothValue values = InfallibleTArray<BluetoothNamedValue>();
+  if (!IsReady()) {
+    errorStr.AssignLiteral("Bluetooth service is not ready yet!");
+    DispatchBluetoothReply(aRunnable, values, errorStr);
+    return NS_OK;
+  }
+
+  nsTArray<nsString> deviceAddresses;
+  if (aProfileId == BluetoothServiceClass::HANDSFREE ||
+      aProfileId == BluetoothServiceClass::HEADSET) {
+    BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
+    if (hfp->IsConnected()) {
+      nsString address;
+      hfp->GetAddress(address);
+      deviceAddresses.AppendElement(address);
+    }
+  } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
+    BluetoothOppManager* opp = BluetoothOppManager::Get();
+    if (opp->IsTransferring()) {
+      nsString address;
+      opp->GetAddress(address);
+      deviceAddresses.AppendElement(address);
+    }
+  } else {
+    errorStr.AssignLiteral("Unknown profile");
+    DispatchBluetoothReply(aRunnable, values, errorStr);
+    return NS_OK;
+  }
+
+  nsRefPtr<BluetoothReplyRunnable> runnable = aRunnable;
+  nsRefPtr<nsRunnable> func(
+    new BluetoothArrayOfDevicePropertiesRunnable(deviceAddresses,
+                                                 runnable,
+                                                 GetConnectedDevicesFilter));
+
+  if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
+    NS_WARNING("Cannot dispatch task!");
+    return NS_ERROR_FAILURE;
+  }
+
+  runnable.forget();
+  return NS_OK;
+}
+
+nsresult
 BluetoothDBusService::GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
                                      BluetoothReplyRunnable* aRunnable)
 {
   if (!IsReady()) {
     BluetoothValue v;
     nsAutoString errorStr;
     errorStr.AssignLiteral("Bluetooth service is not ready yet!");
     DispatchBluetoothReply(aRunnable, v, errorStr);
     return NS_OK;
   }
 
   nsRefPtr<BluetoothReplyRunnable> runnable = aRunnable;
   nsRefPtr<nsRunnable> func(
-    new BluetoothPairedDevicePropertiesRunnable(runnable, aDeviceAddresses));
+    new BluetoothArrayOfDevicePropertiesRunnable(aDeviceAddresses,
+                                                 runnable,
+                                                 GetPairedDevicesFilter));
   if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
     NS_WARNING("Cannot dispatch task!");
     return NS_ERROR_FAILURE;
   }
 
   runnable.forget();
   return NS_OK;
 }
@@ -2482,17 +2552,18 @@ BluetoothDBusService::Connect(const nsAS
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     if (!opp->Connect(GetObjectPathFromAddress(sAdapterPath, aDeviceAddress),
                       aRunnable)) {
       errorStr.AssignLiteral("BluetoothOppManager has been connected/is \
                               connecting!");
       DispatchBluetoothReply(aRunnable, v, errorStr);
     }
   } else {
-    NS_WARNING("Unknown Profile");
+    errorStr.AssignLiteral("Unknown profile");
+    DispatchBluetoothReply(aRunnable, v, errorStr);
   }
 }
 
 void
 BluetoothDBusService::Disconnect(const uint16_t aProfileId,
                                  BluetoothReplyRunnable* aRunnable)
 {
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
@@ -2714,17 +2785,17 @@ BluetoothDBusService::SendFile(const nsA
   // Currently we only support one device sending one file at a time,
   // so we don't need aDeviceAddress here because the target device
   // has been determined when calling 'Connect()'. Nevertheless, keep
   // it for future use.
   BluetoothOppManager* opp = BluetoothOppManager::Get();
   BluetoothValue v = true;
   nsAutoString errorStr;
 
-  if (!opp->SendFile(aBlobParent)) {
+  if (!opp->SendFile(aDeviceAddress, aBlobParent)) {
     errorStr.AssignLiteral("Calling SendFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, v, errorStr);
 }
 
 void
 BluetoothDBusService::StopSendingFile(const nsAString& aDeviceAddress,
--- a/dom/bluetooth/linux/BluetoothDBusService.h
+++ b/dom/bluetooth/linux/BluetoothDBusService.h
@@ -27,20 +27,25 @@ public:
   bool IsReady();
 
   virtual nsresult StartInternal();
 
   virtual nsresult StopInternal();
 
   virtual bool IsEnabledInternal();
 
-  virtual nsresult GetDefaultAdapterPathInternal(BluetoothReplyRunnable* aRunnable);
+  virtual nsresult GetDefaultAdapterPathInternal(
+                                             BluetoothReplyRunnable* aRunnable);
 
-  virtual nsresult GetPairedDevicePropertiesInternal(const nsTArray<nsString>& aDeviceAddresses,
-                                                     BluetoothReplyRunnable* aRunnable);
+  virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
+                                             BluetoothReplyRunnable* aRunnable);
+
+  virtual nsresult GetPairedDevicePropertiesInternal(
+                                     const nsTArray<nsString>& aDeviceAddresses,
+                                     BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult
   GetDevicePropertiesInternal(const BluetoothSignal& aSignal);
 
--- a/dom/bluetooth/nsIDOMBluetoothAdapter.idl
+++ b/dom/bluetooth/nsIDOMBluetoothAdapter.idl
@@ -5,17 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMEventTarget.idl"
 
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 interface nsIDOMBluetoothDevice;
 
-[scriptable, builtinclass, uuid(4321647b-0d45-4231-920b-8d238b6d1700)]
+[scriptable, builtinclass, uuid(88a5638f-f55a-4d67-8437-392d0a9a87c7)]
 interface nsIDOMBluetoothAdapter : nsIDOMEventTarget
 {
   readonly attribute DOMString address;
   [binaryname(AdapterClass)] readonly attribute unsigned long class;
   readonly attribute bool discovering;
 
   [implicit_jscontext]
   readonly attribute jsval devices;
@@ -31,16 +31,17 @@ interface nsIDOMBluetoothAdapter : nsIDO
   nsIDOMDOMRequest setName(in DOMString name);
   nsIDOMDOMRequest setDiscoverable(in bool discoverable);
   nsIDOMDOMRequest setDiscoverableTimeout(in unsigned long timeout);
   nsIDOMDOMRequest startDiscovery();
   nsIDOMDOMRequest stopDiscovery();
   nsIDOMDOMRequest pair(in nsIDOMBluetoothDevice aDevice);
   nsIDOMDOMRequest unpair(in nsIDOMBluetoothDevice aDevice);
   nsIDOMDOMRequest getPairedDevices();
+  nsIDOMDOMRequest getConnectedDevices(in unsigned short aProfile);
   nsIDOMDOMRequest setPinCode(in DOMString aDeviceAddress, in DOMString aPinCode);
   nsIDOMDOMRequest setPasskey(in DOMString aDeviceAddress, in unsigned long aPasskey);
   nsIDOMDOMRequest setPairingConfirmation(in DOMString aDeviceAddress, in bool aConfirmation);
   nsIDOMDOMRequest setAuthorization(in DOMString aDeviceAddress, in bool aAllow);
 
   /** 
    * Connect/Disconnect to a specific service of a target remote device. 
    * To check the value of service UUIDs, please check "Bluetooth Assigned 
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -342,20 +342,23 @@ ContactDB.prototype = {
         objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true});
         objectStore.openCursor().onsuccess = function(event) {
           let cursor = event.target.result;
           if (cursor) {
             if (cursor.value.properties.tel) {
               cursor.value.search.parsedTel = [];
               cursor.value.properties.tel.forEach(
                 function(tel) {
-                  cursor.value.search.parsedTel.push(parsed.nationalNumber);
-                  cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.nationalFormat));
-                  cursor.value.search.parsedTel.push(parsed.internationalNumber);
-                  cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.internationalFormat));
+                  let parsed = PhoneNumberUtils.parse(tel.value.toString());
+                  if (parsed) {
+                    cursor.value.search.parsedTel.push(parsed.nationalNumber);
+                    cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.nationalFormat));
+                    cursor.value.search.parsedTel.push(parsed.internationalNumber);
+                    cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.internationalFormat));
+                  }
                   cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(tel.value.toString()));
                 }
               );
               cursor.update(cursor.value);
             }
             cursor.continue();
           }
         };
@@ -452,68 +455,68 @@ ContactDB.prototype = {
 
     for (let field in aContact.properties) {
       contact.properties[field] = aContact.properties[field];
       // Add search fields
       if (aContact.properties[field] && contact.search[field]) {
         for (let i = 0; i <= aContact.properties[field].length; i++) {
           if (aContact.properties[field][i]) {
             if (field == "tel") {
-              // Special case telephone number.
-              // "+1-234-567" should also be found with 1234, 234-56, 23456
+              let number = aContact.properties.tel[i].value.toString();
+              let normalized = PhoneNumberUtils.normalize(number);
+              // We use an object here to avoid duplicates
+              let containsSearch = {};
+              let matchSearch = {};
 
-              // Chop off the first characters
-              let number = aContact.properties[field][i].value;
-              let search = {};
-              if (number) {
-                number = number.toString();
-                contact.search.exactTel.push(PhoneNumberUtils.normalize(number));
-                contact.search.parsedTel.push(PhoneNumberUtils.normalize(number));
-                for (let i = 0; i < number.length; i++) {
-                  search[number.substring(i, number.length)] = 1;
-                }
-                // Store +1-234-567 as ["1234567", "234567"...]
-                let digits = number.match(/\d/g);
-                if (digits && number.length != digits.length) {
-                  digits = digits.join('');
-                  for(let i = 0; i < digits.length; i++) {
-                    search[digits.substring(i, digits.length)] = 1;
-                  }
-                }
-                if (DEBUG) debug("lookup: " + JSON.stringify(contact.search[field]));
-                let parsedNumber = PhoneNumberUtils.parse(number.toString());
+              if (normalized) {
+                // exactTel holds normalized version of entered phone number.
+                // normalized: +1 (949) 123 - 4567 -> +19491234567
+                contact.search.exactTel.push(normalized);
+                // matchSearch holds normalized version of entered phone number,
+                // nationalNumber, nationalFormat, internationalNumber, internationalFormat
+                matchSearch[normalized] = 1;
+                let parsedNumber = PhoneNumberUtils.parse(number);
                 if (parsedNumber) {
                   if (DEBUG) {
                     debug("InternationalFormat: " + parsedNumber.internationalFormat);
                     debug("InternationalNumber: " + parsedNumber.internationalNumber);
                     debug("NationalNumber: " + parsedNumber.nationalNumber);
                     debug("NationalFormat: " + parsedNumber.nationalFormat);
                   }
-
-                  contact.search.parsedTel.push(parsedNumber.nationalNumber);
-                  contact.search.parsedTel.push(PhoneNumberUtils.normalize(parsedNumber.nationalFormat));
-                  contact.search.parsedTel.push(parsedNumber.internationalNumber);
-                  contact.search.parsedTel.push(PhoneNumberUtils.normalize(parsedNumber.internationalFormat));
+                  matchSearch[parsedNumber.nationalNumber] = 1;
+                  matchSearch[parsedNumber.internationalNumber] = 1;
+                  matchSearch[PhoneNumberUtils.normalize(parsedNumber.nationalFormat)] = 1;
+                  matchSearch[PhoneNumberUtils.normalize(parsedNumber.internationalFormat)] = 1
+                }
 
-                  if (parsedNumber.internationalNumber &&
-                      number !== parsedNumber.internationalNumber) {
-                    let digits = parsedNumber.internationalNumber.match(/\d/g);
-                    if (digits) {
-                      digits = digits.join('');
-                      for(let i = 0; i < digits.length; i++) {
-                        search[digits.substring(i, digits.length)] = 1;
-                      }
+                // containsSearch holds incremental search values for:
+                // normalized number, national format, international format
+                for (let i = 0; i < normalized.length; i++) {
+                  containsSearch[normalized.substring(i, normalized.length)] = 1;
+                }
+                if (parsedNumber) {
+                  if (parsedNumber.nationalFormat) {
+                    let number = PhoneNumberUtils.normalize(parsedNumber.nationalFormat);
+                    for (let i = 0; i < number.length; i++) {
+                      containsSearch[number.substring(i, number.length)] = 1;
                     }
                   }
-                } else {
-                  dump("Warning: No international number found for " + number + "\n");
+                  if (parsedNumber.internationalFormat) {
+                    let number = PhoneNumberUtils.normalize(parsedNumber.internationalFormat);
+                    for (let i = 0; i < number.length; i++) {
+                      containsSearch[number.substring(i, number.length)] = 1;
+                    }
+                  }
                 }
               }
-              for (let num in search) {
-                contact.search[field].push(num);
+              for (let num in containsSearch) {
+                contact.search.tel.push(num);
+              }
+              for (let num in matchSearch) {
+                contact.search.parsedTel.push(num);
               }
             } else if (field == "impp" || field == "email") {
               let value = aContact.properties[field][i].value;
               if (value && typeof value == "string") {
                 contact.search[field].push(value.toLowerCase());
               }
             } else {
               let val = aContact.properties[field][i];
@@ -841,20 +844,17 @@ ContactDB.prototype = {
 
         let index = store.index("telMatch");
         let normalized = PhoneNumberUtils.normalize(options.filterValue)
         request = index.mozGetAll(normalized, limit);
       } else {
         // not case sensitive
         let tmp = options.filterValue.toString().toLowerCase();
         if (key === 'tel') {
-          let digits = tmp.match(/\d/g);
-          if (digits) {
-            tmp = digits.join('');
-          }
+          tmp = PhoneNumberUtils.normalize(tmp);
         }
         let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF");
         let index = store.index(key + "LowerCase");
         request = index.mozGetAll(range, limit);
       }
       if (!txn.result)
         txn.result = {};
 
--- a/dom/ipc/preload.js
+++ b/dom/ipc/preload.js
@@ -74,16 +74,22 @@ const BrowserElementIsPreloaded = true;
   Cc["@mozilla.org/system-info;1"].getService(Ci["nsIPropertyBag2"]);
   Cc["@mozilla.org/thread-manager;1"].getService(Ci["nsIThreadManager"]);
   Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci["nsIAppStartup"]);
   Cc["@mozilla.org/uriloader;1"].getService(Ci["nsIURILoader"]);
   Cc["@mozilla.org/contentsecuritypolicy;1"].createInstance(Ci["nsIContentSecurityPolicy"]);
 
   /* Applications Specific Helper */
   Cc["@mozilla.org/settingsManager;1"].getService(Ci["nsIDOMSettingsManager"]);
+  try {
+    if (Services.prefs.getBoolPref("dom.sysmsg.enabled")) {
+      Cc["@mozilla.org/system-message-manager;1"].getService(Ci["nsIDOMNavigatorSystemMessages"]);
+    }
+  } catch(e) {
+  }
 
   // This is a produc-specific file that's sometimes unavailable.
   try {
     Services.scriptloader.loadSubScript("chrome://browser/content/forms.js", global);
   } catch (e) {
   }
   Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
   Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js", global);
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3927,16 +3927,20 @@ pref("image.mem.max_decoded_image_kb", 5
 // Whether we decode images on multiple background threads rather than the
 // foreground thread.
 pref("image.multithreaded_decoding.enabled", true);
 
 // How many threads we'll use for multithreaded decoding. If < 0, will be
 // automatically determined based on the system's number of cores.
 pref("image.multithreaded_decoding.limit", -1);
 
+// Limit for the canvas image cache. 0 means we don't limit the size of the
+// cache.
+pref("canvas.image.cache.limit", 0);
+
 // WebGL prefs
 pref("gl.msaa-level", 2);
 pref("webgl.force-enabled", false);
 pref("webgl.disabled", false);
 pref("webgl.shader_validator", true);
 pref("webgl.prefer-native-gl", false);
 pref("webgl.min_capability_mode", false);
 pref("webgl.disable-extensions", false);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -1,38 +1,39 @@
 accounts.google.com: max-age too low: 2592000
 aladdinschools.appspot.com: did not receive HSTS header
-alpha.irccloud.com: did not receive HSTS header
 api.mega.co.nz: could not connect to host
 api.recurly.com: did not receive HSTS header
+api.simple.com: did not receive HSTS header
 apis.google.com: did not receive HSTS header
 appengine.google.com: did not receive HSTS header
 betnet.fr: could not connect to host
 bigshinylock.minazo.net: could not connect to host
 braintreegateway.com: could not connect to host
 braintreepayments.com: did not receive HSTS header
 browserid.org: did not receive HSTS header
 cert.se: max-age too low: 2628001
 checkout.google.com: did not receive HSTS header
+chrome-devtools-frontend.appspot.com: did not receive HSTS header
 chrome.google.com: did not receive HSTS header
 chromiumcodereview.appspot.com: did not receive HSTS header
 code.google.com: did not receive HSTS header
 codereview.appspot.com: did not receive HSTS header
 codereview.chromium.org: did not receive HSTS header
 crypto.is: did not receive HSTS header
 dl.google.com: did not receive HSTS header
 docs.google.com: did not receive HSTS header
 download.jitsi.org: did not receive HSTS header
 drive.google.com: did not receive HSTS header
 dropcam.com: did not receive HSTS header
 emailprivacytester.com: max-age too low: 8640000
 encrypted.google.com: did not receive HSTS header
 epoxate.com: max-age too low: 259200
-factor.cc: could not connect to host
 fatzebra.com.au: did not receive HSTS header
+fj.simple.com: did not receive HSTS header
 gmail.com: did not receive HSTS header
 googlemail.com: did not receive HSTS header
 googleplex.com: could not connect to host
 greplin.com: did not receive HSTS header
 grepular.com: max-age too low: 8640000
 groups.google.com: did not receive HSTS header
 health.google.com: did not receive HSTS header
 history.google.com: did not receive HSTS header
@@ -41,18 +42,18 @@ iop.intuit.com: max-age too low: 86400
 irccloud.com: did not receive HSTS header
 jitsi.org: did not receive HSTS header
 jottit.com: did not receive HSTS header
 kiwiirc.com: max-age too low: 5256000
 ledgerscope.net: max-age too low: 86400
 linx.net: did not receive HSTS header
 lists.mayfirst.org: did not receive HSTS header
 mail.google.com: did not receive HSTS header
+makeyourlaws.org: could not connect to host
 market.android.com: did not receive HSTS header
-mega.co.nz: could not connect to host
 my.alfresco.com: did not receive HSTS header
 mydigipass.com: did not receive HSTS header
 neonisi.com: could not connect to host
 openshift.redhat.com: did not receive HSTS header
 ottospora.nl: could not connect to host
 packagist.org: max-age too low: 2592000
 passwd.io: could not connect to host
 plus.google.com: did not receive HSTS header
@@ -85,9 +86,8 @@ www.lastpass.com: did not receive HSTS h
 www.ledgerscope.net: max-age too low: 86400
 www.logentries.com: did not receive HSTS header
 www.makeyourlaws.org: did not receive HSTS header
 www.moneybookers.com: did not receive HSTS header
 www.neonisi.com: could not connect to host
 www.paycheckrecords.com: did not receive HSTS header
 www.paypal.com: max-age too low: 14400
 www.sandbox.mydigipass.com: could not connect to host
-www.twitter.com: did not receive HSTS header
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,46 +3,49 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsStrictTransportSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include "mozilla/StandardInteger.h"
-const PRTime gPreloadListExpirationTime = INT64_C(1374654832387000);
+const PRTime gPreloadListExpirationTime = INT64_C(1377890142384000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
 static const nsSTSPreload kSTSPreloadList[] = {
+  { "alpha.irccloud.com", false },
   { "api.intercom.io", false },
   { "app.recurly.com", false },
   { "arivo.com.br", true },
+  { "bassh.net", true },
   { "blog.torproject.org", false },
   { "bugzilla.mozilla.org", true },
   { "business.medbank.com.mt", true },
   { "check.torproject.org", false },
   { "cloudsecurityalliance.org", true },
+  { "controlcenter.gigahost.dk", true },
   { "crate.io", true },
   { "crm.onlime.ch", false },
   { "crypto.cat", false },
   { "csawctf.poly.edu", true },
   { "developer.mydigipass.com", false },
   { "dist.torproject.org", false },
   { "dm.lookout.com", false },
   { "dm.mylookout.com", false },
   { "ebanking.indovinabank.com.vn", false },
   { "entropia.de", false },
   { "espra.com", true },
-  { "factor.cc", true },
+  { "factor.cc", false },
   { "gocardless.com", true },
   { "howrandom.org", true },
   { "id.mayfirst.org", false },
   { "intercom.io", false },
   { "itriskltd.com", true },
   { "keyerror.com", true },
   { "lastpass.com", false },
   { "lockify.com", true },
@@ -55,25 +58,27 @@ static const nsSTSPreload kSTSPreloadLis
   { "mattmccutchen.net", true },
   { "mega.co.nz", false },
   { "members.mayfirst.org", false },
   { "members.nearlyfreespeech.net", false },
   { "my.onlime.ch", false },
   { "mylookout.com", false },
   { "neg9.org", false },
   { "passwd.io", true },
+  { "pay.gigahost.dk", true },
   { "paymill.com", true },
   { "paymill.de", true },
   { "piratenlogin.de", true },
   { "pixi.me", true },
   { "riseup.net", true },
   { "roundcube.mayfirst.org", false },
   { "sandbox.mydigipass.com", false },
   { "serverdensity.io", true },
   { "silentcircle.com", true },
+  { "simple.com", false },
   { "stocktrade.de", false },
   { "stripe.com", true },
   { "support.mayfirst.org", false },
   { "surfeasy.com", false },
   { "therapynotes.com", false },
   { "twitter.com", false },
   { "ubertt.org", true },
   { "webmail.gigahost.dk", false },
@@ -87,13 +92,15 @@ static const nsSTSPreload kSTSPreloadLis
   { "www.entropia.de", false },
   { "www.gov.uk", false },
   { "www.intercom.io", false },
   { "www.irccloud.com", false },
   { "www.lookout.com", false },
   { "www.mydigipass.com", false },
   { "www.mylookout.com", false },
   { "www.noisebridge.net", false },
+  { "www.simple.com", false },
   { "www.surfeasy.com", false },
   { "www.therapynotes.com", false },
   { "www.torproject.org", false },
+  { "www.twitter.com", false },
   { "zoo24.de", true },
 };