Bug 1360167 - Move dll loading from WindowsUIUtils::GetInTabletMode off of the main thread draft
authorKirk Steuber <ksteuber@mozilla.com>
Tue, 25 Jul 2017 09:53:57 -0700
changeset 615336 90c80f3c0ee37b5506647dca6930c14e11d2f45e
parent 614544 462d7561089c98e33382384896434861ad7bc491
child 639148 dc06054e052898c20737d1dd6f9f1e07825dbcf2
push id70325
push userksteuber@mozilla.com
push dateTue, 25 Jul 2017 20:29:23 +0000
bugs1360167
milestone56.0a1
Bug 1360167 - Move dll loading from WindowsUIUtils::GetInTabletMode off of the main thread WindowsUIUtils::GetInTabletMode blocks the UI thread during startup while the dll needed to determine if Windows is using Tablet mode is loaded. This patch moves that loading process off of the main thread, with a synchronous fallback if the worker thread is too slow. MozReview-Commit-ID: 7B5ZAmPPEUc
widget/windows/WindowsUIUtils.cpp
widget/windows/WindowsUIUtils.h
--- a/widget/windows/WindowsUIUtils.cpp
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -16,16 +16,18 @@
 #include "nsIDocShell.h"
 #include "nsIAppShellService.h"
 #include "nsAppShellCID.h"
 #include "nsIXULWindow.h"
 #include "mozilla/Services.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsString.h"
 #include "nsIWidget.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsThreadUtils.h"
 
 /* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it until it's fixed. */
 #ifndef __MINGW32__
 
 #include <windows.ui.viewmanagement.h>
 
 #pragma comment(lib, "runtimeobject.lib")
 
@@ -87,44 +89,26 @@ IUIViewSettingsInterop : public IInspect
 {
 public:
   virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid, void **ppv) = 0;
 };
 #endif
 
 #endif
 
-WindowsUIUtils::WindowsUIUtils() :
-  mInTabletMode(eTabletModeUnknown)
-{
-}
-
-WindowsUIUtils::~WindowsUIUtils()
-{
-}
+// The amount of time, in milliseconds, that the dll load thread will remain
+// allocated after it is used. This value chosen because to match other uses
+// of LazyIdleThread.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
 
-/*
- * Implement the nsISupports methods...
- */
-NS_IMPL_ISUPPORTS(WindowsUIUtils,
-                  nsIWindowsUIUtils)
-
-NS_IMETHODIMP
-WindowsUIUtils::GetInTabletMode(bool* aResult)
+static nsresult
+GetTabletModeState(TabletModeState* aResult)
 {
-  if (mInTabletMode == eTabletModeUnknown) {
-    UpdateTabletModeState();
-  }
-  *aResult = mInTabletMode == eTabletModeOn;
-  return NS_OK;
-}
+  *aResult = eTabletModeUnknown;
 
-NS_IMETHODIMP
-WindowsUIUtils::UpdateTabletModeState()
-{
 #ifndef __MINGW32__
   if (!IsWin10OrLater()) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
   nsCOMPtr<nsIXULWindow> hiddenWindow;
 
@@ -158,23 +142,154 @@ WindowsUIUtils::UpdateTabletModeState()
       &uiViewSettingsInterop);
   if (SUCCEEDED(hr)) {
     ComPtr<IUIViewSettings> uiViewSettings;
     hr = uiViewSettingsInterop->GetForWindow(winPtr, IID_PPV_ARGS(&uiViewSettings));
     if (SUCCEEDED(hr)) {
       UserInteractionMode mode;
       hr = uiViewSettings->get_UserInteractionMode(&mode);
       if (SUCCEEDED(hr)) {
-        TabletModeState oldTabletModeState = mInTabletMode;
-        mInTabletMode = (mode == UserInteractionMode_Touch) ? eTabletModeOn : eTabletModeOff;
-        if (mInTabletMode != oldTabletModeState) {
-          nsCOMPtr<nsIObserverService> observerService =
-            mozilla::services::GetObserverService();
-          observerService->NotifyObservers(nullptr, "tablet-mode-change",
-            ((mInTabletMode == eTabletModeOn) ? u"tablet-mode" : u"normal-mode"));
-        }
+        *aResult = (mode == UserInteractionMode_Touch) ? eTabletModeOn : eTabletModeOff;
       }
     }
   }
 #endif
 
   return NS_OK;
 }
+
+// This class passes the tablet mode from a worker thread back to the main thread
+class LoadDllCompleteEvent final : public nsIRunnable
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  LoadDllCompleteEvent(WindowsUIUtils* aTarget)
+    : mTarget(aTarget)
+    , mTabletModeState(eTabletModeUnknown)
+  {
+  }
+
+  NS_IMETHODIMP Dispatch(TabletModeState aTabletMode)
+  {
+    mTabletModeState = aTabletMode;
+    return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+  }
+
+  NS_IMETHODIMP Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "LoadDllCompleteEvent::Run should be on the main thread");
+
+    if (mTarget->mInTabletMode == eTabletModeUnknown) {
+      mTarget->SetTabletModeState(mTabletModeState);
+    }
+    return NS_OK;
+  }
+
+private:
+  virtual ~LoadDllCompleteEvent() = default;
+
+protected:
+  TabletModeState mTabletModeState;
+  WindowsUIUtils* mTarget;
+};
+
+NS_IMPL_ISUPPORTS(LoadDllCompleteEvent, nsIRunnable);
+
+// This class runs on the worker thread to determine the current tablet mode
+class LoadDllRunnable final : public nsIRunnable
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  LoadDllRunnable(LoadDllCompleteEvent* aCompleteEvent)
+    : mCompleteEvent(aCompleteEvent)
+  {
+  }
+
+  NS_IMETHODIMP Run() override
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "LoadDllRunnable::Run should be off the main thread");
+
+    TabletModeState newState;
+    nsresult rv = GetTabletModeState(&newState);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return mCompleteEvent->Dispatch(newState);
+  }
+
+private:
+  virtual ~LoadDllRunnable() = default;
+
+protected:
+  RefPtr<LoadDllCompleteEvent> mCompleteEvent;
+};
+
+NS_IMPL_ISUPPORTS(LoadDllRunnable, nsIRunnable);
+
+WindowsUIUtils::WindowsUIUtils()
+  : mInTabletMode(eTabletModeUnknown)
+  , mIdleThread(nullptr)
+{
+#ifndef __MINGW32__
+  if (IsWin10OrLater()) {
+    RefPtr<LoadDllCompleteEvent> completeEvent = new LoadDllCompleteEvent(this);
+    nsCOMPtr<nsIRunnable> preloadDll = new LoadDllRunnable(completeEvent);
+
+    mIdleThread = new mozilla::LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+                                              NS_LITERAL_CSTRING("WindowsUIUtils"));
+    if (mIdleThread) {
+      mIdleThread->Dispatch(preloadDll, NS_DISPATCH_NORMAL);
+    }
+  }
+#endif
+}
+
+WindowsUIUtils::~WindowsUIUtils()
+{
+}
+
+/*
+ * Implement the nsISupports methods...
+ */
+NS_IMPL_ISUPPORTS(WindowsUIUtils,
+                  nsIWindowsUIUtils)
+
+NS_IMETHODIMP
+WindowsUIUtils::GetInTabletMode(bool* aResult)
+{
+  if (mInTabletMode == eTabletModeUnknown) {
+    UpdateTabletModeState();
+  }
+  *aResult = mInTabletMode == eTabletModeOn;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::UpdateTabletModeState()
+{
+#ifndef __MINGW32__
+  if (IsWin10OrLater()) {
+    TabletModeState newState;
+    nsresult rv = GetTabletModeState(&newState);
+    NS_ENSURE_SUCCESS(rv, rv);
+    SetTabletModeState(newState);
+  }
+#endif
+
+  return NS_OK;
+}
+
+void
+WindowsUIUtils::SetTabletModeState(TabletModeState aNewState)
+{
+  if (aNewState == eTabletModeUnknown) {
+    return;
+  }
+
+  TabletModeState oldTabletModeState = mInTabletMode;
+  mInTabletMode = aNewState;
+  if (mInTabletMode != oldTabletModeState) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->NotifyObservers(nullptr, "tablet-mode-change",
+      ((mInTabletMode == eTabletModeOn) ? u"tablet-mode" : u"normal-mode"));
+  }
+}
--- a/widget/windows/WindowsUIUtils.h
+++ b/widget/windows/WindowsUIUtils.h
@@ -2,28 +2,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_widget_WindowsUIUtils_h__
 #define mozilla_widget_WindowsUIUtils_h__
 
 #include "nsIWindowsUIUtils.h"
+#include "mozilla/LazyIdleThread.h"
 
 enum TabletModeState {
   eTabletModeUnknown = 0,
   eTabletModeOff,
   eTabletModeOn
 };
 
+class LoadDllCompleteEvent;
+
 class WindowsUIUtils final : public nsIWindowsUIUtils {
+  friend class LoadDllCompleteEvent;
+
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIWINDOWSUIUTILS
 
   WindowsUIUtils();
 protected:
   ~WindowsUIUtils();
+  void SetTabletModeState(TabletModeState aNewState);
 
   TabletModeState mInTabletMode;
+
+  // Thread to preload dll
+  RefPtr<mozilla::LazyIdleThread> mIdleThread;
 };
 
 #endif // mozilla_widget_WindowsUIUtils_h__