Bug 1153134 - Part 2, support TLS control server. r=junior.
authorShih-Chiang Chien <schien@mozilla.com>
Tue, 26 Jul 2016 10:59:52 +0800
changeset 308829 99869310b0ecaaa03693ab0438c0ca01c248003a
parent 308828 10159ae9f4999a30952f17035c914bc0399e9231
child 308830 c25ff855651a568e027d7190a07890ba14cee6f5
push id20279
push usercbook@mozilla.com
push dateWed, 10 Aug 2016 14:04:43 +0000
treeherderfx-team@531100c1d950 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjunior
bugs1153134
milestone51.0a1
Bug 1153134 - Part 2, support TLS control server. r=junior. MozReview-Commit-ID: 2jVShMuEzTi
dom/presentation/interfaces/nsIPresentationControlService.idl
dom/presentation/provider/ControllerStateMachine.jsm
dom/presentation/provider/DisplayDeviceProvider.cpp
dom/presentation/provider/MulticastDNSDeviceProvider.cpp
dom/presentation/provider/MulticastDNSDeviceProvider.h
dom/presentation/provider/PresentationControlService.js
dom/presentation/provider/ReceiverStateMachine.jsm
dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
dom/presentation/tests/xpcshell/test_tcp_control_channel.js
modules/libpref/init/all.js
--- a/dom/presentation/interfaces/nsIPresentationControlService.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlService.idl
@@ -24,23 +24,33 @@ interface nsITCPDeviceInfo: nsISupports
   // server doesn't support TLS or not available.
   readonly attribute AUTF8String certFingerprint;
 };
 
 [scriptable, uuid(09bddfaf-fcc2-4dc9-b33e-a509a1c2fb6d)]
 interface nsIPresentationControlServerListener: nsISupports
 {
   /**
-   * Callback while the server socket changes port.
-   * This event won't be cached so you should get current port after setting
-   * this listener to make sure the value is updated.
+   * Callback while the server is ready or restarted.
    * @param   aPort
    *          The port of the server socket.
+   * @param   aCertFingerprint
+   *          The SHA-256 fingerprint of TLS server certificate.
+   *          Empty string represents server started without encryption.
    */
-  void onPortChange(in uint16_t aPort);
+  void onServerReady(in uint16_t aPort, in AUTF8String aCertFingerprint);
+
+  /**
+   * Callback while the server is stopped or fails to start.
+   * @param   aResult
+   *          The error cause of server stopped or the reason of
+   *          start failure.
+   *          NS_OK means the server is stopped by close.
+   */
+  void onServerStopped(in nsresult aResult);
 
   /**
    * Callback while the remote host is requesting to start a presentation session.
    * @param aDeviceInfo The device information related to the remote host.
    * @param aUrl The URL requested to open by remote device.
    * @param aPresentationId The Id for representing this session.
    * @param aControlChannel The control channel for this session.
    */
@@ -77,24 +87,27 @@ interface nsIPresentationControlServerLi
 /**
  * Presentation control service which can be used for both presentation
  * control client and server.
  */
 [scriptable, uuid(55d6b605-2389-4aae-a8fe-60d4440540ea)]
 interface nsIPresentationControlService: nsISupports
 {
   /**
-   * This method initialize server socket.
+   * This method initializes server socket. Caller should set listener and
+   * monitor onServerReady event to get the correct server info.
+   * @param   aEncrypted
+   *          True for using TLS control channel.
    * @param   aPort
    *          The port of the server socket.  Pass 0 or opt-out to indicate no
    *          preference, and a port will be selected automatically.
    * @throws  NS_ERROR_FAILURE if the server socket has been inited or the
    *          server socket can not be inited.
    */
-  void startServer([optional] in uint16_t aPort);
+  void startServer(in boolean aEncrypted, [optional] in uint16_t aPort);
 
   /**
    * Request connection to designated remote presentation control receiver.
    * @param   aDeviceInfo
    *          The remtoe device info for establish connection.
    * @returns The control channel for this session.
    * @throws  NS_ERROR_FAILURE if the Id hasn't been inited.
    */
@@ -108,29 +121,36 @@ interface nsIPresentationControlService:
   boolean isCompatibleServer(in uint32_t aVersion);
 
   /**
    * Close server socket and call |listener.onClose(NS_OK)|
    */
   void close();
 
   /**
-   * Get the listen port of the TCP socket, valid after |init|. 0 indicates
-   * the server socket is not inited or closed.
+   * Get the listen port of the TCP socket, valid after the server is ready.
+   * 0 indicates the server socket is not ready or is closed.
    */
   readonly attribute uint16_t port;
 
   /**
    * The protocol version implemented by this server.
    */
   readonly attribute uint32_t version;
 
   /**
    * The id of the TCP presentation server. |requestSession| won't
    * work until the |id| is set.
    */
   attribute AUTF8String id;
 
   /**
-   * the listener for handling events of this presentation control server
+   * The fingerprint of the TLS server certificate.
+   * Empty string indicates the server is not ready or not encrypted.
+   */
+  attribute AUTF8String certFingerprint;
+
+  /**
+   * The listener for handling events of this presentation control server.
+   * Listener must be provided before invoke |startServer| and |close|.
    */
   attribute nsIPresentationControlServerListener listener;
 };
--- a/dom/presentation/provider/ControllerStateMachine.jsm
+++ b/dom/presentation/provider/ControllerStateMachine.jsm
@@ -65,18 +65,26 @@ var handlers = [
         break;
       default:
         debug("unexpected command: " + JSON.stringify(command));
         // ignore unexpected command.
         break;
     }
   },
   function _closingHandler(stateMachine, command) {
-    // ignore every command in closing state.
-    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+    switch (command.type) {
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command.
+        break;
+    }
   },
   function _closedHandler(stateMachine, command) {
     // ignore every command in closed state.
     DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
   },
 ];
 
 function ControllerStateMachine(channel, deviceId) {
--- a/dom/presentation/provider/DisplayDeviceProvider.cpp
+++ b/dom/presentation/provider/DisplayDeviceProvider.cpp
@@ -260,34 +260,32 @@ DisplayDeviceProvider::StartTCPService()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   /*
    * If |servicePort| is non-zero, it means PresentationServer is running.
    * Otherwise, we should make it start serving.
    */
-  if (!servicePort) {
-    rv = mPresentationService->SetListener(mWrappedListener);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = mPresentationService->StartServer(0);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = mPresentationService->GetPort(&servicePort);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+  if (servicePort) {
+    mPort = servicePort;
+    return NS_OK;
   }
 
-  mPort = servicePort;
+  rv = mPresentationService->SetListener(mWrappedListener);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // 1-UA doesn't need encryption.
+  rv = mPresentationService->StartServer(/* aEncrypted = */ false,
+                                         /* aPort = */ 0);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   return NS_OK;
 }
 
 nsresult
 DisplayDeviceProvider::AddExternalScreen()
 {
   MOZ_ASSERT(mDeviceListener);
@@ -362,20 +360,36 @@ DisplayDeviceProvider::SetListener(nsIPr
 NS_IMETHODIMP
 DisplayDeviceProvider::ForceDiscovery()
 {
   return NS_OK;
 }
 
 // nsIPresentationControlServerListener
 NS_IMETHODIMP
-DisplayDeviceProvider::OnPortChange(uint16_t aPort)
+DisplayDeviceProvider::OnServerReady(uint16_t aPort,
+                                     const nsACString& aCertFingerprint)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPort = aPort;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DisplayDeviceProvider::OnServerStopped(nsresult aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Try restart server if it is stopped abnormally.
+  if (NS_FAILED(aResult)) {
+    return NS_DispatchToMainThread(
+             NewRunnableMethod(this, &DisplayDeviceProvider::StartTCPService));
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DisplayDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
                                       const nsAString& aUrl,
                                       const nsAString& aPresentationId,
                                       nsIPresentationControlChannel* aControlChannel)
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -18,16 +18,17 @@
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "nsIPropertyBag2.h"
 #endif // MOZ_WIDGET_ANDROID
 
 #define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
 #define PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS "dom.presentation.discovery.timeout_ms"
 #define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
+#define PREF_PRESENTATION_DISCOVERABLE_ENCRYPTED "dom.presentation.discoverable.encrypted"
 #define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
 
 #define SERVICE_TYPE "_presentation-ctrl._tcp"
 #define PROTOCOL_VERSION_TAG "version"
 #define CERT_FINGERPRINT_TAG "certFingerprint"
 
 static mozilla::LazyLogModule sMulticastDNSProviderLogModule("MulticastDNSDeviceProvider");
 
@@ -144,33 +145,34 @@ MulticastDNSDeviceProvider::Init()
     return rv;
   }
 
   Preferences::AddStrongObservers(this, kObservedPrefs);
 
   mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY);
   mDiscoveryTimeoutMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERY_TIMEOUT_MS);
   mDiscoverable = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE);
+  mDiscoverableEncrypted = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE_ENCRYPTED);
   mServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
 
 #ifdef MOZ_WIDGET_ANDROID
   // FIXME: Bug 1185806 - Provide a common device name setting.
   if (mServiceName.IsEmpty()) {
     GetAndroidDeviceName(mServiceName);
     Unused << Preferences::SetCString(PREF_PRESENTATION_DEVICE_NAME, mServiceName);
   }
 #endif // MOZ_WIDGET_ANDROID
 
   Unused << mPresentationService->SetId(mServiceName);
 
   if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
     return rv;
   }
 
-  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
+  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = StartServer()))) {
     return rv;
   }
 
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
@@ -182,66 +184,100 @@ MulticastDNSDeviceProvider::Uninit()
     return NS_OK;
   }
 
   ClearDevices();
 
   Preferences::RemoveObservers(this, kObservedPrefs);
 
   StopDiscovery(NS_OK);
-  UnregisterService(NS_OK);
+  StopServer();
 
   mMulticastDNS = nullptr;
 
   if (mWrappedListener) {
     mWrappedListener->SetListener(nullptr);
     mWrappedListener = nullptr;
   }
 
   mInitialized = false;
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RegisterService()
+MulticastDNSDeviceProvider::StartServer()
 {
-  LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable);
+  LOG_I("StartServer: %s (%d)", mServiceName.get(), mDiscoverable);
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDiscoverable) {
     return NS_OK;
   }
 
   nsresult rv;
 
   uint16_t servicePort;
   if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->GetPort(&servicePort)))) {
     return rv;
   }
 
   /**
-    * If |servicePort| is non-zero, it means PresentationServer is running.
+    * If |servicePort| is non-zero, it means PresentationControlService is running.
     * Otherwise, we should make it start serving.
     */
-  if (!servicePort) {
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetListener(mWrappedListener)))) {
-      return rv;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->StartServer(0)))) {
-      return rv;
-    }
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->GetPort(&servicePort)))) {
-      return rv;
-    }
+  if (servicePort) {
+    return RegisterMDNSService();
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetListener(mWrappedListener)))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->StartServer(mDiscoverableEncrypted, 0)))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::StopServer()
+{
+  LOG_I("StopServer: %s", mServiceName.get());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  UnregisterMDNSService(NS_OK);
+
+  if (mPresentationService) {
+    mPresentationService->SetListener(nullptr);
+    mPresentationService->Close();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::RegisterMDNSService()
+{
+  LOG_I("RegisterMDNSService: %s", mServiceName.get());
+
+  if (!mDiscoverable) {
+    return NS_OK;
   }
 
   // Cancel on going service registration.
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
-    mRegisterRequest = nullptr;
+  UnregisterMDNSService(NS_OK);
+
+  nsresult rv;
+
+  uint16_t servicePort;
+  if (NS_FAILED(rv = mPresentationService->GetPort(&servicePort)) ||
+      !servicePort) {
+    // Abort service registration if server port is not available.
+    return rv;
   }
 
   /**
    * Register the presentation control channel server as an mDNS service.
    */
   nsCOMPtr<nsIDNSServiceInfo> serviceInfo =
     do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -264,40 +300,47 @@ MulticastDNSDeviceProvider::RegisterServ
 
   uint32_t version;
   rv = mPresentationService->GetVersion(&version);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   rv = propBag->SetPropertyAsUint32(NS_LITERAL_STRING(PROTOCOL_VERSION_TAG),
                                     version);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+  if (mDiscoverableEncrypted) {
+    nsAutoCString certFingerprint;
+    rv = mPresentationService->GetCertFingerprint(certFingerprint);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    rv = propBag->SetPropertyAsACString(NS_LITERAL_STRING(CERT_FINGERPRINT_TAG),
+                                        certFingerprint);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+
   if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetAttributes(propBag)))) {
     return rv;
   }
 
   return mMulticastDNS->RegisterService(serviceInfo,
                                         mWrappedListener,
                                         getter_AddRefs(mRegisterRequest));
 }
 
 nsresult
-MulticastDNSDeviceProvider::UnregisterService(nsresult aReason)
+MulticastDNSDeviceProvider::UnregisterMDNSService(nsresult aReason)
 {
+  LOG_I("UnregisterMDNSService: %s (0x%08x)", mServiceName.get(), aReason);
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mRegisterRequest) {
     mRegisterRequest->Cancel(aReason);
     mRegisterRequest = nullptr;
   }
 
-  if (mPresentationService) {
-    mPresentationService->SetListener(nullptr);
-    mPresentationService->Close();
-  }
-
   return NS_OK;
 }
 
 nsresult
 MulticastDNSDeviceProvider::StopDiscovery(nsresult aReason)
 {
   LOG_I("StopDiscovery (0x%08x)", aReason);
 
@@ -763,17 +806,17 @@ MulticastDNSDeviceProvider::OnRegistrati
 {
   LOG_E("OnRegistrationFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   mRegisterRequest = nullptr;
 
   if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) {
     return NS_DispatchToMainThread(
-             NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterService));
+             NewRunnableMethod(this, &MulticastDNSDeviceProvider::RegisterMDNSService));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo,
                                                    int32_t aErrorCode)
@@ -875,23 +918,40 @@ MulticastDNSDeviceProvider::OnResolveFai
   LOG_E("OnResolveFailed: %d", aErrorCode);
   MOZ_ASSERT(NS_IsMainThread());
 
   return NS_OK;
 }
 
 // nsIPresentationControlServerListener
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort)
+MulticastDNSDeviceProvider::OnServerReady(uint16_t aPort,
+                                          const nsACString& aCertFingerprint)
 {
-  LOG_I("OnPortChange: %d", aPort);
+  LOG_I("OnServerReady: %d, %s", aPort, PromiseFlatCString(aCertFingerprint).get());
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mDiscoverable) {
-    RegisterService();
+    RegisterMDNSService();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServerStopped(nsresult aResult)
+{
+  LOG_I("OnServerStopped: (0x%08x)", aResult);
+
+  UnregisterMDNSService(aResult);
+
+  // Try restart server if it is stopped abnormally.
+  if (NS_FAILED(aResult) && mDiscoverable) {
+    return NS_DispatchToMainThread(
+             NewRunnableMethod(this, &MulticastDNSDeviceProvider::StartServer));
   }
 
   return NS_OK;
 }
 
 // Create a new device if we were unable to find one with the address.
 already_AddRefed<MulticastDNSDeviceProvider::Device>
 MulticastDNSDeviceProvider::GetOrCreateDevice(nsITCPDeviceInfo* aDeviceInfo)
@@ -1055,37 +1115,37 @@ nsresult
 MulticastDNSDeviceProvider::OnDiscoverableChanged(bool aEnabled)
 {
   LOG_I("Discoverable = %d\n", aEnabled);
   MOZ_ASSERT(NS_IsMainThread());
 
   mDiscoverable = aEnabled;
 
   if (mDiscoverable) {
-    return RegisterService();
+    return StartServer();
   }
 
-  return UnregisterService(NS_OK);
+  return StopServer();
 }
 
 nsresult
 MulticastDNSDeviceProvider::OnServiceNameChanged(const nsACString& aServiceName)
 {
   LOG_I("serviceName = %s\n", PromiseFlatCString(aServiceName).get());
   MOZ_ASSERT(NS_IsMainThread());
 
   mServiceName = aServiceName;
 
   nsresult rv;
-  if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(NS_OK)))) {
+  if (NS_WARN_IF(NS_FAILED(rv = UnregisterMDNSService(NS_OK)))) {
     return rv;
   }
 
   if (mDiscoverable) {
-    return RegisterService();
+    return RegisterMDNSService();
   }
 
   return NS_OK;
 }
 
 // MulticastDNSDeviceProvider::Device
 NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider::Device,
                   nsIPresentationDevice)
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -144,18 +144,20 @@ private:
 
   struct DeviceAddressComparator {
     bool Equals(const RefPtr<Device>& aA, const RefPtr<Device>& aB) const {
       return aA->Address() == aB->Address();
     }
   };
 
   virtual ~MulticastDNSDeviceProvider();
-  nsresult RegisterService();
-  nsresult UnregisterService(nsresult aReason);
+  nsresult StartServer();
+  nsresult StopServer();
+  nsresult RegisterMDNSService();
+  nsresult UnregisterMDNSService(nsresult aReason);
   nsresult StopDiscovery(nsresult aReason);
   nsresult Connect(Device* aDevice,
                    nsIPresentationControlChannel** aRetVal);
   bool IsCompatibleServer(nsIDNSServiceInfo* aServiceInfo);
 
   // device manipulation
   nsresult AddDevice(const nsACString& aId,
                      const nsACString& aServiceName,
@@ -201,16 +203,17 @@ private:
   nsTArray<RefPtr<Device>> mDevices;
 
   bool mDiscoveryEnabled = false;
   bool mIsDiscovering = false;
   uint32_t mDiscoveryTimeoutMs;
   nsCOMPtr<nsITimer> mDiscoveryTimer;
 
   bool mDiscoverable = false;
+  bool mDiscoverableEncrypted = false;
 
   nsCString mServiceName;
   nsCString mRegisteredName;
 };
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/provider/PresentationControlService.js
+++ b/dom/presentation/provider/PresentationControlService.js
@@ -8,25 +8,28 @@
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /* globals XPCOMUtils */
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 /* globals Services */
 Cu.import("resource://gre/modules/Services.jsm");
 /* globals NetUtil */
 Cu.import("resource://gre/modules/NetUtil.jsm");
+/* globals setTimeout, clearTimeout */
+Cu.import("resource://gre/modules/Timer.jsm");
 
 /* globals ControllerStateMachine */
 XPCOMUtils.defineLazyModuleGetter(this, "ControllerStateMachine", // jshint ignore:line
                                   "resource://gre/modules/presentation/ControllerStateMachine.jsm");
 /* global ReceiverStateMachine */
 XPCOMUtils.defineLazyModuleGetter(this, "ReceiverStateMachine", // jshint ignore:line
                                   "resource://gre/modules/presentation/ReceiverStateMachine.jsm");
 
 const kProtocolVersion = 1; // need to review isCompatibleServer while fiddling the version number.
+const kLocalCertName = "presentation";
 
 const DEBUG = Services.prefs.getBoolPref("dom.presentation.tcp_server.debug");
 function log(aMsg) {
   dump("-*- PresentationControlService.js: " + aMsg + "\n");
 }
 
 function TCPDeviceInfo(aAddress, aPort, aId, aCertFingerprint) {
   this.address = aAddress;
@@ -44,53 +47,103 @@ function PresentationControlService() {
 PresentationControlService.prototype = {
   /**
    * If a user agent connects to this server, we create a control channel but
    * hand it to |TCPDevice.listener| when the initial information exchange
    * finishes. Therefore, we hold the control channels in this period.
    */
   _controlChannels: [],
 
-  startServer: function(aPort) {
+  startServer: function(aEncrypted, aPort) {
     if (this._isServiceInit()) {
       DEBUG && log("PresentationControlService - server socket has been initialized");  // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     /**
      * 0 or undefined indicates opt-out parameter, and a port will be selected
      * automatically.
      */
     let serverSocketPort = (typeof aPort !== "undefined" && aPort !== 0) ? aPort : -1;
 
-    this._serverSocket = Cc["@mozilla.org/network/server-socket;1"]
-                         .createInstance(Ci.nsIServerSocket);
+    if (aEncrypted) {
+      let self = this;
+      let localCertService = Cc["@mozilla.org/security/local-cert-service;1"]
+                               .getService(Ci.nsILocalCertService);
+      localCertService.getOrCreateCert(kLocalCertName, {
+        handleCert: function(aCert, aRv) {
+          DEBUG && log("PresentationControlService - handleCert");  // jshint ignore:line
+          if (aRv) {
+            self._notifyServerStopped(aRv);
+          } else {
+            self._serverSocket = Cc["@mozilla.org/network/tls-server-socket;1"]
+                                   .createInstance(Ci.nsITLSServerSocket);
 
+            self._serverSocketInit(serverSocketPort, aCert);
+          }
+        }
+      });
+    } else {
+      this._serverSocket = Cc["@mozilla.org/network/server-socket;1"]
+                             .createInstance(Ci.nsIServerSocket);
+
+      this._serverSocketInit(serverSocketPort, null);
+    }
+  },
+
+  _serverSocketInit: function(aPort, aCert) {
     if (!this._serverSocket) {
       DEBUG && log("PresentationControlService - create server socket fail."); // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     try {
-      this._serverSocket.init(serverSocketPort, false, -1);
+      this._serverSocket.init(aPort, false, -1);
+
+      if (aCert) {
+        this._serverSocket.serverCert = aCert;
+        this._serverSocket.setSessionCache(false);
+        this._serverSocket.setSessionTickets(false);
+        let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
+        this._serverSocket.setRequestClientCertificate(requestCert);
+      }
+
       this._serverSocket.asyncListen(this);
     } catch (e) {
       // NS_ERROR_SOCKET_ADDRESS_IN_USE
       DEBUG && log("PresentationControlService - init server socket fail: " + e); // jshint ignore:line
       throw Cr.NS_ERROR_FAILURE;
     }
 
     this._port = this._serverSocket.port;
 
     DEBUG && log("PresentationControlService - service start on port: " + this._port); // jshint ignore:line
 
     // Monitor network interface change to restart server socket.
     // Only B2G has nsINetworkManager
     Services.obs.addObserver(this, "network-active-changed", false);
     Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+    this._notifyServerReady();
+  },
+
+  _notifyServerReady: function() {
+    Services.tm.mainThread.dispatch(() => {
+      if (this._listener) {
+        this._listener.onServerReady(this._port, this.certFingerprint);
+      }
+    }, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  _notifyServerStopped: function(aRv) {
+    Services.tm.mainThread.dispatch(() => {
+      if (this._listener) {
+        this._listener.onServerStopped(aRv);
+      }
+    }, Ci.nsIThread.DISPATCH_NORMAL);
   },
 
   isCompatibleServer: function(aVersion) {
     // No compatibility issue for the first version of control protocol
     return this.version === aVersion;
   },
 
   get id() {
@@ -104,16 +157,24 @@ PresentationControlService.prototype = {
   get port() {
     return this._port;
   },
 
   get version() {
     return kProtocolVersion;
   },
 
+  get certFingerprint() {
+    if (!this._serverSocket.serverCert) {
+      return null;
+    }
+
+    return this._serverSocket.serverCert.sha256Fingerprint;
+  },
+
   set listener(aListener) {
     this._listener = aListener;
   },
 
   get listener() {
     return this._listener;
   },
 
@@ -260,16 +321,18 @@ PresentationControlService.prototype = {
     DEBUG && log("PresentationControlService - close"); // jshint ignore:line
     if (this._isServiceInit()) {
       DEBUG && log("PresentationControlService - close server socket"); // jshint ignore:line
       this._serverSocket.close();
       this._serverSocket = null;
 
       Services.obs.removeObserver(this, "network-active-changed");
       Services.obs.removeObserver(this, "network:offline-status-changed");
+
+      this._notifyServerStopped(Cr.NS_OK);
     }
     this._port = 0;
   },
 
   // nsIObserver
   observe: function(aSubject, aTopic, aData) {
     DEBUG && log("PresentationControlService - observe: " + aTopic); // jshint ignore:line
     switch (aTopic) {
@@ -299,24 +362,20 @@ PresentationControlService.prototype = {
     }
   },
 
   _restartServer: function() {
     DEBUG && log("PresentationControlService - restart service"); // jshint ignore:line
 
     // restart server socket
     if (this._isServiceInit()) {
-      let port = this._port;
       this.close();
 
       try {
         this.startServer();
-        if (this._listener && this._port !== port) {
-           this._listener.onPortChange(this._port);
-        }
       } catch (e) {
         DEBUG && log("PresentationControlService - restart service fail: " + e); // jshint ignore:line
       }
     }
   },
 
   classID: Components.ID("{f4079b8b-ede5-4b90-a112-5b415a931deb}"),
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIServerSocketListener,
@@ -387,51 +446,66 @@ function discriptionAsJson(aDescription)
       break;
     case Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL:
       json.dataChannelSDP = aDescription.dataChannelSDP;
       break;
   }
   return json;
 }
 
+const kDisconnectTimeout = 5000;
+const kTerminateTimeout = 5000;
+
 function TCPControlChannel(presentationService,
                            transport,
                            deviceInfo,
                            direction) {
   DEBUG && log("create TCPControlChannel for : " + direction); // jshint ignore:line
   this._deviceInfo = deviceInfo;
   this._direction = direction;
   this._transport = transport;
 
   this._presentationService = presentationService;
 
+  if (direction === "receiver") {
+    // Need to set security observer before I/O stream operation.
+    this._setSecurityObserver(this);
+  }
+
   let currentThread = Services.tm.currentThread;
   transport.setEventSink(this, currentThread);
 
   this._input = this._transport.openInputStream(0, 0, 0)
                                .QueryInterface(Ci.nsIAsyncInputStream);
   this._input.asyncWait(this.QueryInterface(Ci.nsIStreamListener),
                         Ci.nsIAsyncInputStream.WAIT_CLOSURE_ONLY,
                         0,
                         currentThread);
 
   this._output = this._transport
-                     .openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+                     .openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED, 0, 0)
+                     .QueryInterface(Ci.nsIAsyncOutputStream);
+
+  this._outgoingMsgs = [];
+
 
   this._stateMachine =
     (direction === "sender") ? new ControllerStateMachine(this, presentationService.id)
                              : new ReceiverStateMachine(this);
-  // Since the transport created by server socket is already CONNECTED_TO
-  if (this._direction === "receiver") {
+
+  if (direction === "receiver" && !transport.securityInfo) {
+    // Since the transport created by server socket is already CONNECTED_TO.
+    this._outgoingEnabled = true;
     this._createInputStreamPump();
   }
 }
 
 TCPControlChannel.prototype = {
-  _connected: false,
+  _outgoingEnabled: false,
+  _incomingEnabled: false,
   _pendingOpen: false,
   _pendingOffer: null,
   _pendingAnswer: null,
   _pendingClose: null,
   _pendingCloseReason: null,
   _pendingReconnect: false,
 
   sendOffer: function(aOffer) {
@@ -449,22 +523,41 @@ TCPControlChannel.prototype = {
   launch: function(aPresentationId, aUrl) {
     this._stateMachine.launch(aPresentationId, aUrl);
   },
 
   terminate: function(aPresentationId) {
     if (!this._terminatingId) {
       this._terminatingId = aPresentationId;
       this._stateMachine.terminate(aPresentationId);
+
+      // Start a guard timer to ensure terminateAck is processed.
+      this._terminateTimer = setTimeout(() => {
+        DEBUG && log("TCPControlChannel - terminate timeout: " + aPresentationId); // jshint ignore:line
+        delete this._terminateTimer;
+        if (this._pendingDisconnect) {
+          this._pendingDisconnect();
+        } else {
+          this.disconnect(Cr.NS_OK);
+        }
+      }, kTerminateTimeout);
     } else {
       this._stateMachine.terminateAck(aPresentationId);
       delete this._terminatingId;
     }
   },
 
+  _flushOutgoing: function() {
+    if (!this._outgoingEnabled || this._outgoingMsgs.length === 0) {
+      return;
+    }
+
+    this._output.asyncWait(this, 0, 0, Services.tm.currentThread);
+  },
+
   // may throw an exception
   _send: function(aMsg) {
     DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); // jshint ignore:line
 
     /**
      * XXX In TCP streaming, it is possible that more than one message in one
      * TCP packet. We use line delimited JSON to identify where one JSON encoded
      * object ends and the next begins. Therefore, we do not allow newline
@@ -475,45 +568,85 @@ TCPControlChannel.prototype = {
     try {
       this._output.write(message, message.length);
     } catch(e) {
       DEBUG && log("TCPControlChannel - Failed to send message: " + e.name); // jshint ignore:line
       throw e;
     }
   },
 
+  _setSecurityObserver: function(observer) {
+    if (this._transport && this._transport.securityInfo) {
+      DEBUG && log("TCPControlChannel - setSecurityObserver: " + observer); // jshint ignore:line
+      let connectionInfo = this._transport.securityInfo
+                               .QueryInterface(Ci.nsITLSServerConnectionInfo);
+      connectionInfo.setSecurityObserver(observer);
+    }
+  },
+
+  // nsITLSServerSecurityObserver
+  onHandshakeDone: function(socket, clientStatus) {
+    log("TCPControlChannel - onHandshakeDone: TLS version: " + clientStatus.tlsVersionUsed.toString(16));
+    this._setSecurityObserver(null);
+
+    // Process input/output after TLS handshake is complete.
+    this._outgoingEnabled = true;
+    this._createInputStreamPump();
+  },
+
+  // nsIAsyncOutputStream
+  onOutputStreamReady: function() {
+    DEBUG && log("TCPControlChannel - onOutputStreamReady"); // jshint ignore:line
+    if (this._outgoingMsgs.length === 0) {
+      return;
+    }
+
+    try {
+      this._send(this._outgoingMsgs[0]);
+    } catch (e) {
+      if (e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+        this._output.asyncWait(this, 0, 0, Services.tm.currentThread);
+        return;
+      }
+
+      this._closeTransport();
+      return;
+    }
+    this._outgoingMsgs.shift();
+    this._flushOutgoing();
+  },
+
   // nsIAsyncInputStream (Triggered by nsIInputStream.asyncWait)
   // Only used for detecting connection refused
   onInputStreamReady: function(aStream) {
+    DEBUG && log("TCPControlChannel - onInputStreamReady"); // jshint ignore:line
     try {
       aStream.available();
     } catch (e) {
       DEBUG && log("TCPControlChannel - onInputStreamReady error: " + e.name); // jshint ignore:line
       // NS_ERROR_CONNECTION_REFUSED
-      this._listener.notifyDisconnected(e.result);
+      this._notifyDisconnected(e.result);
     }
   },
 
   // nsITransportEventSink (Triggered by nsISocketTransport.setEventSink)
   onTransportStatus: function(aTransport, aStatus) {
     DEBUG && log("TCPControlChannel - onTransportStatus: " + aStatus.toString(16) +
                  " with role: " + this._direction); // jshint ignore:line
     if (aStatus === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
-      this._connected = true;
-
-      if (!this._pump) {
-        this._createInputStreamPump();
-      }
+      this._outgoingEnabled = true;
+      this._createInputStreamPump();
     }
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStartRequest: function() {
     DEBUG && log("TCPControlChannel - onStartRequest with role: " +
                  this._direction); // jshint ignore:line
+    this._incomingEnabled = true;
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStopRequest: function(aRequest, aContext, aStatus) {
     DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus +
                  " with role: " + this._direction); // jshint ignore:line
     this._stateMachine.onChannelClosed(aStatus, true);
   },
@@ -535,16 +668,20 @@ TCPControlChannel.prototype = {
         DEBUG && log("TCPSignalingChannel - error in parsing json: " + e); // jshint ignore:line
       }
 
       this._handleMessage(msg);
     }
   },
 
   _createInputStreamPump: function() {
+    if (this._pump) {
+      return;
+    }
+
     DEBUG && log("TCPControlChannel - create pump with role: " +
                  this._direction); // jshint ignore:line
     this._pump = Cc["@mozilla.org/network/input-stream-pump;1"].
                createInstance(Ci.nsIInputStreamPump);
     this._pump.init(this._input, -1, -1, 0, 0, false);
     this._pump.asyncRead(this, null);
     this._stateMachine.onChannelReady();
   },
@@ -603,58 +740,56 @@ TCPControlChannel.prototype = {
     }
   },
 
   /**
    * These functions are designed to handle the interaction with listener
    * appropriately. |_FUNC| is to handle |this._listener.FUNC|.
    */
   _onOffer: function(aOffer) {
-    if (!this._connected) {
+    if (!this._incomingEnabled) {
       return;
     }
     if (!this._listener) {
       this._pendingOffer = aOffer;
       return;
     }
     DEBUG && log("TCPControlChannel - notify offer: " +
                  JSON.stringify(aOffer)); // jshint ignore:line
     this._listener.onOffer(new ChannelDescription(aOffer));
   },
 
   _onAnswer: function(aAnswer) {
-    if (!this._connected) {
+    if (!this._incomingEnabled) {
       return;
     }
     if (!this._listener) {
       this._pendingAnswer = aAnswer;
       return;
     }
     DEBUG && log("TCPControlChannel - notify answer: " +
                  JSON.stringify(aAnswer)); // jshint ignore:line
     this._listener.onAnswer(new ChannelDescription(aAnswer));
   },
 
   _notifyConnected: function() {
-    this._connected = true;
     this._pendingClose = false;
     this._pendingCloseReason = Cr.NS_OK;
 
     if (!this._listener) {
       this._pendingOpen = true;
       return;
     }
 
     DEBUG && log("TCPControlChannel - notify opened with role: " +
                  this._direction); // jshint ignore:line
     this._listener.notifyConnected();
   },
 
   _notifyDisconnected: function(aReason) {
-    this._connected = false;
     this._pendingOpen = false;
     this._pendingOffer = null;
     this._pendingAnswer = null;
 
     // Remote endpoint closes the control channel with abnormal reason.
     if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
       aReason = this._pendingCloseReason;
     }
@@ -676,69 +811,103 @@ TCPControlChannel.prototype = {
       return;
     }
 
     DEBUG && log("TCPControlChannel - notify reconnected with role: " +
                  this._direction); // jshint ignore:line
     this._listener.notifyReconnected();
   },
 
+  _closeOutgoing: function() {
+    if (this._outgoingEnabled) {
+      this._output.close();
+      this._outgoingEnabled = false;
+    }
+  },
+  _closeIncoming: function() {
+    if (this._incomingEnabled) {
+      this._pump = null;
+      this._input.close();
+      this._incomingEnabled = false;
+    }
+  },
   _closeTransport: function() {
-    if (this._connected) {
-      this._transport.setEventSink(null, null);
-      this._pump = null;
+    if (this._disconnectTimer) {
+      clearTimeout(this._disconnectTimer);
+      delete this._disconnectTimer;
+    }
 
-      this._input.close();
-      this._output.close();
-      this._presentationService.releaseControlChannel(this);
+    if (this._terminateTimer) {
+      clearTimeout(this._terminateTimer);
+      delete this._terminateTimer;
     }
+
+    delete this._pendingDisconnect;
+
+    this._transport.setEventSink(null, null);
+
+    this._closeIncoming();
+    this._closeOutgoing();
+    this._presentationService.releaseControlChannel(this);
   },
 
   disconnect: function(aReason) {
     DEBUG && log("TCPControlChannel - disconnect with reason: " + aReason); // jshint ignore:line
 
-    if (this._connected) {
+    // Pending disconnect during termination procedure.
+    if (this._terminateTimer) {
+      // Store only the first disconnect action.
+      if (!this._pendingDisconnect) {
+        this._pendingDisconnect = this.disconnect.bind(this, aReason);
+      }
+      return;
+    }
+
+    if (this._outgoingEnabled && !this._disconnectTimer) {
       // default reason is NS_OK
       aReason = !aReason ? Cr.NS_OK : aReason;
+
       this._stateMachine.onChannelClosed(aReason, false);
 
-      this._closeTransport();
-
-      this._connected = false;
+      // Start a guard timer to ensure the transport will be closed.
+      this._disconnectTimer = setTimeout(() => {
+        DEBUG && log("TCPControlChannel - disconnect timeout"); // jshint ignore:line
+        this._closeTransport();
+      }, kDisconnectTimeout);
     }
   },
 
   reconnect: function(aPresentationId, aUrl) {
     DEBUG && log("TCPControlChannel - reconnect with role: " +
                  this._direction); // jshint ignore:line
     if (this._direction != "sender") {
       return Cr.NS_ERROR_FAILURE;
     }
 
     this._stateMachine.reconnect(aPresentationId, aUrl);
   },
 
   // callback from state machine
   sendCommand: function(command) {
-    this._send(command);
+    this._outgoingMsgs.push(command);
+    this._flushOutgoing();
   },
 
   notifyDeviceConnected: function(deviceId) {
     switch (this._direction) {
       case "receiver":
         this._deviceInfo.id = deviceId;
         break;
     }
     this._notifyConnected();
   },
 
   notifyDisconnected: function(reason) {
+    this._closeTransport();
     this._notifyDisconnected(reason);
-    this._closeTransport();
-    this._connected = false;
   },
 
   notifyLaunch: function(presentationId, url) {
     switch (this._direction) {
       case "receiver":
         this._presentationService.onSessionRequest(this._deviceInfo,
                                                    url,
                                                    presentationId,
@@ -752,24 +921,33 @@ TCPControlChannel.prototype = {
       this._terminatingId = presentationId;
       this._presentationService.onSessionTerminate(this._deviceInfo,
                                                    presentationId,
                                                    this,
                                                    this._direction === "sender");
       return;
     }
 
+    // Cancel terminate guard timer after receiving terminate-ack.
+    if (this._terminateTimer) {
+      clearTimeout(this._terminateTimer);
+      delete this._terminateTimer;
+    }
+
     if (this._terminatingId !== presentationId) {
       // Requested presentation Id doesn't matched with the one in ACK.
       // Disconnect the control channel with error.
       DEBUG && log("TCPControlChannel - unmatched terminatingId: " + presentationId); // jshint ignore:line
       this.disconnect(Cr.NS_ERROR_FAILURE);
     }
 
     delete this._terminatingId;
+    if (this._pendingDisconnect) {
+      this._pendingDisconnect();
+    }
   },
 
   notifyReconnect: function(presentationId, url) {
     switch (this._direction) {
       case "receiver":
         this._presentationService.onSessionReconnect(this._deviceInfo,
                                                      url,
                                                      presentationId,
--- a/dom/presentation/provider/ReceiverStateMachine.jsm
+++ b/dom/presentation/provider/ReceiverStateMachine.jsm
@@ -78,18 +78,26 @@ var handlers = [
         break;
       default:
         debug("unexpected command: " + JSON.stringify(command));
         // ignore unexpected command
         break;
     }
   },
   function _closingHandler(stateMachine, command) {
-    // ignore every command in closing state.
-    DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
+    switch (command.type) {
+      case CommandType.DISCONNECT:
+        stateMachine.state = State.CLOSED;
+        stateMachine._notifyDisconnected(command.reason);
+        break;
+      default:
+        debug("unexpected command: " + JSON.stringify(command));
+        // ignore unexpected command
+        break;
+    }
   },
   function _closedHandler(stateMachine, command) {
     // ignore every command in closed state.
     DEBUG && debug("unexpected command: " + JSON.stringify(command)); // jshint ignore:line
   },
 ];
 
 function ReceiverStateMachine(channel) {
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -2,16 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* global Services, do_register_cleanup, do_test_pending */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const INFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
 const PROVIDER_CONTRACT_ID = "@mozilla.org/presentation-device/multicastdns-provider;1";
 const SD_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
 const UUID_CONTRACT_ID = "@mozilla.org/uuid-generator;1";
 const SERVER_CONTRACT_ID = "@mozilla.org/presentation/control-service;1";
@@ -23,16 +24,31 @@ const PREF_DEVICENAME= "dom.presentation
 const LATEST_VERSION = 1;
 const SERVICE_TYPE = "_presentation-ctrl._tcp";
 const versionAttr = Cc["@mozilla.org/hash-property-bag;1"]
                       .createInstance(Ci.nsIWritablePropertyBag2);
 versionAttr.setPropertyAsUint32("version", LATEST_VERSION);
 
 var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
 
+function sleep(aMs) {
+  let deferred = Promise.defer();
+
+  let timer = Cc["@mozilla.org/timer;1"]
+                .createInstance(Ci.nsITimer);
+
+  timer.initWithCallback({
+    notify: function () {
+      deferred.resolve();
+    },
+  }, aMs, timer.TYPE_ONE_SHOT);
+
+  return deferred.promise;
+}
+
 function MockFactory(aClass) {
   this._cls = aClass;
 }
 MockFactory.prototype = {
   createInstance: function(aOuter, aIID) {
     if (aOuter) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
@@ -208,20 +224,23 @@ function createDevice(host, port, servic
   device.domainName = domainName || "";
   device.attributes = attributes || versionAttr;
   return device;
 }
 
 function registerService() {
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
 
+  let deferred = Promise.defer();
+
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {},
     registerService: function(serviceInfo, listener) {
+      deferred.resolve();
       this.serviceRegistered++;
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           this.serviceUnregistered++;
         }.bind(this)
       };
     },
@@ -238,62 +257,79 @@ function registerService() {
   // Register
   provider.listener = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
                                            Ci.nsISupportsWeakReference]),
     addDevice: function(device) {},
     removeDevice: function(device) {},
     updateDevice: function(device) {},
   };
-  Assert.equal(mockObj.serviceRegistered, 1);
-  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  deferred.promise.then(function() {
+    Assert.equal(mockObj.serviceRegistered, 1);
+    Assert.equal(mockObj.serviceUnregistered, 0);
 
-  // Unregister
-  provider.listener = null;
-  Assert.equal(mockObj.serviceRegistered, 1);
-  Assert.equal(mockObj.serviceUnregistered, 1);
+    // Unregister
+    provider.listener = null;
+    Assert.equal(mockObj.serviceRegistered, 1);
+    Assert.equal(mockObj.serviceUnregistered, 1);
 
-  run_next_test();
+    run_next_test();
+  });
 }
 
 function noRegisterService() {
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
 
+  let deferred = Promise.defer();
+
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {},
     registerService: function(serviceInfo, listener) {
+      deferred.resolve();
       Assert.ok(false, "should not register service if not discoverable");
     },
     resolveService: function(serviceInfo, listener) {},
   };
 
   let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
 
   // Try register
   provider.listener = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
                                            Ci.nsISupportsWeakReference]),
     addDevice: function(device) {},
     removeDevice: function(device) {},
     updateDevice: function(device) {},
   };
-  provider.listener = null;
+
+  let race = Promise.race([
+    deferred.promise,
+    sleep(1000),
+  ]);
 
-  run_next_test();
+  race.then(() => {
+    provider.listener = null;
+
+    run_next_test();
+  });
 }
 
 function registerServiceDynamically() {
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
 
+  let deferred = Promise.defer();
+
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {},
     registerService: function(serviceInfo, listener) {
+      deferred.resolve();
       this.serviceRegistered++;
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           this.serviceUnregistered++;
         }.bind(this)
       };
     },
@@ -310,35 +346,39 @@ function registerServiceDynamically() {
   // Try Register
   provider.listener = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
                                            Ci.nsISupportsWeakReference]),
     addDevice: function(device) {},
     removeDevice: function(device) {},
     updateDevice: function(device) {},
   };
+
   Assert.equal(mockObj.serviceRegistered, 0);
   Assert.equal(mockObj.serviceUnregistered, 0);
 
   // Enable registration
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
-  Assert.equal(mockObj.serviceRegistered, 1);
-  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  deferred.promise.then(function() {
+    Assert.equal(mockObj.serviceRegistered, 1);
+    Assert.equal(mockObj.serviceUnregistered, 0);
 
-  // Disable registration
-  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
-  Assert.equal(mockObj.serviceRegistered, 1);
-  Assert.equal(mockObj.serviceUnregistered, 1);
+    // Disable registration
+    Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+    Assert.equal(mockObj.serviceRegistered, 1);
+    Assert.equal(mockObj.serviceUnregistered, 1);
 
-  // Try unregister
-  provider.listener = null;
-  Assert.equal(mockObj.serviceRegistered, 1);
-  Assert.equal(mockObj.serviceUnregistered, 1);
+    // Try unregister
+    provider.listener = null;
+    Assert.equal(mockObj.serviceRegistered, 1);
+    Assert.equal(mockObj.serviceUnregistered, 1);
 
-  run_next_test();
+    run_next_test();
+  });
 }
 
 function addDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
@@ -398,17 +438,16 @@ function handleSessionRequest() {
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
                                 SERVICE_TYPE);
   let mockSDObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
-      do_print('xxx start discovery');
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
@@ -657,30 +696,118 @@ function noAddDevice() {
 function ignoreIncompatibleDevice() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
   Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
                                 SERVICE_TYPE);
+
+  let deferred = Promise.defer();
+
   let mockSDObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
       listener.onServiceFound(createDevice("",
                                            0,
                                            mockDevice.serviceName,
                                            mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
       };
     },
     registerService: function(serviceInfo, listener) {
+      deferred.resolve();
+      listener.onServiceRegistered(createDevice("",
+                                                54321,
+                                                mockDevice.serviceName,
+                                                mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let mockServerObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
+    startServer: function() {
+      Services.tm.currentThread.dispatch(() => {
+        this.listener.onServerReady(this.port, this.certFingerprint);
+      }, Ci.nsIThread.DISPATCH_NORMAL);
+    },
+    sessionRequest: function() {},
+    close: function() {},
+    id: '',
+    version: LATEST_VERSION,
+    isCompatibleServer: function(version) {
+      return false;
+    },
+    port: 54321,
+    certFingerprint: 'mock-cert-fingerprint',
+    listener: null,
+  };
+
+  let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
+  let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = new TestPresentationDeviceListener();
+
+  // Register service
+  provider.listener = listener;
+
+  deferred.promise.then(function() {
+    Assert.equal(mockServerObj.id, mockDevice.host);
+
+    // Start discovery
+    Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+    Assert.equal(listener.count(), 0);
+
+    provider.listener = null;
+
+    run_next_test();
+  });
+}
+
+function ignoreSelfDevice() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                SERVICE_TYPE);
+
+  let deferred = Promise.defer();
+  let mockSDObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {
+      deferred.resolve();
       listener.onServiceRegistered(createDevice("",
                                                 0,
                                                 mockDevice.serviceName,
                                                 mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
       };
@@ -692,117 +819,51 @@ function ignoreIncompatibleDevice() {
                                               mockDevice.port,
                                               mockDevice.serviceName,
                                               mockDevice.serviceType));
     }
   };
 
   let mockServerObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
-    startServer: function() {},
+    startServer: function() {
+      Services.tm.currentThread.dispatch(() => {
+        this.listener.onServerReady(this.port, this.certFingerprint);
+      }, Ci.nsIThread.DISPATCH_NORMAL);
+    },
     sessionRequest: function() {},
     close: function() {},
     id: '',
     version: LATEST_VERSION,
     isCompatibleServer: function(version) {
-      return false;
+      return this.version === version;
     },
-    port: 0,
+    port: 54321,
+    certFingerprint: 'mock-cert-fingerprint',
     listener: null,
   };
 
   let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
   let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = new TestPresentationDeviceListener();
 
   // Register service
   provider.listener = listener;
-  Assert.equal(mockServerObj.id, mockDevice.host);
-
-  // Start discovery
-  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
-  Assert.equal(listener.count(), 0);
-
-  provider.listener = null;
-
-  run_next_test();
-}
-
-function ignoreSelfDevice() {
-  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
-  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+  deferred.promise.then(() => {
+    Assert.equal(mockServerObj.id, mockDevice.host);
 
-  let mockDevice = createDevice("device.local",
-                                12345,
-                                "service.name",
-                                SERVICE_TYPE);
-  let mockSDObj = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
-    startDiscovery: function(serviceType, listener) {
-      listener.onDiscoveryStarted(serviceType);
-      listener.onServiceFound(createDevice("",
-                                           0,
-                                           mockDevice.serviceName,
-                                           mockDevice.serviceType));
-      return {
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
-        cancel: function() {}
-      };
-    },
-    registerService: function(serviceInfo, listener) {
-      listener.onServiceRegistered(createDevice("",
-                                                0,
-                                                mockDevice.serviceName,
-                                                mockDevice.serviceType));
-      return {
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
-        cancel: function() {}
-      };
-    },
-    resolveService: function(serviceInfo, listener) {
-      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
-      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
-      listener.onServiceResolved(createDevice(mockDevice.host,
-                                              mockDevice.port,
-                                              mockDevice.serviceName,
-                                              mockDevice.serviceType));
-    }
-  };
+    // Start discovery
+    Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+    Assert.equal(listener.count(), 0);
 
-  let mockServerObj = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
-    startServer: function() {},
-    sessionRequest: function() {},
-    close: function() {},
-    id: '',
-    version: LATEST_VERSION,
-    isCompatibleServer: function(version) {
-      return this.version === version;
-    },
-    port: 0,
-    listener: null,
-  };
+    provider.listener = null;
 
-  let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj);
-  let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj);
-  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
-  let listener = new TestPresentationDeviceListener();
-
-  // Register service
-  provider.listener = listener;
-  Assert.equal(mockServerObj.id, mockDevice.host);
-
-  // Start discovery
-  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
-  Assert.equal(listener.count(), 0);
-
-  provider.listener = null;
-
-  run_next_test();
+    run_next_test();
+  });
 }
 
 function addDeviceDynamically() {
   Services.prefs.setBoolPref(PREF_DISCOVERY, false);
 
   let mockDevice = createDevice("device.local",
                                 12345,
                                 "service.name",
@@ -1085,32 +1146,37 @@ function serverClosed() {
 
   provider.listener = listener;
   Assert.equal(mockObj.serviceRegistered, 1);
   Assert.equal(mockObj.serviceUnregistered, 0);
   Assert.equal(listener.devices.length, 1);
 
   let serverListener = provider.QueryInterface(Ci.nsIPresentationControlServerListener);
   let randomPort = 9527;
-  serverListener.onPortChange(randomPort);
+  serverListener.onServerReady(randomPort, '');
 
   Assert.equal(mockObj.serviceRegistered, 2);
   Assert.equal(mockObj.serviceUnregistered, 1);
   Assert.equal(listener.devices.length, 1);
 
   // Unregister
   provider.listener = null;
   Assert.equal(mockObj.serviceRegistered, 2);
   Assert.equal(mockObj.serviceUnregistered, 2);
   Assert.equal(listener.devices.length, 1);
 
   run_next_test();
 }
 
 function run_test() {
+  // Need profile dir to store the key / cert
+  do_get_profile();
+  // Ensure PSM is initialized
+  Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
   let infoHook = new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo);
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref(PREF_DISCOVERY);
     Services.prefs.clearUserPref(PREF_DISCOVERABLE);
   });
 
   add_test(registerService);
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -54,19 +54,24 @@ const OFFER_PORT = 123;
 // controller's presentation channel description
 const ANSWER_ADDRESS = '192.168.321.321';
 const ANSWER_PORT = 321;
 
 function loopOfferAnser() {
   pcs = Cc["@mozilla.org/presentation/control-service;1"]
         .createInstance(Ci.nsIPresentationControlService);
   pcs.id = 'controllerID';
-  pcs.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
+  pcs.listener = {
+    onServerReady: function() {
+      testPresentationServer();
+    }
+  };
 
-  testPresentationServer();
+  // First run with TLS enabled.
+  pcs.startServer(true, PRESENTER_CONTROL_CHANNEL_PORT);
 }
 
 
 function testPresentationServer() {
   let yayFuncs = makeJointSuccess(['controllerControlChannelClose',
                                    'presenterControlChannelClose',
                                    'controllerControlChannelReconnect',
                                    'presenterControlChannelReconnect']);
@@ -134,16 +139,17 @@ function testPresentationServer() {
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlServerListener]),
   };
 
   let presenterDeviceInfo = {
     id: 'presentatorID',
     address: '127.0.0.1',
     port: PRESENTER_CONTROL_CHANNEL_PORT,
+    certFingerprint: pcs.certFingerprint,
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
   };
 
   let controllerControlChannel = pcs.connect(presenterDeviceInfo);
 
   controllerControlChannel.listener = {
     status: 'created',
     onOffer: function(offer) {
@@ -197,67 +203,16 @@ function testPresentationServer() {
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function terminateRequest() {
   let yayFuncs = makeJointSuccess(['controllerControlChannelConnected',
                                    'controllerControlChannelDisconnected',
-                                   'presenterControlChannelDisconnected']);
-  let controllerControlChannel;
-
-  pcs.listener = {
-    onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiverj) {
-      controllerControlChannel = controlChannel;
-      Assert.equal(deviceInfo.id, pcs.id, 'expected device id');
-      Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
-      Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
-      Assert.equal(isFromReceiver, false, 'expected request from controller');
-
-      controllerControlChannel.listener = {
-        notifyConnected: function() {
-          Assert.ok(true, 'control channel notify connected');
-          yayFuncs.controllerControlChannelConnected();
-        },
-        notifyDisconnected: function(aReason) {
-          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify disconncted');
-          yayFuncs.controllerControlChannelDisconnected();
-        },
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
-      };
-    },
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
-  };
-
-  let presenterDeviceInfo = {
-    id: 'presentatorID',
-    address: '127.0.0.1',
-    port: PRESENTER_CONTROL_CHANNEL_PORT,
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
-  };
-
-  let presenterControlChannel = pcs.connect(presenterDeviceInfo);
-
-  presenterControlChannel.listener = {
-    notifyConnected: function() {
-      presenterControlChannel.terminate('testPresentationId', 'http://example.com');
-      presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
-    },
-    notifyDisconnected: function(aReason) {
-      Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify disconnected');
-      yayFuncs.presenterControlChannelDisconnected();
-    },
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
-  };
-}
-
-function terminateRequest() {
-  let yayFuncs = makeJointSuccess(['controllerControlChannelConnected',
-                                   'controllerControlChannelDisconnected',
                                    'presenterControlChannelDisconnected',
                                    'terminatedByController',
                                    'terminatedByReceiver']);
   let controllerControlChannel;
   let terminatePhase = 'controller';
 
   pcs.listener = {
     onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiver) {
@@ -294,16 +249,17 @@ function terminateRequest() {
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
   };
 
   let presenterDeviceInfo = {
     id: 'presentatorID',
     address: '127.0.0.1',
     port: PRESENTER_CONTROL_CHANNEL_PORT,
+    certFingerprint: pcs.certFingerprint,
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
   };
 
   let presenterControlChannel = pcs.connect(presenterDeviceInfo);
 
   presenterControlChannel.listener = {
     notifyConnected: function() {
       presenterControlChannel.terminate('testPresentationId');
@@ -346,16 +302,17 @@ function terminateRequestAbnormal() {
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
   };
 
   let presenterDeviceInfo = {
     id: 'presentatorID',
     address: '127.0.0.1',
     port: PRESENTER_CONTROL_CHANNEL_PORT,
+    certFingerprint: pcs.certFingerprint,
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
   };
 
   let presenterControlChannel = pcs.connect(presenterDeviceInfo);
 
   presenterControlChannel.listener = {
     notifyConnected: function() {
       presenterControlChannel.terminate('testPresentationId');
@@ -365,43 +322,49 @@ function terminateRequestAbnormal() {
       yayFuncs.presenterControlChannelDisconnected();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
   pcs.listener = {
-    onPortChange: function(aPort) {
+    onServerReady: function(aPort, aCertFingerprint) {
       Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid');
       pcs.close();
       run_next_test();
     },
   };
 
   // Let the server socket restart automatically.
   Services.io.offline = true;
   Services.io.offline = false;
 }
 
 function oneMoreLoop() {
   try {
-    pcs.startServer(PRESENTER_CONTROL_CHANNEL_PORT);
-    testPresentationServer();
+    pcs.listener = {
+      onServerReady: function() {
+        testPresentationServer();
+      }
+    };
+
+    // Second run with TLS disabled.
+    pcs.startServer(false, PRESENTER_CONTROL_CHANNEL_PORT);
   } catch (e) {
     Assert.ok(false, 'TCP presentation init fail:' + e);
     run_next_test();
   }
 }
 
 
 function shutdown()
 {
   pcs.listener = {
-    onPortChange: function(aPort) {
+    onServerReady: function(aPort, aCertFingerprint) {
       Assert.ok(false, 'TCPPresentationServer port changed');
     },
   };
   pcs.close();
   Assert.equal(pcs.port, 0, "TCPPresentationServer closed");
   run_next_test();
 }
 
@@ -415,16 +378,21 @@ add_test(loopOfferAnser);
 add_test(terminateRequest);
 add_test(terminateRequestAbnormal);
 add_test(setOffline);
 add_test(changeCloseReason);
 add_test(oneMoreLoop);
 add_test(shutdown);
 
 function run_test() {
+  // Need profile dir to store the key / cert
+  do_get_profile();
+  // Ensure PSM is initialized
+  Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
   Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true);
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("dom.presentation.tcp_server.debug");
   });
 
   run_next_test();
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5269,16 +5269,17 @@ pref("dom.beforeAfterKeyboardEvent.enabl
 
 // Presentation API
 pref("dom.presentation.enabled", false);
 pref("dom.presentation.tcp_server.debug", false);
 pref("dom.presentation.discovery.enabled", false);
 pref("dom.presentation.discovery.legacy.enabled", false);
 pref("dom.presentation.discovery.timeout_ms", 10000);
 pref("dom.presentation.discoverable", false);
+pref("dom.presentation.discoverable.encrypted", true);
 pref("dom.presentation.session_transport.data_channel.enable", false);
 
 #ifdef XP_MACOSX
 #if !defined(RELEASE_BUILD) || defined(DEBUG)
 // In non-release builds we crash by default on insecure text input (when a
 // password editor has focus but secure event input isn't enabled).  The
 // following pref, when turned on, disables this behavior.  See bug 1188425.
 pref("intl.allow-insecure-text-input", false);