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
--- 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__