Bug 1258602 - Part3: Changes for making the initial state to “connecting”, r=smaug
authorKershaw Chang <kechang@mozilla.com>
Sun, 29 May 2016 23:01:00 +0200
changeset 299567 3da545fd691fa27d09ab8865b1fa2916418cad7f
parent 299566 e0f15af55efbef722c2a6981203a0d3a06892577
child 299568 f5804d79ba15e4e16aaa004cd37331a4b2233a9f
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1258602
milestone49.0a1
Bug 1258602 - Part3: Changes for making the initial state to “connecting”, r=smaug
dom/presentation/PresentationService.cpp
dom/presentation/PresentationService.h
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
dom/presentation/tests/mochitest/test_presentation_dc_sender.html
dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -35,47 +35,54 @@ namespace dom {
 class PresentationDeviceRequest final : public nsIPresentationDeviceRequest
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEREQUEST
 
   PresentationDeviceRequest(const nsAString& aRequestUrl,
                             const nsAString& aId,
-                            const nsAString& aOrigin);
+                            const nsAString& aOrigin,
+                            uint64_t aWindowId,
+                            nsIPresentationServiceCallback* aCallback);
 
 private:
-  virtual ~PresentationDeviceRequest();
+  virtual ~PresentationDeviceRequest() = default;
+  nsresult CreateSessionInfo(nsIPresentationDevice* aDevice);
 
   nsString mRequestUrl;
   nsString mId;
   nsString mOrigin;
+  uint64_t mWindowId;
+  nsCOMPtr<nsIPresentationServiceCallback> mCallback;
 };
 
 LazyLogModule gPresentationLog("Presentation");
 
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
 
-PresentationDeviceRequest::PresentationDeviceRequest(const nsAString& aRequestUrl,
-                                                     const nsAString& aId,
-                                                     const nsAString& aOrigin)
+PresentationDeviceRequest::PresentationDeviceRequest(
+                                      const nsAString& aRequestUrl,
+                                      const nsAString& aId,
+                                      const nsAString& aOrigin,
+                                      uint64_t aWindowId,
+                                      nsIPresentationServiceCallback* aCallback)
   : mRequestUrl(aRequestUrl)
   , mId(aId)
   , mOrigin(aOrigin)
+  , mWindowId(aWindowId)
+  , mCallback(aCallback)
 {
   MOZ_ASSERT(!mRequestUrl.IsEmpty());
   MOZ_ASSERT(!mId.IsEmpty());
   MOZ_ASSERT(!mOrigin.IsEmpty());
-}
-
-PresentationDeviceRequest::~PresentationDeviceRequest()
-{
+  MOZ_ASSERT(mCallback);
 }
 
 NS_IMETHODIMP
 PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
 {
   aOrigin = mOrigin;
   return NS_OK;
 }
@@ -88,26 +95,38 @@ PresentationDeviceRequest::GetRequestURL
 }
 
 NS_IMETHODIMP
 PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aDevice);
 
+  nsresult rv = CreateSessionInfo(aDevice);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mCallback->NotifyError(rv);
+    return rv;
+  }
+
+  return mCallback->NotifySuccess();
+}
+
+nsresult
+PresentationDeviceRequest::CreateSessionInfo(nsIPresentationDevice* aDevice)
+{
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  // Update device in the session info.
+  // Create the controlling session info
   RefPtr<PresentationSessionInfo> info =
     static_cast<PresentationService*>(service.get())->
-      GetSessionInfo(mId, nsIPresentationService::ROLE_CONTROLLER);
+      CreateControllingSessionInfo(mRequestUrl, mId, mWindowId);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   info->SetDevice(aDevice);
 
   // Establish a control channel. If we failed to do so, the callback is called
   // with an error message.
   nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
@@ -123,30 +142,17 @@ PresentationDeviceRequest::Select(nsIPre
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationDeviceRequest::Cancel()
 {
-  nsCOMPtr<nsIPresentationService> service =
-    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
-  if (NS_WARN_IF(!service)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  RefPtr<PresentationSessionInfo> info =
-    static_cast<PresentationService*>(service.get())->
-      GetSessionInfo(mId, nsIPresentationService::ROLE_CONTROLLER);
-  if (NS_WARN_IF(!info)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
+  return mCallback->NotifyError(NS_ERROR_DOM_ABORT_ERR);
 }
 
 /*
  * Implementation of PresentationService
  */
 
 NS_IMPL_ISUPPORTS_INHERITED(PresentationService,
                             PresentationServiceBase,
@@ -398,60 +404,56 @@ PresentationService::StartSession(const 
                                   const nsAString& aDeviceId,
                                   uint64_t aWindowId,
                                   nsIPresentationServiceCallback* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  // Create session info  and set the callback. The callback is called when the
-  // request is finished.
-  RefPtr<PresentationSessionInfo> info =
-    new PresentationControllingInfo(aUrl, aSessionId, aCallback);
-  mSessionInfoAtController.Put(aSessionId, info);
-
-  AddRespondingSessionId(aWindowId, aSessionId);
-
   nsCOMPtr<nsIPresentationDeviceRequest> request =
-    new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
+    new PresentationDeviceRequest(aUrl,
+                                  aSessionId,
+                                  aOrigin,
+                                  aWindowId,
+                                  aCallback);
 
   if (aDeviceId.IsVoid()) {
     // Pop up a prompt and ask user to select a device.
     nsCOMPtr<nsIPresentationDevicePrompt> prompt =
       do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
     if (NS_WARN_IF(!prompt)) {
-      return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+      return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
     }
 
     nsresult rv = prompt->PromptDeviceSelection(request);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+      return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
     }
 
     return NS_OK;
   }
 
   // Find the designated device from available device list.
   nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
     do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
   if (NS_WARN_IF(!deviceManager)) {
-    return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   nsCOMPtr<nsIArray> devices;
   nsresult rv = deviceManager->GetAvailableDevices(getter_AddRefs(devices));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   rv = devices->Enumerate(getter_AddRefs(enumerator));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   NS_ConvertUTF16toUTF8 utf8DeviceId(aDeviceId);
   bool hasMore;
   while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore){
     nsCOMPtr<nsISupports> isupports;
     rv = enumerator->GetNext(getter_AddRefs(isupports));
 
@@ -461,17 +463,36 @@ PresentationService::StartSession(const 
     nsAutoCString id;
     if (NS_SUCCEEDED(device->GetId(id)) && id.Equals(utf8DeviceId)) {
       request->Select(device);
       return NS_OK;
     }
   }
 
   // Reject if designated device is not available.
-  return info->ReplyError(NS_ERROR_DOM_NOT_FOUND_ERR);
+  return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR);
+}
+
+already_AddRefed<PresentationSessionInfo>
+PresentationService::CreateControllingSessionInfo(const nsAString& aUrl,
+                                                  const nsAString& aSessionId,
+                                                  uint64_t aWindowId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aSessionId.IsEmpty()) {
+    return nullptr;
+  }
+
+  RefPtr<PresentationSessionInfo> info =
+    new PresentationControllingInfo(aUrl, aSessionId);
+
+  mSessionInfoAtController.Put(aSessionId, info);
+  AddRespondingSessionId(aWindowId, aSessionId);
+  return info.forget();
 }
 
 NS_IMETHODIMP
 PresentationService::SendSessionMessage(const nsAString& aSessionId,
                                         uint8_t aRole,
                                         const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -14,16 +14,17 @@
 #include "PresentationSessionInfo.h"
 
 class nsIPresentationSessionRequest;
 class nsIURI;
 
 namespace mozilla {
 namespace dom {
 
+class PresentationDeviceRequest;
 class PresentationRespondingInfo;
 
 class PresentationService final : public nsIPresentationService
                                 , public nsIObserver
                                 , public PresentationServiceBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -49,23 +50,31 @@ public:
     }
   }
 
   bool IsSessionAccessible(const nsAString& aSessionId,
                            const uint8_t aRole,
                            base::ProcessId aProcessId);
 
 private:
+  friend class PresentationDeviceRequest;
+
   virtual ~PresentationService();
   void HandleShutdown();
   nsresult HandleDeviceChange();
   nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   void NotifyAvailableChange(bool aIsAvailable);
   bool IsAppInstalled(nsIURI* aUri);
 
+  // This is meant to be called by PresentationDeviceRequest.
+  already_AddRefed<PresentationSessionInfo>
+  CreateControllingSessionInfo(const nsAString& aUrl,
+                               const nsAString& aSessionId,
+                               uint64_t aWindowId);
+
   bool mIsAvailable;
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>> mAvailabilityListeners;
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtController;
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfoAtReceiver;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -289,35 +289,24 @@ PresentationSessionInfo::Close(nsresult 
   Shutdown(aReason);
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplySuccess()
 {
   SetState(nsIPresentationSessionListener::STATE_CONNECTED);
-
-  if (mCallback) {
-    NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
-    SetCallback(nullptr);
-  }
-
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplyError(nsresult aError)
 {
   Shutdown(aError);
 
-  if (mCallback) {
-    NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aError)));
-    SetCallback(nullptr);
-  }
-
   // Remove itself since it never succeeds.
   return UntrackFromService();
 }
 
 /* virtual */ nsresult
 PresentationSessionInfo::UntrackFromService()
 {
   nsCOMPtr<nsIPresentationService> service =
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -33,24 +33,22 @@ class PresentationSessionInfo : public n
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
   NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER
 
   PresentationSessionInfo(const nsAString& aUrl,
                           const nsAString& aSessionId,
-                          const uint8_t aRole,
-                          nsIPresentationServiceCallback* aCallback)
+                          const uint8_t aRole)
     : mUrl(aUrl)
     , mSessionId(aSessionId)
     , mIsResponderReady(false)
     , mIsTransportReady(false)
     , mState(nsIPresentationSessionListener::STATE_CONNECTING)
-    , mCallback(aCallback)
   {
     MOZ_ASSERT(!mUrl.IsEmpty());
     MOZ_ASSERT(!mSessionId.IsEmpty());
     MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
                aRole == nsIPresentationService::ROLE_RECEIVER);
     mRole = aRole;
   }
 
@@ -66,21 +64,16 @@ public:
     return mSessionId;
   }
 
   uint8_t GetRole() const
   {
     return mRole;
   }
 
-  void SetCallback(nsIPresentationServiceCallback* aCallback)
-  {
-    mCallback = aCallback;
-  }
-
   nsresult SetListener(nsIPresentationSessionListener* aListener);
 
   void SetDevice(nsIPresentationDevice* aDevice)
   {
     mDevice = aDevice;
   }
 
   already_AddRefed<nsIPresentationDevice> GetDevice() const
@@ -147,17 +140,16 @@ protected:
   nsString mUrl;
   nsString mSessionId;
   // mRole should be nsIPresentationService::ROLE_CONTROLLER
   //              or nsIPresentationService::ROLE_RECEIVER.
   uint8_t mRole;
   bool mIsResponderReady;
   bool mIsTransportReady;
   uint32_t mState; // CONNECTED, CLOSED, TERMINATED
-  nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
   nsCOMPtr<nsIPresentationDevice> mDevice;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
   nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
   nsCOMPtr<nsIPresentationSessionTransportBuilder> mBuilder;
 };
 
 // Session info with controlling browsing context (sender side) behaviors.
@@ -165,25 +157,21 @@ class PresentationControllingInfo final 
                                         , public nsIServerSocketListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
   NS_DECL_NSISERVERSOCKETLISTENER
 
   PresentationControllingInfo(const nsAString& aUrl,
-                              const nsAString& aSessionId,
-                              nsIPresentationServiceCallback* aCallback)
+                              const nsAString& aSessionId)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
-                              nsIPresentationService::ROLE_CONTROLLER,
-                              aCallback)
-  {
-    MOZ_ASSERT(mCallback);
-  }
+                              nsIPresentationService::ROLE_CONTROLLER)
+  {}
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
 private:
   ~PresentationControllingInfo()
   {
     Shutdown(NS_OK);
   }
@@ -220,18 +208,17 @@ public:
   NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER
   NS_DECL_NSITIMERCALLBACK
 
   PresentationPresentingInfo(const nsAString& aUrl,
                              const nsAString& aSessionId,
                              nsIPresentationDevice* aDevice)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
-                              nsIPresentationService::ROLE_RECEIVER,
-                              nullptr)
+                              nsIPresentationService::ROLE_RECEIVER)
   {
     MOZ_ASSERT(aDevice);
     SetDevice(aDevice);
   }
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
   nsresult NotifyResponderReady();
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.html
@@ -123,18 +123,22 @@ function testCreateRequest() {
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start().then((aConnection) => {
       connection = aConnection;
       ok(connection, "Sender: Connection should be available.");
       ok(connection.id, "Sender: Connection ID should be set.");
-      is(connection.state, "connected", "Sender: Connection state at sender side should be connected by default.");
-      aResolve();
+      is(connection.state, "connecting", "The initial state should be connecting.");
+      connection.onstatechange = function() {
+        connection.onstatechange = null;
+        is(connection.state, "connected", "Connection should be connected.");
+        aResolve();
+      };
     }).catch((aError) => {
       ok(false, "Sender: Error occurred when establishing a connection: " + aError);
       teardown();
       aReject();
     });
   });
 }
 
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
@@ -129,18 +129,22 @@ function testCreateRequest() {
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start()
     .then((aConnection) => {
       connection = aConnection;
       ok(connection, "Sender: Connection should be available.");
       ok(connection.id, "Sender: Connection ID should be set.");
-      is(connection.state, "connected", "Sender: Connection state at sender side should be connected by default.");
-      aResolve();
+      is(connection.state, "connecting", "The initial state should be connecting.");
+      connection.onstatechange = function() {
+        connection.onstatechange = null;
+        is(connection.state, "connected", "Connection should be connected.");
+        aResolve();
+      };
     })
     .catch((aError) => {
       ok(false, "Sender: Error occurred when establishing a connection: " + aError);
       teardown();
       aReject();
     });
   });
 }
--- a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -95,31 +95,34 @@ function testStartConnection() {
     var connectionFromEvent;
     request.onconnectionavailable = function(aEvent) {
       request.onconnectionavailable = null;
       connectionFromEvent = aEvent.connection;
       ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
 
       if (connection) {
         is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
-        aResolve();
       }
     };
 
     request.start().then(
       function(aConnection) {
         connection = aConnection;
         ok(connection, "Connection should be available.");
         ok(connection.id, "Connection ID should be set.");
-        is(connection.state, "connected", "Connection state at sender side should be connected by default.");
+        is(connection.state, "connecting", "The initial state should be connecting.");
 
         if (connectionFromEvent) {
           is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+        }
+        connection.onstatechange = function() {
+          connection.onstatechange = null;
+          is(connection.state, "connected", "Connection should be connected.");
           aResolve();
-        }
+        };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
     );
   });
--- a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
@@ -91,31 +91,34 @@ function testStartConnectionWithDevice()
     var connectionFromEvent;
     request.onconnectionavailable = function(aEvent) {
       request.onconnectionavailable = null;
       connectionFromEvent = aEvent.connection;
       ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
 
       if (connection) {
         is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
-        aResolve();
       }
     };
 
     request.startWithDevice('id').then(
       function(aConnection) {
         connection = aConnection;
         ok(connection, "Connection should be available.");
         ok(connection.id, "Connection ID should be set.");
-        is(connection.state, "connected", "Connection state at sender side should be connected by default.");
+        is(connection.state, "connecting", "The initial state should be connecting.");
 
         if (connectionFromEvent) {
           is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+        }
+        connection.onstatechange = function() {
+          connection.onstatechange = null;
+          is(connection.state, "connected", "Connection should be connected.");
           aResolve();
-        }
+        };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
     );
   });
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
@@ -90,31 +90,34 @@ function testStartConnection() {
     var connectionFromEvent;
     request.onconnectionavailable = function(aEvent) {
       request.onconnectionavailable = null;
       connectionFromEvent = aEvent.connection;
       ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
 
       if (connection) {
         is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
-        aResolve();
       }
     };
 
     request.start().then(
       function(aConnection) {
         connection = aConnection;
         ok(connection, "Connection should be available.");
         ok(connection.id, "Connection ID should be set.");
-        is(connection.state, "connected", "Connection state at sender side should be connected by default.");
+        is(connection.state, "connecting", "The initial state should be connecting.");
 
         if (connectionFromEvent) {
           is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+        }
+        connection.onstatechange = function() {
+          connection.onstatechange = null;
+          is(connection.state, "connected", "Connection should be connected.");
           aResolve();
-        }
+        };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
     );
   });
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
@@ -83,18 +83,22 @@ function testStartConnection() {
 
     ok(!navigator.presentation.receiver, "Sender shouldn't get a presentation receiver instance.");
 
     navigator.presentation.defaultRequest.onconnectionavailable = function(aEvent) {
       navigator.presentation.defaultRequest.onconnectionavailable = null;
       connection = aEvent.connection;
       ok(connection, "|connectionavailable| event is fired with a connection.");
       ok(connection.id, "Connection ID should be set.");
-      is(connection.state, "connected", "Connection state at sender side should be connected by default.");
-      aResolve();
+      is(connection.state, "connecting", "The initial state should be connecting.");
+      connection.onstatechange = function() {
+        connection.onstatechange = null;
+        is(connection.state, "connected", "Connection should be connected.");
+        aResolve();
+      };
     };
 
     // Simulate the UA triggers |start()| of the default request.
     navigator.presentation.defaultRequest.start();
   });
 }
 
 function testTerminateConnection() {
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
@@ -87,18 +87,22 @@ function testStartConnection() {
       info("Data notification is enabled for data transport channel.");
     });
 
     request.start().then(
       function(aConnection) {
         connection = aConnection;
         ok(connection, "Connection should be available.");
         ok(connection.id, "Connection ID should be set.");
-        is(connection.state, "connected", "Connection state at sender side should be connected by default.");
-        aResolve();
+        is(connection.state, "connecting", "The initial state should be connecting.");
+        connection.onstatechange = function() {
+          connection.onstatechange = null;
+          is(connection.state, "connected", "Connection should be connected.");
+          aResolve();
+        };
       },
       function(aError) {
         ok(false, "Error occurred when establishing a connection: " + aError);
         teardown();
         aReject();
       }
     );
   });
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
@@ -99,22 +99,27 @@ function testStartConnectionUnexpectedCo
     gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
       gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
     });
 
     request.start().then(
       function(aConnection) {
-        ok(false, "|start| shouldn't succeed in this case.");
-        aReject();
+        is(aConnection.state, "connecting", "The initial state should be connecting.");
+        aConnection.onstatechange = function() {
+          aConnection.onstatechange = null;
+          is(aConnection.state, "terminated", "Connection should be terminated.");
+          aResolve();
+        };
       },
       function(aError) {
-        is(aError.name, "OperationError", "OperationError is expected when a connection error happens during establishing a connection.");
-        aResolve();
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
       }
     );
   });
 }
 
 function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@@ -142,22 +147,27 @@ function testStartConnectionUnexpectedCo
     gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
       gScript.removeMessageListener('offer-sent', offerSentHandler);
       ok(aIsValid, "A valid offer is sent out.");
       gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
     });
 
     request.start().then(
       function(aConnection) {
-        ok(false, "|start| shouldn't succeed in this case.");
-        aReject();
+        is(aConnection.state, "connecting", "The initial state should be connecting.");
+        aConnection.onstatechange = function() {
+          aConnection.onstatechange = null;
+          is(aConnection.state, "terminated", "Connection should be terminated.");
+          aResolve();
+        };
       },
       function(aError) {
-        is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
-        aResolve();
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
       }
     );
   });
 }
 
 function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@@ -196,22 +206,27 @@ function testStartConnectionUnexpectedCo
 
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
-        ok(false, "|start| shouldn't succeed in this case.");
-        aReject();
+        is(aConnection.state, "connecting", "The initial state should be connecting.");
+        aConnection.onstatechange = function() {
+          aConnection.onstatechange = null;
+          is(aConnection.state, "terminated", "Connection should be terminated.");
+          aResolve();
+        };
       },
       function(aError) {
-        is(aError.name, "OperationError", "OperationError is expected when a connection error happens during establishing a connection.");
-        aResolve();
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
       }
     );
   });
 }
 
 function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@@ -250,22 +265,27 @@ function testStartConnectionUnexpectedCo
 
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
-        ok(false, "|start| shouldn't succeed in this case.");
-        aReject();
+        is(aConnection.state, "connecting", "The initial state should be connecting.");
+        aConnection.onstatechange = function() {
+          aConnection.onstatechange = null;
+          is(aConnection.state, "terminated", "Connection should be terminated.");
+          aResolve();
+        };
       },
       function(aError) {
-        is(aError.name, "OperationError", "OperationError is expected when a connection closed during establishing a connection.");
-        aResolve();
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
       }
     );
   });
 }
 
 function testStartConnectionUnexpectedDataTransportClose() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
@@ -304,22 +324,27 @@ function testStartConnectionUnexpectedDa
 
     gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
       gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
       info("The data transport is closed. " + aReason);
     });
 
     request.start().then(
       function(aConnection) {
-        ok(false, "|start| shouldn't succeed in this case.");
-        aReject();
+        is(aConnection.state, "connecting", "The initial state should be connecting.");
+        aConnection.onstatechange = function() {
+          aConnection.onstatechange = null;
+          is(aConnection.state, "terminated", "Connection should be terminated.");
+          aResolve();
+        };
       },
       function(aError) {
-        is(aError.name, "OperationError", "OperationError is expected when a connection error happens during establishing a connection.");
-        aResolve();
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
       }
     );
   });
 }
 
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
     gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);