Bug 1153134 - Part 3, retry startServer after timeout. r=junior.
authorShih-Chiang Chien <schien@mozilla.com>
Tue, 02 Aug 2016 10:48:22 +0800
changeset 398692 c25ff855651a568e027d7190a07890ba14cee6f5
parent 398691 99869310b0ecaaa03693ab0438c0ca01c248003a
child 398693 626f8cc8b7bc7d983cfa378ffd4ad5753c142225
push id25600
push userbmo:tchiovoloni@mozilla.com
push dateTue, 09 Aug 2016 16:33:05 +0000
reviewersjunior
bugs1153134
milestone51.0a1
Bug 1153134 - Part 3, retry startServer after timeout. r=junior. MozReview-Commit-ID: 94cyXVPf5FN
dom/presentation/provider/DisplayDeviceProvider.cpp
dom/presentation/provider/DisplayDeviceProvider.h
dom/presentation/provider/MulticastDNSDeviceProvider.cpp
dom/presentation/provider/MulticastDNSDeviceProvider.h
dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
modules/libpref/init/all.js
--- a/dom/presentation/provider/DisplayDeviceProvider.cpp
+++ b/dom/presentation/provider/DisplayDeviceProvider.cpp
@@ -20,16 +20,17 @@
 
 static mozilla::LazyLogModule gDisplayDeviceProviderLog("DisplayDeviceProvider");
 
 #define LOG(format) MOZ_LOG(gDisplayDeviceProviderLog, mozilla::LogLevel::Debug, format)
 
 #define DISPLAY_CHANGED_NOTIFICATION "display-changed"
 #define DEFAULT_CHROME_FEATURES_PREF "toolkit.defaultChromeFeatures"
 #define CHROME_REMOTE_URL_PREF       "b2g.multiscreen.chrome_remote_url"
+#define PREF_PRESENTATION_DISCOVERABLE_RETRY_MS "dom.presentation.discoverable.retry_ms"
 
 namespace mozilla {
 namespace dom {
 namespace presentation {
 
 /**
  * This wrapper is used to break circular-reference problem.
  */
@@ -190,16 +191,22 @@ DisplayDeviceProvider::Init()
 {
   // Provider must be initialized only once.
   if (mInitialized) {
     return NS_OK;
   }
 
   nsresult rv;
 
+  mServerRetryMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERABLE_RETRY_MS);
+  mServerRetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   MOZ_ASSERT(obs);
 
   obs->AddObserver(this, DISPLAY_CHANGED_NOTIFICATION, false);
 
   mDevice = new HDMIDisplayDevice(this);
 
   mWrappedListener = new DisplayDeviceProviderWrappedListener();
@@ -234,16 +241,18 @@ DisplayDeviceProvider::Uninit()
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, DISPLAY_CHANGED_NOTIFICATION);
   }
 
   // Remove device from device manager when the provider is uninit
   RemoveExternalScreen();
 
+  AbortServerRetry();
+
   mInitialized = false;
   mWrappedListener->SetListener(nullptr);
   return NS_OK;
 }
 
 nsresult
 DisplayDeviceProvider::StartTCPService()
 {
@@ -270,26 +279,37 @@ DisplayDeviceProvider::StartTCPService()
     return NS_OK;
   }
 
   rv = mPresentationService->SetListener(mWrappedListener);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  AbortServerRetry();
+
   // 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;
 }
 
+void
+DisplayDeviceProvider::AbortServerRetry()
+{
+  if (mIsServerRetrying) {
+    mIsServerRetrying = false;
+    mServerRetryTimer->Cancel();
+  }
+}
+
 nsresult
 DisplayDeviceProvider::AddExternalScreen()
 {
   MOZ_ASSERT(mDeviceListener);
 
   nsresult rv;
   nsCOMPtr<nsIPresentationDeviceListener> listener;
   rv = GetListener(getter_AddRefs(listener));
@@ -376,18 +396,18 @@ DisplayDeviceProvider::OnServerReady(uin
 
 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));
+    mIsServerRetrying = true;
+    mServerRetryTimer->Init(this, mServerRetryMs, nsITimer::TYPE_ONE_SHOT);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DisplayDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
                                       const nsAString& aUrl,
@@ -499,16 +519,26 @@ DisplayDeviceProvider::Observe(nsISuppor
     displayInfo->GetId(&type);
 
     if (type == DisplayType::DISPLAY_EXTERNAL) {
       nsresult rv = isConnected ? AddExternalScreen() : RemoveExternalScreen();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
+  } else if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
+    nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
+    if (!timer) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    if (timer == mServerRetryTimer) {
+      mIsServerRetrying = false;
+      StartTCPService();
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 DisplayDeviceProvider::Connect(HDMIDisplayDevice* aDevice,
                                nsIPresentationControlChannel** aControlChannel)
--- a/dom/presentation/provider/DisplayDeviceProvider.h
+++ b/dom/presentation/provider/DisplayDeviceProvider.h
@@ -11,16 +11,17 @@
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMWindow.h"
 #include "nsIDisplayInfo.h"
 #include "nsIObserver.h"
 #include "nsIPresentationDeviceProvider.h"
 #include "nsIPresentationLocalDevice.h"
 #include "nsIPresentationControlService.h"
+#include "nsITimer.h"
 #include "nsIWindowWatcher.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 namespace presentation {
@@ -101,29 +102,35 @@ private:
   nsresult Init();
   nsresult Uninit();
 
   nsresult AddExternalScreen();
   nsresult RemoveExternalScreen();
 
   nsresult StartTCPService();
 
+  void AbortServerRetry();
+
   // Now support HDMI display only and there should be only one HDMI display.
   nsCOMPtr<nsIPresentationLocalDevice> mDevice = nullptr;
   // weak pointer
   // PresentationDeviceManager (mDeviceListener) hold strong pointer to
   // DisplayDeviceProvider. Use nsWeakPtr to avoid reference cycle.
   nsWeakPtr mDeviceListener = nullptr;
   nsCOMPtr<nsIPresentationControlService> mPresentationService;
   // Used to prevent reference cycle between DisplayDeviceProvider and
   // TCPPresentationServer.
   RefPtr<DisplayDeviceProviderWrappedListener> mWrappedListener;
 
   bool mInitialized = false;
   uint16_t mPort;
+
+  bool mIsServerRetrying = false;
+  uint32_t mServerRetryMs;
+  nsCOMPtr<nsITimer> mServerRetryTimer;
 };
 
 } // mozilla
 } // dom
 } // presentation
 
 #endif // mozilla_dom_presentation_provider_DisplayDeviceProvider_h
 
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -19,16 +19,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_DISCOVERABLE_RETRY_MS "dom.presentation.discoverable.retry_ms"
 #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");
 
@@ -140,22 +141,27 @@ MulticastDNSDeviceProvider::Init()
     return rv;
   }
 
   mDiscoveryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  mServerRetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    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);
+  mServerRetryMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERABLE_RETRY_MS);
   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);
   }
@@ -226,39 +232,52 @@ MulticastDNSDeviceProvider::StartServer(
   if (servicePort) {
     return RegisterMDNSService();
   }
 
   if (NS_WARN_IF(NS_FAILED(rv = mPresentationService->SetListener(mWrappedListener)))) {
     return rv;
   }
 
+  AbortServerRetry();
+
   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);
 
+  AbortServerRetry();
+
   if (mPresentationService) {
     mPresentationService->SetListener(nullptr);
     mPresentationService->Close();
   }
 
   return NS_OK;
 }
 
+void
+MulticastDNSDeviceProvider::AbortServerRetry()
+{
+  if (mIsServerRetrying) {
+    mIsServerRetrying = false;
+    mServerRetryTimer->Cancel();
+  }
+}
+
 nsresult
 MulticastDNSDeviceProvider::RegisterMDNSService()
 {
   LOG_I("RegisterMDNSService: %s", mServiceName.get());
 
   if (!mDiscoverable) {
     return NS_OK;
   }
@@ -940,18 +959,18 @@ 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));
+    mIsServerRetrying = true;
+    mServerRetryTimer->Init(this, mServerRetryMs, nsITimer::TYPE_ONE_SHOT);
   }
 
   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)
@@ -1074,17 +1093,27 @@ MulticastDNSDeviceProvider::Observe(nsIS
       OnDiscoverableChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE));
     } else if (data.EqualsLiteral(PREF_PRESENTATION_DEVICE_NAME)) {
       nsAdoptingCString newServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
       if (!mServiceName.Equals(newServiceName)) {
         OnServiceNameChanged(newServiceName);
       }
     }
   } else if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
-    StopDiscovery(NS_OK);
+    nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
+    if (!timer) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    if (timer == mDiscoveryTimer) {
+      StopDiscovery(NS_OK);
+    } else if (timer == mServerRetryTimer) {
+      mIsServerRetrying = false;
+      StartServer();
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 MulticastDNSDeviceProvider::OnDiscoveryChanged(bool aEnabled)
 {
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -146,16 +146,17 @@ private:
     bool Equals(const RefPtr<Device>& aA, const RefPtr<Device>& aB) const {
       return aA->Address() == aB->Address();
     }
   };
 
   virtual ~MulticastDNSDeviceProvider();
   nsresult StartServer();
   nsresult StopServer();
+  void AbortServerRetry();
   nsresult RegisterMDNSService();
   nsresult UnregisterMDNSService(nsresult aReason);
   nsresult StopDiscovery(nsresult aReason);
   nsresult Connect(Device* aDevice,
                    nsIPresentationControlChannel** aRetVal);
   bool IsCompatibleServer(nsIDNSServiceInfo* aServiceInfo);
 
   // device manipulation
@@ -204,16 +205,19 @@ private:
 
   bool mDiscoveryEnabled = false;
   bool mIsDiscovering = false;
   uint32_t mDiscoveryTimeoutMs;
   nsCOMPtr<nsITimer> mDiscoveryTimer;
 
   bool mDiscoverable = false;
   bool mDiscoverableEncrypted = false;
+  bool mIsServerRetrying = false;
+  uint32_t mServerRetryMs;
+  nsCOMPtr<nsITimer> mServerRetryTimer;
 
   nsCString mServiceName;
   nsCString mRegisteredName;
 };
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -1161,16 +1161,72 @@ function serverClosed() {
   provider.listener = null;
   Assert.equal(mockObj.serviceRegistered, 2);
   Assert.equal(mockObj.serviceUnregistered, 2);
   Assert.equal(listener.devices.length, 1);
 
   run_next_test();
 }
 
+function serverRetry() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let isRetrying = false;
+
+  let mockSDObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {},
+    registerService: function(serviceInfo, listener) {
+      Assert.ok(isRetrying, "register service after retrying startServer");
+      provider.listener = null;
+      run_next_test();
+    },
+    resolveService: function(serviceInfo, listener) {}
+  };
+
+  let mockServerObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]),
+    startServer: function(encrypted, port) {
+      if (!isRetrying) {
+        isRetrying = true;
+        Services.tm.currentThread.dispatch(() => {
+          this.listener.onServerStopped(Cr.NS_ERROR_FAILURE);
+        }, Ci.nsIThread.DISPATCH_NORMAL);
+      } else {
+        this.port = 54321;
+        Services.tm.currentThread.dispatch(() => {
+          this.listener.onServerReady(this.port, this.certFingerprint);
+        }, Ci.nsIThread.DISPATCH_NORMAL);
+      }
+    },
+    sessionRequest: function() {},
+    close: function() {},
+    id: '',
+    version: LATEST_VERSION,
+    port: 0,
+    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 = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+    onSessionRequest: function(device, url, presentationId, controlChannel) {}
+  };
+
+  provider.listener = listener;
+}
+
 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);
 
@@ -1188,11 +1244,12 @@ function run_test() {
   add_test(handleOnSessionRequestFromUnknownDevice);
   add_test(noAddDevice);
   add_test(ignoreIncompatibleDevice);
   add_test(ignoreSelfDevice);
   add_test(addDeviceDynamically);
   add_test(updateDevice);
   add_test(diffDiscovery);
   add_test(serverClosed);
+  add_test(serverRetry);
 
   run_next_test();
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5270,16 +5270,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.discoverable.retry_ms", 5000);
 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);