summaryrefslogtreecommitdiffstats
path: root/widget/windows
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /widget/windows
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows')
-rw-r--r--widget/windows/AudioSession.cpp346
-rw-r--r--widget/windows/AudioSession.h19
-rw-r--r--widget/windows/CheckInvariantWrapper.h78
-rw-r--r--widget/windows/CompositorWidgetChild.cpp114
-rw-r--r--widget/windows/CompositorWidgetChild.h62
-rw-r--r--widget/windows/CompositorWidgetParent.cpp232
-rw-r--r--widget/windows/CompositorWidgetParent.h92
-rw-r--r--widget/windows/DirectManipulationOwner.cpp722
-rw-r--r--widget/windows/DirectManipulationOwner.h54
-rw-r--r--widget/windows/GfxInfo.cpp2082
-rw-r--r--widget/windows/GfxInfo.h105
-rw-r--r--widget/windows/IEnumFE.cpp139
-rw-r--r--widget/windows/IEnumFE.h88
-rw-r--r--widget/windows/IMMHandler.cpp2408
-rw-r--r--widget/windows/IMMHandler.h427
-rw-r--r--widget/windows/InProcessWinCompositorWidget.cpp368
-rw-r--r--widget/windows/InProcessWinCompositorWidget.h111
-rw-r--r--widget/windows/InputDeviceUtils.cpp61
-rw-r--r--widget/windows/InputDeviceUtils.h26
-rw-r--r--widget/windows/JumpListBuilder.cpp818
-rw-r--r--widget/windows/JumpListBuilder.h102
-rw-r--r--widget/windows/KeyboardLayout.cpp5442
-rw-r--r--widget/windows/KeyboardLayout.h1156
-rw-r--r--widget/windows/LSPAnnotator.cpp135
-rw-r--r--widget/windows/LegacyJumpListBuilder.cpp647
-rw-r--r--widget/windows/LegacyJumpListBuilder.h71
-rw-r--r--widget/windows/LegacyJumpListItem.cpp559
-rw-r--r--widget/windows/LegacyJumpListItem.h133
-rw-r--r--widget/windows/MediaKeysEventSourceFactory.cpp20
-rw-r--r--widget/windows/OSKInputPaneManager.cpp101
-rw-r--r--widget/windows/OSKInputPaneManager.h24
-rw-r--r--widget/windows/OSKTabTipManager.cpp112
-rw-r--r--widget/windows/OSKTabTipManager.h22
-rw-r--r--widget/windows/OSKVRManager.cpp35
-rw-r--r--widget/windows/OSKVRManager.h22
-rw-r--r--widget/windows/PCompositorWidget.ipdl49
-rw-r--r--widget/windows/PlatformWidgetTypes.ipdlh34
-rw-r--r--widget/windows/RemoteBackbuffer.cpp713
-rw-r--r--widget/windows/RemoteBackbuffer.h95
-rw-r--r--widget/windows/ScreenHelperWin.cpp157
-rw-r--r--widget/windows/ScreenHelperWin.h26
-rw-r--r--widget/windows/ShellHeaderOnlyUtils.h183
-rw-r--r--widget/windows/SystemStatusBar.cpp339
-rw-r--r--widget/windows/SystemStatusBar.h33
-rw-r--r--widget/windows/TSFTextStore.cpp7513
-rw-r--r--widget/windows/TSFTextStore.h1161
-rw-r--r--widget/windows/TaskbarPreview.cpp413
-rw-r--r--widget/windows/TaskbarPreview.h132
-rw-r--r--widget/windows/TaskbarPreviewButton.cpp137
-rw-r--r--widget/windows/TaskbarPreviewButton.h47
-rw-r--r--widget/windows/TaskbarTabPreview.cpp344
-rw-r--r--widget/windows/TaskbarTabPreview.h70
-rw-r--r--widget/windows/TaskbarWindowPreview.cpp326
-rw-r--r--widget/windows/TaskbarWindowPreview.h85
-rw-r--r--widget/windows/ToastNotification.cpp915
-rw-r--r--widget/windows/ToastNotification.h83
-rw-r--r--widget/windows/ToastNotificationHandler.cpp1167
-rw-r--r--widget/windows/ToastNotificationHandler.h162
-rw-r--r--widget/windows/ToastNotificationHeaderOnlyUtils.h155
-rw-r--r--widget/windows/UrlmonHeaderOnlyUtils.h76
-rw-r--r--widget/windows/WidgetTraceEvent.cpp121
-rw-r--r--widget/windows/WinCompositorWidget.cpp105
-rw-r--r--widget/windows/WinCompositorWidget.h103
-rw-r--r--widget/windows/WinCompositorWindowThread.cpp294
-rw-r--r--widget/windows/WinCompositorWindowThread.h67
-rw-r--r--widget/windows/WinEventObserver.cpp223
-rw-r--r--widget/windows/WinEventObserver.h115
-rw-r--r--widget/windows/WinHeaderOnlyUtils.h820
-rw-r--r--widget/windows/WinIMEHandler.cpp1081
-rw-r--r--widget/windows/WinIMEHandler.h247
-rw-r--r--widget/windows/WinMessages.h93
-rw-r--r--widget/windows/WinModifierKeyState.h59
-rw-r--r--widget/windows/WinMouseScrollHandler.cpp1634
-rw-r--r--widget/windows/WinMouseScrollHandler.h567
-rw-r--r--widget/windows/WinPointerEvents.cpp181
-rw-r--r--widget/windows/WinPointerEvents.h75
-rw-r--r--widget/windows/WinRegistry.cpp325
-rw-r--r--widget/windows/WinRegistry.h235
-rw-r--r--widget/windows/WinTaskbar.cpp493
-rw-r--r--widget/windows/WinTaskbar.h46
-rw-r--r--widget/windows/WinTextEventDispatcherListener.cpp68
-rw-r--r--widget/windows/WinTextEventDispatcherListener.h50
-rw-r--r--widget/windows/WinUtils.cpp2107
-rw-r--r--widget/windows/WinUtils.h684
-rw-r--r--widget/windows/WinWindowOcclusionTracker.cpp1471
-rw-r--r--widget/windows/WinWindowOcclusionTracker.h333
-rw-r--r--widget/windows/WindowHook.cpp113
-rw-r--r--widget/windows/WindowHook.h76
-rw-r--r--widget/windows/WindowsConsole.cpp53
-rw-r--r--widget/windows/WindowsConsole.h16
-rw-r--r--widget/windows/WindowsEMF.cpp94
-rw-r--r--widget/windows/WindowsEMF.h106
-rw-r--r--widget/windows/WindowsEventLog.h99
-rw-r--r--widget/windows/WindowsSMTCProvider.cpp716
-rw-r--r--widget/windows/WindowsSMTCProvider.h128
-rw-r--r--widget/windows/WindowsUIUtils.cpp809
-rw-r--r--widget/windows/WindowsUIUtils.h50
-rw-r--r--widget/windows/components.conf220
-rw-r--r--widget/windows/docs/blocklist.rst347
-rw-r--r--widget/windows/docs/index.rst9
-rw-r--r--widget/windows/docs/windows-pointing-device/apple_vision.jpgbin0 -> 8013 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/apple_vision_user.webpbin0 -> 18126 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/index.rst1384
-rw-r--r--widget/windows/docs/windows-pointing-device/mouse.jpgbin0 -> 74248 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/touch_media_queries.pngbin0 -> 45966 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/touchpad.jpgbin0 -> 8055 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/touchscreen.jpgbin0 -> 14494 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/trackpoint.jpgbin0 -> 7494 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/vrcontroller.jpgbin0 -> 79483 bytes
-rw-r--r--widget/windows/docs/windows-pointing-device/wacom_tablet.pngbin0 -> 509107 bytes
-rw-r--r--widget/windows/filedialog/PWinFileDialog.ipdl32
-rw-r--r--widget/windows/filedialog/WinFileDialogChild.cpp110
-rw-r--r--widget/windows/filedialog/WinFileDialogChild.h52
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.cpp460
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.h74
-rw-r--r--widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh49
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.cpp94
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.h90
-rw-r--r--widget/windows/filedialog/moz.build27
-rw-r--r--widget/windows/metrics.yaml58
-rw-r--r--widget/windows/moz.build213
-rw-r--r--widget/windows/nsAppShell.cpp1000
-rw-r--r--widget/windows/nsAppShell.h64
-rw-r--r--widget/windows/nsBidiKeyboard.cpp169
-rw-r--r--widget/windows/nsBidiKeyboard.h34
-rw-r--r--widget/windows/nsClipboard.cpp1472
-rw-r--r--widget/windows/nsClipboard.h113
-rw-r--r--widget/windows/nsColorPicker.cpp202
-rw-r--r--widget/windows/nsColorPicker.h55
-rw-r--r--widget/windows/nsDataObj.cpp2276
-rw-r--r--widget/windows/nsDataObj.h315
-rw-r--r--widget/windows/nsDataObjCollection.cpp370
-rw-r--r--widget/windows/nsDataObjCollection.h92
-rw-r--r--widget/windows/nsDeviceContextSpecWin.cpp680
-rw-r--r--widget/windows/nsDeviceContextSpecWin.h98
-rw-r--r--widget/windows/nsDragService.cpp664
-rw-r--r--widget/windows/nsDragService.h67
-rw-r--r--widget/windows/nsFilePicker.cpp1078
-rw-r--r--widget/windows/nsFilePicker.h140
-rw-r--r--widget/windows/nsLookAndFeel.cpp916
-rw-r--r--widget/windows/nsLookAndFeel.h124
-rw-r--r--widget/windows/nsNativeDragSource.cpp98
-rw-r--r--widget/windows/nsNativeDragSource.h68
-rw-r--r--widget/windows/nsNativeDragTarget.cpp472
-rw-r--r--widget/windows/nsNativeDragTarget.h103
-rw-r--r--widget/windows/nsNativeThemeWin.cpp1975
-rw-r--r--widget/windows/nsNativeThemeWin.h166
-rw-r--r--widget/windows/nsPrintDialogUtil.cpp360
-rw-r--r--widget/windows/nsPrintDialogUtil.h11
-rw-r--r--widget/windows/nsPrintDialogWin.cpp147
-rw-r--r--widget/windows/nsPrintDialogWin.h39
-rw-r--r--widget/windows/nsPrintSettingsServiceWin.cpp127
-rw-r--r--widget/windows/nsPrintSettingsServiceWin.h29
-rw-r--r--widget/windows/nsPrintSettingsWin.cpp477
-rw-r--r--widget/windows/nsPrintSettingsWin.h62
-rw-r--r--widget/windows/nsPrinterWin.cpp521
-rw-r--r--widget/windows/nsPrinterWin.h53
-rw-r--r--widget/windows/nsSharePicker.cpp81
-rw-r--r--widget/windows/nsSharePicker.h29
-rw-r--r--widget/windows/nsSound.cpp331
-rw-r--r--widget/windows/nsSound.h47
-rw-r--r--widget/windows/nsToolkit.cpp69
-rw-r--r--widget/windows/nsToolkit.h47
-rw-r--r--widget/windows/nsUXThemeConstants.h256
-rw-r--r--widget/windows/nsUXThemeData.cpp98
-rw-r--r--widget/windows/nsUXThemeData.h69
-rw-r--r--widget/windows/nsUserIdleServiceWin.cpp20
-rw-r--r--widget/windows/nsUserIdleServiceWin.h48
-rw-r--r--widget/windows/nsWidgetFactory.cpp60
-rw-r--r--widget/windows/nsWidgetFactory.h21
-rw-r--r--widget/windows/nsWinGesture.cpp388
-rw-r--r--widget/windows/nsWinGesture.h91
-rw-r--r--widget/windows/nsWindow.cpp9000
-rw-r--r--widget/windows/nsWindow.h905
-rw-r--r--widget/windows/nsWindowDbg.cpp1612
-rw-r--r--widget/windows/nsWindowDbg.h153
-rw-r--r--widget/windows/nsWindowDefs.h119
-rw-r--r--widget/windows/nsWindowGfx.cpp718
-rw-r--r--widget/windows/nsWindowGfx.h35
-rw-r--r--widget/windows/nsWindowLoggedMessages.cpp307
-rw-r--r--widget/windows/nsWindowLoggedMessages.h26
-rw-r--r--widget/windows/nsWindowTaskbarConcealer.cpp384
-rw-r--r--widget/windows/nsWindowTaskbarConcealer.h53
-rw-r--r--widget/windows/nsdefs.h58
-rw-r--r--widget/windows/res/aliasb.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/cell.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/col_resize.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/copy.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/grab.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/grabbing.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/none.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/row_resize.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/select.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/vertical_text.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/zoom_in.curbin0 -> 326 bytes
-rw-r--r--widget/windows/res/zoom_out.curbin0 -> 326 bytes
-rw-r--r--widget/windows/resource.h16
-rw-r--r--widget/windows/tests/TestUriValidation.cpp135
-rw-r--r--widget/windows/tests/TestUrisToValidate.h471
-rw-r--r--widget/windows/tests/gtest/TestJumpListBuilder.cpp823
-rw-r--r--widget/windows/tests/gtest/TestWinDND.cpp728
-rw-r--r--widget/windows/tests/gtest/moz.build19
-rw-r--r--widget/windows/tests/moz.build33
-rw-r--r--widget/windows/tests/unit/test_windows_alert_service.js667
-rw-r--r--widget/windows/tests/unit/xpcshell.toml3
-rw-r--r--widget/windows/touchinjection_sdk80.h171
-rw-r--r--widget/windows/widget.rc30
207 files changed, 81182 insertions, 0 deletions
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp
new file mode 100644
index 0000000000..c14278f56c
--- /dev/null
+++ b/widget/windows/AudioSession.cpp
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include <atomic>
+#include <audiopolicy.h>
+#include <windows.h>
+#include <mmdeviceapi.h>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIStringBundle.h"
+
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/mscom/AgileReference.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/WindowsVersion.h"
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * To take advantage of what Vista+ have to offer with respect to audio,
+ * we need to maintain an audio session. This class wraps IAudioSessionControl
+ * and implements IAudioSessionEvents (for callbacks from Windows)
+ */
+class AudioSession final : public IAudioSessionEvents {
+ public:
+ AudioSession();
+
+ static AudioSession* GetSingleton();
+
+ // COM IUnknown
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release();
+
+ // IAudioSessionEvents
+ STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
+ float aChannelVolumeArray[],
+ DWORD aChangedChannel, LPCGUID aContext);
+ STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
+ STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
+ STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
+ STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
+ STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
+ LPCGUID aContext);
+ STDMETHODIMP OnStateChanged(AudioSessionState aState);
+
+ void Start();
+ void Stop(bool shouldRestart = false);
+
+ nsresult GetSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath);
+ nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
+ const nsString& aIconPath);
+
+ private:
+ ~AudioSession() = default;
+
+ void StopInternal(const MutexAutoLock& aProofOfLock,
+ bool shouldRestart = false);
+
+ protected:
+ RefPtr<IAudioSessionControl> mAudioSessionControl;
+ nsString mDisplayName;
+ nsString mIconPath;
+ nsID mSessionGroupingParameter;
+ // Guards the IAudioSessionControl
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+StaticRefPtr<AudioSession> sService;
+
+void StartAudioSession() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sService);
+ sService = new AudioSession();
+
+ // Destroy AudioSession only after any background task threads have been
+ // stopped or abandoned.
+ ClearOnShutdown(&sService, ShutdownPhase::XPCOMShutdownFinal);
+
+ NS_DispatchBackgroundTask(
+ NS_NewCancelableRunnableFunction("StartAudioSession", []() -> void {
+ MOZ_ASSERT(AudioSession::GetSingleton(),
+ "AudioSession should outlive background threads");
+ AudioSession::GetSingleton()->Start();
+ }));
+}
+
+void StopAudioSession() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sService);
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("StopAudioSession", []() -> void {
+ MOZ_ASSERT(AudioSession::GetSingleton(),
+ "AudioSession should outlive background threads");
+ AudioSession::GetSingleton()->Stop();
+ }));
+}
+
+AudioSession* AudioSession::GetSingleton() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+ return sService;
+}
+
+// It appears Windows will use us on a background thread ...
+NS_IMPL_ADDREF(AudioSession)
+NS_IMPL_RELEASE(AudioSession)
+
+STDMETHODIMP
+AudioSession::QueryInterface(REFIID iid, void** ppv) {
+ const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
+ if ((IID_IUnknown == iid) || (IID_IAudioSessionEvents == iid)) {
+ *ppv = static_cast<IAudioSessionEvents*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+AudioSession::AudioSession() : mMutex("AudioSessionControl") {
+ // This func must be run on the main thread as
+ // nsStringBundle is not thread safe otherwise
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Should only get here in a chrome process!");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ MOZ_ASSERT(bundleService);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ MOZ_ASSERT(bundle);
+ bundle->GetStringFromName("brandFullName", mDisplayName);
+
+ wchar_t* buffer;
+ mIconPath.GetMutableData(&buffer, MAX_PATH);
+ ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
+
+ [[maybe_unused]] nsresult rv =
+ nsID::GenerateUUIDInPlace(mSessionGroupingParameter);
+ MOZ_ASSERT(rv == NS_OK);
+}
+
+// Once we are started Windows will hold a reference to us through our
+// IAudioSessionEvents interface that will keep us alive until the appshell
+// calls Stop.
+void AudioSession::Start() {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+ const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
+
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mAudioSessionControl);
+ MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
+ "Should never happen ...");
+
+ auto scopeExit = MakeScopeExit([&] { StopInternal(lock); });
+
+ RefPtr<IMMDeviceEnumerator> enumerator;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
+ IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RefPtr<IMMDevice> device;
+ hr = enumerator->GetDefaultAudioEndpoint(
+ EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RefPtr<IAudioSessionManager> manager;
+ hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
+ getter_AddRefs(manager));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ hr = manager->GetAudioSessionControl(&GUID_NULL, 0,
+ getter_AddRefs(mAudioSessionControl));
+
+ if (FAILED(hr) || !mAudioSessionControl) {
+ return;
+ }
+
+ // Increments refcount of 'this'.
+ hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ hr = mAudioSessionControl->SetGroupingParam(
+ (LPGUID) & (mSessionGroupingParameter), nullptr);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ scopeExit.release();
+}
+
+void AudioSession::Stop(bool shouldRestart) {
+ MOZ_ASSERT(mscom::IsCurrentThreadMTA());
+
+ MutexAutoLock lock(mMutex);
+ StopInternal(lock, shouldRestart);
+}
+
+void AudioSession::StopInternal(const MutexAutoLock& aProofOfLock,
+ bool shouldRestart) {
+ if (!mAudioSessionControl) {
+ return;
+ }
+
+ // Decrement refcount of 'this'
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+
+ // Deleting the IAudioSessionControl COM object requires the STA/main thread.
+ // Audio code may concurrently be running on the main thread and it may
+ // block waiting for this to complete, creating deadlock. So we destroy the
+ // IAudioSessionControl on the main thread instead. In order to do that, we
+ // need to marshall the object to the main thread's apartment with an
+ // AgileReference.
+ mscom::AgileReference agileAsc(mAudioSessionControl);
+ mAudioSessionControl = nullptr;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "FreeAudioSession",
+ [agileAsc = std::move(agileAsc), shouldRestart]() mutable {
+ // Now release the AgileReference which holds our only reference to the
+ // IAudioSessionControl, then maybe restart.
+ agileAsc = nullptr;
+ if (shouldRestart) {
+ NS_DispatchBackgroundTask(
+ NS_NewCancelableRunnableFunction("RestartAudioSession", [] {
+ AudioSession* as = AudioSession::GetSingleton();
+ MOZ_ASSERT(as);
+ as->Start();
+ }));
+ }
+ }));
+}
+
+void CopynsID(nsID& lhs, const nsID& rhs) {
+ lhs.m0 = rhs.m0;
+ lhs.m1 = rhs.m1;
+ lhs.m2 = rhs.m2;
+ for (int i = 0; i < 8; i++) {
+ lhs.m3[i] = rhs.m3[i];
+ }
+}
+
+nsresult AudioSession::GetSessionData(nsID& aID, nsString& aSessionName,
+ nsString& aIconPath) {
+ CopynsID(aID, mSessionGroupingParameter);
+ aSessionName = mDisplayName;
+ aIconPath = mIconPath;
+
+ return NS_OK;
+}
+
+nsresult AudioSession::SetSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath) {
+ MOZ_ASSERT(!XRE_IsParentProcess(),
+ "Should never get here in a chrome process!");
+ CopynsID(mSessionGroupingParameter, aID);
+ mDisplayName = aSessionName;
+ mIconPath = aIconPath;
+ return NS_OK;
+}
+
+STDMETHODIMP
+AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
+ float aChannelVolumeArray[],
+ DWORD aChangedChannel, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
+ Stop(true /* shouldRestart */);
+ return S_OK;
+}
+
+STDMETHODIMP
+AudioSession::OnSimpleVolumeChanged(float aVolume, BOOL aMute,
+ LPCGUID aContext) {
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnStateChanged(AudioSessionState aState) {
+ return S_OK; // NOOP
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/AudioSession.h b/widget/windows/AudioSession.h
new file mode 100644
index 0000000000..612a5e209b
--- /dev/null
+++ b/widget/windows/AudioSession.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+// Start the audio session in the current process
+void StartAudioSession();
+
+// Stop the audio session in the current process
+void StopAudioSession();
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CheckInvariantWrapper.h b/widget/windows/CheckInvariantWrapper.h
new file mode 100644
index 0000000000..da6024ec21
--- /dev/null
+++ b/widget/windows/CheckInvariantWrapper.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// A wrapper that uses RAII to ensure that a class invariant is checked
+// before and after any public function is called
+
+#ifndef CHECKINVARIANTWRAPPER_H_
+#define CHECKINVARIANTWRAPPER_H_
+
+#include "mozilla/Attributes.h"
+#include <utility>
+
+namespace mozilla {
+
+//
+// Wraps an object of type T and allows access to its public API by
+// deferencing it using the pointer syntax "->".
+//
+// Using that operator will return a temporary RAII object that
+// calls a method named "CheckInvariant" in its constructor, calls the
+// requested method, and then calls "CheckInvariant" again in its
+// destructor.
+//
+// The only thing your class requires is a method with the following signature:
+//
+// void CheckInvariant() const;
+//
+template <typename T>
+class CheckInvariantWrapper {
+ public:
+ class Wrapper {
+ public:
+ explicit Wrapper(T& aObject) : mObject(aObject) {
+ mObject.CheckInvariant();
+ }
+ ~Wrapper() { mObject.CheckInvariant(); }
+
+ T* operator->() { return &mObject; }
+
+ private:
+ T& mObject;
+ };
+
+ class ConstWrapper {
+ public:
+ explicit ConstWrapper(const T& aObject) : mObject(aObject) {
+ mObject.CheckInvariant();
+ }
+ ~ConstWrapper() { mObject.CheckInvariant(); }
+
+ const T* operator->() const { return &mObject; }
+
+ private:
+ const T& mObject;
+ };
+
+ CheckInvariantWrapper() = default;
+
+ MOZ_IMPLICIT CheckInvariantWrapper(T aObject) : mObject(std::move(aObject)) {}
+
+ template <typename... Args>
+ explicit CheckInvariantWrapper(std::in_place_t, Args&&... args)
+ : mObject(std::forward<Args>(args)...) {}
+
+ const ConstWrapper operator->() const { return ConstWrapper(mObject); }
+
+ Wrapper operator->() { return Wrapper(mObject); }
+
+ private:
+ T mObject;
+};
+
+} // namespace mozilla
+
+#endif // CHECKINVARIANTWRAPPER_H_
diff --git a/widget/windows/CompositorWidgetChild.cpp b/widget/windows/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..b294efef51
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+#include "gfxPlatform.h"
+#include "RemoteBackbuffer.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(
+ RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData& aInitData)
+ : mVsyncDispatcher(aVsyncDispatcher),
+ mVsyncObserver(aVsyncObserver),
+ mCompositorWnd(nullptr),
+ mWnd(reinterpret_cast<HWND>(
+ aInitData.get_WinCompositorWidgetInitData().hWnd())),
+ mTransparencyMode(
+ aInitData.get_WinCompositorWidgetInitData().transparencyMode()),
+ mRemoteBackbufferProvider() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!gfxPlatform::IsHeadless());
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+CompositorWidgetChild::~CompositorWidgetChild() {}
+
+bool CompositorWidgetChild::Initialize() {
+ mRemoteBackbufferProvider = std::make_unique<remote_backbuffer::Provider>();
+ if (!mRemoteBackbufferProvider->Initialize(mWnd, OtherPid(),
+ mTransparencyMode)) {
+ return false;
+ }
+
+ auto maybeRemoteHandles = mRemoteBackbufferProvider->CreateRemoteHandles();
+ if (!maybeRemoteHandles) {
+ return false;
+ }
+
+ Unused << SendInitialize(*maybeRemoteHandles);
+
+ return true;
+}
+
+void CompositorWidgetChild::EnterPresentLock() {
+ Unused << SendEnterPresentLock();
+}
+
+void CompositorWidgetChild::LeavePresentLock() {
+ Unused << SendLeavePresentLock();
+}
+
+void CompositorWidgetChild::OnDestroyWindow() {}
+
+bool CompositorWidgetChild::OnWindowResize(const LayoutDeviceIntSize& aSize) {
+ return true;
+}
+
+void CompositorWidgetChild::OnWindowModeChange(nsSizeMode aSizeMode) {}
+
+void CompositorWidgetChild::UpdateTransparency(TransparencyMode aMode) {
+ mTransparencyMode = aMode;
+ mRemoteBackbufferProvider->UpdateTransparencyMode(aMode);
+ Unused << SendUpdateTransparency(aMode);
+}
+
+void CompositorWidgetChild::NotifyVisibilityUpdated(nsSizeMode aSizeMode,
+ bool aIsFullyOccluded) {
+ Unused << SendNotifyVisibilityUpdated(aSizeMode, aIsFullyOccluded);
+};
+
+void CompositorWidgetChild::ClearTransparentWindow() {
+ Unused << SendClearTransparentWindow();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUpdateCompositorWnd(
+ const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd,
+ UpdateCompositorWndResolver&& aResolve) {
+ HWND parentWnd = reinterpret_cast<HWND>(aParentWnd);
+ if (mWnd == parentWnd) {
+ mCompositorWnd = reinterpret_cast<HWND>(aCompositorWnd);
+ ::SetParent(mCompositorWnd, mWnd);
+ aResolve(true);
+ } else {
+ aResolve(false);
+ gfxCriticalNote << "Parent winow does not match";
+ MOZ_ASSERT_UNREACHABLE("unexpected to happen");
+ }
+
+ return IPC_OK();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h
new file mode 100644
index 0000000000..bec8ead98e
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_CompositorWidgetChild_h
+#define widget_windows_CompositorWidgetChild_h
+
+#include "WinCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+class CompositorVsyncDispatcher;
+
+namespace widget {
+
+namespace remote_backbuffer {
+class Provider;
+}
+
+class CompositorWidgetChild final : public PCompositorWidgetChild,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetChild() override;
+
+ bool Initialize();
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ bool OnWindowResize(const LayoutDeviceIntSize& aSize) override;
+ void OnWindowModeChange(nsSizeMode aSizeMode) override;
+ void UpdateTransparency(TransparencyMode aMode) override;
+ void NotifyVisibilityUpdated(nsSizeMode aSizeMode,
+ bool aIsFullyOccluded) override;
+ void ClearTransparentWindow() override;
+
+ mozilla::ipc::IPCResult RecvObserveVsync() override;
+ mozilla::ipc::IPCResult RecvUnobserveVsync() override;
+ mozilla::ipc::IPCResult RecvUpdateCompositorWnd(
+ const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd,
+ UpdateCompositorWndResolver&& aResolve) override;
+
+ private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+ HWND mCompositorWnd;
+
+ HWND mWnd;
+ TransparencyMode mTransparencyMode;
+
+ std::unique_ptr<remote_backbuffer::Provider> mRemoteBackbufferProvider;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_CompositorWidgetChild_h
diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp
new file mode 100644
index 0000000000..b25d30d9d5
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "CompositorWidgetParent.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+#include "RemoteBackbuffer.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+CompositorWidgetParent::CompositorWidgetParent(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : WinCompositorWidget(aInitData.get_WinCompositorWidgetInitData(),
+ aOptions),
+ mWnd(reinterpret_cast<HWND>(
+ aInitData.get_WinCompositorWidgetInitData().hWnd())),
+ mTransparencyMode(uint32_t(
+ aInitData.get_WinCompositorWidgetInitData().transparencyMode())),
+ mSizeMode(nsSizeMode_Normal),
+ mIsFullyOccluded(false),
+ mRemoteBackbufferClient() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+CompositorWidgetParent::~CompositorWidgetParent() {}
+
+bool CompositorWidgetParent::Initialize(
+ const RemoteBackbufferHandles& aRemoteHandles) {
+ mRemoteBackbufferClient = std::make_unique<remote_backbuffer::Client>();
+ if (!mRemoteBackbufferClient->Initialize(aRemoteHandles)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool CompositorWidgetParent::PreRender(WidgetRenderingContext* aContext) {
+ // This can block waiting for WM_SETTEXT to finish
+ // Using PreRender is unnecessarily pessimistic because
+ // we technically only need to block during the present call
+ // not all of compositor rendering
+ mPresentLock.Enter();
+ return true;
+}
+
+void CompositorWidgetParent::PostRender(WidgetRenderingContext* aContext) {
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize CompositorWidgetParent::GetClientSize() {
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidgetParent::StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) {
+ MOZ_ASSERT(mRemoteBackbufferClient);
+ MOZ_ASSERT(aBufferMode);
+
+ // Because we use remote backbuffering, there is no need to use a local
+ // backbuffer too.
+ (*aBufferMode) = layers::BufferMode::BUFFER_NONE;
+
+ return mRemoteBackbufferClient->BorrowDrawTarget();
+}
+
+void CompositorWidgetParent::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ Unused << mRemoteBackbufferClient->PresentDrawTarget(
+ aInvalidRegion.ToUnknownRegion());
+}
+
+bool CompositorWidgetParent::NeedsToDeferEndRemoteDrawing() { return false; }
+
+already_AddRefed<gfx::DrawTarget>
+CompositorWidgetParent::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
+ const gfx::IntRect& aRect,
+ bool* aOutIsCleared) {
+ MOZ_CRASH(
+ "Unexpected call to GetBackBufferDrawTarget() with remote "
+ "backbuffering in use");
+}
+
+already_AddRefed<gfx::SourceSurface>
+CompositorWidgetParent::EndBackBufferDrawing() {
+ MOZ_CRASH(
+ "Unexpected call to EndBackBufferDrawing() with remote "
+ "backbuffering in use");
+}
+
+bool CompositorWidgetParent::InitCompositor(layers::Compositor* aCompositor) {
+ return true;
+}
+
+bool CompositorWidgetParent::IsHidden() const { return ::IsIconic(mWnd); }
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvInitialize(
+ const RemoteBackbufferHandles& aRemoteHandles) {
+ Unused << Initialize(aRemoteHandles);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvEnterPresentLock() {
+ mPresentLock.Enter();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvLeavePresentLock() {
+ mPresentLock.Leave();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvUpdateTransparency(
+ const TransparencyMode& aMode) {
+ mTransparencyMode = uint32_t(aMode);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvNotifyVisibilityUpdated(
+ const nsSizeMode& aSizeMode, const bool& aIsFullyOccluded) {
+ mSizeMode = aSizeMode;
+ mIsFullyOccluded = aIsFullyOccluded;
+ return IPC_OK();
+}
+
+nsSizeMode CompositorWidgetParent::CompositorWidgetParent::GetWindowSizeMode()
+ const {
+ nsSizeMode sizeMode = mSizeMode;
+ return sizeMode;
+}
+
+bool CompositorWidgetParent::CompositorWidgetParent::GetWindowIsFullyOccluded()
+ const {
+ bool isFullyOccluded = mIsFullyOccluded;
+ return isFullyOccluded;
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvClearTransparentWindow() {
+ gfx::CriticalSectionAutoEnter lock(&mPresentLock);
+
+ RefPtr<DrawTarget> drawTarget = mRemoteBackbufferClient->BorrowDrawTarget();
+ if (!drawTarget) {
+ return IPC_OK();
+ }
+
+ IntSize size = drawTarget->GetSize();
+ if (size.IsEmpty()) {
+ return IPC_OK();
+ }
+
+ drawTarget->ClearRect(Rect(0, 0, size.width, size.height));
+
+ Unused << mRemoteBackbufferClient->PresentDrawTarget(
+ IntRect(0, 0, size.width, size.height));
+
+ return IPC_OK();
+}
+
+nsIWidget* CompositorWidgetParent::RealWidget() { return nullptr; }
+
+void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) {
+ if (aObserver) {
+ Unused << SendObserveVsync();
+ } else {
+ Unused << SendUnobserveVsync();
+ }
+ mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ return mVsyncObserver;
+}
+
+void CompositorWidgetParent::UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(mRootLayerTreeID.isSome());
+
+ RefPtr<CompositorWidgetParent> self = this;
+ SendUpdateCompositorWnd(reinterpret_cast<WindowsHandle>(aCompositorWnd),
+ reinterpret_cast<WindowsHandle>(aParentWnd))
+ ->Then(
+ layers::CompositorThread(), __func__,
+ [self](const bool& aSuccess) {
+ if (aSuccess && self->mRootLayerTreeID.isSome() &&
+ layers::CompositorThreadHolder::IsActive()) {
+ self->mSetParentCompleted = true;
+ // Schedule composition after ::SetParent() call in parent
+ // process.
+ layers::CompositorBridgeParent::ScheduleForcedComposition(
+ self->mRootLayerTreeID.ref(), wr::RenderReasons::WIDGET);
+ }
+ },
+ [self](const mozilla::ipc::ResponseRejectReason&) {});
+}
+
+void CompositorWidgetParent::SetRootLayerTreeID(
+ const layers::LayersId& aRootLayerTreeId) {
+ mRootLayerTreeID = Some(aRootLayerTreeId);
+}
+
+void CompositorWidgetParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetParent.h b/widget/windows/CompositorWidgetParent.h
new file mode 100644
index 0000000000..0f91fa7ccb
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_CompositorWidgetParent_h
+#define widget_windows_CompositorWidgetParent_h
+
+#include "WinCompositorWidget.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+enum class TransparencyMode : uint8_t;
+
+namespace remote_backbuffer {
+class Client;
+}
+
+class CompositorWidgetParent final : public PCompositorWidgetParent,
+ public WinCompositorWidget {
+ public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~CompositorWidgetParent() override;
+
+ bool Initialize(const RemoteBackbufferHandles& aRemoteHandles);
+
+ bool PreRender(WidgetRenderingContext*) override;
+ void PostRender(WidgetRenderingContext*) override;
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool NeedsToDeferEndRemoteDrawing() override;
+ LayoutDeviceIntSize GetClientSize() override;
+ already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ bool IsHidden() const override;
+
+ nsSizeMode GetWindowSizeMode() const override;
+ bool GetWindowIsFullyOccluded() const override;
+
+ mozilla::ipc::IPCResult RecvInitialize(
+ const RemoteBackbufferHandles& aRemoteHandles) override;
+ mozilla::ipc::IPCResult RecvEnterPresentLock() override;
+ mozilla::ipc::IPCResult RecvLeavePresentLock() override;
+ mozilla::ipc::IPCResult RecvUpdateTransparency(
+ const TransparencyMode& aMode) override;
+ mozilla::ipc::IPCResult RecvNotifyVisibilityUpdated(
+ const nsSizeMode& aSizeMode, const bool& aIsFullyOccluded) override;
+ mozilla::ipc::IPCResult RecvClearTransparentWindow() override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsIWidget* RealWidget() override;
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ // PlatformCompositorWidgetDelegate Overrides
+ void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) override;
+ void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override;
+
+ private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+ Maybe<layers::LayersId> mRootLayerTreeID;
+
+ HWND mWnd;
+
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ mozilla::Atomic<uint32_t, MemoryOrdering::Relaxed> mTransparencyMode;
+
+ // Visibility handling.
+ mozilla::Atomic<nsSizeMode, MemoryOrdering::Relaxed> mSizeMode;
+ mozilla::Atomic<bool, MemoryOrdering::Relaxed> mIsFullyOccluded;
+
+ std::unique_ptr<remote_backbuffer::Client> mRemoteBackbufferClient;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_CompositorWidgetParent_h
diff --git a/widget/windows/DirectManipulationOwner.cpp b/widget/windows/DirectManipulationOwner.cpp
new file mode 100644
index 0000000000..569dd4e189
--- /dev/null
+++ b/widget/windows/DirectManipulationOwner.cpp
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "DirectManipulationOwner.h"
+#include "nsWindow.h"
+#include "WinModifierKeyState.h"
+#include "InputData.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/SwipeTracker.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/VsyncDispatcher.h"
+
+#include "directmanipulation.h"
+
+namespace mozilla {
+namespace widget {
+
+class DManipEventHandler : public IDirectManipulationViewportEventHandler,
+ public IDirectManipulationInteractionEventHandler {
+ public:
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+ friend class DirectManipulationOwner;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler)
+
+ STDMETHODIMP QueryInterface(REFIID, void**) override;
+
+ friend class DirectManipulationOwner;
+
+ explicit DManipEventHandler(nsWindow* aWindow,
+ DirectManipulationOwner* aOwner,
+ const LayoutDeviceIntRect& aBounds);
+
+ HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
+ IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
+ DIRECTMANIPULATION_STATUS previous) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnViewportUpdated(IDirectManipulationViewport* viewport) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnContentUpdated(IDirectManipulationViewport* viewport,
+ IDirectManipulationContent* content) override;
+
+ HRESULT STDMETHODCALLTYPE
+ OnInteraction(IDirectManipulationViewport2* viewport,
+ DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
+
+ void Update();
+
+ class VObserver final : public mozilla::VsyncObserver {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler::VObserver,
+ override)
+
+ public:
+ void NotifyVsync(const mozilla::VsyncEvent& aVsync) override {
+ if (mOwner) {
+ mOwner->Update();
+ }
+ }
+ explicit VObserver(DManipEventHandler* aOwner) : mOwner(aOwner) {}
+
+ void ClearOwner() { mOwner = nullptr; }
+
+ private:
+ virtual ~VObserver() {}
+ DManipEventHandler* mOwner;
+ };
+
+ enum class State { eNone, ePanning, eInertia, ePinching };
+ void TransitionToState(State aNewState);
+
+ enum class Phase { eStart, eMiddle, eEnd };
+ // Return value indicates if we sent an event or not and hence if we should
+ // update mLastScale. (We only want to send pinch events if the computed
+ // deltaY for the corresponding WidgetWheelEvent would be non-zero.)
+ bool SendPinch(Phase aPhase, float aScale);
+ void SendPan(Phase aPhase, float x, float y, bool aIsInertia);
+ static void SendPanCommon(nsWindow* aWindow, Phase aPhase,
+ ScreenPoint aPosition, double aDeltaX,
+ double aDeltaY, Modifiers aMods, bool aIsInertia);
+
+ static void SynthesizeNativeTouchpadPan(
+ nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags);
+
+ private:
+ virtual ~DManipEventHandler() = default;
+
+ nsWindow* mWindow;
+ DirectManipulationOwner* mOwner;
+ RefPtr<VObserver> mObserver;
+ float mLastScale;
+ float mLastXOffset;
+ float mLastYOffset;
+ LayoutDeviceIntRect mBounds;
+ bool mShouldSendPanStart;
+ bool mShouldSendPinchStart;
+ State mState = State::eNone;
+};
+
+DManipEventHandler::DManipEventHandler(nsWindow* aWindow,
+ DirectManipulationOwner* aOwner,
+ const LayoutDeviceIntRect& aBounds)
+ : mWindow(aWindow),
+ mOwner(aOwner),
+ mLastScale(1.f),
+ mLastXOffset(0.f),
+ mLastYOffset(0.f),
+ mBounds(aBounds),
+ mShouldSendPanStart(false),
+ mShouldSendPinchStart(false) {}
+
+STDMETHODIMP
+DManipEventHandler::QueryInterface(REFIID iid, void** ppv) {
+ const IID IID_IDirectManipulationViewportEventHandler =
+ __uuidof(IDirectManipulationViewportEventHandler);
+ const IID IID_IDirectManipulationInteractionEventHandler =
+ __uuidof(IDirectManipulationInteractionEventHandler);
+
+ if ((IID_IUnknown == iid) ||
+ (IID_IDirectManipulationViewportEventHandler == iid)) {
+ *ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);
+ AddRef();
+ return S_OK;
+ }
+ if (IID_IDirectManipulationInteractionEventHandler == iid) {
+ *ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+HRESULT
+DManipEventHandler::OnViewportStatusChanged(
+ IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,
+ DIRECTMANIPULATION_STATUS previous) {
+ if (current == previous) {
+ return S_OK;
+ }
+
+ if (current == DIRECTMANIPULATION_INERTIA) {
+ if (previous != DIRECTMANIPULATION_RUNNING || mState != State::ePanning) {
+ // xxx transition to none?
+ return S_OK;
+ }
+
+ TransitionToState(State::eInertia);
+ }
+
+ if (current == DIRECTMANIPULATION_RUNNING) {
+ // INERTIA -> RUNNING, should start a new sequence.
+ if (previous == DIRECTMANIPULATION_INERTIA) {
+ TransitionToState(State::eNone);
+ }
+ }
+
+ if (current != DIRECTMANIPULATION_ENABLED &&
+ current != DIRECTMANIPULATION_READY) {
+ return S_OK;
+ }
+
+ // A session has ended, reset the transform.
+ if (mLastScale != 1.f || mLastXOffset != 0.f || mLastYOffset != 0.f) {
+ HRESULT hr =
+ viewport->ZoomToRect(0, 0, mBounds.width, mBounds.height, false);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("ZoomToRect failed");
+ }
+ }
+ mLastScale = 1.f;
+ mLastXOffset = 0.f;
+ mLastYOffset = 0.f;
+
+ TransitionToState(State::eNone);
+
+ return S_OK;
+}
+
+HRESULT
+DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) {
+ return S_OK;
+}
+
+void DManipEventHandler::TransitionToState(State aNewState) {
+ if (mState == aNewState) {
+ return;
+ }
+
+ State prevState = mState;
+ mState = aNewState;
+
+ // End the previous sequence.
+ switch (prevState) {
+ case State::ePanning: {
+ // ePanning -> *: PanEnd
+ SendPan(Phase::eEnd, 0.f, 0.f, false);
+ break;
+ }
+ case State::eInertia: {
+ // eInertia -> *: MomentumEnd
+ SendPan(Phase::eEnd, 0.f, 0.f, true);
+ break;
+ }
+ case State::ePinching: {
+ MOZ_ASSERT(aNewState == State::eNone);
+ // ePinching -> eNone: PinchEnd. ePinching should only transition to
+ // eNone.
+ // Only send a pinch end if we sent a pinch start.
+ if (!mShouldSendPinchStart) {
+ SendPinch(Phase::eEnd, 0.f);
+ }
+ mShouldSendPinchStart = false;
+ break;
+ }
+ case State::eNone: {
+ // eNone -> *: no cleanup is needed.
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ }
+
+ // Start the new sequence.
+ switch (aNewState) {
+ case State::ePanning: {
+ // eInertia, eNone -> ePanning: PanStart.
+ // We're being called from OnContentUpdated, it has the coords we need to
+ // pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we
+ // return OnContentUpdated will check it and call SendPan(Phase::eStart).
+ mShouldSendPanStart = true;
+ break;
+ }
+ case State::eInertia: {
+ // Only ePanning can transition to eInertia.
+ MOZ_ASSERT(prevState == State::ePanning);
+ SendPan(Phase::eStart, 0.f, 0.f, true);
+ break;
+ }
+ case State::ePinching: {
+ // * -> ePinching: PinchStart.
+ // Pinch gesture may begin with some scroll events.
+ // We're being called from OnContentUpdated, it has the scale we need to
+ // pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when
+ // we return OnContentUpdated will check it and call
+ // SendPinch(Phase::eStart).
+ mShouldSendPinchStart = true;
+ break;
+ }
+ case State::eNone: {
+ // * -> eNone: only cleanup is needed.
+ break;
+ }
+ default:
+ MOZ_ASSERT(false);
+ }
+}
+
+HRESULT
+DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport,
+ IDirectManipulationContent* content) {
+ float transform[6];
+ HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("GetContentTransform failed");
+ return S_OK;
+ }
+
+ float scale = transform[0];
+ float xoffset = transform[4];
+ float yoffset = transform[5];
+
+ // Not different from last time.
+ if (FuzzyEqualsMultiplicative(scale, mLastScale) && xoffset == mLastXOffset &&
+ yoffset == mLastYOffset) {
+ return S_OK;
+ }
+
+ // Consider this is a Scroll when scale factor equals 1.0.
+ if (FuzzyEqualsMultiplicative(scale, 1.f)) {
+ if (mState == State::eNone) {
+ TransitionToState(State::ePanning);
+ }
+ } else {
+ // Pinch gesture may begin with some scroll events.
+ TransitionToState(State::ePinching);
+ }
+
+ if (mState == State::ePanning || mState == State::eInertia) {
+ // Accumulate the offset (by not updating mLastX/YOffset) until we have at
+ // least one pixel.
+ float dx = std::abs(mLastXOffset - xoffset);
+ float dy = std::abs(mLastYOffset - yoffset);
+ if (dx < 1.f && dy < 1.f) {
+ return S_OK;
+ }
+ }
+
+ bool updateLastScale = true;
+ if (mState == State::ePanning) {
+ if (mShouldSendPanStart) {
+ SendPan(Phase::eStart, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ false);
+ mShouldSendPanStart = false;
+ } else {
+ SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ false);
+ }
+ } else if (mState == State::eInertia) {
+ SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset,
+ true);
+ } else if (mState == State::ePinching) {
+ if (mShouldSendPinchStart) {
+ updateLastScale = SendPinch(Phase::eStart, scale);
+ // Only clear mShouldSendPinchStart if we actually sent the event
+ // (updateLastScale tells us if we sent an event).
+ if (updateLastScale) {
+ mShouldSendPinchStart = false;
+ }
+ } else {
+ updateLastScale = SendPinch(Phase::eMiddle, scale);
+ }
+ }
+
+ if (updateLastScale) {
+ mLastScale = scale;
+ }
+ mLastXOffset = xoffset;
+ mLastYOffset = yoffset;
+
+ return S_OK;
+}
+
+HRESULT
+DManipEventHandler::OnInteraction(
+ IDirectManipulationViewport2* viewport,
+ DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
+ if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
+ if (!mObserver) {
+ mObserver = new VObserver(this);
+ }
+
+ gfxWindowsPlatform::GetPlatform()
+ ->GetGlobalVsyncDispatcher()
+ ->AddMainThreadObserver(mObserver);
+ }
+
+ if (mObserver && interaction == DIRECTMANIPULATION_INTERACTION_END) {
+ gfxWindowsPlatform::GetPlatform()
+ ->GetGlobalVsyncDispatcher()
+ ->RemoveMainThreadObserver(mObserver);
+ }
+
+ return S_OK;
+}
+
+void DManipEventHandler::Update() {
+ if (mOwner) {
+ mOwner->Update();
+ }
+}
+
+void DirectManipulationOwner::Update() {
+ if (mDmUpdateManager) {
+ mDmUpdateManager->Update(nullptr);
+ }
+}
+
+DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow)
+ : mWindow(aWindow) {}
+
+DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); }
+
+bool DManipEventHandler::SendPinch(Phase aPhase, float aScale) {
+ if (!mWindow) {
+ return false;
+ }
+
+ if (aScale == mLastScale && aPhase != Phase::eEnd) {
+ return false;
+ }
+
+ PinchGestureInput::PinchGestureType pinchGestureType =
+ PinchGestureInput::PINCHGESTURE_SCALE;
+ switch (aPhase) {
+ case Phase::eStart:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ break;
+ case Phase::eMiddle:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ break;
+ case Phase::eEnd:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+
+ TimeStamp eventTimeStamp = TimeStamp::Now();
+
+ ModifierKeyState modifierKeyState;
+ Modifiers mods = modifierKeyState.GetModifiers();
+
+ ExternalPoint screenOffset = ViewAs<ExternalPixel>(
+ mWindow->WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ POINT cursor_pos;
+ ::GetCursorPos(&cursor_pos);
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+ ::ScreenToClient(wnd, &cursor_pos);
+ ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
+
+ PinchGestureInput event{pinchGestureType,
+ PinchGestureInput::TRACKPAD,
+ eventTimeStamp,
+ screenOffset,
+ position,
+ 100.0 * ((aPhase == Phase::eEnd) ? 1.f : aScale),
+ 100.0 * ((aPhase == Phase::eEnd) ? 1.f : mLastScale),
+ mods};
+
+ if (!event.SetLineOrPageDeltaY(mWindow)) {
+ return false;
+ }
+
+ mWindow->SendAnAPZEvent(event);
+
+ return true;
+}
+
+void DManipEventHandler::SendPan(Phase aPhase, float x, float y,
+ bool aIsInertia) {
+ if (!mWindow) {
+ return;
+ }
+
+ ModifierKeyState modifierKeyState;
+ Modifiers mods = modifierKeyState.GetModifiers();
+
+ POINT cursor_pos;
+ ::GetCursorPos(&cursor_pos);
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+ ::ScreenToClient(wnd, &cursor_pos);
+ ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y};
+
+ SendPanCommon(mWindow, aPhase, position, x, y, mods, aIsInertia);
+}
+
+/* static */
+void DManipEventHandler::SendPanCommon(nsWindow* aWindow, Phase aPhase,
+ ScreenPoint aPosition, double aDeltaX,
+ double aDeltaY, Modifiers aMods,
+ bool aIsInertia) {
+ if (!aWindow) {
+ return;
+ }
+
+ PanGestureInput::PanGestureType panGestureType =
+ PanGestureInput::PANGESTURE_PAN;
+ if (aIsInertia) {
+ switch (aPhase) {
+ case Phase::eStart:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMSTART;
+ break;
+ case Phase::eMiddle:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMPAN;
+ break;
+ case Phase::eEnd:
+ panGestureType = PanGestureInput::PANGESTURE_MOMENTUMEND;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+ } else {
+ switch (aPhase) {
+ case Phase::eStart:
+ panGestureType = PanGestureInput::PANGESTURE_START;
+ break;
+ case Phase::eMiddle:
+ panGestureType = PanGestureInput::PANGESTURE_PAN;
+ break;
+ case Phase::eEnd:
+ panGestureType = PanGestureInput::PANGESTURE_END;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("handle all enum values");
+ }
+ }
+
+ TimeStamp eventTimeStamp = TimeStamp::Now();
+
+ PanGestureInput event{panGestureType, eventTimeStamp, aPosition,
+ ScreenPoint(aDeltaX, aDeltaY), aMods};
+
+ aWindow->SendAnAPZEvent(event);
+}
+
+void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) {
+ HRESULT hr = CoCreateInstance(
+ CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IDirectManipulationManager, getter_AddRefs(mDmManager));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed");
+ mDmManager = nullptr;
+ return;
+ }
+
+ hr = mDmManager->GetUpdateManager(IID_IDirectManipulationUpdateManager,
+ getter_AddRefs(mDmUpdateManager));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("GetUpdateManager failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ return;
+ }
+
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+
+ hr = mDmManager->CreateViewport(nullptr, wnd, IID_IDirectManipulationViewport,
+ getter_AddRefs(mDmViewport));
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("CreateViewport failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ DIRECTMANIPULATION_CONFIGURATION configuration =
+ DIRECTMANIPULATION_CONFIGURATION_INTERACTION |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |
+ DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA |
+ DIRECTMANIPULATION_CONFIGURATION_RAILS_X |
+ DIRECTMANIPULATION_CONFIGURATION_RAILS_Y;
+ if (StaticPrefs::apz_allow_zooming()) {
+ configuration |= DIRECTMANIPULATION_CONFIGURATION_SCALING;
+ }
+
+ hr = mDmViewport->ActivateConfiguration(configuration);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("ActivateConfiguration failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ hr = mDmViewport->SetViewportOptions(
+ DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportOptions failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ return;
+ }
+
+ mDmHandler = new DManipEventHandler(mWindow, this, aBounds);
+
+ hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(),
+ &mDmViewportHandlerCookie);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("AddEventHandler failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
+ hr = mDmViewport->SetViewportRect(&rect);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportRect failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmManager->Activate(wnd);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("manager Activate failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmViewport->Enable();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Enable failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+
+ hr = mDmUpdateManager->Update(nullptr);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmUpdateManager->Update failed");
+ mDmManager = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmViewport = nullptr;
+ mDmHandler = nullptr;
+ return;
+ }
+}
+
+void DirectManipulationOwner::ResizeViewport(
+ const LayoutDeviceIntRect& aBounds) {
+ if (mDmHandler) {
+ mDmHandler->mBounds = aBounds;
+ }
+
+ if (mDmViewport) {
+ RECT rect = {0, 0, aBounds.Width(), aBounds.Height()};
+ HRESULT hr = mDmViewport->SetViewportRect(&rect);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("SetViewportRect failed");
+ }
+ }
+}
+
+void DirectManipulationOwner::Destroy() {
+ if (mDmHandler) {
+ mDmHandler->mWindow = nullptr;
+ mDmHandler->mOwner = nullptr;
+ if (mDmHandler->mObserver) {
+ gfxWindowsPlatform::GetPlatform()
+ ->GetGlobalVsyncDispatcher()
+ ->RemoveMainThreadObserver(mDmHandler->mObserver);
+ mDmHandler->mObserver->ClearOwner();
+ mDmHandler->mObserver = nullptr;
+ }
+ }
+
+ HRESULT hr;
+ if (mDmViewport) {
+ hr = mDmViewport->Stop();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Stop() failed");
+ }
+
+ hr = mDmViewport->Disable();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Disable() failed");
+ }
+
+ hr = mDmViewport->RemoveEventHandler(mDmViewportHandlerCookie);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->RemoveEventHandler() failed");
+ }
+
+ hr = mDmViewport->Abandon();
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmViewport->Abandon() failed");
+ }
+ }
+
+ if (mWindow) {
+ HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW));
+
+ if (mDmManager) {
+ hr = mDmManager->Deactivate(wnd);
+ if (!SUCCEEDED(hr)) {
+ NS_WARNING("mDmManager->Deactivate() failed");
+ }
+ }
+ }
+
+ mDmHandler = nullptr;
+ mDmViewport = nullptr;
+ mDmUpdateManager = nullptr;
+ mDmManager = nullptr;
+ mWindow = nullptr;
+}
+
+void DirectManipulationOwner::SetContact(UINT aContactId) {
+ if (mDmViewport) {
+ mDmViewport->SetContact(aContactId);
+ }
+}
+
+/*static */ void DirectManipulationOwner::SynthesizeNativeTouchpadPan(
+ nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags) {
+ DManipEventHandler::SynthesizeNativeTouchpadPan(
+ aWindow, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
+}
+
+/*static */ void DManipEventHandler::SynthesizeNativeTouchpadPan(
+ nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags) {
+ ScreenPoint position = {(float)aPoint.x, (float)aPoint.y};
+ Phase phase = Phase::eStart;
+ if (aEventPhase == nsIWidget::PHASE_UPDATE) {
+ phase = Phase::eMiddle;
+ }
+
+ if (aEventPhase == nsIWidget::PHASE_END) {
+ phase = Phase::eEnd;
+ }
+ SendPanCommon(aWindow, phase, position, aDeltaX, aDeltaY, aModifierFlags,
+ /* aIsInertia = */ false);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/DirectManipulationOwner.h b/widget/windows/DirectManipulationOwner.h
new file mode 100644
index 0000000000..b4d5877d70
--- /dev/null
+++ b/widget/windows/DirectManipulationOwner.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 DirectManipulationOwner_h__
+#define DirectManipulationOwner_h__
+
+#include <windows.h>
+#include "Units.h"
+#include "nsIWidget.h" // for TouchpadGesturePhase
+
+class nsWindow;
+class IDirectManipulationManager;
+class IDirectManipulationUpdateManager;
+class IDirectManipulationViewport;
+
+namespace mozilla {
+namespace widget {
+
+class DManipEventHandler;
+
+class DirectManipulationOwner {
+ public:
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+ explicit DirectManipulationOwner(nsWindow* aWindow);
+ ~DirectManipulationOwner();
+ void Init(const LayoutDeviceIntRect& aBounds);
+ void ResizeViewport(const LayoutDeviceIntRect& aBounds);
+ void Destroy();
+
+ void SetContact(UINT aContactId);
+
+ void Update();
+
+ static void SynthesizeNativeTouchpadPan(
+ nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags);
+
+ private:
+ nsWindow* mWindow;
+ DWORD mDmViewportHandlerCookie;
+ RefPtr<IDirectManipulationManager> mDmManager;
+ RefPtr<IDirectManipulationUpdateManager> mDmUpdateManager;
+ RefPtr<IDirectManipulationViewport> mDmViewport;
+ RefPtr<DManipEventHandler> mDmHandler;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef DirectManipulationOwner_h__
diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp
new file mode 100644
index 0000000000..6f033785f5
--- /dev/null
+++ b/widget/windows/GfxInfo.cpp
@@ -0,0 +1,2082 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "GfxInfo.h"
+
+#include "gfxConfig.h"
+#include "GfxDriverInfo.h"
+#include "gfxWindowsPlatform.h"
+#include "jsapi.h"
+#include "js/PropertyAndElement.h" // JS_SetElement, JS_SetProperty
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#include "nsUnicharUtils.h"
+#include "prenv.h"
+#include "prprf.h"
+#include "xpcpublic.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/SSE.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+#include <intrin.h>
+#include <windows.h>
+#include <devguid.h> // for GUID_DEVCLASS_BATTERY
+#include <setupapi.h> // for SetupDi*
+#include <winioctl.h> // for IOCTL_*
+#include <batclass.h> // for BATTERY_*
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+static void AssertNotWin32kLockdown() {
+ // Check that we are not in Win32k lockdown
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown(),
+ "Invalid Windows GfxInfo API with Win32k lockdown");
+}
+
+/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after
+ * gfxPlatform initialization has occurred because they depend on it for
+ * information. (See bug 591561) */
+nsresult GfxInfo::GetD2DEnabled(bool* aEnabled) {
+ // Telemetry queries this during XPCOM initialization, and there's no
+ // gfxPlatform by then. Just bail out if gfxPlatform isn't initialized.
+ if (!gfxPlatform::Initialized()) {
+ *aEnabled = false;
+ return NS_OK;
+ }
+
+ // We check gfxConfig rather than the actual render mode, since the UI
+ // process does not use Direct2D if the GPU process is enabled. However,
+ // content processes can still use Direct2D.
+ *aEnabled = gfx::gfxConfig::IsEnabled(gfx::Feature::DIRECT2D);
+ return NS_OK;
+}
+
+nsresult GfxInfo::GetDWriteEnabled(bool* aEnabled) {
+ *aEnabled = gfxWindowsPlatform::GetPlatform()->DWriteEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) {
+ gfxWindowsPlatform::GetDLLVersion(L"dwrite.dll", aDwriteVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetHasBattery(bool* aHasBattery) {
+ AssertNotWin32kLockdown();
+
+ *aHasBattery = mHasBattery;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) {
+ *aEmbeddedInFirefoxReality = gfxVars::FxREmbedded();
+ return NS_OK;
+}
+
+#define PIXEL_STRUCT_RGB 1
+#define PIXEL_STRUCT_BGR 2
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) {
+ nsTArray<ClearTypeParameterInfo> clearTypeParams;
+
+ gfxWindowsPlatform::GetPlatform()->GetCleartypeParams(clearTypeParams);
+ uint32_t d, numDisplays = clearTypeParams.Length();
+ bool displayNames = (numDisplays > 1);
+ bool foundData = false;
+ nsString outStr;
+
+ for (d = 0; d < numDisplays; d++) {
+ ClearTypeParameterInfo& params = clearTypeParams[d];
+
+ if (displayNames) {
+ outStr.AppendPrintf(
+ "%S [ ", static_cast<const wchar_t*>(params.displayName.get()));
+ }
+
+ if (params.gamma >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("Gamma: %.4g ", params.gamma / 1000.0);
+ }
+
+ if (params.pixelStructure >= 0) {
+ foundData = true;
+ if (params.pixelStructure == PIXEL_STRUCT_RGB ||
+ params.pixelStructure == PIXEL_STRUCT_BGR) {
+ outStr.AppendPrintf(
+ "Pixel Structure: %s ",
+ (params.pixelStructure == PIXEL_STRUCT_RGB ? "RGB" : "BGR"));
+ } else {
+ outStr.AppendPrintf("Pixel Structure: %d ", params.pixelStructure);
+ }
+ }
+
+ if (params.clearTypeLevel >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("ClearType Level: %d ", params.clearTypeLevel);
+ }
+
+ if (params.enhancedContrast >= 0) {
+ foundData = true;
+ outStr.AppendPrintf("Enhanced Contrast: %d ", params.enhancedContrast);
+ }
+
+ if (displayNames) {
+ outStr.Append(u"] ");
+ }
+ }
+
+ if (foundData) {
+ aCleartypeParams.Assign(outStr);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetTestType(nsAString& aTestType) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName,
+ uint32_t& destValue, int type) {
+ MOZ_ASSERT(type == REG_DWORD || type == REG_QWORD);
+ HKEY key;
+ DWORD dwcbData;
+ DWORD dValue;
+ DWORD resultType;
+ LONG result;
+ nsresult retval = NS_OK;
+
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ switch (type) {
+ case REG_DWORD: {
+ // We only use this for vram size
+ dwcbData = sizeof(dValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)&dValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_DWORD) {
+ destValue = (uint32_t)(dValue / 1024 / 1024);
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case REG_QWORD: {
+ // We only use this for vram size
+ LONGLONG qValue;
+ dwcbData = sizeof(qValue);
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)&qValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_QWORD) {
+ destValue = (uint32_t)(qValue / 1024 / 1024);
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName,
+ nsAString& destString, int type) {
+ MOZ_ASSERT(type == REG_MULTI_SZ);
+
+ HKEY key;
+ DWORD dwcbData;
+ DWORD resultType;
+ LONG result;
+ nsresult retval = NS_OK;
+
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // A chain of null-separated strings; we convert the nulls to spaces
+ WCHAR wCharValue[1024];
+ dwcbData = sizeof(wCharValue);
+
+ result = RegQueryValueExW(key, keyName, nullptr, &resultType,
+ (LPBYTE)wCharValue, &dwcbData);
+ if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) {
+ // This bit here could probably be cleaner.
+ bool isValid = false;
+
+ DWORD strLen = dwcbData / sizeof(wCharValue[0]);
+ for (DWORD i = 0; i < strLen; i++) {
+ if (wCharValue[i] == '\0') {
+ if (i < strLen - 1 && wCharValue[i + 1] == '\0') {
+ isValid = true;
+ break;
+ } else {
+ wCharValue[i] = ' ';
+ }
+ }
+ }
+
+ // ensure wCharValue is null terminated
+ wCharValue[strLen - 1] = '\0';
+
+ if (isValid) destString = wCharValue;
+
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+
+ RegCloseKey(key);
+
+ return retval;
+}
+
+static nsresult GetKeyValues(const WCHAR* keyLocation, const WCHAR* keyName,
+ nsTArray<nsString>& destStrings) {
+ // First ask for the size of the value
+ DWORD size;
+ LONG rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName,
+ RRF_RT_REG_MULTI_SZ, nullptr, nullptr, &size);
+ if (rv != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create a buffer with the proper size and retrieve the value
+ WCHAR* wCharValue = new WCHAR[size / sizeof(WCHAR)];
+ rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName,
+ RRF_RT_REG_MULTI_SZ, nullptr, (LPBYTE)wCharValue, &size);
+ if (rv != ERROR_SUCCESS) {
+ delete[] wCharValue;
+ return NS_ERROR_FAILURE;
+ }
+
+ // The value is a sequence of null-terminated strings, usually terminated by
+ // an empty string (\0). RegGetValue ensures that the value is properly
+ // terminated with a null character.
+ DWORD i = 0;
+ DWORD strLen = size / sizeof(WCHAR);
+ while (i < strLen) {
+ nsString value(wCharValue + i);
+ if (!value.IsEmpty()) {
+ destStrings.AppendElement(value);
+ }
+ i += value.Length() + 1;
+ }
+ delete[] wCharValue;
+
+ return NS_OK;
+}
+
+// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
+// this function is used to extract the id's out of it
+uint32_t ParseIDFromDeviceID(const nsAString& key, const nsAString& prefix,
+ int length) {
+ nsAutoString id(key);
+ ToUpperCase(id);
+ int32_t start = id.Find(prefix);
+ if (start != -1) {
+ id.Cut(0, start + prefix.Length());
+ id.Truncate(length);
+ }
+ if (id.Equals(L"QCOM", nsCaseInsensitiveStringComparator)) {
+ // String format assumptions are broken, so use a Qualcomm PCI Vendor ID
+ // for now. See also GfxDriverInfo::GetDeviceVendor.
+ return 0x5143;
+ }
+ nsresult err;
+ return id.ToInteger(&err, 16);
+}
+
+// OS version in 16.16 major/minor form
+// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
+enum {
+ kWindowsUnknown = 0,
+ kWindows7 = 0x60001,
+ kWindows8 = 0x60002,
+ kWindows8_1 = 0x60003,
+ kWindows10 = 0xA0000
+};
+
+static bool HasBattery() {
+ // Helper classes to manage lifetimes of Windows structs.
+ class MOZ_STACK_CLASS HDevInfoHolder final {
+ public:
+ explicit HDevInfoHolder(HDEVINFO aHandle) : mHandle(aHandle) {}
+
+ ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle); }
+
+ private:
+ HDEVINFO mHandle;
+ };
+
+ class MOZ_STACK_CLASS HandleHolder final {
+ public:
+ explicit HandleHolder(HANDLE aHandle) : mHandle(aHandle) {}
+
+ ~HandleHolder() { ::CloseHandle(mHandle); }
+
+ private:
+ HANDLE mHandle;
+ };
+
+ HDEVINFO hdev =
+ ::SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (hdev == INVALID_HANDLE_VALUE) {
+ return true;
+ }
+
+ HDevInfoHolder hdevHolder(hdev);
+
+ DWORD i = 0;
+ SP_DEVICE_INTERFACE_DATA did = {0};
+ did.cbSize = sizeof(did);
+
+ while (::SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVCLASS_BATTERY, i,
+ &did)) {
+ DWORD bufferSize = 0;
+ ::SetupDiGetDeviceInterfaceDetail(hdev, &did, nullptr, 0, &bufferSize,
+ nullptr);
+ if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return true;
+ }
+
+ UniquePtr<uint8_t[]> buffer(new (std::nothrow) uint8_t[bufferSize]);
+ if (!buffer) {
+ return true;
+ }
+
+ PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd =
+ reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.get());
+ pdidd->cbSize = sizeof(*pdidd);
+ if (!::SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, bufferSize,
+ &bufferSize, nullptr)) {
+ return true;
+ }
+
+ HANDLE hbat = ::CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hbat == INVALID_HANDLE_VALUE) {
+ return true;
+ }
+
+ HandleHolder hbatHolder(hbat);
+
+ BATTERY_QUERY_INFORMATION bqi = {0};
+ DWORD dwWait = 0;
+ DWORD dwOut;
+
+ // We need the tag to query the information below.
+ if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_TAG, &dwWait,
+ sizeof(dwWait), &bqi.BatteryTag,
+ sizeof(bqi.BatteryTag), &dwOut, nullptr) ||
+ !bqi.BatteryTag) {
+ return true;
+ }
+
+ BATTERY_INFORMATION bi = {0};
+ bqi.InformationLevel = BatteryInformation;
+
+ if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, &bqi,
+ sizeof(bqi), &bi, sizeof(bi), &dwOut, nullptr)) {
+ return true;
+ }
+
+ // If a battery intended for general use (i.e. system use) is not a UPS
+ // (i.e. short term), then we know for certain we have a battery.
+ if ((bi.Capabilities & BATTERY_SYSTEM_BATTERY) &&
+ !(bi.Capabilities & BATTERY_IS_SHORT_TERM)) {
+ return true;
+ }
+
+ // Otherwise we check the next battery.
+ ++i;
+ }
+
+ // If we fail to enumerate because there are no more batteries to check, then
+ // we can safely say there are indeed no system batteries.
+ return ::GetLastError() != ERROR_NO_MORE_ITEMS;
+}
+
+/* Other interesting places for info:
+ * IDXGIAdapter::GetDesc()
+ * IDirectDraw7::GetAvailableVidMem()
+ * e->GetAvailableTextureMem()
+ * */
+
+#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
+nsresult GfxInfo::Init() {
+ nsresult rv = GfxInfoBase::Init();
+
+ // If we are locked down in a content process, we can't call any of the
+ // Win32k APIs below. Any method that accesses members of this class should
+ // assert that it's not used in content
+ if (IsWin32kLockedDown()) {
+ return rv;
+ }
+
+ mHasBattery = HasBattery();
+
+ DISPLAY_DEVICEW displayDevice;
+ displayDevice.cb = sizeof(displayDevice);
+ int deviceIndex = 0;
+
+ const char* spoofedWindowsVersion =
+ PR_GetEnv("MOZ_GFX_SPOOF_WINDOWS_VERSION");
+ if (spoofedWindowsVersion) {
+ PR_sscanf(spoofedWindowsVersion, "%x,%u", &mWindowsVersion,
+ &mWindowsBuildNumber);
+ } else {
+ OSVERSIONINFO vinfo;
+ vinfo.dwOSVersionInfoSize = sizeof(vinfo);
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4996)
+#endif
+ if (!GetVersionEx(&vinfo)) {
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+ mWindowsVersion = kWindowsUnknown;
+ } else {
+ mWindowsVersion =
+ int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion;
+ mWindowsBuildNumber = vinfo.dwBuildNumber;
+ }
+ }
+
+ mDeviceKeyDebug = u"PrimarySearch"_ns;
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
+ mDeviceKeyDebug = u"NullSearch"_ns;
+ break;
+ }
+ deviceIndex++;
+ }
+
+ // make sure the string is nullptr terminated
+ if (wcsnlen(displayDevice.DeviceKey, ArrayLength(displayDevice.DeviceKey)) ==
+ ArrayLength(displayDevice.DeviceKey)) {
+ // we did not find a nullptr
+ return rv;
+ }
+
+ mDeviceKeyDebug = displayDevice.DeviceKey;
+
+ /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
+ /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
+ /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need
+ * to compare case insenstively */
+ /* If the device key is empty, we are most likely in a remote desktop
+ * environment. In this case we set the devicekey to an empty string so
+ * it can be handled later.
+ */
+ if (displayDevice.DeviceKey[0] != '\0') {
+ if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX,
+ ArrayLength(DEVICE_KEY_PREFIX) - 1) != 0) {
+ return rv;
+ }
+
+ // chop off DEVICE_KEY_PREFIX
+ mDeviceKey[0] =
+ displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX) - 1;
+ } else {
+ mDeviceKey[0].Truncate();
+ }
+
+ mDeviceID[0] = displayDevice.DeviceID;
+ mDeviceString[0] = displayDevice.DeviceString;
+
+ // On Windows 8 and Server 2012 hosts, we want to not block RDP
+ // sessions from attempting hardware acceleration. RemoteFX
+ // provides features and functionaltiy that can give a good D3D10 +
+ // D2D + DirectWrite experience emulated via a software GPU.
+ //
+ // Unfortunately, the Device ID is nullptr, and we can't enumerate
+ // it using the setup infrastructure (SetupDiGetClassDevsW below
+ // will return INVALID_HANDLE_VALUE).
+ UINT flags = DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES;
+ if (mWindowsVersion >= kWindows8 && mDeviceID[0].Length() == 0 &&
+ mDeviceString[0].EqualsLiteral("RDPUDD Chained DD")) {
+ WCHAR sysdir[255];
+ UINT len = GetSystemDirectory(sysdir, sizeof(sysdir));
+ if (len < sizeof(sysdir)) {
+ nsString rdpudd(sysdir);
+ rdpudd.AppendLiteral("\\rdpudd.dll");
+ gfxWindowsPlatform::GetDLLVersion(rdpudd.BeginReading(),
+ mDriverVersion[0]);
+ mDriverDate[0].AssignLiteral("01-01-1970");
+
+ // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code
+ mDeviceID[0].AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000");
+ flags |= DIGCF_DEVICEINTERFACE;
+ }
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo =
+ SetupDiGetClassDevsW(nullptr, mDeviceID[0].get(), nullptr, flags);
+
+ if (devinfo != INVALID_HANDLE_VALUE) {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+
+ devinfoData.cbSize = sizeof(devinfoData);
+ constexpr auto driverKeyPre =
+ u"System\\CurrentControlSet\\Control\\Class\\"_ns;
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(devinfo, &devinfoData, SPDRP_DRIVER,
+ nullptr, (PBYTE)value,
+ sizeof(value), nullptr)) {
+ nsAutoString driverKey(driverKeyPre);
+ driverKey += value;
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey.get(), 0,
+ KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ /* we've found the driver we're looking for */
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverVersion[0] = value;
+ } else {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ mDriverVersion[0].AssignLiteral("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverDate[0] = value;
+ } else {
+ // Again, assume the worst
+ mDriverDate[0].AssignLiteral("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+
+ // It is convenient to have these as integers
+ uint32_t adapterVendorID[2] = {0, 0};
+ uint32_t adapterDeviceID[2] = {0, 0};
+ uint32_t adapterSubsysID[2] = {0, 0};
+
+ adapterVendorID[0] = ParseIDFromDeviceID(mDeviceID[0], u"VEN_"_ns, 4);
+ adapterDeviceID[0] = ParseIDFromDeviceID(mDeviceID[0], u"&DEV_"_ns, 4);
+ adapterSubsysID[0] = ParseIDFromDeviceID(mDeviceID[0], u"&SUBSYS_"_ns, 8);
+
+ // Sometimes we don't get the valid device using this method. For now,
+ // allow zero vendor or device as valid, as long as the other value is
+ // non-zero.
+ bool foundValidDevice = (adapterVendorID[0] != 0 || adapterDeviceID[0] != 0);
+
+ // We now check for second display adapter. If we didn't find the valid
+ // device using the original approach, we will try the alternative.
+
+ // Device interface class for display adapters.
+ CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
+ HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
+ &GUID_DISPLAY_DEVICE_ARRIVAL);
+ if (hresult == NOERROR) {
+ devinfo =
+ SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, nullptr, nullptr,
+ DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
+
+ if (devinfo != INVALID_HANDLE_VALUE) {
+ HKEY key;
+ LONG result;
+ WCHAR value[255];
+ DWORD dwcbData;
+ SP_DEVINFO_DATA devinfoData;
+ DWORD memberIndex = 0;
+ devinfoData.cbSize = sizeof(devinfoData);
+
+ nsAutoString adapterDriver2;
+ nsAutoString deviceID2;
+ nsAutoString driverVersion2;
+ nsAutoString driverDate2;
+
+ constexpr auto driverKeyPre =
+ u"System\\CurrentControlSet\\Control\\Class\\"_ns;
+ /* enumerate device information elements in the device information set */
+ while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) {
+ /* get a string that identifies the device's driver key */
+ if (SetupDiGetDeviceRegistryPropertyW(
+ devinfo, &devinfoData, SPDRP_DRIVER, nullptr, (PBYTE)value,
+ sizeof(value), nullptr)) {
+ nsAutoString driverKey2(driverKeyPre);
+ driverKey2 += value;
+ result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey2.get(), 0,
+ KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
+ nullptr, (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ continue;
+ }
+ deviceID2 = value;
+ adapterVendorID[1] = ParseIDFromDeviceID(deviceID2, u"VEN_"_ns, 4);
+ adapterDeviceID[1] = ParseIDFromDeviceID(deviceID2, u"&DEV_"_ns, 4);
+ // Skip the devices we already considered, as well as any
+ // "zero" ones.
+ if ((adapterVendorID[0] == adapterVendorID[1] &&
+ adapterDeviceID[0] == adapterDeviceID[1]) ||
+ (adapterVendorID[1] == 0 && adapterDeviceID[1] == 0)) {
+ RegCloseKey(key);
+ continue;
+ }
+
+ // If this device is missing driver information, it is unlikely to
+ // be a real display adapter.
+ if (NS_FAILED(GetKeyValue(driverKey2.get(),
+ L"InstalledDisplayDrivers",
+ adapterDriver2, REG_MULTI_SZ))) {
+ RegCloseKey(key);
+ continue;
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(key);
+ continue;
+ }
+ driverVersion2 = value;
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(key);
+ continue;
+ }
+ driverDate2 = value;
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"Device Description", nullptr,
+ nullptr, (LPBYTE)value, &dwcbData);
+ if (result != ERROR_SUCCESS) {
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ }
+ RegCloseKey(key);
+ if (result == ERROR_SUCCESS) {
+ // If we didn't find a valid device with the original method
+ // take this one, and continue looking for the second GPU.
+ if (!foundValidDevice) {
+ foundValidDevice = true;
+ adapterVendorID[0] = adapterVendorID[1];
+ adapterDeviceID[0] = adapterDeviceID[1];
+ mDeviceString[0] = value;
+ mDeviceID[0] = deviceID2;
+ mDeviceKey[0] = driverKey2;
+ mDriverVersion[0] = driverVersion2;
+ mDriverDate[0] = driverDate2;
+ adapterSubsysID[0] =
+ ParseIDFromDeviceID(mDeviceID[0], u"&SUBSYS_"_ns, 8);
+ continue;
+ }
+
+ mHasDualGPU = true;
+ mDeviceString[1] = value;
+ mDeviceID[1] = deviceID2;
+ mDeviceKey[1] = driverKey2;
+ mDriverVersion[1] = driverVersion2;
+ mDriverDate[1] = driverDate2;
+ adapterSubsysID[1] =
+ ParseIDFromDeviceID(mDeviceID[1], u"&SUBSYS_"_ns, 8);
+ mAdapterVendorID[1].AppendPrintf("0x%04x", adapterVendorID[1]);
+ mAdapterDeviceID[1].AppendPrintf("0x%04x", adapterDeviceID[1]);
+ mAdapterSubsysID[1].AppendPrintf("%08x", adapterSubsysID[1]);
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+
+ mAdapterVendorID[0].AppendPrintf("0x%04x", adapterVendorID[0]);
+ mAdapterDeviceID[0].AppendPrintf("0x%04x", adapterDeviceID[0]);
+ mAdapterSubsysID[0].AppendPrintf("%08x", adapterSubsysID[0]);
+
+ // Sometimes, the enumeration is not quite right and the two adapters
+ // end up being swapped. Actually enumerate the adapters that come
+ // back from the DXGI factory to check, and tag the second as active
+ // if found.
+ if (mHasDualGPU) {
+ nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll"));
+ decltype(CreateDXGIFactory)* createDXGIFactory =
+ (decltype(CreateDXGIFactory)*)GetProcAddress(dxgiModule,
+ "CreateDXGIFactory");
+
+ if (createDXGIFactory) {
+ RefPtr<IDXGIFactory> factory = nullptr;
+ createDXGIFactory(__uuidof(IDXGIFactory), (void**)(&factory));
+ if (factory) {
+ RefPtr<IDXGIAdapter> adapter;
+ if (SUCCEEDED(factory->EnumAdapters(0, getter_AddRefs(adapter)))) {
+ DXGI_ADAPTER_DESC desc;
+ PodZero(&desc);
+ if (SUCCEEDED(adapter->GetDesc(&desc))) {
+ if (desc.VendorId != adapterVendorID[0] &&
+ desc.DeviceId != adapterDeviceID[0] &&
+ desc.VendorId == adapterVendorID[1] &&
+ desc.DeviceId == adapterDeviceID[1]) {
+ mActiveGPUIndex = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mHasDriverVersionMismatch = false;
+ if (mAdapterVendorID[mActiveGPUIndex] ==
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel)) {
+ // we've had big crashers (bugs 590373 and 595364) apparently correlated
+ // with bad Intel driver installations where the DriverVersion reported
+ // by the registry was not the version of the DLL.
+
+ // Note that these start without the .dll extension but eventually gain it.
+ bool is64bitApp = sizeof(void*) == 8;
+ nsAutoString dllFileName(is64bitApp ? u"igd10umd64" : u"igd10umd32");
+ nsAutoString dllFileName2(is64bitApp ? u"igd10iumd64" : u"igd10iumd32");
+
+ nsString dllVersion, dllVersion2;
+ uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0,
+ driverNumericVersion = 0, knownSafeMismatchVersion = 0;
+
+ // Only parse the DLL version for those found in the driver list
+ nsAutoString eligibleDLLs;
+ if (NS_SUCCEEDED(GetAdapterDriver(eligibleDLLs))) {
+ if (FindInReadable(dllFileName, eligibleDLLs)) {
+ dllFileName += u".dll"_ns;
+ gfxWindowsPlatform::GetDLLVersion(dllFileName.get(), dllVersion);
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ }
+ if (FindInReadable(dllFileName2, eligibleDLLs)) {
+ dllFileName2 += u".dll"_ns;
+ gfxWindowsPlatform::GetDLLVersion(dllFileName2.get(), dllVersion2);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+ }
+ }
+
+ // Sometimes the DLL is not in the System32 nor SysWOW64 directories. But
+ // UserModeDriverName (or UserModeDriverNameWow, if available) might provide
+ // the full path to the DLL in some DriverStore FileRepository.
+ if (dllNumericVersion == 0 && dllNumericVersion2 == 0) {
+ nsTArray<nsString> eligibleDLLpaths;
+ const WCHAR* keyLocation = mDeviceKey[mActiveGPUIndex].get();
+ GetKeyValues(keyLocation, L"UserModeDriverName", eligibleDLLpaths);
+ GetKeyValues(keyLocation, L"UserModeDriverNameWow", eligibleDLLpaths);
+ size_t length = eligibleDLLpaths.Length();
+ for (size_t i = 0;
+ i < length && dllNumericVersion == 0 && dllNumericVersion2 == 0;
+ ++i) {
+ if (FindInReadable(dllFileName, eligibleDLLpaths[i])) {
+ gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(),
+ dllVersion);
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ } else if (FindInReadable(dllFileName2, eligibleDLLpaths[i])) {
+ gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(),
+ dllVersion2);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+ }
+ }
+ }
+
+ ParseDriverVersion(mDriverVersion[mActiveGPUIndex], &driverNumericVersion);
+ ParseDriverVersion(u"9.17.10.0"_ns, &knownSafeMismatchVersion);
+
+ // If there's a driver version mismatch, consider this harmful only when
+ // the driver version is less than knownSafeMismatchVersion. See the
+ // above comment about crashes with old mismatches. If the GetDllVersion
+ // call fails, we are not calling it a mismatch.
+ if ((dllNumericVersion != 0 && dllNumericVersion != driverNumericVersion) ||
+ (dllNumericVersion2 != 0 &&
+ dllNumericVersion2 != driverNumericVersion)) {
+ if (driverNumericVersion < knownSafeMismatchVersion ||
+ std::max(dllNumericVersion, dllNumericVersion2) <
+ knownSafeMismatchVersion) {
+ mHasDriverVersionMismatch = true;
+ gfxCriticalNoteOnce
+ << "Mismatched driver versions between the registry "
+ << NS_ConvertUTF16toUTF8(mDriverVersion[mActiveGPUIndex]).get()
+ << " and DLL(s) " << NS_ConvertUTF16toUTF8(dllVersion).get() << ", "
+ << NS_ConvertUTF16toUTF8(dllVersion2).get() << " reported.";
+ }
+ } else if (dllNumericVersion == 0 && dllNumericVersion2 == 0) {
+ // Leave it as an asserting error for now, to see if we can find
+ // a system that exhibits this kind of a problem internally.
+ gfxCriticalErrorOnce()
+ << "Potential driver version mismatch ignored due to missing DLLs "
+ << NS_ConvertUTF16toUTF8(dllFileName).get()
+ << " v=" << NS_ConvertUTF16toUTF8(dllVersion).get() << " and "
+ << NS_ConvertUTF16toUTF8(dllFileName2).get()
+ << " v=" << NS_ConvertUTF16toUTF8(dllVersion2).get();
+ }
+ }
+
+ const char* spoofedDriverVersionString =
+ PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION");
+ if (spoofedDriverVersionString) {
+ mDriverVersion[mActiveGPUIndex].AssignASCII(spoofedDriverVersionString);
+ }
+
+ const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID");
+ if (spoofedVendor) {
+ mAdapterVendorID[mActiveGPUIndex].AssignASCII(spoofedVendor);
+ }
+
+ const char* spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID");
+ if (spoofedDevice) {
+ mAdapterDeviceID[mActiveGPUIndex].AssignASCII(spoofedDevice);
+ }
+
+ AddCrashReportAnnotations();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDescription = mDeviceString[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDescription = mDeviceString[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ AssertNotWin32kLockdown();
+
+ uint32_t result = 0;
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"HardwareInformation.qwMemorySize", result,
+ REG_QWORD)) ||
+ result == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"HardwareInformation.MemorySize", result,
+ REG_DWORD))) {
+ result = 0;
+ }
+ }
+ *aAdapterRAM = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) {
+ AssertNotWin32kLockdown();
+
+ uint32_t result = 0;
+ if (mHasDualGPU) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"HardwareInformation.qwMemorySize", result,
+ REG_QWORD)) ||
+ result == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"HardwareInformation.MemorySize", result,
+ REG_DWORD))) {
+ result = 0;
+ }
+ }
+ }
+ *aAdapterRAM = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ AssertNotWin32kLockdown();
+
+ if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(),
+ L"InstalledDisplayDrivers", aAdapterDriver,
+ REG_MULTI_SZ)))
+ aAdapterDriver = L"Unknown";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ AssertNotWin32kLockdown();
+
+ if (!mHasDualGPU) {
+ aAdapterDriver.Truncate();
+ } else if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(),
+ L"InstalledDisplayDrivers", aAdapterDriver,
+ REG_MULTI_SZ))) {
+ aAdapterDriver = L"Unknown";
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ aAdapterDriverVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDriverVersion = mDriverVersion[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDriverDate = mDriverDate[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ aAdapterDriverVendor.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDriverVersion = mDriverVersion[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDriverDate = mDriverDate[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterVendorID = mAdapterVendorID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterVendorID = mAdapterVendorID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDeviceID = mAdapterDeviceID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterDeviceID = mAdapterDeviceID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterSubsysID = mAdapterSubsysID[mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ AssertNotWin32kLockdown();
+
+ aAdapterSubsysID = mAdapterSubsysID[1 - mActiveGPUIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) {
+ // This is never the case, as the active GPU ends up being
+ // the first one. It should probably be removed.
+ *aIsGPU2Active = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* Cisco's VPN software can cause corruption of the floating point state.
+ * Make a note of this in our crash reports so that some weird crashes
+ * make more sense */
+static void CheckForCiscoVPN() {
+ LONG result;
+ HKEY key;
+ /* This will give false positives, but hopefully no false negatives */
+ result =
+ RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Cisco Systems\\VPN Client",
+ 0, KEY_QUERY_VALUE, &key);
+ if (result == ERROR_SUCCESS) {
+ RegCloseKey(key);
+ CrashReporter::AppendAppNotesToCrashReport("Cisco VPN\n"_ns);
+ }
+}
+
+void GfxInfo::AddCrashReportAnnotations() {
+ AssertNotWin32kLockdown();
+
+ CheckForCiscoVPN();
+
+ if (mHasDriverVersionMismatch) {
+ CrashReporter::AppendAppNotesToCrashReport("DriverVersionMismatch\n"_ns);
+ }
+
+ nsString deviceID, vendorID, driverVersion, subsysID;
+ nsCString narrowDeviceID, narrowVendorID, narrowDriverVersion, narrowSubsysID;
+
+ GetAdapterDeviceID(deviceID);
+ CopyUTF16toUTF8(deviceID, narrowDeviceID);
+ GetAdapterVendorID(vendorID);
+ CopyUTF16toUTF8(vendorID, narrowVendorID);
+ GetAdapterDriverVersion(driverVersion);
+ CopyUTF16toUTF8(driverVersion, narrowDriverVersion);
+ GetAdapterSubsysID(subsysID);
+ CopyUTF16toUTF8(subsysID, narrowSubsysID);
+
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
+ narrowVendorID);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
+ narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVersion, narrowDriverVersion);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterSubsysID,
+ narrowSubsysID);
+
+ /* Add an App Note, this contains extra information. */
+ nsAutoCString note;
+
+ // TODO: We should probably convert this into a proper annotation
+ if (vendorID == GfxDriverInfo::GetDeviceVendor(DeviceVendor::All)) {
+ /* if we didn't find a valid vendorID lets append the mDeviceID string to
+ * try to find out why */
+ LossyAppendUTF16toASCII(mDeviceID[mActiveGPUIndex], note);
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ }
+ note.AppendLiteral("\n");
+
+ if (mHasDualGPU) {
+ nsString deviceID2, vendorID2, subsysID2;
+ nsAutoString adapterDriverVersionString2;
+ nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2;
+
+ // Make a slight difference between the two cases so that we
+ // can see it in the crash reports. It may come in handy.
+ if (mActiveGPUIndex == 1) {
+ note.AppendLiteral("Has dual GPUs. GPU-#2: ");
+ } else {
+ note.AppendLiteral("Has dual GPUs. GPU #2: ");
+ }
+ GetAdapterDeviceID2(deviceID2);
+ CopyUTF16toUTF8(deviceID2, narrowDeviceID2);
+ GetAdapterVendorID2(vendorID2);
+ CopyUTF16toUTF8(vendorID2, narrowVendorID2);
+ GetAdapterDriverVersion2(adapterDriverVersionString2);
+ GetAdapterSubsysID(subsysID2);
+ CopyUTF16toUTF8(subsysID2, narrowSubsysID2);
+ note.AppendLiteral("AdapterVendorID2: ");
+ note.Append(narrowVendorID2);
+ note.AppendLiteral(", AdapterDeviceID2: ");
+ note.Append(narrowDeviceID2);
+ note.AppendLiteral(", AdapterSubsysID2: ");
+ note.Append(narrowSubsysID2);
+ note.AppendLiteral(", AdapterDriverVersion2: ");
+ note.Append(NS_LossyConvertUTF16toASCII(adapterDriverVersionString2));
+ }
+ CrashReporter::AppendAppNotesToCrashReport(note);
+}
+
+static OperatingSystem WindowsVersionToOperatingSystem(
+ int32_t aWindowsVersion) {
+ switch (aWindowsVersion) {
+ case kWindows7:
+ return OperatingSystem::Windows7;
+ case kWindows8:
+ return OperatingSystem::Windows8;
+ case kWindows8_1:
+ return OperatingSystem::Windows8_1;
+ case kWindows10:
+ return OperatingSystem::Windows10;
+ case kWindowsUnknown:
+ default:
+ return OperatingSystem::Unknown;
+ }
+}
+
+// Return true if the CPU supports AVX, but the operating system does not.
+#if defined(_M_X64)
+static inline bool DetectBrokenAVX() {
+ int regs[4];
+ __cpuid(regs, 0);
+ if (regs[0] == 0) {
+ // Level not supported.
+ return false;
+ }
+
+ __cpuid(regs, 1);
+
+ const unsigned AVX = 1u << 28;
+ const unsigned XSAVE = 1u << 26;
+ if ((regs[2] & (AVX | XSAVE)) != (AVX | XSAVE)) {
+ // AVX is not supported on this CPU.
+ return false;
+ }
+
+ const unsigned OSXSAVE = 1u << 27;
+ if ((regs[2] & OSXSAVE) != OSXSAVE) {
+ // AVX is supported, but the OS didn't enable it.
+ // This can be forced via bcdedit /set xsavedisable 1.
+ return true;
+ }
+
+ const unsigned AVX_CTRL_BITS = (1 << 1) | (1 << 2);
+ return (xgetbv(0) & AVX_CTRL_BITS) != AVX_CTRL_BITS;
+}
+#endif
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (!sDriverInfo->Length()) {
+ /*
+ * It should be noted here that more specialized rules on certain features
+ * should be inserted -before- more generalized restriction. As the first
+ * match for feature/OS/device found in the list will be used for the final
+ * blocklisting call.
+ */
+
+ /*
+ * NVIDIA entries
+ */
+ /*
+ * The last 5 digit of the NVIDIA driver version maps to the version that
+ * NVIDIA uses. The minor version (15, 16, 17) corresponds roughtly to the
+ * OS (Vista, Win7, Win7) but they show up in smaller numbers across all
+ * OS versions (perhaps due to OS upgrades). So we want to support
+ * October 2009+ drivers across all these minor versions.
+ *
+ * 187.45 (late October 2009) and earlier contain a bug which can cause us
+ * to crash on shutdown.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(8, 15, 11, 8745), "FEATURE_FAILURE_NV_W7_15",
+ "nVidia driver > 187.45");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 16, 10, 0000), V(8, 16, 11, 8745),
+ "FEATURE_FAILURE_NV_W7_16", "nVidia driver > 187.45");
+ // Telemetry doesn't show any driver in this range so it might not even be
+ // required.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 17, 10, 0000), V(8, 17, 11, 8745),
+ "FEATURE_FAILURE_NV_W7_17", "nVidia driver > 187.45");
+
+ /*
+ * AMD/ATI entries. 8.56.1.15 is the driver that shipped with Windows 7 RTM
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8, 56, 1, 15),
+ "FEATURE_FAILURE_AMD1", "8.56.1.15");
+
+ // Bug 1099252
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 832, 0, 0),
+ "FEATURE_FAILURE_BUG_1099252");
+
+ // Bug 1118695
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8, 783, 2, 2000),
+ "FEATURE_FAILURE_BUG_1118695");
+
+ // Bug 1587155
+ //
+ // There are a several reports of strange rendering corruptions with this
+ // driver version, with and without webrender. We weren't able to
+ // reproduce these problems, but the users were able to update their
+ // drivers and it went away. So just to be safe, let's blocklist all
+ // gpu use with this particular (very old) driver, restricted
+ // to Win10 since we only have reports from that platform.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(22, 19, 162, 4), "FEATURE_FAILURE_BUG_1587155");
+
+ // Bug 1829487 - Work around a gen6 driver bug that miscompiles shaders
+ // resulting
+ // in black squares. Disabling shader optimization pass
+ // appears to work around this for now.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelSandyBridge,
+ nsIGfxInfo::FEATURE_WEBRENDER_OPTIMIZED_SHADERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1829487");
+
+ // Bug 1198815
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(15, 200, 0, 0), V(15, 200, 1062, 1004), "FEATURE_FAILURE_BUG_1198815",
+ "15.200.0.0-15.200.1062.1004");
+
+ // Bug 1267970
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(15, 200, 0, 0), V(15, 301, 2301, 1002), "FEATURE_FAILURE_BUG_1267970",
+ "15.200.0.0-15.301.2301.1002");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(16, 100, 0, 0), V(16, 300, 2311, 0), "FEATURE_FAILURE_BUG_1267970",
+ "16.100.0.0-16.300.2311.0");
+
+ /*
+ * Bug 783517 - crashes in AMD driver on Windows 8
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows8, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8, 982, 0, 0), V(8, 983, 0, 0),
+ "FEATURE_FAILURE_BUG_783517_AMD", "!= 8.982.*.*");
+
+ /*
+ * Bug 1599981 - crashes in AMD driver on Windows 10
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::RadeonCaicos,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(15, 301, 1901, 0), "FEATURE_FAILURE_BUG_1599981");
+
+ /* OpenGL on any ATI/AMD hardware is discouraged
+ * See:
+ * bug 619773 - WebGL: Crash with blue screen : "NMI: Parity Check / Memory
+ * Parity Error" bugs 584403, 584404, 620924 - crashes in atioglxx
+ * + many complaints about incorrect rendering
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_OGL_ATI_DIS");
+
+/*
+ * Intel entries
+ */
+
+/* The driver versions used here come from bug 594877. They might not
+ * be particularly relevant anymore.
+ */
+#define IMPLEMENT_INTEL_DRIVER_BLOCKLIST(winVer, devFamily, driverVer, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST2(winVer, devFamily, \
+ GfxDriverInfo::optionalFeatures, \
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_LESS_THAN, driverVer, ruleId)
+
+#define IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(winVer, devFamily, driverVer, \
+ ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST2(winVer, devFamily, nsIGfxInfo::FEATURE_DIRECT2D, \
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId)
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA500, 2026,
+ "FEATURE_FAILURE_594877_7");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA900,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA950, 1930,
+ "FEATURE_FAILURE_594877_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMA3150, 2117,
+ "FEATURE_FAILURE_594877_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMAX3000, 1930,
+ "FEATURE_FAILURE_594877_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ 2202, "FEATURE_FAILURE_594877_12");
+
+ /* Disable Direct2D on Intel GMAX4500 devices because of rendering
+ * corruption discovered in bug 1180379. These seems to affect even the most
+ * recent drivers. We're black listing all of the devices to be safe even
+ * though we've only confirmed the issue on the G45
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelGMAX4500HD,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_1180379");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA500, V(5, 0, 0, 2026),
+ "FEATURE_FAILURE_INTEL_16");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA900,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA950,
+ V(8, 15, 10, 1930), "FEATURE_FAILURE_INTEL_18");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMA3150,
+ V(8, 14, 10, 1972), "FEATURE_FAILURE_INTEL_19");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX3000,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_20");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_21");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_22");
+
+ // Bug 1074378
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_1", "8.15.10.2342");
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(8, 15, 10, 1749), "FEATURE_FAILURE_BUG_1074378_2", "8.15.10.2342");
+
+ /* OpenGL on any Intel hardware is discouraged */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_INTEL_OGL_DIS");
+
+ /**
+ * Disable acceleration on Intel HD 3000 for graphics drivers
+ * <= 8.15.10.2321. See bug 1018278 and bug 1060736.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows,
+ DeviceFamily::IntelSandyBridge,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2321,
+ "FEATURE_FAILURE_BUG_1018278", "X.X.X.2342");
+
+ /**
+ * Disable D2D on Win7 on Intel Haswell for graphics drivers build id <=
+ * 4578. See bug 1432610
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ DeviceFamily::IntelHaswell,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4578,
+ "FEATURE_FAILURE_BUG_1432610");
+ /**
+ * Disable VP8 HW decoding on Windows 8.1 on Intel Haswel and a certain
+ * driver version. See bug 1760464 comment 6 and bug 1761332.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows8_1, DeviceFamily::IntelHaswell,
+ nsIGfxInfo::FEATURE_VP8_HW_DECODE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1760464");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows8_1, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_VP8_HW_DECODE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(10, 18, 14, 4264), "FEATURE_FAILURE_BUG_1761332");
+
+ /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302
+ * See bug 806786
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelMobileHDGraphics,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_806786");
+
+ /* Disable D2D on Win8 on Intel HD Graphics on driver <= 8.15.10.2302
+ * See bug 804144 and 863683
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows8, DeviceFamily::IntelMobileHDGraphics,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_804144");
+
+ /* Disable D2D on Win7 on Intel HD Graphics on driver == 8.15.10.2418
+ * See bug 1433790
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(8, 15, 10, 2418), "FEATURE_FAILURE_BUG_1433790");
+
+ /* Disable D3D11 layers on Intel G41 express graphics and Intel GM965, Intel
+ * X3100, for causing device resets. See bug 1116812.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1116812,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1116812");
+
+ /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared
+ * handle for textures. See bug 1207665. Additionally block D2D so we don't
+ * accidentally use WARP.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1207665,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_1");
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Bug1207665,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1207665_2");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::QualcommAll,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_QUALCOMM");
+
+ // Bug 1548410. Disable hardware accelerated video decoding on
+ // Qualcomm drivers used on Windows on ARM64 which are known to
+ // cause BSOD's and output suprious green frames while decoding video.
+ // Bug 1592826 expands the blocklist.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::QualcommAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL,
+ V(25, 18, 10440, 0), "FEATURE_FAILURE_BUG_1592826");
+
+ /* Disable D2D on AMD Catalyst 14.4 until 14.6
+ * See bug 984488
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_DIRECT2D,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(14, 1, 0, 0), V(14, 2, 0, 0),
+ "FEATURE_FAILURE_BUG_984488_1", "ATI Catalyst 14.6+");
+
+ /* Disable D3D9 layers on NVIDIA 6100/6150/6200 series due to glitches
+ * whilst scrolling. See bugs: 612007, 644787 & 645872.
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaBlockD3D9Layers,
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_612007");
+
+ /* Microsoft RemoteFX; blocked less than 6.2.0.0 */
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Windows, DeviceFamily::MicrosoftAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(6, 2, 0, 0), "< 6.2.0.0", "FEATURE_FAILURE_REMOTE_FX");
+
+ /* Bug 1008759: Optimus (NVidia) crash. Disable D2D on NV 310M. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Nvidia310M,
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1008759");
+
+ /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(15, 200, 1006, 0), "FEATURE_FAILURE_BUG_1139503");
+
+ /* Bug 1213107: D3D9 crashes with ATI cards on Windows 7. */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_1",
+ "Radeon driver > 8.862.6.5000");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_2",
+ "Radeon driver > 8.862.6.5000");
+
+ /* This may not be needed at all */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1155608,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(8, 15, 10, 2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING");
+
+ /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(9, 17, 10, 0), V(9, 17, 10, 2849), "FEATURE_FAILURE_BUG_1203199_1",
+ "Intel driver > 9.17.10.2849");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::Nvidia8800GTS,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL,
+ V(9, 18, 13, 4052), "FEATURE_FAILURE_BUG_1203199_2");
+
+ /* Bug 1137716: XXX this should really check for the matching Intel piece as
+ * well. Unfortunately, we don't have the infrastructure to do that */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1137716,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 17, 12, 5730), V(8, 17, 12, 6901), "FEATURE_FAILURE_BUG_1137716",
+ "Nvidia driver > 8.17.12.6901");
+
+ /* Bug 1336710: Crash in rx::Blit9::initialize. */
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::WindowsXP, DeviceFamily::IntelGMAX4500HD,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1336710");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::WindowsXP, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_FAILURE_BUG_1336710");
+
+ /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ DeviceFamily::IntelGMAX3000,
+ nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749,
+ "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS");
+
+ /* Bug 1717519/1717911: Crashes while drawing with swgl.
+ * Reproducible but not investigated yet.*/
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 15, 10, 2125), V(8, 15, 10, 2141), "FEATURE_FAILURE_BUG_1717911",
+ "Intel driver > 8.15.10.2141");
+
+#if defined(_M_X64)
+ if (DetectBrokenAVX()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1403353");
+ }
+#endif
+
+ ////////////////////////////////////
+ // WebGL
+
+ // Older than 5-15-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, V(16, 200, 1010, 1002), "WEBGL_NATIVE_GL_OLD_AMD");
+
+ // Older than 11-18-2015
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_BUILD_ID_LESS_THAN, 4331, "WEBGL_NATIVE_GL_OLD_INTEL");
+
+ // Older than 2-23-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, V(10, 18, 13, 6200), "WEBGL_NATIVE_GL_OLD_NVIDIA");
+
+ ////////////////////////////////////
+ // FEATURE_DX_INTEROP2
+
+ // All AMD.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_DX_INTEROP2,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH");
+
+ ////////////////////////////////////
+ // FEATURE_D3D11_KEYED_MUTEX
+
+ // bug 1359416
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1359416");
+
+ // Bug 1447141, for causing device creation crashes.
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1447141,
+ GfxDriverInfo::optionalFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(15, 201, 2201, 0), "FEATURE_FAILURE_BUG_1447141_1");
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows7, DeviceFamily::Bug1447141,
+ GfxDriverInfo::optionalFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(15, 201, 1701, 0), "FEATURE_FAILURE_BUG_1447141_1");
+
+ // bug 1457758
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::optionalFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_EQUAL, V(24, 21, 13, 9731), "FEATURE_FAILURE_BUG_1457758");
+
+ ////////////////////////////////////
+ // FEATURE_DX_NV12
+
+ // Bug 1437334
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge,
+ nsIGfxInfo::FEATURE_DX_NV12, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4459,
+ "FEATURE_BLOCKED_DRIVER_VERSION");
+
+ ////////////////////////////////////
+ // FEATURE_DX_P010
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_DX_P010, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "FEATURE_UNQUALIFIED_P010_NVIDIA");
+
+ ////////////////////////////////////
+ // FEATURE_VIDEO_OVERLAY - ALLOWLIST
+#ifdef EARLY_BETA_OR_EARLIER
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_VIDEO_OVERLAY, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_ROLLOUT_ALL");
+#else
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_VIDEO_OVERLAY, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_ROLLOUT_INTEL");
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_VIDEO_OVERLAY, nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_ROLLOUT_NVIDIA");
+#endif
+
+ ////////////////////////////////////
+ // FEATURE_HW_DECODED_VIDEO_ZERO_COPY
+
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::IntelSkylake,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(20, 19, 15, 4285), V(20, 19, 15, 4390), "FEATURE_FAILURE_BUG_1763280",
+ "Intel driver 20.19.15.*");
+
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::IntelSkylake,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(10, 18, 15, 4256), V(10, 18, 15, 4293), "FEATURE_FAILURE_BUG_1763280",
+ "Intel driver 10.18.15.*");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows10, DeviceFamily::IntelKabyLake,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1802357");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(21, 21, 13, 7576), "FEATURE_FAILURE_BUG_1767212");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(23, 20, 826, 5120),
+ "FEATURE_FAILURE_BUG_1767212");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::RadeonBlockZeroVideoCopy,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(26, 20, 15000, 37), "FEATURE_FAILURE_BUG_1767212");
+
+ ////////////////////////////////////
+ // FEATURE_HW_DECODED_VIDEO_ZERO_COPY - ALLOWLIST
+
+ // XXX ZeroCopyNV12Texture is disabled with non-intel GPUs for now.
+ // See Bug 1798242
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_ALL");
+
+ ////////////////////////////////////
+ // FEATURE_REUSE_DECODER_DEVICE
+
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::IntelSkylake,
+ nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(20, 19, 15, 4285), V(20, 19, 15, 4390), "FEATURE_FAILURE_BUG_1833809",
+ "Intel driver 20.19.15.*");
+
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows10, DeviceFamily::IntelSkylake,
+ nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(10, 18, 15, 4256), V(10, 18, 15, 4293), "FEATURE_FAILURE_BUG_1833809",
+ "Intel driver 10.18.15.*");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER
+ // Block 8.56.1.15/16
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8, 56, 1, 16),
+ "CRASHY_DRIVERS_BUG_1678808");
+
+ // Shader compilation startup crashes with WebRender on Windows 7.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows7, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE,
+ V(8, 17, 12, 8019), V(8, 17, 12, 8026), "FEATURE_FAILURE_BUG_1709629",
+ "nVidia driver > 280.26");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelWebRenderBlocked,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions,
+ "INTEL_DEVICE_GEN5_OR_OLDER");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::NvidiaWebRenderBlocked,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "EARLY_NVIDIA");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_COMPOSITOR
+
+#ifndef EARLY_BETA_OR_EARLIER
+ // See also bug 1616874
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_EQUAL, V(24, 20, 100, 6293),
+ "FEATURE_FAILURE_BUG_1602511");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8, 17, 10, 1129),
+ "FEATURE_FAILURE_CHROME_BUG_800950");
+#endif
+
+ // WebRender is unable to use scissored clears in some cases
+ APPEND_TO_DRIVER_BLOCKLIST2(
+ OperatingSystem::Windows, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1603515");
+
+ ////////////////////////////////////
+ // FEATURE_BACKDROP_FILTER
+
+ // Backdrop filter crashes the driver. See bug 1785366 and bug 1784093.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(
+ OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToIvyBridge,
+ nsIGfxInfo::FEATURE_BACKDROP_FILTER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_EXCLUSIVE,
+ V(8, 15, 10, 2879), V(10, 18, 10, 4425), "FEATURE_FAILURE_BUG_1785366",
+ "Intel driver >= 10.18.10.4425");
+ }
+ return *sDriverInfo;
+}
+
+OperatingSystem GfxInfo::GetOperatingSystem() {
+ return WindowsVersionToOperatingSystem(mWindowsVersion);
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */) {
+ AssertNotWin32kLockdown();
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = WindowsVersionToOperatingSystem(mWindowsVersion);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // Don't evaluate special cases if we're checking the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ nsAutoString adapterVendorID;
+ nsAutoString adapterDeviceID;
+ nsAutoString adapterDriverVersionString;
+ if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) ||
+ NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) ||
+ NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) {
+ if (OnlyAllowFeatureOnKnownConfig(aFeature)) {
+ aFailureId = "FEATURE_FAILURE_GET_ADAPTER";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ } else {
+ *aStatus = FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ if (OnlyAllowFeatureOnKnownConfig(aFeature) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Microsoft),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Parallels),
+ nsCaseInsensitiveStringComparator) &&
+ !adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::Qualcomm),
+ nsCaseInsensitiveStringComparator) &&
+ // FIXME - these special hex values are currently used in xpcshell tests
+ // introduced by bug 625160 patch 8/8. Maybe these tests need to be
+ // adjusted now that we're only whitelisting intel/ati/nvidia.
+ !adapterVendorID.LowerCaseEqualsLiteral("0xabcd") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xdcba") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xabab") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0xdcdc")) {
+ if (adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::MicrosoftHyperV),
+ nsCaseInsensitiveStringComparator) ||
+ adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::VMWare),
+ nsCaseInsensitiveStringComparator) ||
+ adapterVendorID.Equals(
+ GfxDriverInfo::GetDeviceVendor(DeviceVendor::VirtualBox),
+ nsCaseInsensitiveStringComparator)) {
+ aFailureId = "FEATURE_FAILURE_VM_VENDOR";
+ } else if (adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(
+ DeviceVendor::MicrosoftBasic),
+ nsCaseInsensitiveStringComparator)) {
+ aFailureId = "FEATURE_FAILURE_MICROSOFT_BASIC_VENDOR";
+ } else if (adapterVendorID.IsEmpty()) {
+ aFailureId = "FEATURE_FAILURE_EMPTY_DEVICE_VENDOR";
+ } else {
+ aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR";
+ }
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ if (adapterDriverVersionString.Length() == 0) {
+ if (OnlyAllowFeatureOnKnownConfig(aFeature)) {
+ aFailureId = "FEATURE_FAILURE_EMPTY_DRIVER_VERSION";
+ *aStatus = FEATURE_BLOCKED_DRIVER_VERSION;
+ } else {
+ *aStatus = FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ uint64_t driverVersion;
+ if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) {
+ if (OnlyAllowFeatureOnKnownConfig(aFeature)) {
+ aFailureId = "FEATURE_FAILURE_PARSE_DRIVER";
+ *aStatus = FEATURE_BLOCKED_DRIVER_VERSION;
+ } else {
+ *aStatus = FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(
+ aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+void GfxInfo::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj) {
+ // Add the platform neutral features
+ GfxInfoBase::DescribeFeatures(aCx, aObj);
+
+ JS::Rooted<JSObject*> obj(aCx);
+
+ gfx::FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
+ if (!InitFeatureObject(aCx, aObj, "d3d11", d3d11, &obj)) {
+ return;
+ }
+ if (d3d11.GetValue() == gfx::FeatureStatus::Available) {
+ DeviceManagerDx* dm = DeviceManagerDx::Get();
+ JS::Rooted<JS::Value> val(aCx,
+ JS::Int32Value(dm->GetCompositorFeatureLevel()));
+ JS_SetProperty(aCx, obj, "version", val);
+
+ val = JS::BooleanValue(dm->IsWARP());
+ JS_SetProperty(aCx, obj, "warp", val);
+
+ val = JS::BooleanValue(dm->TextureSharingWorks());
+ JS_SetProperty(aCx, obj, "textureSharing", val);
+
+ bool blocklisted = false;
+ if (nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service()) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (SUCCEEDED(
+ gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
+ discardFailureId, &status))) {
+ blocklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK);
+ }
+ }
+
+ val = JS::BooleanValue(blocklisted);
+ JS_SetProperty(aCx, obj, "blocklisted", val);
+ }
+
+ gfx::FeatureState& d2d = gfxConfig::GetFeature(Feature::DIRECT2D);
+ if (!InitFeatureObject(aCx, aObj, "d2d", d2d, &obj)) {
+ return;
+ }
+ {
+ const char* version = "1.1";
+ JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, version));
+ JS::Rooted<JS::Value> val(aCx, JS::StringValue(str));
+ JS_SetProperty(aCx, obj, "version", val);
+ }
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ mAdapterVendorID[mActiveGPUIndex] = aVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ mAdapterDeviceID[mActiveGPUIndex] = aDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ mDriverVersion[mActiveGPUIndex] = aDriverVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ mWindowsVersion = aVersion;
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h
new file mode 100644
index 0000000000..f2549afa4e
--- /dev/null
+++ b/widget/windows/GfxInfo.h
@@ -0,0 +1,105 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 WIDGET_WINDOWS_GFXINFO_H_
+#define WIDGET_WINDOWS_GFXINFO_H_
+
+#include "GfxInfoBase.h"
+
+namespace mozilla::widget {
+
+class GfxInfo : public GfxInfoBase {
+ public:
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ GfxInfo() = default;
+ nsresult Init() override;
+
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetTestType(nsAString& aTestType) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+
+ uint32_t OperatingSystemVersion() override { return mWindowsVersion; }
+ uint32_t OperatingSystemBuild() override { return mWindowsBuildNumber; }
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ private:
+ ~GfxInfo() = default;
+
+ // Disallow copy/move
+ GfxInfo(const GfxInfo&) = delete;
+ GfxInfo& operator=(const GfxInfo&) = delete;
+ GfxInfo(GfxInfo&&) = delete;
+ GfxInfo& operator=(GfxInfo&&) = delete;
+
+ OperatingSystem GetOperatingSystem() override;
+
+ nsresult GetFeatureStatusImpl(int32_t aFeature, int32_t* aStatus,
+ nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ void DescribeFeatures(JSContext* cx, JS::Handle<JSObject*> aOut) override;
+
+ void AddCrashReportAnnotations();
+
+ nsString mDeviceString[2];
+ nsString mDeviceID[2];
+ nsString mDriverVersion[2];
+ nsString mDriverDate[2];
+ nsString mDeviceKey[2];
+ nsString mDeviceKeyDebug;
+ nsString mAdapterVendorID[2];
+ nsString mAdapterDeviceID[2];
+ nsString mAdapterSubsysID[2];
+ uint32_t mWindowsVersion = 0;
+ uint32_t mWindowsBuildNumber = 0;
+ uint32_t mActiveGPUIndex = 0; // This must be 0 or 1
+ bool mHasDualGPU = false;
+ bool mHasDriverVersionMismatch = false;
+ bool mHasBattery = false;
+};
+
+} // namespace mozilla::widget
+
+#endif // WIDGET_WINDOWS_GFXINFO_H_
diff --git a/widget/windows/IEnumFE.cpp b/widget/windows/IEnumFE.cpp
new file mode 100644
index 0000000000..8fe7138609
--- /dev/null
+++ b/widget/windows/IEnumFE.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "IEnumFE.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+
+CEnumFormatEtc::CEnumFormatEtc() : mRefCnt(0), mCurrentIdx(0) {}
+
+// Constructor used by Clone()
+CEnumFormatEtc::CEnumFormatEtc(nsTArray<FormatEtc>& aArray)
+ : mRefCnt(0), mCurrentIdx(0) {
+ // a deep copy, calls FormatEtc's copy constructor on each
+ mFormatList.AppendElements(aArray);
+}
+
+CEnumFormatEtc::~CEnumFormatEtc() {}
+
+/* IUnknown impl. */
+
+STDMETHODIMP
+CEnumFormatEtc::QueryInterface(REFIID riid, LPVOID* ppv) {
+ *ppv = nullptr;
+
+ if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEnumFORMATETC))
+ *ppv = (LPVOID)this;
+
+ if (*ppv == nullptr) return E_NOINTERFACE;
+
+ // AddRef any interface we'll return.
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG)
+CEnumFormatEtc::AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "CEnumFormatEtc", sizeof(*this));
+ return mRefCnt;
+}
+
+STDMETHODIMP_(ULONG)
+CEnumFormatEtc::Release() {
+ uint32_t refReturn;
+
+ refReturn = --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "CEnumFormatEtc");
+
+ if (mRefCnt == 0) delete this;
+
+ return refReturn;
+}
+
+/* IEnumFORMATETC impl. */
+
+STDMETHODIMP
+CEnumFormatEtc::Next(ULONG aMaxToFetch, FORMATETC* aResult,
+ ULONG* aNumFetched) {
+ // If the method retrieves the number of items requested, the return
+ // value is S_OK. Otherwise, it is S_FALSE.
+
+ if (aNumFetched) *aNumFetched = 0;
+
+ // aNumFetched can be null if aMaxToFetch is 1
+ if (!aNumFetched && aMaxToFetch > 1) return S_FALSE;
+
+ if (!aResult) return S_FALSE;
+
+ // We're done walking the list
+ if (mCurrentIdx >= mFormatList.Length()) return S_FALSE;
+
+ uint32_t left = mFormatList.Length() - mCurrentIdx;
+
+ if (!aMaxToFetch) return S_FALSE;
+
+ uint32_t count = std::min(static_cast<uint32_t>(aMaxToFetch), left);
+
+ uint32_t idx = 0;
+ while (count > 0) {
+ // Copy out to aResult
+ mFormatList[mCurrentIdx++].CopyOut(&aResult[idx++]);
+ count--;
+ }
+
+ if (aNumFetched) *aNumFetched = idx;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Skip(ULONG aSkipNum) {
+ // If the method skips the number of items requested, the return value is
+ // S_OK. Otherwise, it is S_FALSE.
+
+ if ((mCurrentIdx + aSkipNum) >= mFormatList.Length()) return S_FALSE;
+
+ mCurrentIdx += aSkipNum;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Reset(void) {
+ mCurrentIdx = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+CEnumFormatEtc::Clone(LPENUMFORMATETC* aResult) {
+ // Must return a new IEnumFORMATETC interface with the same iterative state.
+
+ if (!aResult) return E_INVALIDARG;
+
+ CEnumFormatEtc* pEnumObj = new CEnumFormatEtc(mFormatList);
+
+ if (!pEnumObj) return E_OUTOFMEMORY;
+
+ pEnumObj->AddRef();
+ pEnumObj->SetIndex(mCurrentIdx);
+
+ *aResult = pEnumObj;
+
+ return S_OK;
+}
+
+/* utils */
+
+void CEnumFormatEtc::AddFormatEtc(LPFORMATETC aFormat) {
+ if (!aFormat) return;
+ FormatEtc* etc = mFormatList.AppendElement();
+ // Make a copy of aFormat
+ if (etc) etc->CopyIn(aFormat);
+}
+
+/* private */
+
+void CEnumFormatEtc::SetIndex(uint32_t aIdx) { mCurrentIdx = aIdx; }
diff --git a/widget/windows/IEnumFE.h b/widget/windows/IEnumFE.h
new file mode 100644
index 0000000000..b8cb6ad9d0
--- /dev/null
+++ b/widget/windows/IEnumFE.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 IEnumeFE_h__
+#define IEnumeFE_h__
+
+/*
+ * CEnumFormatEtc - implements IEnumFORMATETC
+ */
+
+#include <ole2.h>
+
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+// FORMATETC container
+class FormatEtc {
+ public:
+ FormatEtc() { memset(&mFormat, 0, sizeof(FORMATETC)); }
+ FormatEtc(const FormatEtc& copy) { CopyIn(&copy.mFormat); }
+ ~FormatEtc() {
+ if (mFormat.ptd) CoTaskMemFree(mFormat.ptd);
+ }
+
+ void CopyIn(const FORMATETC* aSrc) {
+ if (!aSrc) {
+ memset(&mFormat, 0, sizeof(FORMATETC));
+ return;
+ }
+ mFormat = *aSrc;
+ if (aSrc->ptd) {
+ mFormat.ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(mFormat.ptd) = *(aSrc->ptd);
+ }
+ }
+
+ void CopyOut(LPFORMATETC aDest) {
+ if (!aDest) return;
+ *aDest = mFormat;
+ if (mFormat.ptd) {
+ aDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(aDest->ptd) = *(mFormat.ptd);
+ }
+ }
+
+ private:
+ FORMATETC mFormat;
+};
+
+/*
+ * CEnumFormatEtc is created within IDataObject::EnumFormatEtc. This object
+ * lives on its own, that is, QueryInterface only knows IUnknown and
+ * IEnumFormatEtc, nothing more. We still use an outer unknown for reference
+ * counting, because as long as this enumerator lives, the data object should
+ * live, thereby keeping the application up.
+ */
+
+class CEnumFormatEtc final : public IEnumFORMATETC {
+ public:
+ explicit CEnumFormatEtc(nsTArray<FormatEtc>& aArray);
+ CEnumFormatEtc();
+ ~CEnumFormatEtc();
+
+ // IUnknown impl.
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID* ppv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IEnumFORMATETC impl.
+ STDMETHODIMP Next(ULONG aMaxToFetch, FORMATETC* aResult, ULONG* aNumFetched);
+ STDMETHODIMP Skip(ULONG aSkipNum);
+ STDMETHODIMP Reset();
+ STDMETHODIMP Clone(LPENUMFORMATETC* aResult); // Addrefs
+
+ // Utils
+ void AddFormatEtc(LPFORMATETC aFormat);
+
+ private:
+ nsTArray<FormatEtc> mFormatList; // Formats
+ ULONG mRefCnt; // Object reference count
+ ULONG mCurrentIdx; // Current element
+
+ void SetIndex(uint32_t aIdx);
+};
+
+#endif //_IENUMFE_H_
diff --git a/widget/windows/IMMHandler.cpp b/widget/windows/IMMHandler.cpp
new file mode 100644
index 0000000000..2f64f93922
--- /dev/null
+++ b/widget/windows/IMMHandler.cpp
@@ -0,0 +1,2408 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "mozilla/Logging.h"
+
+#include "IMMHandler.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "KeyboardLayout.h"
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+
+#ifndef IME_PROP_ACCEPT_WIDE_VKEY
+# define IME_PROP_ACCEPT_WIDE_VKEY 0x20
+#endif
+
+//-------------------------------------------------------------------------
+//
+// from
+// http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
+// The document for this has been removed from MSDN...
+//
+//-------------------------------------------------------------------------
+
+#define RWM_MOUSE TEXT("MSIMEMouseOperation")
+
+#define IMEMOUSE_NONE 0x00 // no mouse button was pushed
+#define IMEMOUSE_LDOWN 0x01
+#define IMEMOUSE_RDOWN 0x02
+#define IMEMOUSE_MDOWN 0x04
+#define IMEMOUSE_WUP 0x10 // wheel up
+#define IMEMOUSE_WDOWN 0x20 // wheel down
+
+// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
+// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+extern mozilla::LazyLogModule gIMELog;
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static void HandleSeparator(nsACString& aDesc) {
+ if (!aDesc.IsEmpty()) {
+ aDesc.AppendLiteral(" | ");
+ }
+}
+
+class GetIMEGeneralPropertyName : public nsAutoCString {
+ public:
+ explicit GetIMEGeneralPropertyName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags");
+ return;
+ }
+ if (aFlags & IME_PROP_AT_CARET) {
+ AppendLiteral("IME_PROP_AT_CARET");
+ }
+ if (aFlags & IME_PROP_SPECIAL_UI) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_SPECIAL_UI");
+ }
+ if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
+ }
+ if (aFlags & IME_PROP_UNICODE) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_UNICODE");
+ }
+ if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
+ }
+ if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
+ HandleSeparator(*this);
+ AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
+ }
+ }
+ virtual ~GetIMEGeneralPropertyName() {}
+};
+
+class GetIMEUIPropertyName : public nsAutoCString {
+ public:
+ explicit GetIMEUIPropertyName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags");
+ return;
+ }
+ if (aFlags & UI_CAP_2700) {
+ AppendLiteral("UI_CAP_2700");
+ }
+ if (aFlags & UI_CAP_ROT90) {
+ HandleSeparator(*this);
+ AppendLiteral("UI_CAP_ROT90");
+ }
+ if (aFlags & UI_CAP_ROTANY) {
+ HandleSeparator(*this);
+ AppendLiteral("UI_CAP_ROTANY");
+ }
+ }
+ virtual ~GetIMEUIPropertyName() {}
+};
+
+class GetReconvertStringLog : public nsAutoCString {
+ public:
+ explicit GetReconvertStringLog(RECONVERTSTRING* aReconv) {
+ AssignLiteral("{ dwSize=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwSize));
+ AppendLiteral(", dwVersion=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwVersion));
+ AppendLiteral(", dwStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwStrLen));
+ AppendLiteral(", dwStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset));
+ AppendLiteral(", dwCompStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen));
+ AppendLiteral(", dwCompStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset));
+ AppendLiteral(", dwTargetStrLen=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen));
+ AppendLiteral(", dwTargetStrOffset=");
+ AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset));
+ AppendLiteral(", result str=\"");
+ if (aReconv->dwStrLen) {
+ char16_t* strStart = reinterpret_cast<char16_t*>(
+ reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset);
+ nsDependentString str(strStart, aReconv->dwStrLen);
+ Append(NS_ConvertUTF16toUTF8(str));
+ }
+ AppendLiteral("\" }");
+ }
+ virtual ~GetReconvertStringLog() {}
+};
+
+namespace mozilla {
+namespace widget {
+
+static IMMHandler* gIMMHandler = nullptr;
+
+/******************************************************************************
+ * IMEContext
+ ******************************************************************************/
+
+IMEContext::IMEContext(HWND aWnd) : mWnd(aWnd), mIMC(::ImmGetContext(aWnd)) {}
+
+IMEContext::IMEContext(nsWindow* aWindowBase)
+ : mWnd(aWindowBase->GetWindowHandle()),
+ mIMC(::ImmGetContext(aWindowBase->GetWindowHandle())) {}
+
+void IMEContext::Init(HWND aWnd) {
+ Clear();
+ mWnd = aWnd;
+ mIMC = ::ImmGetContext(mWnd);
+}
+
+void IMEContext::Init(nsWindow* aWindowBase) {
+ Init(aWindowBase->GetWindowHandle());
+}
+
+void IMEContext::Clear() {
+ if (mWnd && mIMC) {
+ ::ImmReleaseContext(mWnd, mIMC);
+ }
+ mWnd = nullptr;
+ mIMC = nullptr;
+}
+
+/******************************************************************************
+ * IMMHandler
+ ******************************************************************************/
+
+static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000
+
+WritingMode IMMHandler::sWritingModeOfCompositionFont;
+nsString IMMHandler::sIMEName;
+UINT IMMHandler::sCodePage = 0;
+DWORD IMMHandler::sIMEProperty = 0;
+DWORD IMMHandler::sIMEUIProperty = 0;
+bool IMMHandler::sAssumeVerticalWritingModeNotSupported = false;
+bool IMMHandler::sHasFocus = false;
+
+#define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \
+ bool IMMHandler::Is##aReadableName##Active() { \
+ return sIMEName.Equals(aActualName); \
+ }
+
+IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006")
+IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007")
+IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008")
+IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009")
+IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010")
+// NOTE: Even on Windows for en-US, the name of Google Japanese Input is
+// written in Japanese.
+IMPL_IS_IME_ACTIVE(GoogleJapaneseInput,
+ u"Google \x65E5\x672C\x8A9E\x5165\x529B "
+ u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB")
+IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003")
+
+#undef IMPL_IS_IME_ACTIVE
+
+// static
+bool IMMHandler::IsActiveIMEInBlockList() {
+ if (sIMEName.IsEmpty()) {
+ return false;
+ }
+#ifdef _WIN64
+ // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010
+ // and earlier have a lot of problems even for daily use. Perhaps, the
+ // reason is Win 8 has a lot of changes around IMM-IME support and TSF,
+ // and ATOK 2010 is released earlier than Win 8.
+ // ATOK 2006 crashes while converting a word with candidate window.
+ // ATOK 2007 doesn't paint and resize suggest window and candidate window
+ // correctly (showing white window or too big window).
+ // ATOK 2008 and ATOK 2009 crash when user just opens their open state.
+ // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of
+ // crash reports.
+ if ((IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() ||
+ IsATOK2009Active() || IsATOK2010Active())) {
+ return true;
+ }
+#endif // #ifdef _WIN64
+ return false;
+}
+
+// static
+void IMMHandler::EnsureHandlerInstance() {
+ if (!gIMMHandler) {
+ gIMMHandler = new IMMHandler();
+ }
+}
+
+// static
+void IMMHandler::Initialize() {
+ if (!sWM_MSIME_MOUSE) {
+ sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
+ }
+ sAssumeVerticalWritingModeNotSupported = Preferences::GetBool(
+ "intl.imm.vertical_writing.always_assume_not_supported", false);
+ InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
+}
+
+// static
+void IMMHandler::Terminate() {
+ if (!gIMMHandler) return;
+ delete gIMMHandler;
+ gIMMHandler = nullptr;
+}
+
+// static
+bool IMMHandler::IsComposingOnOurEditor() {
+ return gIMMHandler && gIMMHandler->mIsComposing;
+}
+
+// static
+bool IMMHandler::IsComposingWindow(nsWindow* aWindow) {
+ return gIMMHandler && gIMMHandler->mComposingWindow == aWindow;
+}
+
+// static
+bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) {
+ if (!gIMMHandler || !gIMMHandler->mComposingWindow) {
+ return false;
+ }
+ HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle();
+ return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
+}
+
+// static
+bool IMMHandler::ShouldDrawCompositionStringOurselves() {
+ // If current IME has special UI or its composition window should not
+ // positioned to caret position, we should now draw composition string
+ // ourselves.
+ return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
+ (sIMEProperty & IME_PROP_AT_CARET);
+}
+
+// static
+bool IMMHandler::IsVerticalWritingSupported() {
+ // Even if IME claims that they support vertical writing mode but it may not
+ // support vertical writing mode for its candidate window.
+ if (sAssumeVerticalWritingModeNotSupported) {
+ return false;
+ }
+ // Google Japanese Input doesn't support vertical writing mode. We should
+ // return false if it's active IME.
+ if (IsGoogleJapaneseInputActive()) {
+ return false;
+ }
+ return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
+}
+
+// static
+void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) {
+ UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
+ if (IMENameLength) {
+ // Add room for the terminating null character
+ sIMEName.SetLength(++IMENameLength);
+ IMENameLength =
+ ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength);
+ // Adjust the length to ignore the terminating null character
+ sIMEName.SetLength(IMENameLength);
+ } else {
+ sIMEName.Truncate();
+ }
+
+ WORD langID = LOWORD(aKeyboardLayout);
+ ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT),
+ LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
+ (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR));
+ sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY);
+ sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
+
+ // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
+ // For hacking some bugs of some TIP, we should set an IME name from the
+ // pref.
+ if (sCodePage == 932 && sIMEName.IsEmpty()) {
+ Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as",
+ sIMEName);
+ }
+
+ // Whether the IME supports vertical writing mode might be changed or
+ // some IMEs may need specific font for their UI. Therefore, we should
+ // update composition font forcibly here.
+ if (aWindow) {
+ MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::InitKeyboardLayout, aKeyboardLayout=%p (\"%s\"), "
+ "sCodePage=%u, sIMEProperty=%s, sIMEUIProperty=%s",
+ aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(), sCodePage,
+ GetIMEGeneralPropertyName(sIMEProperty).get(),
+ GetIMEUIPropertyName(sIMEUIProperty).get()));
+}
+
+// static
+UINT IMMHandler::GetKeyboardCodePage() { return sCodePage; }
+
+// static
+IMENotificationRequests IMMHandler::GetIMENotificationRequests() {
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE |
+ IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
+}
+
+// used for checking the lParam of WM_IME_COMPOSITION
+#define IS_COMPOSING_LPARAM(lParam) \
+ ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
+#define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR)
+// Some IMEs (e.g., the standard IME for Korean) don't have caret position,
+// then, we should not set caret position to compositionchange event.
+#define NO_IME_CARET -1
+
+IMMHandler::IMMHandler()
+ : mComposingWindow(nullptr),
+ mCursorPosition(NO_IME_CARET),
+ mCompositionStart(0),
+ mIsComposing(false) {
+ MOZ_LOG(gIMELog, LogLevel::Debug, ("IMMHandler::IMMHandler is created"));
+}
+
+IMMHandler::~IMMHandler() {
+ if (mIsComposing) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" IMMHandler::~IMMHandler, ERROR, the instance is still composing"));
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug, ("IMMHandler::IMMHandler is destroyed"));
+}
+
+nsresult IMMHandler::EnsureClauseArray(int32_t aCount) {
+ NS_ENSURE_ARG_MIN(aCount, 0);
+ mClauseArray.SetCapacity(aCount + 32);
+ return NS_OK;
+}
+
+nsresult IMMHandler::EnsureAttributeArray(int32_t aCount) {
+ NS_ENSURE_ARG_MIN(aCount, 0);
+ mAttributeArray.SetCapacity(aCount + 64);
+ return NS_OK;
+}
+
+// static
+void IMMHandler::CommitComposition(nsWindow* aWindow, bool aForce) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::CommitComposition, aForce=%s, aWindow=%p, hWnd=%p, "
+ "mComposingWindow=%p%s",
+ GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
+ gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
+ gIMMHandler && gIMMHandler->mComposingWindow
+ ? IsComposingOnOurEditor() ? " (composing on editor)"
+ : " (composing on plug-in)"
+ : ""));
+ if (!aForce && !IsComposingWindow(aWindow)) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ bool associated = context.AssociateDefaultContext();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::CommitComposition, associated=%s",
+ GetBoolName(associated)));
+
+ if (context.IsValid()) {
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ }
+
+ if (associated) {
+ context.Disassociate();
+ }
+}
+
+// static
+void IMMHandler::CancelComposition(nsWindow* aWindow, bool aForce) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::CancelComposition, aForce=%s, aWindow=%p, hWnd=%p, "
+ "mComposingWindow=%p%s",
+ GetBoolName(aForce), aWindow, aWindow->GetWindowHandle(),
+ gIMMHandler ? gIMMHandler->mComposingWindow : nullptr,
+ gIMMHandler && gIMMHandler->mComposingWindow
+ ? IsComposingOnOurEditor() ? " (composing on editor)"
+ : " (composing on plug-in)"
+ : ""));
+ if (!aForce && !IsComposingWindow(aWindow)) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ bool associated = context.AssociateDefaultContext();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::CancelComposition, associated=%s",
+ GetBoolName(associated)));
+
+ if (context.IsValid()) {
+ ::ImmNotifyIME(context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ }
+
+ if (associated) {
+ context.Disassociate();
+ }
+}
+
+// static
+void IMMHandler::OnFocusChange(bool aFocus, nsWindow* aWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
+ "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s",
+ GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
+ GetBoolName(IsComposingWindow(aWindow)),
+ GetBoolName(aWindow->Destroyed())));
+
+ if (!aFocus) {
+ IMEHandler::MaybeDestroyNativeCaret();
+ if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
+ CancelComposition(aWindow);
+ }
+ }
+ if (gIMMHandler) {
+ gIMMHandler->mContentSelection.reset();
+ }
+ sHasFocus = aFocus;
+}
+
+// static
+void IMMHandler::OnUpdateComposition(nsWindow* aWindow) {
+ if (!gIMMHandler) {
+ return;
+ }
+
+ IMEContext context(aWindow);
+ gIMMHandler->SetIMERelatedWindowsPos(aWindow, context);
+}
+
+// static
+void IMMHandler::OnSelectionChange(nsWindow* aWindow,
+ const IMENotification& aIMENotification,
+ bool aIsIMMActive) {
+ if (!aIMENotification.mSelectionChangeData.mCausedByComposition &&
+ aIsIMMActive) {
+ MaybeAdjustCompositionFont(
+ aWindow, aIMENotification.mSelectionChangeData.GetWritingMode());
+ }
+ // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it
+ // after a call of MaybeAdjustCompositionFont().
+ if (gIMMHandler) {
+ gIMMHandler->mContentSelection =
+ Some(ContentSelection(aIMENotification.mSelectionChangeData));
+ }
+}
+
+// static
+void IMMHandler::MaybeAdjustCompositionFont(nsWindow* aWindow,
+ const WritingMode& aWritingMode,
+ bool aForceUpdate) {
+ switch (sCodePage) {
+ case 932: // Japanese Shift-JIS
+ case 936: // Simlified Chinese GBK
+ case 949: // Korean
+ case 950: // Traditional Chinese Big5
+ EnsureHandlerInstance();
+ break;
+ default:
+ // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
+ if (!gIMMHandler) {
+ return;
+ }
+ }
+
+ // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
+ // before sending WM_IME_STARTCOMPOSITION.
+ IMEContext context(aWindow);
+ gIMMHandler->AdjustCompositionFont(aWindow, context, aWritingMode,
+ aForceUpdate);
+}
+
+// static
+bool IMMHandler::ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult) {
+ aResult.mResult = 0;
+ aResult.mConsumed = false;
+ // We don't need to create the instance of the handler here.
+ if (gIMMHandler) {
+ gIMMHandler->OnInputLangChange(aWindow, wParam, lParam, aResult);
+ }
+ InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam));
+ // We can release the instance here, because the instance may be never
+ // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
+ Terminate();
+ // Don't return as "processed", the messages should be processed on nsWindow
+ // too.
+ return false;
+}
+
+// static
+bool IMMHandler::ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
+ LPARAM& lParam, MSGResult& aResult) {
+ // XXX We store the composing window in mComposingWindow. If IME messages are
+ // sent to different window, we should commit the old transaction. And also
+ // if the new window handle is not focused, probably, we should not start
+ // the composition, however, such case should not be, it's just bad scenario.
+
+ aResult.mResult = 0;
+ switch (msg) {
+ case WM_INPUTLANGCHANGE:
+ return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
+ case WM_IME_STARTCOMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEStartComposition(aWindow, aResult);
+ case WM_IME_COMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult);
+ case WM_IME_ENDCOMPOSITION:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMEEndComposition(aWindow, aResult);
+ case WM_IME_CHAR:
+ return OnIMEChar(aWindow, wParam, lParam, aResult);
+ case WM_IME_NOTIFY:
+ return OnIMENotify(aWindow, wParam, lParam, aResult);
+ case WM_IME_REQUEST:
+ EnsureHandlerInstance();
+ return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult);
+ case WM_IME_SELECT:
+ return OnIMESelect(aWindow, wParam, lParam, aResult);
+ case WM_IME_SETCONTEXT:
+ return OnIMESetContext(aWindow, wParam, lParam, aResult);
+ case WM_KEYDOWN:
+ return OnKeyDownEvent(aWindow, wParam, lParam, aResult);
+ case WM_CHAR:
+ if (!gIMMHandler) {
+ return false;
+ }
+ return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult);
+ default:
+ return false;
+ };
+}
+
+/****************************************************************************
+ * message handlers
+ ****************************************************************************/
+
+void IMMHandler::OnInputLangChange(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnInputLangChange, hWnd=%p, wParam=%08zx, "
+ "lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
+ NS_ASSERTION(!mIsComposing, "ResetInputState failed");
+
+ if (mIsComposing) {
+ HandleEndComposition(aWindow);
+ }
+
+ aResult.mConsumed = false;
+}
+
+bool IMMHandler::OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMEStartComposition, hWnd=%p, mIsComposing=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
+ aResult.mConsumed = ShouldDrawCompositionStringOurselves();
+ if (mIsComposing) {
+ NS_WARNING("Composition has been already started");
+ return true;
+ }
+
+ IMEContext context(aWindow);
+ HandleStartComposition(aWindow, context);
+ return true;
+}
+
+bool IMMHandler::OnIMEComposition(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMEComposition, hWnd=%p, lParam=%08" PRIxLPTR
+ ", mIsComposing=%s, "
+ "GCS_RESULTSTR=%s, GCS_COMPSTR=%s, GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, "
+ "GCS_CURSORPOS=%s,",
+ aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposing),
+ GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
+ GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
+ GetBoolName(lParam & GCS_CURSORPOS)));
+
+ IMEContext context(aWindow);
+ aResult.mConsumed = HandleComposition(aWindow, context, lParam);
+ return true;
+}
+
+bool IMMHandler::OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMEEndComposition, hWnd=%p, mIsComposing=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposing)));
+
+ aResult.mConsumed = ShouldDrawCompositionStringOurselves();
+ if (!mIsComposing) {
+ return true;
+ }
+
+ // Korean IME posts WM_IME_ENDCOMPOSITION first when we hit space during
+ // composition. Then, we should ignore the message and commit the composition
+ // string at following WM_IME_COMPOSITION.
+ MSG compositionMsg;
+ if (WinUtils::PeekMessage(&compositionMsg, aWindow->GetWindowHandle(),
+ WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION,
+ PM_NOREMOVE) &&
+ compositionMsg.message == WM_IME_COMPOSITION &&
+ IS_COMMITTING_LPARAM(compositionMsg.lParam)) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::OnIMEEndComposition, WM_IME_ENDCOMPOSITION is "
+ "followed by WM_IME_COMPOSITION, ignoring the message..."));
+ return true;
+ }
+
+ // Otherwise, e.g., ChangJie doesn't post WM_IME_COMPOSITION before
+ // WM_IME_ENDCOMPOSITION when composition string becomes empty.
+ // Then, we should dispatch a compositionupdate event, a compositionchange
+ // event and a compositionend event.
+ // XXX Shouldn't we dispatch the compositionchange event with actual or
+ // latest composition string?
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::OnIMEEndComposition, mCompositionString=\"%s\"%s",
+ NS_ConvertUTF16toUTF8(mCompositionString).get(),
+ mCompositionString.IsEmpty() ? "" : ", but canceling it..."));
+
+ HandleEndComposition(aWindow, &EmptyString());
+
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMEChar, hWnd=%p, char=%08zx",
+ aWindow->GetWindowHandle(), wParam));
+
+ // We don't need to fire any compositionchange events from here. This method
+ // will be called when the composition string of the current IME is not drawn
+ // by us and some characters are committed. In that case, the committed
+ // string was processed in nsWindow::OnIMEComposition already.
+
+ // We need to consume the message so that Windows don't send two WM_CHAR msgs
+ aResult.mConsumed = true;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMECompositionFull, hWnd=%p",
+ aWindow->GetWindowHandle()));
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ switch (wParam) {
+ case IMN_CHANGECANDIDATE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CHANGECANDIDATE, "
+ "lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSECANDIDATE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CLOSECANDIDATE, "
+ "lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSESTATUSWINDOW:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_CLOSESTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_GUIDELINE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_GUIDELINE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_OPENCANDIDATE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_OPENCANDIDATE, "
+ "lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_OPENSTATUSWINDOW:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_OPENSTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCANDIDATEPOS:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCANDIDATEPOS, "
+ "lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_SETCOMPOSITIONFONT:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONFONT",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCOMPOSITIONWINDOW:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCOMPOSITIONWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCONVERSIONMODE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETCONVERSIONMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETOPENSTATUS:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETOPENSTATUS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSENTENCEMODE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSENTENCEMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSTATUSWINDOWPOS:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_SETSTATUSWINDOWPOS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_PRIVATE:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMENotify, hWnd=%p, IMN_PRIVATE",
+ aWindow->GetWindowHandle()));
+ break;
+ }
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+bool IMMHandler::OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ switch (wParam) {
+ case IMR_RECONVERTSTRING:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMERequest, hWnd=%p, IMR_RECONVERTSTRING",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_QUERYCHARPOSITION:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMERequest, hWnd=%p, IMR_QUERYCHARPOSITION",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed =
+ HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_DOCUMENTFEED:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMERequest, hWnd=%p, IMR_DOCUMENTFEED",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
+ return true;
+ default:
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMERequest, hWnd=%p, wParam=%08zx",
+ aWindow->GetWindowHandle(), wParam));
+ aResult.mConsumed = false;
+ return true;
+ }
+}
+
+// static
+bool IMMHandler::OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMESelect, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ // not implement yet
+ aResult.mConsumed = false;
+ return true;
+}
+
+// static
+bool IMMHandler::OnIMESetContext(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnIMESetContext, hWnd=%p, %s, lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
+
+ aResult.mConsumed = false;
+
+ // NOTE: If the aWindow is top level window of the composing window because
+ // when a window on deactive window gets focus, WM_IME_SETCONTEXT (wParam is
+ // TRUE) is sent to the top level window first. After that,
+ // WM_IME_SETCONTEXT (wParam is FALSE) is sent to the top level window.
+ // Finally, WM_IME_SETCONTEXT (wParam is TRUE) is sent to the focused window.
+ // The top level window never becomes composing window, so, we can ignore
+ // the WM_IME_SETCONTEXT on the top level window.
+ if (IsTopLevelWindowOfComposition(aWindow)) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::OnIMESetContext, hWnd=%p is top level window",
+ aWindow->GetWindowHandle()));
+ return true;
+ }
+
+ // When IME context is activating on another window,
+ // we should commit the old composition on the old window.
+ bool cancelComposition = false;
+ if (wParam && gIMMHandler) {
+ cancelComposition = gIMMHandler->CommitCompositionOnPreviousWindow(aWindow);
+ }
+
+ if (wParam && (lParam & ISC_SHOWUICOMPOSITIONWINDOW) &&
+ ShouldDrawCompositionStringOurselves()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::OnIMESetContext, ISC_SHOWUICOMPOSITIONWINDOW is "
+ "removed"));
+ lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ }
+
+ // We should sent WM_IME_SETCONTEXT to the DefWndProc here because the
+ // ancestor windows shouldn't receive this message. If they receive the
+ // message, we cannot know whether which window is the target of the message.
+ aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
+ WM_IME_SETCONTEXT, wParam, lParam);
+
+ // Cancel composition on the new window if we committed our composition on
+ // another window.
+ if (cancelComposition) {
+ CancelComposition(aWindow, true);
+ }
+
+ aResult.mConsumed = true;
+ return true;
+}
+
+bool IMMHandler::OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ // The return value must be same as aResult.mConsumed because only when we
+ // consume the message, the caller shouldn't do anything anymore but
+ // otherwise, the caller should handle the message.
+ aResult.mConsumed = false;
+ if (IsIMECharRecordsEmpty()) {
+ return aResult.mConsumed;
+ }
+ WPARAM recWParam;
+ LPARAM recLParam;
+ DequeueIMECharRecords(recWParam, recLParam);
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::OnChar, aWindow=%p, wParam=%08zx, lParam=%08" PRIxLPTR ", "
+ "recorded: wParam=%08zx, lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), wParam, lParam, recWParam, recLParam));
+ // If an unexpected char message comes, we should reset the records,
+ // of course, this shouldn't happen.
+ if (recWParam != wParam || recLParam != lParam) {
+ ResetIMECharRecords();
+ return aResult.mConsumed;
+ }
+ // Eat the char message which is caused by WM_IME_CHAR because we should
+ // have processed the IME messages, so, this message could be come from
+ // a windowless plug-in.
+ aResult.mConsumed = true;
+ return aResult.mConsumed;
+}
+
+/****************************************************************************
+ * others
+ ****************************************************************************/
+
+TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) {
+ return aWindow == mComposingWindow && mDispatcher
+ ? mDispatcher.get()
+ : aWindow->GetTextEventDispatcher();
+}
+
+void IMMHandler::HandleStartComposition(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ MOZ_ASSERT(!mIsComposing,
+ "HandleStartComposition is called but mIsComposing is TRUE");
+
+ const Maybe<ContentSelection>& contentSelection =
+ GetContentSelectionWithQueryIfNothing(aWindow);
+ if (contentSelection.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleStartComposition, FAILED, due to "
+ "Selection::GetContentSelectionWithQueryIfNothing() failure"));
+ return;
+ }
+ if (!contentSelection->HasRange()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleStartComposition, FAILED, due to "
+ "there is no selection"));
+ return;
+ }
+
+ AdjustCompositionFont(aWindow, aContext, contentSelection->WritingModeRef());
+
+ mCompositionStart = contentSelection->OffsetAndDataRef().StartOffset();
+ mCursorPosition = NO_IME_CARET;
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleStartComposition, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleStartComposition, FAILED, due to "
+ "TextEventDispatcher::StartComposition() failure"));
+ return;
+ }
+
+ mIsComposing = true;
+ mComposingWindow = aWindow;
+ mDispatcher = dispatcher;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleStartComposition, START composition, "
+ "mCompositionStart=%u",
+ mCompositionStart));
+}
+
+bool IMMHandler::HandleComposition(nsWindow* aWindow,
+ const IMEContext& aContext, LPARAM lParam) {
+ // for bug #60050
+ // MS-IME 95/97/98/2000 may send WM_IME_COMPOSITION with non-conversion
+ // mode before it send WM_IME_STARTCOMPOSITION.
+ // However, ATOK sends a WM_IME_COMPOSITION before WM_IME_STARTCOMPOSITION,
+ // and if we access ATOK via some APIs, ATOK will sometimes fail to
+ // initialize its state. If WM_IME_STARTCOMPOSITION is already in the
+ // message queue, we should ignore the strange WM_IME_COMPOSITION message and
+ // skip to the next. So, we should look for next composition message
+ // (WM_IME_STARTCOMPOSITION or WM_IME_ENDCOMPOSITION or WM_IME_COMPOSITION),
+ // and if it's WM_IME_STARTCOMPOSITION, and one more next composition message
+ // is WM_IME_COMPOSITION, current IME is ATOK, probably. Otherwise, we
+ // should start composition forcibly.
+ if (!mIsComposing) {
+ MSG msg1, msg2;
+ HWND wnd = aWindow->GetWindowHandle();
+ if (WinUtils::PeekMessage(&msg1, wnd, WM_IME_STARTCOMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE) &&
+ msg1.message == WM_IME_STARTCOMPOSITION &&
+ WinUtils::PeekMessage(&msg2, wnd, WM_IME_ENDCOMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE) &&
+ msg2.message == WM_IME_COMPOSITION) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleComposition, Ignores due to find a "
+ "WM_IME_STARTCOMPOSITION"));
+ return ShouldDrawCompositionStringOurselves();
+ }
+ }
+
+ bool startCompositionMessageHasBeenSent = mIsComposing;
+
+ //
+ // This catches a fixed result
+ //
+ if (IS_COMMITTING_LPARAM(lParam)) {
+ if (!mIsComposing) {
+ HandleStartComposition(aWindow, aContext);
+ }
+
+ GetCompositionString(aContext, GCS_RESULTSTR, mCompositionString);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleComposition, GCS_RESULTSTR"));
+
+ HandleEndComposition(aWindow, &mCompositionString);
+
+ if (!IS_COMPOSING_LPARAM(lParam)) {
+ return ShouldDrawCompositionStringOurselves();
+ }
+ }
+
+ //
+ // This provides us with a composition string
+ //
+ if (!mIsComposing) {
+ HandleStartComposition(aWindow, aContext);
+ }
+
+ //--------------------------------------------------------
+ // 1. Get GCS_COMPSTR
+ //--------------------------------------------------------
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleComposition, GCS_COMPSTR"));
+
+ nsAutoString previousCompositionString(mCompositionString);
+ GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
+
+ if (!IS_COMPOSING_LPARAM(lParam)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, lParam doesn't indicate composing, "
+ "mCompositionString=\"%s\", previousCompositionString=\"%s\"",
+ NS_ConvertUTF16toUTF8(mCompositionString).get(),
+ NS_ConvertUTF16toUTF8(previousCompositionString).get()));
+
+ // If composition string isn't changed, we can trust the lParam.
+ // So, we need to do nothing.
+ if (previousCompositionString == mCompositionString) {
+ return ShouldDrawCompositionStringOurselves();
+ }
+
+ // IME may send WM_IME_COMPOSITION without composing lParam values
+ // when composition string becomes empty (e.g., using Backspace key).
+ // If composition string is empty, we should dispatch a compositionchange
+ // event with empty string and clear the clause information.
+ if (mCompositionString.IsEmpty()) {
+ mClauseArray.Clear();
+ mAttributeArray.Clear();
+ mCursorPosition = 0;
+ DispatchCompositionChangeEvent(aWindow, aContext);
+ return ShouldDrawCompositionStringOurselves();
+ }
+
+ // Otherwise, we cannot trust the lParam value. We might need to
+ // dispatch compositionchange event with the latest composition string
+ // information.
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=296339
+ if (mCompositionString.IsEmpty() && !startCompositionMessageHasBeenSent) {
+ // In this case, maybe, the sender is MSPinYin. That sends *only*
+ // WM_IME_COMPOSITION with GCS_COMP* and GCS_RESULT* when
+ // user inputted the Chinese full stop. So, that doesn't send
+ // WM_IME_STARTCOMPOSITION and WM_IME_ENDCOMPOSITION.
+ // If WM_IME_STARTCOMPOSITION was not sent and the composition
+ // string is null (it indicates the composition transaction ended),
+ // WM_IME_ENDCOMPOSITION may not be sent. If so, we cannot run
+ // HandleEndComposition() in other place.
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, Aborting GCS_COMPSTR"));
+ HandleEndComposition(aWindow);
+ return IS_COMMITTING_LPARAM(lParam);
+ }
+
+ //--------------------------------------------------------
+ // 2. Get GCS_COMPCLAUSE
+ //--------------------------------------------------------
+ long clauseArrayLength =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_COMPCLAUSE, nullptr, 0);
+ clauseArrayLength /= sizeof(uint32_t);
+
+ if (clauseArrayLength > 0) {
+ nsresult rv = EnsureClauseArray(clauseArrayLength);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Intelligent ABC IME (Simplified Chinese IME, the code page is 936)
+ // will crash in ImmGetCompositionStringW for GCS_COMPCLAUSE (bug 424663).
+ // See comment 35 of the bug for the detail. Therefore, we should use A
+ // API for it, however, we should not kill Unicode support on all IMEs.
+ bool useA_API = !(sIMEProperty & IME_PROP_UNICODE);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, useA_API=%s",
+ useA_API ? "TRUE" : "FALSE"));
+
+ long clauseArrayLength2 =
+ useA_API ? ::ImmGetCompositionStringA(
+ aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
+ mClauseArray.Capacity() * sizeof(uint32_t))
+ : ::ImmGetCompositionStringW(
+ aContext.get(), GCS_COMPCLAUSE, mClauseArray.Elements(),
+ mClauseArray.Capacity() * sizeof(uint32_t));
+ clauseArrayLength2 /= sizeof(uint32_t);
+
+ if (clauseArrayLength != clauseArrayLength2) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, "
+ "clauseArrayLength=%ld but clauseArrayLength2=%ld",
+ clauseArrayLength, clauseArrayLength2));
+ if (clauseArrayLength > clauseArrayLength2)
+ clauseArrayLength = clauseArrayLength2;
+ }
+
+ if (useA_API && clauseArrayLength > 0) {
+ // Convert each values of sIMECompClauseArray. The values mean offset of
+ // the clauses in ANSI string. But we need the values in Unicode string.
+ nsAutoCString compANSIStr;
+ if (ConvertToANSIString(mCompositionString, GetKeyboardCodePage(),
+ compANSIStr)) {
+ uint32_t maxlen = compANSIStr.Length();
+ mClauseArray.SetLength(clauseArrayLength);
+ mClauseArray[0] = 0; // first value must be 0
+ for (int32_t i = 1; i < clauseArrayLength; i++) {
+ uint32_t len = std::min(mClauseArray[i], maxlen);
+ mClauseArray[i] =
+ ::MultiByteToWideChar(GetKeyboardCodePage(), MB_PRECOMPOSED,
+ (LPCSTR)compANSIStr.get(), len, nullptr, 0);
+ }
+ }
+ }
+ }
+ // compClauseArrayLength may be negative. I.e., ImmGetCompositionStringW
+ // may return an error code.
+ mClauseArray.SetLength(std::max<long>(0, clauseArrayLength));
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, GCS_COMPCLAUSE, mClauseLength=%zu",
+ mClauseArray.Length()));
+
+ //--------------------------------------------------------
+ // 3. Get GCS_COMPATTR
+ //--------------------------------------------------------
+ // This provides us with the attribute string necessary
+ // for doing hiliting
+ long attrArrayLength =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_COMPATTR, nullptr, 0);
+ attrArrayLength /= sizeof(uint8_t);
+
+ if (attrArrayLength > 0) {
+ nsresult rv = EnsureAttributeArray(attrArrayLength);
+ NS_ENSURE_SUCCESS(rv, false);
+ attrArrayLength = ::ImmGetCompositionStringW(
+ aContext.get(), GCS_COMPATTR, mAttributeArray.Elements(),
+ mAttributeArray.Capacity() * sizeof(uint8_t));
+ }
+
+ // attrStrLen may be negative. I.e., ImmGetCompositionStringW may return an
+ // error code.
+ mAttributeArray.SetLength(std::max<long>(0, attrArrayLength));
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, GCS_COMPATTR, mAttributeLength=%zu",
+ mAttributeArray.Length()));
+
+ //--------------------------------------------------------
+ // 4. Get GCS_CURSOPOS
+ //--------------------------------------------------------
+ // Some IMEs (e.g., the standard IME for Korean) don't have caret position.
+ if (lParam & GCS_CURSORPOS) {
+ mCursorPosition =
+ ::ImmGetCompositionStringW(aContext.get(), GCS_CURSORPOS, nullptr, 0);
+ if (mCursorPosition < 0) {
+ mCursorPosition = NO_IME_CARET; // The result is error
+ }
+ } else {
+ mCursorPosition = NO_IME_CARET;
+ }
+
+ NS_ASSERTION(mCursorPosition <= (long)mCompositionString.Length(),
+ "illegal pos");
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::HandleComposition, GCS_CURSORPOS, mCursorPosition=%d",
+ mCursorPosition));
+
+ //--------------------------------------------------------
+ // 5. Send the compositionchange event
+ //--------------------------------------------------------
+ DispatchCompositionChangeEvent(aWindow, aContext);
+
+ return ShouldDrawCompositionStringOurselves();
+}
+
+void IMMHandler::HandleEndComposition(nsWindow* aWindow,
+ const nsAString* aCommitString) {
+ MOZ_ASSERT(mIsComposing,
+ "HandleEndComposition is called but mIsComposing is FALSE");
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleEndComposition(aWindow=0x%p, aCommitString=0x%p "
+ "(\"%s\"))",
+ aWindow, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleEndComposition, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->CommitComposition(status, aCommitString, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleStartComposition, FAILED, due to "
+ "TextEventDispatcher::CommitComposition() failure"));
+ return;
+ }
+ mIsComposing = false;
+ // XXX aWindow and mComposingWindow are always same??
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+}
+
+bool IMMHandler::HandleReconvert(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ *oResult = 0;
+ RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
+
+ const Maybe<ContentSelection>& contentSelection =
+ GetContentSelectionWithQueryIfNothing(aWindow);
+ if (contentSelection.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleReconvert, FAILED, due to "
+ "Selection::GetContentSelectionWithQueryIfNothing() failure"));
+ return false;
+ }
+
+ const uint32_t len = contentSelection->HasRange()
+ ? contentSelection->OffsetAndDataRef().Length()
+ : 0u;
+ uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!pReconv) {
+ // Return need size to reconvert.
+ if (len == 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleReconvert, There are not selected text"));
+ return false;
+ }
+ *oResult = needSize;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleReconvert, succeeded, result=%" PRIdLPTR,
+ *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleReconvert, FAILED, pReconv->dwSize=%ld, "
+ "needSize=%u",
+ pReconv->dwSize, needSize));
+ return false;
+ }
+
+ *oResult = needSize;
+
+ // Fill reconvert struct
+ pReconv->dwVersion = 0;
+ pReconv->dwStrLen = len;
+ pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ pReconv->dwCompStrLen = len;
+ pReconv->dwCompStrOffset = 0;
+ pReconv->dwTargetStrLen = len;
+ pReconv->dwTargetStrOffset = 0;
+
+ if (len) {
+ ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
+ contentSelection->OffsetAndDataRef().DataRef().get(),
+ len * sizeof(WCHAR));
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleReconvert, SUCCEEDED, pReconv=%s, result=%" PRIdLPTR,
+ GetReconvertStringLog(pReconv).get(), *oResult));
+
+ return true;
+}
+
+bool IMMHandler::HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ uint32_t len = mIsComposing ? mCompositionString.Length() : 0;
+ *oResult = false;
+ IMECHARPOSITION* pCharPosition = reinterpret_cast<IMECHARPOSITION*>(lParam);
+ if (!pCharPosition) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleQueryCharPosition, FAILED, due to "
+ "pCharPosition is null"));
+ return false;
+ }
+ if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleReconvert, FAILED, pCharPosition->dwSize=%lu, "
+ "sizeof(IMECHARPOSITION)=%zu",
+ pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
+ return false;
+ }
+ if (::GetFocus() != aWindow->GetWindowHandle()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleReconvert, FAILED, ::GetFocus()=%p, "
+ "OurWindowHandle=%p",
+ ::GetFocus(), aWindow->GetWindowHandle()));
+ return false;
+ }
+ if (pCharPosition->dwCharPos > len) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleQueryCharPosition, FAILED, "
+ "pCharPosition->dwCharPos=%ld, len=%u",
+ pCharPosition->dwCharPos, len));
+ return false;
+ }
+
+ LayoutDeviceIntRect r;
+ bool ret =
+ GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
+ NS_ENSURE_TRUE(ret, false);
+
+ LayoutDeviceIntRect screenRect;
+ // We always need top level window that is owner window of the popup window
+ // even if the content of the popup window has focus.
+ ResolveIMECaretPos(aWindow->GetTopLevelWindow(false), r, nullptr, screenRect);
+
+ // XXX This might need to check writing mode. However, MSDN doesn't explain
+ // how to set the values in vertical writing mode. Additionally, IME
+ // doesn't work well with top-left of the character (this is explicitly
+ // documented) and its horizontal width. So, it might be better to set
+ // top-right corner of the character and horizontal width, but we're not
+ // sure if it doesn't cause any problems with a lot of IMEs...
+ pCharPosition->pt.x = screenRect.X();
+ pCharPosition->pt.y = screenRect.Y();
+
+ pCharPosition->cLineHeight = r.Height();
+
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(queryEditorRectEvent);
+ DispatchEvent(aWindow, queryEditorRectEvent);
+ if (NS_WARN_IF(queryEditorRectEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleQueryCharPosition, eQueryEditorRect failed"));
+ ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+ } else {
+ LayoutDeviceIntRect editorRectInWindow = queryEditorRectEvent.mReply->mRect;
+ nsWindow* window = !!queryEditorRectEvent.mReply->mFocusedWidget
+ ? static_cast<nsWindow*>(
+ queryEditorRectEvent.mReply->mFocusedWidget)
+ : aWindow;
+ LayoutDeviceIntRect editorRectInScreen;
+ ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
+ ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(),
+ editorRectInScreen.Y(), editorRectInScreen.XMost(),
+ editorRectInScreen.YMost());
+ }
+
+ *oResult = TRUE;
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleQueryCharPosition, SUCCEEDED, pCharPosition={ "
+ "pt={ x=%ld, y=%ld }, cLineHeight=%d, rcDocument={ left=%ld, top=%ld, "
+ "right=%ld, bottom=%ld } }",
+ pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
+ pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
+ pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
+ return true;
+}
+
+bool IMMHandler::HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult) {
+ *oResult = 0;
+ RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
+
+ LayoutDeviceIntPoint point(0, 0);
+
+ bool hasCompositionString =
+ mIsComposing && ShouldDrawCompositionStringOurselves();
+
+ int32_t targetOffset, targetLength;
+ if (!hasCompositionString) {
+ const Maybe<ContentSelection>& contentSelection =
+ GetContentSelectionWithQueryIfNothing(aWindow);
+ if (contentSelection.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleDocumentFeed, FAILED, due to "
+ "Selection::GetContentSelectionWithQueryIfNothing() failure"));
+ return false;
+ }
+ if (contentSelection->HasRange()) {
+ targetOffset = static_cast<int32_t>(
+ contentSelection->OffsetAndDataRef().StartOffset());
+ targetLength =
+ static_cast<int32_t>(contentSelection->OffsetAndDataRef().Length());
+ } else {
+ // If there is no selection range, let's return all text in the editor.
+ targetOffset = 0;
+ targetLength = INT32_MAX;
+ }
+ } else {
+ targetOffset = int32_t(mCompositionStart);
+ targetLength = int32_t(mCompositionString.Length());
+ }
+
+ // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
+ // we cannot support this message when the current offset is larger than
+ // INT32_MAX.
+ if (targetOffset < 0 || targetLength < 0 || targetOffset + targetLength < 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleDocumentFeed, FAILED, "
+ "due to the selection is out of range"));
+ return false;
+ }
+
+ // Get all contents of the focused editor.
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ aWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ aWindow->InitEvent(queryTextContentEvent, &point);
+ DispatchEvent(aWindow, queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleDocumentFeed, FAILED, "
+ "due to eQueryTextContent failure"));
+ return false;
+ }
+
+ nsAutoString str(queryTextContentEvent.mReply->DataRef());
+ if (targetOffset > static_cast<int32_t>(str.Length())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::HandleDocumentFeed, FAILED, "
+ "due to the caret offset is invalid"));
+ return false;
+ }
+
+ // Get the focused paragraph, we decide that it starts from the previous CRLF
+ // (or start of the editor) to the next one (or the end of the editor).
+ int32_t paragraphStart = 0;
+ if (targetOffset > 0) {
+ paragraphStart = Substring(str, 0, targetOffset).RFind(u"\n") + 1;
+ }
+ int32_t paragraphEnd = str.Find(u"\r", targetOffset + targetLength);
+ if (paragraphEnd < 0) {
+ paragraphEnd = str.Length();
+ }
+ nsDependentSubstring paragraph(str, paragraphStart,
+ paragraphEnd - paragraphStart);
+
+ uint32_t len = paragraph.Length();
+ uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!pReconv) {
+ *oResult = needSize;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleDocumentFeed, succeeded, result=%" PRIdLPTR,
+ *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleDocumentFeed, FAILED, "
+ "pReconv->dwSize=%ld, needSize=%u",
+ pReconv->dwSize, needSize));
+ return false;
+ }
+
+ // Fill reconvert struct
+ pReconv->dwVersion = 0;
+ pReconv->dwStrLen = len;
+ pReconv->dwStrOffset = sizeof(RECONVERTSTRING);
+ if (hasCompositionString) {
+ pReconv->dwCompStrLen = targetLength;
+ pReconv->dwCompStrOffset = (targetOffset - paragraphStart) * sizeof(WCHAR);
+ // Set composition target clause information
+ uint32_t offset, length;
+ if (!GetTargetClauseRange(&offset, &length)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::HandleDocumentFeed, FAILED, "
+ "due to IMMHandler::GetTargetClauseRange() failure"));
+ return false;
+ }
+ pReconv->dwTargetStrLen = length;
+ pReconv->dwTargetStrOffset = (offset - paragraphStart) * sizeof(WCHAR);
+ } else {
+ pReconv->dwTargetStrLen = targetLength;
+ pReconv->dwTargetStrOffset =
+ (targetOffset - paragraphStart) * sizeof(WCHAR);
+ // There is no composition string, so, the length is zero but we should
+ // set the cursor offset to the composition str offset.
+ pReconv->dwCompStrLen = 0;
+ pReconv->dwCompStrOffset = pReconv->dwTargetStrOffset;
+ }
+
+ *oResult = needSize;
+ ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
+ paragraph.BeginReading(), len * sizeof(WCHAR));
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::HandleDocumentFeed, SUCCEEDED, pReconv=%s, "
+ "result=%" PRIdLPTR,
+ GetReconvertStringLog(pReconv).get(), *oResult));
+
+ return true;
+}
+
+bool IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow) {
+ if (!mComposingWindow || mComposingWindow == aWindow) {
+ return false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::CommitCompositionOnPreviousWindow, mIsComposing=%s",
+ GetBoolName(mIsComposing)));
+
+ // If we have composition, we should dispatch composition events internally.
+ if (mIsComposing) {
+ IMEContext context(mComposingWindow);
+ NS_ASSERTION(context.IsValid(), "IME context must be valid");
+
+ HandleEndComposition(mComposingWindow);
+ return true;
+ }
+
+ return false;
+}
+
+static TextRangeType PlatformToNSAttr(uint8_t aAttr) {
+ switch (aAttr) {
+ case ATTR_INPUT_ERROR:
+ // case ATTR_FIXEDCONVERTED:
+ case ATTR_INPUT:
+ return TextRangeType::eRawClause;
+ case ATTR_CONVERTED:
+ return TextRangeType::eConvertedClause;
+ case ATTR_TARGET_NOTCONVERTED:
+ return TextRangeType::eSelectedRawClause;
+ case ATTR_TARGET_CONVERTED:
+ return TextRangeType::eSelectedClause;
+ default:
+ NS_ASSERTION(false, "unknown attribute");
+ return TextRangeType::eCaret;
+ }
+}
+
+// static
+void IMMHandler::DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::DispatchEvent(aWindow=0x%p, aEvent={ mMessage=%s }, "
+ "aWindow->Destroyed()=%s",
+ aWindow, ToChar(aEvent.mMessage), GetBoolName(aWindow->Destroyed())));
+
+ if (aWindow->Destroyed()) {
+ return;
+ }
+
+ aWindow->DispatchWindowEvent(aEvent);
+}
+
+void IMMHandler::DispatchCompositionChangeEvent(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ NS_ASSERTION(mIsComposing, "conflict state");
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::DispatchCompositionChangeEvent"));
+
+ // If we don't need to draw composition string ourselves, we don't need to
+ // fire compositionchange event during composing.
+ if (!ShouldDrawCompositionStringOurselves()) {
+ // But we need to adjust composition window pos and native caret pos, here.
+ SetIMERelatedWindowsPos(aWindow, aContext);
+ return;
+ }
+
+ RefPtr<nsWindow> kungFuDeathGrip(aWindow);
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
+ "TextEventDispatcher::BeginNativeInputTransaction() failure"));
+ return;
+ }
+
+ // NOTE: Calling SetIMERelatedWindowsPos() from this method will be failure
+ // in e10s mode. compositionchange event will notify this of
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, then
+ // SetIMERelatedWindowsPos() will be called.
+
+ // XXX Sogou (Simplified Chinese IME) returns contradictory values:
+ // The cursor position is actual cursor position. However, other values
+ // (composition string and attributes) are empty.
+
+ if (mCompositionString.IsEmpty()) {
+ // Don't append clause information if composition string is empty.
+ } else if (mClauseArray.IsEmpty()) {
+ // Some IMEs don't return clause array information, then, we assume that
+ // all characters in the composition string are in one clause.
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::DispatchCompositionChangeEvent, "
+ "mClauseArray.Length()=0"));
+ rv = dispatcher->SetPendingComposition(mCompositionString, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetPendingComposition() failure"));
+ return;
+ }
+ } else {
+ // iterate over the attributes
+ rv = dispatcher->SetPendingCompositionString(mCompositionString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetPendingCompositionString() failure"));
+ return;
+ }
+ uint32_t lastOffset = 0;
+ for (uint32_t i = 0; i < mClauseArray.Length() - 1; i++) {
+ uint32_t current = mClauseArray[i + 1];
+ if (current > mCompositionString.Length()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::DispatchCompositionChangeEvent, "
+ "mClauseArray[%u]=%u. "
+ "This is larger than mCompositionString.Length()=%zu",
+ i + 1, current, mCompositionString.Length()));
+ current = int32_t(mCompositionString.Length());
+ }
+
+ uint32_t length = current - lastOffset;
+ if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to "
+ "invalid data of mClauseArray or mAttributeArray"));
+ return;
+ }
+ TextRangeType textRangeType =
+ PlatformToNSAttr(mAttributeArray[lastOffset]);
+ rv = dispatcher->AppendClauseToPendingComposition(length, textRangeType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::AppendClauseToPendingComposition() "
+ "failure"));
+ return;
+ }
+
+ lastOffset = current;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::DispatchCompositionChangeEvent, index=%u, "
+ "rangeType=%s, range length=%u",
+ i, ToChar(textRangeType), length));
+ }
+ }
+
+ if (mCursorPosition == NO_IME_CARET) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::DispatchCompositionChangeEvent, no caret"));
+ } else {
+ uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
+ if (cursor > mCompositionString.Length()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::CreateTextRangeArray, mCursorPosition=%d. "
+ "This is larger than mCompositionString.Length()=%zu",
+ mCursorPosition, mCompositionString.Length()));
+ cursor = mCompositionString.Length();
+ }
+
+ // If caret is in the target clause, the target clause will be painted as
+ // normal selection range. Since caret shouldn't be in selection range on
+ // Windows, we shouldn't append caret range in such case.
+ const TextRangeArray* clauses = dispatcher->GetPendingCompositionClauses();
+ const TextRange* targetClause =
+ clauses ? clauses->GetTargetClause() : nullptr;
+ if (targetClause && cursor >= targetClause->mStartOffset &&
+ cursor <= targetClause->mEndOffset) {
+ // Forget the caret position specified by IME since Gecko's caret position
+ // will be at the end of composition string.
+ mCursorPosition = NO_IME_CARET;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::CreateTextRangeArray, no caret due to it's in "
+ "the target clause, now, mCursorPosition is NO_IME_CARET"));
+ }
+
+ if (mCursorPosition != NO_IME_CARET) {
+ rv = dispatcher->SetCaretInPendingComposition(cursor, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::SetCaretInPendingComposition() failure"));
+ return;
+ }
+ }
+ }
+
+ WidgetEventTime eventTime = aWindow->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = dispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::FlushPendingComposition() failure"));
+ return;
+ }
+}
+
+void IMMHandler::GetCompositionString(const IMEContext& aContext, DWORD aIndex,
+ nsAString& aCompositionString) const {
+ aCompositionString.Truncate();
+
+ // Retrieve the size of the required output buffer.
+ long lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex, nullptr, 0);
+ if (lRtn < 0 || !aCompositionString.SetLength((lRtn / sizeof(WCHAR)) + 1,
+ mozilla::fallible)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::GetCompositionString, FAILED, due to OOM"));
+ return; // Error or out of memory.
+ }
+
+ // Actually retrieve the composition string information.
+ lRtn = ::ImmGetCompositionStringW(aContext.get(), aIndex,
+ (LPVOID)aCompositionString.BeginWriting(),
+ lRtn + sizeof(WCHAR));
+ aCompositionString.SetLength(lRtn / sizeof(WCHAR));
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::GetCompositionString, succeeded, aCompositionString=\"%s\"",
+ NS_ConvertUTF16toUTF8(aCompositionString).get()));
+}
+
+bool IMMHandler::GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength) {
+ NS_ENSURE_TRUE(aOffset, false);
+ NS_ENSURE_TRUE(mIsComposing, false);
+ NS_ENSURE_TRUE(ShouldDrawCompositionStringOurselves(), false);
+
+ bool found = false;
+ *aOffset = mCompositionStart;
+ for (uint32_t i = 0; i < mAttributeArray.Length(); i++) {
+ if (mAttributeArray[i] == ATTR_TARGET_NOTCONVERTED ||
+ mAttributeArray[i] == ATTR_TARGET_CONVERTED) {
+ *aOffset = mCompositionStart + i;
+ found = true;
+ break;
+ }
+ }
+
+ if (!aLength) {
+ return true;
+ }
+
+ if (!found) {
+ // The all composition string is targetted when there is no ATTR_TARGET_*
+ // clause. E.g., there is only ATTR_INPUT
+ *aLength = mCompositionString.Length();
+ return true;
+ }
+
+ uint32_t offsetInComposition = *aOffset - mCompositionStart;
+ *aLength = mCompositionString.Length() - offsetInComposition;
+ for (uint32_t i = offsetInComposition; i < mAttributeArray.Length(); i++) {
+ if (mAttributeArray[i] != ATTR_TARGET_NOTCONVERTED &&
+ mAttributeArray[i] != ATTR_TARGET_CONVERTED) {
+ *aLength = i - offsetInComposition;
+ break;
+ }
+ }
+ return true;
+}
+
+bool IMMHandler::ConvertToANSIString(const nsString& aStr, UINT aCodePage,
+ nsACString& aANSIStr) {
+ int len = ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(),
+ aStr.Length(), nullptr, 0, nullptr, nullptr);
+ NS_ENSURE_TRUE(len >= 0, false);
+
+ if (!aANSIStr.SetLength(len, mozilla::fallible)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::ConvertToANSIString, FAILED, due to OOM"));
+ return false;
+ }
+ ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
+ (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
+ return true;
+}
+
+bool IMMHandler::GetCharacterRectOfSelectedTextAt(
+ nsWindow* aWindow, uint32_t aOffset, LayoutDeviceIntRect& aCharRect,
+ WritingMode* aWritingMode) {
+ LayoutDeviceIntPoint point(0, 0);
+
+ const Maybe<ContentSelection>& contentSelection =
+ GetContentSelectionWithQueryIfNothing(aWindow);
+ if (contentSelection.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "Selection::GetContentSelectionWithQueryIfNothing() failure"));
+ return false;
+ }
+
+ // If there is neither a selection range nor composition string, cannot return
+ // character rect, of course.
+ if (!contentSelection->HasRange() && !mIsComposing) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "there is neither a selection range nor composition string"));
+ return false;
+ }
+
+ // If the offset is larger than the end of composition string or selected
+ // string, we should return false since such case must be a bug of the caller
+ // or the active IME. If it's an IME's bug, we need to set targetLength to
+ // aOffset.
+ const uint32_t targetLength =
+ mIsComposing ? mCompositionString.Length()
+ : contentSelection->OffsetAndDataRef().Length();
+ if (NS_WARN_IF(aOffset > targetLength)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("IMMHandler::GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "aOffset is too large (aOffset=%u, targetLength=%u, mIsComposing=%s)",
+ aOffset, targetLength, GetBoolName(mIsComposing)));
+ return false;
+ }
+
+ // If there is caret, we might be able to use caret rect.
+ uint32_t caretOffset = UINT32_MAX;
+ // There is a caret only when the normal selection is collapsed.
+ if (contentSelection.isNothing() ||
+ contentSelection->OffsetAndDataRef().IsDataEmpty()) {
+ if (mIsComposing) {
+ // If it's composing, mCursorPosition is the offset to caret in
+ // the composition string.
+ if (mCursorPosition != NO_IME_CARET) {
+ MOZ_ASSERT(mCursorPosition >= 0);
+ caretOffset = mCursorPosition;
+ } else if (!ShouldDrawCompositionStringOurselves() ||
+ mCompositionString.IsEmpty()) {
+ // Otherwise, if there is no composition string, we should assume that
+ // there is a caret at the start of composition string.
+ caretOffset = 0;
+ }
+ } else {
+ // If there is no composition, the selection offset is the caret offset.
+ caretOffset = 0;
+ }
+ }
+
+ // If there is a caret and retrieving offset is same as the caret offset,
+ // we should use the caret rect.
+ if (aOffset != caretOffset) {
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryTextRectEvent.InitForQueryTextRect(aOffset, 1, options);
+ aWindow->InitEvent(queryTextRectEvent, &point);
+ DispatchEvent(aWindow, queryTextRectEvent);
+ if (queryTextRectEvent.Succeeded()) {
+ aCharRect = queryTextRectEvent.mReply->mRect;
+ if (aWritingMode) {
+ *aWritingMode = queryTextRectEvent.mReply->WritingModeRef();
+ }
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("IMMHandler::GetCharacterRectOfSelectedTextAt, Succeeded, "
+ "aOffset=%u, aCharRect={ x: %d, y: %d, width: %d, height: %d }, "
+ "queryTextRectEvent={ mReply=%s }",
+ aOffset, aCharRect.X(), aCharRect.Y(), aCharRect.Width(),
+ aCharRect.Height(), ToString(queryTextRectEvent.mReply).c_str()));
+ return true;
+ }
+ }
+
+ return GetCaretRect(aWindow, aCharRect, aWritingMode);
+}
+
+bool IMMHandler::GetCaretRect(nsWindow* aWindow,
+ LayoutDeviceIntRect& aCaretRect,
+ WritingMode* aWritingMode) {
+ LayoutDeviceIntPoint point(0, 0);
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryCaretRectEvent.InitForQueryCaretRect(0, options);
+ aWindow->InitEvent(queryCaretRectEvent, &point);
+ DispatchEvent(aWindow, queryCaretRectEvent);
+ if (queryCaretRectEvent.Failed()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::GetCaretRect, FAILED, due to eQueryCaretRect failure"));
+ return false;
+ }
+ aCaretRect = queryCaretRectEvent.mReply->mRect;
+ if (aWritingMode) {
+ *aWritingMode = queryCaretRectEvent.mReply->WritingModeRef();
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::GetCaretRect, SUCCEEDED, "
+ "aCaretRect={ x: %d, y: %d, width: %d, height: %d }, "
+ "queryCaretRectEvent={ mReply=%s }",
+ aCaretRect.X(), aCaretRect.Y(), aCaretRect.Width(),
+ aCaretRect.Height(), ToString(queryCaretRectEvent.mReply).c_str()));
+ return true;
+}
+
+bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext) {
+ // Get first character rect of current a normal selected text or a composing
+ // string.
+ WritingMode writingMode;
+ LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow;
+ bool ret = GetCharacterRectOfSelectedTextAt(
+ aWindow, 0, firstSelectedCharRectRelativeToWindow, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect firstSelectedCharRect;
+ ResolveIMECaretPos(toplevelWindow, firstSelectedCharRectRelativeToWindow,
+ aWindow, firstSelectedCharRect);
+
+ // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
+ // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
+ // Chinese) on XP. But if a11y module is handling native caret, we shouldn't
+ // touch it.
+ if (!IMEHandler::IsA11yHandlingNativeCaret()) {
+ LayoutDeviceIntRect caretRect(firstSelectedCharRect),
+ caretRectRelativeToWindow;
+ if (GetCaretRect(aWindow, caretRectRelativeToWindow)) {
+ ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow,
+ caretRect);
+ } else {
+ NS_WARNING("failed to get caret rect");
+ caretRect.SetWidth(1);
+ }
+ IMEHandler::CreateNativeCaret(aWindow, caretRect);
+ }
+
+ if (ShouldDrawCompositionStringOurselves()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::SetIMERelatedWindowsPos, Set candidate window"));
+
+ // Get a rect of first character in current target in composition string.
+ LayoutDeviceIntRect firstTargetCharRect, lastTargetCharRect;
+ if (mIsComposing && !mCompositionString.IsEmpty()) {
+ // If there are no targetted selection, we should use it's first character
+ // rect instead.
+ uint32_t offset, length;
+ if (!GetTargetClauseRange(&offset, &length)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::SetIMERelatedWindowsPos, FAILED, due to "
+ "GetTargetClauseRange() failure"));
+ return false;
+ }
+ ret =
+ GetCharacterRectOfSelectedTextAt(aWindow, offset - mCompositionStart,
+ firstTargetCharRect, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ if (length) {
+ ret = GetCharacterRectOfSelectedTextAt(
+ aWindow, offset + length - 1 - mCompositionStart,
+ lastTargetCharRect);
+ NS_ENSURE_TRUE(ret, false);
+ } else {
+ lastTargetCharRect = firstTargetCharRect;
+ }
+ } else {
+ // If there are no composition string, we should use a first character
+ // rect.
+ ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, firstTargetCharRect,
+ &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ lastTargetCharRect = firstTargetCharRect;
+ }
+ ResolveIMECaretPos(toplevelWindow, firstTargetCharRect, aWindow,
+ firstTargetCharRect);
+ ResolveIMECaretPos(toplevelWindow, lastTargetCharRect, aWindow,
+ lastTargetCharRect);
+ LayoutDeviceIntRect targetClauseRect;
+ targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
+
+ // Move the candidate window to proper position from the target clause as
+ // far as possible.
+ CANDIDATEFORM candForm;
+ candForm.dwIndex = 0;
+ if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
+ candForm.dwStyle = CFS_EXCLUDE;
+ // Candidate window shouldn't overlap the target clause in any writing
+ // mode.
+ candForm.rcArea.left = targetClauseRect.X();
+ candForm.rcArea.right = targetClauseRect.XMost();
+ candForm.rcArea.top = targetClauseRect.Y();
+ candForm.rcArea.bottom = targetClauseRect.YMost();
+ if (!writingMode.IsVertical()) {
+ // In horizontal layout, current point of interest should be top-left
+ // of the first character.
+ candForm.ptCurrentPos.x = firstTargetCharRect.X();
+ candForm.ptCurrentPos.y = firstTargetCharRect.Y();
+ } else if (writingMode.IsVerticalRL()) {
+ // In vertical layout (RL), candidate window should be positioned right
+ // side of target clause. However, we don't set vertical writing font
+ // to the IME. Therefore, the candidate window may be positioned
+ // bottom-left of target clause rect with these information.
+ candForm.ptCurrentPos.x = targetClauseRect.X();
+ candForm.ptCurrentPos.y = targetClauseRect.Y();
+ } else {
+ MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
+ // In vertical layout (LR), candidate window should be poisitioned left
+ // side of target clause. Although, we don't set vertical writing font
+ // to the IME, the candidate window may be positioned bottom-right of
+ // the target clause rect with these information.
+ candForm.ptCurrentPos.x = targetClauseRect.XMost();
+ candForm.ptCurrentPos.y = targetClauseRect.Y();
+ }
+ } else {
+ // If vertical writing is not supported by IME, let's set candidate
+ // window position to the bottom-left of the target clause because
+ // the position must be the safest position to prevent the candidate
+ // window to overlap with the target clause.
+ candForm.dwStyle = CFS_CANDIDATEPOS;
+ candForm.ptCurrentPos.x = targetClauseRect.X();
+ candForm.ptCurrentPos.y = targetClauseRect.YMost();
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::SetIMERelatedWindowsPos, Calling "
+ "ImmSetCandidateWindow()... ptCurrentPos={ x=%ld, y=%ld }, "
+ "rcArea={ left=%ld, top=%ld, right=%ld, bottom=%ld }, "
+ "writingMode=%s",
+ candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
+ candForm.rcArea.left, candForm.rcArea.top, candForm.rcArea.right,
+ candForm.rcArea.bottom, ToString(writingMode).c_str()));
+ ::ImmSetCandidateWindow(aContext.get(), &candForm);
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::SetIMERelatedWindowsPos, Set composition window"));
+
+ // Move the composition window to caret position (if selected some
+ // characters, we should use first character rect of them).
+ // And in this mode, IME adjusts the candidate window position
+ // automatically. So, we don't need to set it.
+ COMPOSITIONFORM compForm;
+ compForm.dwStyle = CFS_POINT;
+ compForm.ptCurrentPos.x = !writingMode.IsVerticalLR()
+ ? firstSelectedCharRect.X()
+ : firstSelectedCharRect.XMost();
+ compForm.ptCurrentPos.y = firstSelectedCharRect.Y();
+ ::ImmSetCompositionWindow(aContext.get(), &compForm);
+ }
+
+ return true;
+}
+
+void IMMHandler::ResolveIMECaretPos(nsIWidget* aReferenceWidget,
+ LayoutDeviceIntRect& aCursorRect,
+ nsIWidget* aNewOriginWidget,
+ LayoutDeviceIntRect& aOutRect) {
+ aOutRect = aCursorRect;
+
+ if (aReferenceWidget == aNewOriginWidget) return;
+
+ if (aReferenceWidget)
+ aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset());
+
+ if (aNewOriginWidget)
+ aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset());
+}
+
+static void SetHorizontalFontToLogFont(const nsAString& aFontFace,
+ LOGFONTW& aLogFont) {
+ aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
+ if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
+ memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
+ return;
+ }
+ memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
+ aFontFace.Length() * sizeof(wchar_t));
+ aLogFont.lfFaceName[aFontFace.Length()] = 0;
+}
+
+static void SetVerticalFontToLogFont(const nsAString& aFontFace,
+ LOGFONTW& aLogFont) {
+ aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
+ if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
+ memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
+ return;
+ }
+ aLogFont.lfFaceName[0] = '@';
+ memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
+ aFontFace.Length() * sizeof(wchar_t));
+ aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
+}
+
+void IMMHandler::AdjustCompositionFont(nsWindow* aWindow,
+ const IMEContext& aContext,
+ const WritingMode& aWritingMode,
+ bool aForceUpdate) {
+ // An instance of IMMHandler is destroyed when active IME is changed.
+ // Therefore, we need to store the information which are set to the IM
+ // context to static variables since IM context is never recreated.
+ static bool sCompositionFontsInitialized = false;
+ static nsString sCompositionFont;
+ static bool sCompositionFontPrefDone = false;
+ if (!sCompositionFontPrefDone) {
+ sCompositionFontPrefDone = true;
+ Preferences::GetString("intl.imm.composition_font", sCompositionFont);
+ }
+
+ // If composition font is customized by pref, we need to modify the
+ // composition font of the IME context at first time even if the writing mode
+ // is horizontal.
+ bool setCompositionFontForcibly =
+ aForceUpdate ||
+ (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
+
+ static WritingMode sCurrentWritingMode;
+ static nsString sCurrentIMEName;
+ if (!setCompositionFontForcibly &&
+ sWritingModeOfCompositionFont == aWritingMode &&
+ sCurrentIMEName == sIMEName) {
+ // Nothing to do if writing mode isn't being changed.
+ return;
+ }
+
+ // Decide composition fonts for both horizontal writing mode and vertical
+ // writing mode. If the font isn't specified by the pref, use default
+ // font which is already set to the IM context. And also in vertical writing
+ // mode, insert '@' to the start of the font.
+ if (!sCompositionFontsInitialized) {
+ sCompositionFontsInitialized = true;
+ // sCompositionFontH must not start with '@' and its length is less than
+ // LF_FACESIZE since it needs to end with null terminating character.
+ if (sCompositionFont.IsEmpty() ||
+ sCompositionFont.Length() > LF_FACESIZE - 1 ||
+ sCompositionFont[0] == '@') {
+ LOGFONTW defaultLogFont;
+ if (NS_WARN_IF(
+ !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
+ "failed"));
+ sCompositionFont.AssignLiteral("System");
+ } else {
+ // The font face is typically, "System".
+ sCompositionFont.Assign(defaultLogFont.lfFaceName);
+ }
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" IMMHandler::AdjustCompositionFont, sCompositionFont=\"%s\" is "
+ "initialized",
+ NS_ConvertUTF16toUTF8(sCompositionFont).get()));
+ }
+
+ static nsString sCompositionFontForJapanist2003;
+ if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
+ const char* kCompositionFontForJapanist2003 =
+ "intl.imm.composition_font.japanist_2003";
+ Preferences::GetString(kCompositionFontForJapanist2003,
+ sCompositionFontForJapanist2003);
+ // If the font name is not specified properly, let's use
+ // "MS PGothic" instead.
+ if (sCompositionFontForJapanist2003.IsEmpty() ||
+ sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
+ sCompositionFontForJapanist2003[0] == '@') {
+ sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
+ }
+ }
+
+ sWritingModeOfCompositionFont = aWritingMode;
+ sCurrentIMEName = sIMEName;
+
+ LOGFONTW logFont;
+ memset(&logFont, 0, sizeof(logFont));
+ if (!::ImmGetCompositionFont(aContext.get(), &logFont)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::AdjustCompositionFont, ::ImmGetCompositionFont() "
+ "failed"));
+ logFont.lfFaceName[0] = 0;
+ }
+ // Need to reset some information which should be recomputed with new font.
+ logFont.lfWidth = 0;
+ logFont.lfWeight = FW_DONTCARE;
+ logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ logFont.lfPitchAndFamily = DEFAULT_PITCH;
+
+ if (aWritingMode.IsVertical() && IsVerticalWritingSupported()) {
+ SetVerticalFontToLogFont(IsJapanist2003Active()
+ ? sCompositionFontForJapanist2003
+ : sCompositionFont,
+ logFont);
+ } else {
+ SetHorizontalFontToLogFont(IsJapanist2003Active()
+ ? sCompositionFontForJapanist2003
+ : sCompositionFont,
+ logFont);
+ }
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ (" IMMHandler::AdjustCompositionFont, calling "
+ "::ImmSetCompositionFont(\"%s\")",
+ NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
+ ::ImmSetCompositionFontW(aContext.get(), &logFont);
+}
+
+// static
+nsresult IMMHandler::OnMouseButtonEvent(
+ nsWindow* aWindow, const IMENotification& aIMENotification) {
+ // We don't need to create the instance of the handler here.
+ if (!gIMMHandler) {
+ return NS_OK;
+ }
+
+ if (!sWM_MSIME_MOUSE || !IsComposingOnOurEditor() ||
+ !ShouldDrawCompositionStringOurselves()) {
+ return NS_OK;
+ }
+
+ // We need to handle only mousedown event.
+ if (aIMENotification.mMouseButtonEventData.mEventMessage != eMouseDown) {
+ return NS_OK;
+ }
+
+ // If the character under the cursor is not in the composition string,
+ // we don't need to notify IME of it.
+ uint32_t compositionStart = gIMMHandler->mCompositionStart;
+ uint32_t compositionEnd =
+ compositionStart + gIMMHandler->mCompositionString.Length();
+ if (aIMENotification.mMouseButtonEventData.mOffset < compositionStart ||
+ aIMENotification.mMouseButtonEventData.mOffset >= compositionEnd) {
+ return NS_OK;
+ }
+
+ BYTE button;
+ switch (aIMENotification.mMouseButtonEventData.mButton) {
+ case MouseButton::ePrimary:
+ button = IMEMOUSE_LDOWN;
+ break;
+ case MouseButton::eMiddle:
+ button = IMEMOUSE_MDOWN;
+ break;
+ case MouseButton::eSecondary:
+ button = IMEMOUSE_RDOWN;
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // calcurate positioning and offset
+ // char : JCH1|JCH2|JCH3
+ // offset: 0011 1122 2233
+ // positioning: 2301 2301 2301
+ LayoutDeviceIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos;
+ LayoutDeviceIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect;
+ int32_t cursorXInChar = cursorPos.x - charRect.X();
+ // The event might hit to zero-width character, see bug 694913.
+ // The reason might be:
+ // * There are some zero-width characters are actually.
+ // * font-size is specified zero.
+ // But nobody reproduced this bug actually...
+ // We should assume that user clicked on right most of the zero-width
+ // character in such case.
+ int positioning = 1;
+ if (charRect.Width() > 0) {
+ positioning = cursorXInChar * 4 / charRect.Width();
+ positioning = (positioning + 2) % 4;
+ }
+
+ int offset =
+ aIMENotification.mMouseButtonEventData.mOffset - compositionStart;
+ if (positioning < 2) {
+ offset++;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::OnMouseButtonEvent, x,y=%d,%d, offset=%d, "
+ "positioning=%d",
+ cursorPos.x.value, cursorPos.y.value, offset, positioning));
+
+ // send MS_MSIME_MOUSE message to default IME window.
+ HWND imeWnd = ::ImmGetDefaultIMEWnd(aWindow->GetWindowHandle());
+ IMEContext context(aWindow);
+ if (::SendMessageW(imeWnd, sWM_MSIME_MOUSE,
+ MAKELONG(MAKEWORD(button, positioning), offset),
+ (LPARAM)context.get()) == 1) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ return NS_OK;
+}
+
+// static
+bool IMMHandler::OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("IMMHandler::OnKeyDownEvent, hWnd=%p, wParam=%08zx, lParam=%08" PRIxLPTR,
+ aWindow->GetWindowHandle(), wParam, lParam));
+ aResult.mConsumed = false;
+ switch (wParam) {
+ case VK_TAB:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_END:
+ case VK_HOME:
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_RETURN:
+ // If IME didn't process the key message (the virtual key code wasn't
+ // converted to VK_PROCESSKEY), and the virtual key code event causes
+ // moving caret or editing text with keeping composing state, we should
+ // cancel the composition here because we cannot support moving
+ // composition string with DOM events (IE also cancels the composition
+ // in same cases). Then, this event will be dispatched.
+ if (IsComposingOnOurEditor()) {
+ // NOTE: We don't need to cancel the composition on another window.
+ CancelComposition(aWindow, false);
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
+Maybe<ContentSelection> IMMHandler::QueryContentSelection(nsWindow* aWindow) {
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ aWindow);
+ LayoutDeviceIntPoint point(0, 0);
+ aWindow->InitEvent(querySelectedTextEvent, &point);
+ DispatchEvent(aWindow, querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::Selection::Init, FAILED, due to eQuerySelectedText "
+ "failure"));
+ return Nothing();
+ }
+ // If the window is destroyed during querying selected text, we shouldn't
+ // do anymore.
+ if (aWindow->Destroyed()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" IMMHandler::Selection::Init, FAILED, due to the widget destroyed"));
+ return Nothing();
+ }
+
+ ContentSelection contentSelection(querySelectedTextEvent);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("IMMHandler::Selection::Init, querySelectedTextEvent={ mReply=%s }",
+ ToString(querySelectedTextEvent.mReply).c_str()));
+
+ if (contentSelection.HasRange() &&
+ !contentSelection.OffsetAndDataRef().IsValid()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" IMMHandler::Selection::Init, FAILED, due to invalid range"));
+ return Nothing();
+ }
+ return Some(contentSelection);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/IMMHandler.h b/widget/windows/IMMHandler.h
new file mode 100644
index 0000000000..e012541fae
--- /dev/null
+++ b/widget/windows/IMMHandler.h
@@ -0,0 +1,427 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 IMMHandler_h_
+#define IMMHandler_h_
+
+#include "mozilla/ContentData.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/WritingModes.h"
+
+#include "windef.h"
+#include "winnetwk.h"
+#include "npapi.h"
+
+#include "nsCOMPtr.h"
+#include "nsIWidget.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include <windows.h>
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class IMEContext final {
+ public:
+ IMEContext() : mWnd(nullptr), mIMC(nullptr) {}
+
+ explicit IMEContext(HWND aWnd);
+ explicit IMEContext(nsWindow* aWindowBase);
+
+ ~IMEContext() { Clear(); }
+
+ HIMC get() const { return mIMC; }
+
+ void Init(HWND aWnd);
+ void Init(nsWindow* aWindowBase);
+ void Clear();
+
+ bool IsValid() const { return !!mIMC; }
+
+ void SetOpenState(bool aOpen) const {
+ if (!mIMC) {
+ return;
+ }
+ ::ImmSetOpenStatus(mIMC, aOpen);
+ }
+
+ bool GetOpenState() const {
+ if (!mIMC) {
+ return false;
+ }
+ return (::ImmGetOpenStatus(mIMC) != FALSE);
+ }
+
+ bool AssociateDefaultContext() {
+ // We assume that there is only default IMC, no new IMC has been created.
+ if (mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) {
+ return false;
+ }
+ mIMC = ::ImmGetContext(mWnd);
+ return (mIMC != nullptr);
+ }
+
+ bool Disassociate() {
+ if (!mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) {
+ return false;
+ }
+ ::ImmReleaseContext(mWnd, mIMC);
+ mIMC = nullptr;
+ return true;
+ }
+
+ protected:
+ IMEContext(const IMEContext& aOther) { MOZ_CRASH("Don't copy IMEContext"); }
+
+ HWND mWnd;
+ HIMC mIMC;
+};
+
+class IMMHandler final {
+ public:
+ static void Initialize();
+ static void Terminate();
+
+ // If Process*() returns true, the caller shouldn't do anything anymore.
+ static bool ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
+ LPARAM& lParam, MSGResult& aResult);
+ static bool IsComposing() { return IsComposingOnOurEditor(); }
+ static bool IsComposingOn(nsWindow* aWindow) {
+ return IsComposing() && IsComposingWindow(aWindow);
+ }
+
+#ifdef DEBUG
+ /**
+ * IsIMEAvailable() returns TRUE when current keyboard layout has IME.
+ * Otherwise, FALSE.
+ */
+ static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); }
+#endif
+
+ // If aForce is TRUE, these methods doesn't check whether we have composition
+ // or not. If you don't set it to TRUE, these method doesn't commit/cancel
+ // the composition on uexpected window.
+ static void CommitComposition(nsWindow* aWindow, bool aForce = false);
+ static void CancelComposition(nsWindow* aWindow, bool aForce = false);
+ static void OnFocusChange(bool aFocus, nsWindow* aWindow);
+ static void OnUpdateComposition(nsWindow* aWindow);
+ static void OnSelectionChange(nsWindow* aWindow,
+ const IMENotification& aIMENotification,
+ bool aIsIMMActive);
+
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
+ // IME. Otherwise, NS_OK.
+ static nsresult OnMouseButtonEvent(nsWindow* aWindow,
+ const IMENotification& aIMENotification);
+
+#define DECL_IS_IME_ACTIVE(aReadableName) \
+ static bool Is##aReadableName##Active();
+
+ // Japanese IMEs
+ DECL_IS_IME_ACTIVE(ATOK2006)
+ DECL_IS_IME_ACTIVE(ATOK2007)
+ DECL_IS_IME_ACTIVE(ATOK2008)
+ DECL_IS_IME_ACTIVE(ATOK2009)
+ DECL_IS_IME_ACTIVE(ATOK2010)
+ DECL_IS_IME_ACTIVE(GoogleJapaneseInput)
+ DECL_IS_IME_ACTIVE(Japanist2003)
+
+#undef DECL_IS_IME_ACTIVE
+
+ /**
+ * IsActiveIMEInBlockList() returns true if we know active keyboard layout's
+ * IME has some crash bugs or something which make some damage to us. When
+ * this returns true, IMC shouldn't be associated with any windows.
+ */
+ static bool IsActiveIMEInBlockList();
+
+ protected:
+ static void EnsureHandlerInstance();
+
+ static bool IsComposingOnOurEditor();
+ static bool IsComposingWindow(nsWindow* aWindow);
+
+ static bool ShouldDrawCompositionStringOurselves();
+ static bool IsVerticalWritingSupported();
+ // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
+ static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
+ static UINT GetKeyboardCodePage();
+
+ /**
+ * Checks whether the window is top level window of the composing window.
+ * In this method, the top level window means in all windows, not only in all
+ * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE.
+ */
+ static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);
+
+ static bool ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult);
+
+ IMMHandler();
+ ~IMMHandler();
+
+ // On*() methods return true if the caller of message handler shouldn't do
+ // anything anymore. Otherwise, false.
+ static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult);
+ bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
+ bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // These message handlers don't use instance members, we should not create
+ // the instance by the messages. So, they should be static.
+ static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult);
+ static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // The result of Handle* method mean "Processed" when it's TRUE.
+ void HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext);
+ bool HandleComposition(nsWindow* aWindow, const IMEContext& aContext,
+ LPARAM lParam);
+ // If aCommitString is null, this commits composition with the latest
+ // dispatched data. Otherwise, commits composition with the value.
+ void HandleEndComposition(nsWindow* aWindow,
+ const nsAString* aCommitString = nullptr);
+ bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);
+ bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
+ LRESULT* oResult);
+ bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);
+
+ /**
+ * When a window's IME context is activating but we have composition on
+ * another window, we should commit our composition because IME context is
+ * shared by all our windows (including plug-ins).
+ * @param aWindow is a new activated window.
+ * If aWindow is our composing window, this method does nothing.
+ * Otherwise, this commits the composition on the previous window.
+ * If this method did commit a composition, this returns TRUE.
+ */
+ bool CommitCompositionOnPreviousWindow(nsWindow* aWindow);
+
+ /**
+ * ResolveIMECaretPos
+ * Convert the caret rect of a composition event to another widget's
+ * coordinate system.
+ *
+ * @param aReferenceWidget The origin widget of aCursorRect.
+ * Typically, this is mReferenceWidget of the
+ * composing events. If the aCursorRect is in screen
+ * coordinates, set nullptr.
+ * @param aCursorRect The cursor rect.
+ * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If
+ * this is nullptr, aOutRect will be in screen
+ * coordinates.
+ * @param aOutRect The converted cursor rect.
+ */
+ void ResolveIMECaretPos(nsIWidget* aReferenceWidget,
+ mozilla::LayoutDeviceIntRect& aCursorRect,
+ nsIWidget* aNewOriginWidget,
+ mozilla::LayoutDeviceIntRect& aOutRect);
+
+ bool ConvertToANSIString(const nsString& aStr, UINT aCodePage,
+ nsACString& aANSIStr);
+
+ bool SetIMERelatedWindowsPos(nsWindow* aWindow, const IMEContext& aContext);
+ /**
+ * GetCharacterRectOfSelectedTextAt() returns character rect of the offset
+ * from the selection start or the start of composition string if there is
+ * a composition.
+ *
+ * @param aWindow The window which has focus.
+ * @param aOffset Offset from the selection start or the start of
+ * composition string when there is a composition.
+ * This must be in the selection range or
+ * the composition string.
+ * @param aCharRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCharacterRectOfSelectedTextAt(
+ nsWindow* aWindow, uint32_t aOffset,
+ mozilla::LayoutDeviceIntRect& aCharRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ /**
+ * GetCaretRect() returns caret rect at current selection start.
+ *
+ * @param aWindow The window which has focus.
+ * @param aCaretRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCaretRect(nsWindow* aWindow, mozilla::LayoutDeviceIntRect& aCaretRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ void GetCompositionString(const IMEContext& aContext, DWORD aIndex,
+ nsAString& aCompositionString) const;
+
+ /**
+ * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
+ * If aForceUpdate is true, it will update composition font even if writing
+ * mode isn't being changed.
+ */
+ void AdjustCompositionFont(nsWindow* aWindow, const IMEContext& aContext,
+ const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
+ * locale of active IME is CJK. Note that this creates an instance even
+ * when there is no composition but the locale is CJK.
+ */
+ static void MaybeAdjustCompositionFont(
+ nsWindow* aWindow, const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * Get the current target clause of composition string.
+ * If there are one or more characters whose attribute is ATTR_TARGET_*,
+ * this returns the first character's offset and its length.
+ * Otherwise, e.g., the all characters are ATTR_INPUT, this returns
+ * the composition string range because the all is the current target.
+ *
+ * aLength can be null (default), but aOffset must not be null.
+ *
+ * The aOffset value is offset in the contents. So, when you need offset
+ * in the composition string, you need to subtract mCompositionStart from it.
+ */
+ bool GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength = nullptr);
+
+ /**
+ * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet.
+ */
+ static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent);
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches eCompositionChange event
+ * with clause information (it'll be retrieved by CreateTextRangeArray()).
+ * I.e., this should be called only during composing. If a composition is
+ * being committed, only HandleCompositionEnd() should be called.
+ *
+ * @param aWindow The window which has the composition.
+ * @param aContext Native IME context which has the composition.
+ */
+ void DispatchCompositionChangeEvent(nsWindow* aWindow,
+ const IMEContext& aContext);
+
+ nsresult EnsureClauseArray(int32_t aCount);
+ nsresult EnsureAttributeArray(int32_t aCount);
+
+ /**
+ * When WM_IME_CHAR is received and passed to DefWindowProc, we need to
+ * record the messages. In other words, we should record the messages
+ * when we receive WM_IME_CHAR on windowless plug-in (if we have focus,
+ * we always eat them). When focus is moved from a windowless plug-in to
+ * our window during composition, WM_IME_CHAR messages were received when
+ * the plug-in has focus. However, WM_CHAR messages are received after the
+ * plug-in lost focus. So, we need to ignore the WM_CHAR messages because
+ * they make unexpected text input events on us.
+ */
+ nsTArray<MSG> mPassedIMEChar;
+
+ bool IsIMECharRecordsEmpty() { return mPassedIMEChar.IsEmpty(); }
+ void ResetIMECharRecords() { mPassedIMEChar.Clear(); }
+ void DequeueIMECharRecords(WPARAM& wParam, LPARAM& lParam) {
+ MSG msg = mPassedIMEChar.ElementAt(0);
+ wParam = msg.wParam;
+ lParam = msg.lParam;
+ mPassedIMEChar.RemoveElementAt(0);
+ }
+ void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) {
+ MSG msg;
+ msg.wParam = wParam;
+ msg.lParam = lParam;
+ mPassedIMEChar.AppendElement(msg);
+ }
+
+ TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow);
+
+ nsWindow* mComposingWindow;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ nsString mCompositionString;
+ nsTArray<uint32_t> mClauseArray;
+ nsTArray<uint8_t> mAttributeArray;
+
+ int32_t mCursorPosition;
+ uint32_t mCompositionStart;
+
+ // mContentSelection stores the latest selection data only when sHasFocus is
+ // true. Don't access mContentSelection directly. You should use
+ // GetContentSelectionWithQueryIfNothing() for getting proper state.
+ Maybe<ContentSelection> mContentSelection;
+
+ const Maybe<ContentSelection>& GetContentSelectionWithQueryIfNothing(
+ nsWindow* aWindow) {
+ // When IME has focus, mContentSelection is automatically updated by
+ // NOTIFY_IME_OF_SELECTION_CHANGE.
+ if (sHasFocus) {
+ if (mContentSelection.isNothing()) {
+ // But if this is the first access of mContentSelection, we need to
+ // query selection now.
+ mContentSelection = QueryContentSelection(aWindow);
+ }
+ return mContentSelection;
+ }
+ // Otherwise, i.e., While IME doesn't have focus, we cannot observe
+ // selection changes. So, in such case, we need to query selection
+ // when it's necessary.
+ static Maybe<ContentSelection> sTempContentSelection;
+ sTempContentSelection = QueryContentSelection(aWindow);
+ return sTempContentSelection;
+ }
+
+ /**
+ * Query content selection on aWindow with WidgetQueryContent event.
+ */
+ static Maybe<ContentSelection> QueryContentSelection(nsWindow* aWindow);
+
+ bool mIsComposing;
+
+ static mozilla::WritingMode sWritingModeOfCompositionFont;
+ static nsString sIMEName;
+ static UINT sCodePage;
+ static DWORD sIMEProperty;
+ static DWORD sIMEUIProperty;
+ static bool sAssumeVerticalWritingModeNotSupported;
+ static bool sHasFocus;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // IMMHandler_h_
diff --git a/widget/windows/InProcessWinCompositorWidget.cpp b/widget/windows/InProcessWinCompositorWidget.cpp
new file mode 100644
index 0000000000..de491b734b
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "InProcessWinCompositorWidget.h"
+
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "gfxPlatform.h"
+#include "HeadlessCompositorWidget.h"
+#include "HeadlessWidget.h"
+#include "nsIWidget.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+
+#include <ddraw.h>
+
+namespace mozilla::widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+/* static */
+RefPtr<CompositorWidget> CompositorWidget::CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget) {
+ if (aInitData.type() ==
+ CompositorWidgetInitData::THeadlessCompositorWidgetInitData) {
+ return new HeadlessCompositorWidget(
+ aInitData.get_HeadlessCompositorWidgetInitData(), aOptions,
+ static_cast<HeadlessWidget*>(aWidget));
+ } else {
+ return new InProcessWinCompositorWidget(
+ aInitData.get_WinCompositorWidgetInitData(), aOptions,
+ static_cast<nsWindow*>(aWidget));
+ }
+}
+
+InProcessWinCompositorWidget::InProcessWinCompositorWidget(
+ const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : WinCompositorWidget(aInitData, aOptions),
+ mWindow(aWindow),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mTransparentSurfaceLock("mTransparentSurfaceLock"),
+ mTransparencyMode(uint32_t(aInitData.transparencyMode())),
+ mMemoryDC(nullptr),
+ mCompositeDC(nullptr),
+ mLockedBackBufferData(nullptr) {
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+
+ // mNotDeferEndRemoteDrawing is set on the main thread during init,
+ // but is only accessed after on the compositor thread.
+ mNotDeferEndRemoteDrawing =
+ StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 ||
+ gfxPlatform::IsInLayoutAsapMode() || gfxPlatform::ForceSoftwareVsync();
+}
+
+void InProcessWinCompositorWidget::OnDestroyWindow() {
+ gfx::CriticalSectionAutoEnter presentLock(&mPresentLock);
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+}
+
+bool InProcessWinCompositorWidget::OnWindowResize(
+ const LayoutDeviceIntSize& aSize) {
+ return true;
+}
+
+void InProcessWinCompositorWidget::OnWindowModeChange(nsSizeMode aSizeMode) {}
+
+bool InProcessWinCompositorWidget::PreRender(WidgetRenderingContext* aContext) {
+ // This can block waiting for WM_SETTEXT to finish
+ // Using PreRender is unnecessarily pessimistic because
+ // we technically only need to block during the present call
+ // not all of compositor rendering
+ mPresentLock.Enter();
+ return true;
+}
+
+void InProcessWinCompositorWidget::PostRender(
+ WidgetRenderingContext* aContext) {
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize InProcessWinCompositorWidget::GetClientSize() {
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessWinCompositorWidget::StartRemoteDrawing() {
+ MutexAutoLock lock(mTransparentSurfaceLock);
+
+ MOZ_ASSERT(!mCompositeDC);
+
+ RefPtr<gfxASurface> surf;
+ if (TransparencyModeIs(TransparencyMode::Transparent)) {
+ surf = EnsureTransparentSurface();
+ }
+
+ // Must call this after EnsureTransparentSurface(), since it could update
+ // the DC.
+ HDC dc = GetWindowSurface();
+ if (!surf) {
+ if (!dc) {
+ return nullptr;
+ }
+ uint32_t flags = TransparencyModeIs(TransparencyMode::Opaque)
+ ? 0
+ : gfxWindowsSurface::FLAG_IS_TRANSPARENT;
+ surf = new gfxWindowsSurface(dc, flags);
+ }
+
+ IntSize size = surf->GetSize();
+ if (size.width <= 0 || size.height <= 0) {
+ if (dc) {
+ FreeWindowSurface(dc);
+ }
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(
+ surf->CairoSurface(), size);
+ if (dt) {
+ mCompositeDC = dc;
+ } else {
+ FreeWindowSurface(dc);
+ }
+
+ return dt.forget();
+}
+
+void InProcessWinCompositorWidget::EndRemoteDrawing() {
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ if (TransparencyModeIs(TransparencyMode::Transparent)) {
+ MOZ_ASSERT(mTransparentSurface);
+ RedrawTransparentWindow();
+ }
+ if (mCompositeDC) {
+ FreeWindowSurface(mCompositeDC);
+ }
+ mCompositeDC = nullptr;
+}
+
+bool InProcessWinCompositorWidget::NeedsToDeferEndRemoteDrawing() {
+ if (mNotDeferEndRemoteDrawing) {
+ return false;
+ }
+
+ IDirectDraw7* ddraw = DeviceManagerDx::Get()->GetDirectDraw();
+ if (!ddraw) {
+ return false;
+ }
+
+ DWORD scanLine = 0;
+ int height = ::GetSystemMetrics(SM_CYSCREEN);
+ HRESULT ret = ddraw->GetScanLine(&scanLine);
+ if (ret == DDERR_VERTICALBLANKINPROGRESS) {
+ scanLine = 0;
+ } else if (ret != DD_OK) {
+ return false;
+ }
+
+ // Check if there is a risk of tearing with GDI.
+ if (static_cast<int>(scanLine) > height / 2) {
+ // No need to defer.
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget>
+InProcessWinCompositorWidget::GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) {
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ RefPtr<gfx::DrawTarget> target = CompositorWidget::GetBackBufferDrawTarget(
+ aScreenTarget, aRect, aOutIsCleared);
+ if (!target) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(target->GetBackendType() == BackendType::CAIRO);
+
+ uint8_t* destData;
+ IntSize destSize;
+ int32_t destStride;
+ SurfaceFormat destFormat;
+ if (!target->LockBits(&destData, &destSize, &destStride, &destFormat)) {
+ // LockBits is not supported. Use original DrawTarget.
+ return target.forget();
+ }
+
+ RefPtr<gfx::DrawTarget> dataTarget = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, destData, destSize, destStride, destFormat);
+ mLockedBackBufferData = destData;
+
+ return dataTarget.forget();
+}
+
+already_AddRefed<gfx::SourceSurface>
+InProcessWinCompositorWidget::EndBackBufferDrawing() {
+ if (mLockedBackBufferData) {
+ MOZ_ASSERT(mLastBackBuffer);
+ mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
+ mLockedBackBufferData = nullptr;
+ }
+ return CompositorWidget::EndBackBufferDrawing();
+}
+
+bool InProcessWinCompositorWidget::InitCompositor(
+ layers::Compositor* aCompositor) {
+ return true;
+}
+
+void InProcessWinCompositorWidget::EnterPresentLock() { mPresentLock.Enter(); }
+
+void InProcessWinCompositorWidget::LeavePresentLock() { mPresentLock.Leave(); }
+
+RefPtr<gfxASurface> InProcessWinCompositorWidget::EnsureTransparentSurface() {
+ mTransparentSurfaceLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(TransparencyModeIs(TransparencyMode::Transparent));
+
+ IntSize size = GetClientSize().ToUnknownSize();
+ if (!mTransparentSurface || mTransparentSurface->GetSize() != size) {
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+ CreateTransparentSurface(size);
+ }
+
+ RefPtr<gfxASurface> surface = mTransparentSurface;
+ return surface.forget();
+}
+
+void InProcessWinCompositorWidget::CreateTransparentSurface(
+ const gfx::IntSize& aSize) {
+ mTransparentSurfaceLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
+ RefPtr<gfxWindowsSurface> surface =
+ new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
+ mTransparentSurface = surface;
+ mMemoryDC = surface->GetDC();
+}
+
+void InProcessWinCompositorWidget::UpdateTransparency(TransparencyMode aMode) {
+ gfx::CriticalSectionAutoEnter presentLock(&mPresentLock);
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ if (TransparencyModeIs(aMode)) {
+ return;
+ }
+
+ mTransparencyMode = uint32_t(aMode);
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+
+ if (aMode == TransparencyMode::Transparent) {
+ EnsureTransparentSurface();
+ }
+}
+
+void InProcessWinCompositorWidget::NotifyVisibilityUpdated(
+ nsSizeMode aSizeMode, bool aIsFullyOccluded) {
+ mSizeMode = aSizeMode;
+ mIsFullyOccluded = aIsFullyOccluded;
+}
+
+nsSizeMode InProcessWinCompositorWidget::GetWindowSizeMode() const {
+ nsSizeMode sizeMode = mSizeMode;
+ return sizeMode;
+}
+
+bool InProcessWinCompositorWidget::GetWindowIsFullyOccluded() const {
+ bool isFullyOccluded = mIsFullyOccluded;
+ return isFullyOccluded;
+}
+
+void InProcessWinCompositorWidget::ClearTransparentWindow() {
+ gfx::CriticalSectionAutoEnter presentLock(&mPresentLock);
+ MutexAutoLock lock(mTransparentSurfaceLock);
+ if (!mTransparentSurface) {
+ return;
+ }
+
+ EnsureTransparentSurface();
+
+ IntSize size = mTransparentSurface->GetSize();
+ if (!size.IsEmpty()) {
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::CreateDrawTargetForSurface(mTransparentSurface, size);
+ if (!drawTarget) {
+ return;
+ }
+ drawTarget->ClearRect(Rect(0, 0, size.width, size.height));
+ RedrawTransparentWindow();
+ }
+}
+
+bool InProcessWinCompositorWidget::RedrawTransparentWindow() {
+ MOZ_ASSERT(TransparencyModeIs(TransparencyMode::Transparent));
+
+ LayoutDeviceIntSize size = GetClientSize();
+
+ ::GdiFlush();
+
+ BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ SIZE winSize = {size.width, size.height};
+ POINT srcPos = {0, 0};
+ HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ RECT winRect;
+ ::GetWindowRect(hWnd, &winRect);
+
+ // perform the alpha blend
+ return !!::UpdateLayeredWindow(hWnd, nullptr, (POINT*)&winRect, &winSize,
+ mMemoryDC, &srcPos, 0, &bf, ULW_ALPHA);
+}
+
+HDC InProcessWinCompositorWidget::GetWindowSurface() {
+ return TransparencyModeIs(TransparencyMode::Transparent) ? mMemoryDC
+ : ::GetDC(mWnd);
+}
+
+void InProcessWinCompositorWidget::FreeWindowSurface(HDC dc) {
+ if (!TransparencyModeIs(TransparencyMode::Transparent)) {
+ ::ReleaseDC(mWnd, dc);
+ }
+}
+
+bool InProcessWinCompositorWidget::IsHidden() const { return ::IsIconic(mWnd); }
+
+nsIWidget* InProcessWinCompositorWidget::RealWidget() { return mWindow; }
+
+void InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWindow->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+void InProcessWinCompositorWidget::UpdateCompositorWnd(
+ const HWND aCompositorWnd, const HWND aParentWnd) {
+ MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(aCompositorWnd && aParentWnd);
+ MOZ_ASSERT(aParentWnd == mWnd);
+
+ // Since we're in the parent process anyway, we can just call SetParent
+ // directly.
+ ::SetParent(aCompositorWnd, aParentWnd);
+ mSetParentCompleted = true;
+}
+} // namespace mozilla::widget
diff --git a/widget/windows/InProcessWinCompositorWidget.h b/widget/windows/InProcessWinCompositorWidget.h
new file mode 100644
index 0000000000..e287204461
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_InProcessWinCompositorWidget_h
+#define widget_windows_InProcessWinCompositorWidget_h
+
+#include "WinCompositorWidget.h"
+
+class nsWindow;
+class gfxASurface;
+
+namespace mozilla::widget {
+
+// This is the Windows-specific implementation of CompositorWidget. For
+// the most part it only requires an HWND, however it maintains extra state
+// for transparent windows, as well as for synchronizing WM_SETTEXT messages
+// with the compositor.
+class InProcessWinCompositorWidget final
+ : public WinCompositorWidget,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ InProcessWinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ nsWindow* aWindow);
+
+ bool PreRender(WidgetRenderingContext*) override;
+ void PostRender(WidgetRenderingContext*) override;
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+ bool NeedsToDeferEndRemoteDrawing() override;
+ LayoutDeviceIntSize GetClientSize() override;
+ already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget(
+ gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
+ bool* aOutIsCleared) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+ bool IsHidden() const override;
+
+ // PlatformCompositorWidgetDelegate Overrides
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ bool OnWindowResize(const LayoutDeviceIntSize& aSize) override;
+ void OnWindowModeChange(nsSizeMode aSizeMode) override;
+ void UpdateTransparency(TransparencyMode aMode) override;
+ void NotifyVisibilityUpdated(nsSizeMode aSizeMode,
+ bool aIsFullyOccluded) override;
+ void ClearTransparentWindow() override;
+
+ bool RedrawTransparentWindow();
+
+ // Ensure that a transparent surface exists, then return it.
+ RefPtr<gfxASurface> EnsureTransparentSurface();
+
+ HDC GetTransparentDC() const { return mMemoryDC; }
+
+ mozilla::Mutex& GetTransparentSurfaceLock() {
+ return mTransparentSurfaceLock;
+ }
+
+ nsSizeMode GetWindowSizeMode() const override;
+ bool GetWindowIsFullyOccluded() const override;
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ nsIWidget* RealWidget() override;
+
+ void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) override;
+ void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override {}
+
+ private:
+ HDC GetWindowSurface();
+ void FreeWindowSurface(HDC dc);
+
+ void CreateTransparentSurface(const gfx::IntSize& aSize);
+
+ nsWindow* mWindow;
+
+ HWND mWnd;
+
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ mozilla::Mutex mTransparentSurfaceLock MOZ_UNANNOTATED;
+ mozilla::Atomic<uint32_t, MemoryOrdering::Relaxed> mTransparencyMode;
+
+ bool TransparencyModeIs(TransparencyMode aMode) const {
+ return TransparencyMode(uint32_t(mTransparencyMode)) == aMode;
+ }
+
+ // Visibility handling.
+ mozilla::Atomic<nsSizeMode, MemoryOrdering::Relaxed> mSizeMode;
+ mozilla::Atomic<bool, MemoryOrdering::Relaxed> mIsFullyOccluded;
+
+ RefPtr<gfxASurface> mTransparentSurface;
+ HDC mMemoryDC;
+ HDC mCompositeDC;
+
+ // Locked back buffer of BasicCompositor
+ uint8_t* mLockedBackBufferData;
+
+ bool mNotDeferEndRemoteDrawing;
+};
+
+} // namespace mozilla::widget
+
+#endif // widget_windows_InProcessWinCompositorWidget_h
diff --git a/widget/windows/InputDeviceUtils.cpp b/widget/windows/InputDeviceUtils.cpp
new file mode 100644
index 0000000000..3636b93d6a
--- /dev/null
+++ b/widget/windows/InputDeviceUtils.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "InputDeviceUtils.h"
+
+#define INITGUID
+#include <dbt.h>
+#include <hidclass.h>
+#include <ntddmou.h>
+#include <setupapi.h>
+
+namespace mozilla {
+namespace widget {
+
+HDEVNOTIFY
+InputDeviceUtils::RegisterNotification(HWND aHwnd) {
+ DEV_BROADCAST_DEVICEINTERFACE filter = {};
+
+ filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
+ filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ // Some touchsreen devices are not GUID_DEVINTERFACE_MOUSE, so here we use
+ // GUID_DEVINTERFACE_HID instead.
+ filter.dbcc_classguid = GUID_DEVINTERFACE_HID;
+ return RegisterDeviceNotification(aHwnd, &filter,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void InputDeviceUtils::UnregisterNotification(HDEVNOTIFY aHandle) {
+ if (!aHandle) {
+ return;
+ }
+ UnregisterDeviceNotification(aHandle);
+}
+
+DWORD
+InputDeviceUtils::CountMouseDevices() {
+ HDEVINFO hdev =
+ SetupDiGetClassDevs(&GUID_DEVINTERFACE_MOUSE, nullptr, nullptr,
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (hdev == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+
+ DWORD count = 0;
+ SP_INTERFACE_DEVICE_DATA info = {};
+ info.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
+ while (SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVINTERFACE_MOUSE,
+ count, &info)) {
+ if (info.Flags & SPINT_ACTIVE) {
+ count++;
+ }
+ }
+ SetupDiDestroyDeviceInfoList(hdev);
+ return count;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/InputDeviceUtils.h b/widget/windows/InputDeviceUtils.h
new file mode 100644
index 0000000000..d16698a03a
--- /dev/null
+++ b/widget/windows/InputDeviceUtils.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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_InputDeviceUtils_h__
+#define mozilla_widget_InputDeviceUtils_h__
+
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class InputDeviceUtils {
+ public:
+ static HDEVNOTIFY RegisterNotification(HWND aHwnd);
+ static void UnregisterNotification(HDEVNOTIFY aHandle);
+
+ // Returns the number of mouse type devices connected to this system.
+ static DWORD CountMouseDevices();
+};
+
+} // namespace widget
+} // namespace mozilla
+#endif // mozilla_widget_InputDeviceUtils_h__
diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp
new file mode 100644
index 0000000000..80b1c29aa7
--- /dev/null
+++ b/widget/windows/JumpListBuilder.cpp
@@ -0,0 +1,818 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <windows.h>
+#include <shobjidl.h>
+#include <propkey.h>
+#include <propvarutil.h>
+#include <shellapi.h>
+#include "JumpListBuilder.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowsJumpListShortcutDescriptionBinding.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "WinUtils.h"
+
+using mozilla::dom::Promise;
+using mozilla::dom::WindowsJumpListShortcutDescription;
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
+
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
+#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
+
+// The amount of time, in milliseconds, that our IO thread will stay alive after
+// the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
+
+/**
+ * A wrapper around a ICustomDestinationList that implements the JumpListBackend
+ * interface. This is an implementation of JumpListBackend that actually causes
+ * items to appear in a Windows jump list.
+ */
+class NativeJumpListBackend : public JumpListBackend {
+ // We use NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET because this
+ // class might be destroyed on a different thread than the one it
+ // was created on, since it's maintained by a LazyIdleThread.
+ //
+ // This is a workaround for bug 1648031.
+ NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(JumpListBackend, override)
+
+ NativeJumpListBackend() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mscom::EnsureMTA([&]() {
+ RefPtr<ICustomDestinationList> destList;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(destList));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ mWindowsDestList = destList;
+ });
+ }
+
+ virtual bool IsAvailable() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ return mWindowsDestList != nullptr;
+ }
+
+ virtual HRESULT SetAppID(LPCWSTR pszAppID) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->SetAppID(pszAppID);
+ }
+
+ virtual HRESULT BeginList(UINT* pcMinSlots, REFIID riid,
+ void** ppv) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->BeginList(pcMinSlots, riid, ppv);
+ }
+
+ virtual HRESULT AddUserTasks(IObjectArray* poa) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->AddUserTasks(poa);
+ }
+
+ virtual HRESULT AppendCategory(LPCWSTR pszCategory,
+ IObjectArray* poa) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->AppendCategory(pszCategory, poa);
+ }
+
+ virtual HRESULT CommitList() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->CommitList();
+ }
+
+ virtual HRESULT AbortList() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->AbortList();
+ }
+
+ virtual HRESULT DeleteList(LPCWSTR pszAppID) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->DeleteList(pszAppID);
+ }
+
+ virtual HRESULT AppendKnownCategory(KNOWNDESTCATEGORY category) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mWindowsDestList);
+
+ return mWindowsDestList->AppendKnownCategory(category);
+ }
+
+ protected:
+ virtual ~NativeJumpListBackend() override{};
+
+ private:
+ RefPtr<ICustomDestinationList> mWindowsDestList;
+};
+
+JumpListBuilder::JumpListBuilder(const nsAString& aAppUserModelId,
+ RefPtr<JumpListBackend> aTestingBackend) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mAppUserModelId.Assign(aAppUserModelId);
+
+ Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
+
+ // Make a lazy thread for any IO.
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List",
+ LazyIdleThread::ManualShutdown);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
+ observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
+ }
+
+ nsCOMPtr<nsIRunnable> runnable;
+
+ if (aTestingBackend) {
+ // Dispatch a task that hands a reference to the testing backend
+ // to the background thread. The testing backend was probably
+ // constructed on the main thread, and is responsible for doing
+ // any locking as well as cleanup.
+ runnable = NewRunnableMethod<RefPtr<JumpListBackend>>(
+ "SetupTestingBackend", this, &JumpListBuilder::DoSetupTestingBackend,
+ aTestingBackend);
+
+ } else {
+ // Dispatch a task that constructs the native jump list backend.
+ runnable = NewRunnableMethod("SetupBackend", this,
+ &JumpListBuilder::DoSetupBackend);
+ }
+
+ mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+
+ // MSIX packages explicitly do not support setting the appid from within
+ // the app, as it is set in the package manifest instead.
+ if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
+ mIOThread->Dispatch(
+ NewRunnableMethod<nsString>(
+ "SetAppID", this, &JumpListBuilder::DoSetAppID, aAppUserModelId),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+JumpListBuilder::~JumpListBuilder() {
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+}
+
+void JumpListBuilder::DoSetupTestingBackend(
+ RefPtr<JumpListBackend> aTestingBackend) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mJumpListBackend = aTestingBackend;
+}
+
+void JumpListBuilder::DoSetupBackend() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!mJumpListBackend);
+ mJumpListBackend = new NativeJumpListBackend();
+}
+
+void JumpListBuilder::DoShutdownBackend() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mJumpListBackend = nullptr;
+}
+
+void JumpListBuilder::DoSetAppID(nsString aAppUserModelID) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mJumpListBackend);
+ mJumpListBackend->SetAppID(aAppUserModelID.get());
+}
+
+NS_IMETHODIMP
+JumpListBuilder::ObtainAndCacheFavicon(nsIURI* aFaviconURI,
+ nsAString& aCachedIconPath) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsString iconFilePath;
+ nsresult rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile(
+ aFaviconURI, iconFilePath, mIOThread, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aCachedIconPath = iconFilePath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JumpListBuilder::IsAvailable(JSContext* aCx, Promise** aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mIOThread);
+
+ ErrorResult result;
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aCx), result);
+
+ if (MOZ_UNLIKELY(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsMainThreadPtrHandle<Promise> promiseHolder(
+ new nsMainThreadPtrHolder<Promise>("JumpListBuilder::IsAvailable promise",
+ promise));
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<nsMainThreadPtrHandle<Promise>>(
+ "IsAvailable", this, &JumpListBuilder::DoIsAvailable,
+ std::move(promiseHolder));
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JumpListBuilder::CheckForRemovals(JSContext* aCx, Promise** aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mIOThread);
+
+ ErrorResult result;
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aCx), result);
+
+ if (MOZ_UNLIKELY(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsMainThreadPtrHandle<Promise> promiseHolder(
+ new nsMainThreadPtrHolder<Promise>(
+ "JumpListBuilder::CheckForRemovals promise", promise));
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<nsMainThreadPtrHandle<Promise>>(
+ "CheckForRemovals", this, &JumpListBuilder::DoCheckForRemovals,
+ std::move(promiseHolder));
+
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JumpListBuilder::PopulateJumpList(
+ const nsTArray<JS::Value>& aTaskDescriptions, const nsAString& aCustomTitle,
+ const nsTArray<JS::Value>& aCustomDescriptions, JSContext* aCx,
+ Promise** aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mIOThread);
+
+ if (aCustomDescriptions.Length() && aCustomTitle.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Get rid of the old icons
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ nsTArray<WindowsJumpListShortcutDescription> taskDescs;
+ for (auto& jsval : aTaskDescriptions) {
+ JS::Rooted<JS::Value> rootedVal(aCx);
+ if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ WindowsJumpListShortcutDescription desc;
+ if (!desc.Init(aCx, rootedVal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ taskDescs.AppendElement(std::move(desc));
+ }
+
+ nsTArray<WindowsJumpListShortcutDescription> customDescs;
+ for (auto& jsval : aCustomDescriptions) {
+ JS::Rooted<JS::Value> rootedVal(aCx);
+ if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ WindowsJumpListShortcutDescription desc;
+ if (!desc.Init(aCx, rootedVal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ customDescs.AppendElement(std::move(desc));
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aCx), result);
+
+ if (MOZ_UNLIKELY(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsMainThreadPtrHandle<Promise> promiseHolder(
+ new nsMainThreadPtrHolder<Promise>(
+ "JumpListBuilder::PopulateJumpList promise", promise));
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<
+ StoreCopyPassByRRef<nsTArray<WindowsJumpListShortcutDescription>>,
+ nsString,
+ StoreCopyPassByRRef<nsTArray<WindowsJumpListShortcutDescription>>,
+ nsMainThreadPtrHandle<Promise>>(
+ "PopulateJumpList", this, &JumpListBuilder::DoPopulateJumpList,
+ std::move(taskDescs), aCustomTitle, std::move(customDescs),
+ std::move(promiseHolder));
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JumpListBuilder::ClearJumpList(JSContext* aCx, Promise** aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(mIOThread);
+
+ ErrorResult result;
+ RefPtr<Promise> promise =
+ Promise::Create(xpc::CurrentNativeGlobal(aCx), result);
+
+ if (MOZ_UNLIKELY(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsMainThreadPtrHandle<Promise> promiseHolder(
+ new nsMainThreadPtrHolder<Promise>(
+ "JumpListBuilder::ClearJumpList promise", promise));
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<nsMainThreadPtrHandle<Promise>>(
+ "ClearJumpList", this, &JumpListBuilder::DoClearJumpList,
+ std::move(promiseHolder));
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+void JumpListBuilder::DoIsAvailable(
+ const nsMainThreadPtrHandle<Promise>& aPromiseHolder) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPromiseHolder);
+
+ if (!mJumpListBackend) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoIsAvailable", [promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeResolve(false);
+ }));
+ return;
+ }
+
+ bool isAvailable = mJumpListBackend->IsAvailable();
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoIsAvailable",
+ [promiseHolder = std::move(aPromiseHolder), isAvailable]() {
+ promiseHolder.get()->MaybeResolve(isAvailable);
+ }));
+}
+
+void JumpListBuilder::DoCheckForRemovals(
+ const nsMainThreadPtrHandle<Promise>& aPromiseHolder) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPromiseHolder);
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ auto errorHandler = MakeScopeExit([&aPromiseHolder, &rv]() {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoCheckForRemovals",
+ [promiseHolder = std::move(aPromiseHolder), rv]() {
+ promiseHolder.get()->MaybeReject(rv);
+ }));
+ });
+
+ MOZ_ASSERT(mJumpListBackend);
+ if (!mJumpListBackend) {
+ return;
+ }
+
+ // Abort any ongoing list building that might not have been committed,
+ // otherwise BeginList will give us problems.
+ Unused << mJumpListBackend->AbortList();
+
+ nsTArray<nsString> urisToRemove;
+ RefPtr<IObjectArray> objArray;
+ uint32_t maxItems = 0;
+
+ HRESULT hr = mJumpListBackend->BeginList(
+ &maxItems,
+ IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
+
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
+
+ // We began a list in order to get the removals, which we can now abort.
+ Unused << mJumpListBackend->AbortList();
+
+ errorHandler.release();
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoCheckForRemovals", [urisToRemove = std::move(urisToRemove),
+ promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeResolve(urisToRemove);
+ }));
+}
+
+void JumpListBuilder::DoPopulateJumpList(
+ const nsTArray<WindowsJumpListShortcutDescription>&& aTaskDescriptions,
+ const nsAString& aCustomTitle,
+ const nsTArray<WindowsJumpListShortcutDescription>&& aCustomDescriptions,
+ const nsMainThreadPtrHandle<Promise>& aPromiseHolder) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPromiseHolder);
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ auto errorHandler = MakeScopeExit([&aPromiseHolder, &rv]() {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoPopulateJumpList",
+ [promiseHolder = std::move(aPromiseHolder), rv]() {
+ promiseHolder.get()->MaybeReject(rv);
+ }));
+ });
+
+ MOZ_ASSERT(mJumpListBackend);
+ if (!mJumpListBackend) {
+ return;
+ }
+
+ // Abort any ongoing list building that might not have been committed,
+ // otherwise BeginList will give us problems.
+ Unused << mJumpListBackend->AbortList();
+
+ nsTArray<nsString> urisToRemove;
+ RefPtr<IObjectArray> objArray;
+ uint32_t maxItems = 0;
+
+ HRESULT hr = mJumpListBackend->BeginList(
+ &maxItems,
+ IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
+
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ if (urisToRemove.Length()) {
+ // It'd be nice if we could return a more descriptive error here so that
+ // the caller knows that they should have called checkForRemovals first.
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ if (aTaskDescriptions.Length()) {
+ RefPtr<IObjectCollection> taskCollection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(taskCollection));
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ // Start by building the task list
+ for (auto& desc : aTaskDescriptions) {
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = GetShellLinkFromDescription(desc, link);
+ if (NS_FAILED(rv)) {
+ // Let the errorHandler deal with this.
+ return;
+ }
+ taskCollection->AddObject(link);
+ }
+
+ RefPtr<IObjectArray> pTaskArray;
+ hr = taskCollection->QueryInterface(IID_IObjectArray,
+ getter_AddRefs(pTaskArray));
+
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ hr = mJumpListBackend->AddUserTasks(pTaskArray);
+
+ if (FAILED(hr)) {
+ rv = NS_ERROR_FAILURE;
+ return;
+ }
+ }
+
+ if (aCustomDescriptions.Length()) {
+ // Then build the custom list
+ RefPtr<IObjectCollection> customCollection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(customCollection));
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ for (auto& desc : aCustomDescriptions) {
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = GetShellLinkFromDescription(desc, link);
+ if (NS_FAILED(rv)) {
+ // Let the errorHandler deal with this.
+ return;
+ }
+ customCollection->AddObject(link);
+ }
+
+ RefPtr<IObjectArray> pCustomArray;
+ hr = customCollection->QueryInterface(IID_IObjectArray,
+ getter_AddRefs(pCustomArray));
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ hr = mJumpListBackend->AppendCategory(
+ reinterpret_cast<const wchar_t*>(aCustomTitle.BeginReading()),
+ pCustomArray);
+
+ if (FAILED(hr)) {
+ rv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+ }
+
+ hr = mJumpListBackend->CommitList();
+ if (FAILED(hr)) {
+ rv = NS_ERROR_FAILURE;
+ return;
+ }
+
+ errorHandler.release();
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoPopulateJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeResolveWithUndefined();
+ }));
+}
+
+void JumpListBuilder::DoClearJumpList(
+ const nsMainThreadPtrHandle<Promise>& aPromiseHolder) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(aPromiseHolder);
+
+ if (!mJumpListBackend) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeReject(NS_ERROR_UNEXPECTED);
+ }));
+ return;
+ }
+
+ if (SUCCEEDED(mJumpListBackend->DeleteList(mAppUserModelId.get()))) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeResolveWithUndefined();
+ }));
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
+ promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE);
+ }));
+ }
+}
+
+// RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
+// avoid unnecessary extra XPCOM incantations. For each object in the input
+// array, if it's an IShellLinkW, this deletes the cached icon and adds the
+// url param to a list of URLs to be removed from the places database.
+void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
+ IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Early return here just in case some versions of Windows don't populate this
+ if (!aObjArray) {
+ return;
+ }
+
+ uint32_t count = 0;
+ aObjArray->GetCount(&count);
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ RefPtr<IShellLinkW> pLink;
+
+ if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
+ static_cast<void**>(getter_AddRefs(pLink))))) {
+ continue;
+ }
+
+ wchar_t buf[MAX_PATH];
+ HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist && numArgs > 0) {
+ nsString spec(arglist[0]);
+ aURISpecs.AppendElement(std::move(spec));
+ ::LocalFree(arglist);
+ }
+ }
+
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ nsDependentString spec(buf);
+ DeleteIconFromDisk(spec);
+ }
+ }
+}
+
+void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ icoFile->Remove(false);
+ }
+}
+
+// Converts a WindowsJumpListShortcutDescription into a IShellLinkW
+nsresult JumpListBuilder::GetShellLinkFromDescription(
+ const WindowsJumpListShortcutDescription& aDesc,
+ RefPtr<IShellLinkW>& aShellLink) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ HRESULT hr;
+ IShellLinkW* psl;
+
+ // Shell links:
+ // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx
+ // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
+
+ // Create a IShellLink
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (LPVOID*)&psl);
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Store the title of the app
+ if (!aDesc.mTitle.IsEmpty()) {
+ IPropertyStore* pPropStore = nullptr;
+ hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PROPVARIANT pv;
+ ::InitPropVariantFromString(aDesc.mTitle.get(), &pv);
+
+ pPropStore->SetValue(PKEY_Title, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+ }
+
+ // Store the rest of the params
+ hr = psl->SetPath(aDesc.mPath.get());
+
+ // According to the documentation at [1], the maximum description length is
+ // INFOTIPSIZE, so we copy the const string from the description into a buffer
+ // of that maximum size. However, testing reveals that INFOTIPSIZE is still
+ // sometimes too large. MAX_PATH seems to work instead.
+ //
+ // We truncate to MAX_PATH - 1, since nsAutoString's don't include the null
+ // character in their capacity calculations, but the string for IShellLinkW
+ // description does. So by truncating to MAX_PATH - 1, the full contents of
+ // the truncated nsAutoString will be copied into the IShellLink description
+ // buffer.
+ //
+ // [1]:
+ // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinka-setdescription
+ nsAutoString descriptionCopy(aDesc.mDescription.get());
+ if (descriptionCopy.Length() >= MAX_PATH) {
+ descriptionCopy.Truncate(MAX_PATH - 1);
+ }
+
+ hr = psl->SetDescription(descriptionCopy.get());
+
+ if (aDesc.mArguments.WasPassed() && !aDesc.mArguments.Value().IsEmpty()) {
+ hr = psl->SetArguments(aDesc.mArguments.Value().get());
+ } else {
+ hr = psl->SetArguments(L"");
+ }
+
+ // Set up the fallback icon in the event that a valid icon URI has
+ // not been supplied.
+ hr = psl->SetIconLocation(aDesc.mPath.get(), aDesc.mFallbackIconIndex);
+
+ if (aDesc.mIconPath.WasPassed() && !aDesc.mIconPath.Value().IsEmpty()) {
+ // Always use the first icon in the ICO file, as our encoded icon only has 1
+ // resource
+ hr = psl->SetIconLocation(aDesc.mIconPath.Value().get(), 0);
+ }
+
+ aShellLink = dont_AddRef(psl);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JumpListBuilder::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(aTopic);
+ if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
+ observerService->RemoveObserver(this, TOPIC_CLEAR_PRIVATE_DATA);
+ }
+
+ mIOThread->Dispatch(NewRunnableMethod("ShutdownBackend", this,
+ &JumpListBuilder::DoShutdownBackend),
+ NS_DISPATCH_NORMAL);
+
+ mIOThread->Shutdown();
+ } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
+ nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
+ bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
+ if (!enabled) {
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
+ // Delete JumpListCache icons from Disk, if any.
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/JumpListBuilder.h b/widget/windows/JumpListBuilder.h
new file mode 100644
index 0000000000..e228669f11
--- /dev/null
+++ b/widget/windows/JumpListBuilder.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __JumpListBuilder_h__
+#define __JumpListBuilder_h__
+
+#include "nsIJumpListBuilder.h"
+
+#include "nsIObserver.h"
+#include "nsProxyRelease.h"
+#include "mozilla/LazyIdleThread.h"
+
+namespace mozilla {
+
+namespace dom {
+struct WindowsJumpListShortcutDescription;
+} // namespace dom
+
+namespace widget {
+
+/**
+ * This is an abstract class for a backend to write to the Windows Jump List.
+ *
+ * It has a 1-to-1 method mapping with ICustomDestinationList. The abtract
+ * class allows us to implement a "fake" backend for automated testing.
+ */
+class JumpListBackend {
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual bool IsAvailable() = 0;
+
+ virtual HRESULT SetAppID(LPCWSTR pszAppID) = 0;
+ virtual HRESULT BeginList(UINT* pcMinSlots, REFIID riid, void** ppv) = 0;
+ virtual HRESULT AddUserTasks(IObjectArray* poa) = 0;
+ virtual HRESULT AppendCategory(LPCWSTR pszCategory, IObjectArray* poa) = 0;
+ virtual HRESULT CommitList() = 0;
+ virtual HRESULT AbortList() = 0;
+ virtual HRESULT DeleteList(LPCWSTR pszAppID) = 0;
+ virtual HRESULT AppendKnownCategory(KNOWNDESTCATEGORY category) = 0;
+
+ protected:
+ virtual ~JumpListBackend() {}
+};
+
+/**
+ * JumpListBuilder is a component that can be used to manage the Windows
+ * Jump List off of the main thread.
+ */
+class JumpListBuilder : public nsIJumpListBuilder, public nsIObserver {
+ virtual ~JumpListBuilder();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIJUMPLISTBUILDER
+ NS_DECL_NSIOBSERVER
+
+ explicit JumpListBuilder(const nsAString& aAppUserModelId,
+ RefPtr<JumpListBackend> aTestingBackend = nullptr);
+
+ private:
+ // These all run on the lazy helper thread.
+ void DoSetupBackend();
+ void DoSetupTestingBackend(RefPtr<JumpListBackend> aTestingBackend);
+ void DoShutdownBackend();
+ void DoSetAppID(nsString aAppUserModelID);
+ void DoIsAvailable(const nsMainThreadPtrHandle<dom::Promise>& aPromiseHolder);
+ void DoCheckForRemovals(
+ const nsMainThreadPtrHandle<dom::Promise>& aPromiseHolder);
+ void DoPopulateJumpList(
+ const nsTArray<dom::WindowsJumpListShortcutDescription>&&
+ aTaskDescriptions,
+ const nsAString& aCustomTitle,
+ const nsTArray<dom::WindowsJumpListShortcutDescription>&&
+ aCustomDescriptions,
+ const nsMainThreadPtrHandle<dom::Promise>& aPromiseHolder);
+ void DoClearJumpList(
+ const nsMainThreadPtrHandle<dom::Promise>& aPromiseHolder);
+ void RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray* aObjArray,
+ nsTArray<nsString>& aURISpecs);
+ void DeleteIconFromDisk(const nsAString& aPath);
+ nsresult GetShellLinkFromDescription(
+ const dom::WindowsJumpListShortcutDescription& aDesc,
+ RefPtr<IShellLinkW>& aShellLink);
+
+ // This is written to once during construction on the main thread before the
+ // lazy helper thread is created. After that, the lazy helper thread might
+ // read from it.
+ nsString mAppUserModelId;
+
+ // This is only accessed by the lazy helper thread.
+ RefPtr<JumpListBackend> mJumpListBackend;
+
+ // This is only accessed by the main thread.
+ RefPtr<LazyIdleThread> mIOThread;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __JumpListBuilder_h__ */
diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp
new file mode 100644
index 0000000000..3b59aa9319
--- /dev/null
+++ b/widget/windows/KeyboardLayout.cpp
@@ -0,0 +1,5442 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/widget/WinRegistry.h"
+
+#include "nsExceptionHandler.h"
+#include "nsGkAtoms.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowDbg.h"
+
+#include "KeyboardLayout.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+
+#include "npapi.h"
+
+#include <windows.h>
+#include <winnls.h>
+#include <winuser.h>
+#include <algorithm>
+
+#ifndef WINABLEAPI
+# include <winable.h>
+#endif
+
+// For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync`
+// rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+mozilla::LazyLogModule gKeyLog("KeyboardHandler");
+
+namespace mozilla {
+namespace widget {
+
+static const char* const kVirtualKeyName[] = {
+ "NULL",
+ "VK_LBUTTON",
+ "VK_RBUTTON",
+ "VK_CANCEL",
+ "VK_MBUTTON",
+ "VK_XBUTTON1",
+ "VK_XBUTTON2",
+ "0x07",
+ "VK_BACK",
+ "VK_TAB",
+ "0x0A",
+ "0x0B",
+ "VK_CLEAR",
+ "VK_RETURN",
+ "0x0E",
+ "0x0F",
+
+ "VK_SHIFT",
+ "VK_CONTROL",
+ "VK_MENU",
+ "VK_PAUSE",
+ "VK_CAPITAL",
+ "VK_KANA, VK_HANGUL",
+ "0x16",
+ "VK_JUNJA",
+ "VK_FINAL",
+ "VK_HANJA, VK_KANJI",
+ "0x1A",
+ "VK_ESCAPE",
+ "VK_CONVERT",
+ "VK_NONCONVERT",
+ "VK_ACCEPT",
+ "VK_MODECHANGE",
+
+ "VK_SPACE",
+ "VK_PRIOR",
+ "VK_NEXT",
+ "VK_END",
+ "VK_HOME",
+ "VK_LEFT",
+ "VK_UP",
+ "VK_RIGHT",
+ "VK_DOWN",
+ "VK_SELECT",
+ "VK_PRINT",
+ "VK_EXECUTE",
+ "VK_SNAPSHOT",
+ "VK_INSERT",
+ "VK_DELETE",
+ "VK_HELP",
+
+ "VK_0",
+ "VK_1",
+ "VK_2",
+ "VK_3",
+ "VK_4",
+ "VK_5",
+ "VK_6",
+ "VK_7",
+ "VK_8",
+ "VK_9",
+ "0x3A",
+ "0x3B",
+ "0x3C",
+ "0x3D",
+ "0x3E",
+ "0x3F",
+
+ "0x40",
+ "VK_A",
+ "VK_B",
+ "VK_C",
+ "VK_D",
+ "VK_E",
+ "VK_F",
+ "VK_G",
+ "VK_H",
+ "VK_I",
+ "VK_J",
+ "VK_K",
+ "VK_L",
+ "VK_M",
+ "VK_N",
+ "VK_O",
+
+ "VK_P",
+ "VK_Q",
+ "VK_R",
+ "VK_S",
+ "VK_T",
+ "VK_U",
+ "VK_V",
+ "VK_W",
+ "VK_X",
+ "VK_Y",
+ "VK_Z",
+ "VK_LWIN",
+ "VK_RWIN",
+ "VK_APPS",
+ "0x5E",
+ "VK_SLEEP",
+
+ "VK_NUMPAD0",
+ "VK_NUMPAD1",
+ "VK_NUMPAD2",
+ "VK_NUMPAD3",
+ "VK_NUMPAD4",
+ "VK_NUMPAD5",
+ "VK_NUMPAD6",
+ "VK_NUMPAD7",
+ "VK_NUMPAD8",
+ "VK_NUMPAD9",
+ "VK_MULTIPLY",
+ "VK_ADD",
+ "VK_SEPARATOR",
+ "VK_SUBTRACT",
+ "VK_DECIMAL",
+ "VK_DIVIDE",
+
+ "VK_F1",
+ "VK_F2",
+ "VK_F3",
+ "VK_F4",
+ "VK_F5",
+ "VK_F6",
+ "VK_F7",
+ "VK_F8",
+ "VK_F9",
+ "VK_F10",
+ "VK_F11",
+ "VK_F12",
+ "VK_F13",
+ "VK_F14",
+ "VK_F15",
+ "VK_F16",
+
+ "VK_F17",
+ "VK_F18",
+ "VK_F19",
+ "VK_F20",
+ "VK_F21",
+ "VK_F22",
+ "VK_F23",
+ "VK_F24",
+ "0x88",
+ "0x89",
+ "0x8A",
+ "0x8B",
+ "0x8C",
+ "0x8D",
+ "0x8E",
+ "0x8F",
+
+ "VK_NUMLOCK",
+ "VK_SCROLL",
+ "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
+ "VK_OEM_FJ_MASSHOU",
+ "VK_OEM_FJ_TOUROKU",
+ "VK_OEM_FJ_LOYA",
+ "VK_OEM_FJ_ROYA",
+ "0x97",
+ "0x98",
+ "0x99",
+ "0x9A",
+ "0x9B",
+ "0x9C",
+ "0x9D",
+ "0x9E",
+ "0x9F",
+
+ "VK_LSHIFT",
+ "VK_RSHIFT",
+ "VK_LCONTROL",
+ "VK_RCONTROL",
+ "VK_LMENU",
+ "VK_RMENU",
+ "VK_BROWSER_BACK",
+ "VK_BROWSER_FORWARD",
+ "VK_BROWSER_REFRESH",
+ "VK_BROWSER_STOP",
+ "VK_BROWSER_SEARCH",
+ "VK_BROWSER_FAVORITES",
+ "VK_BROWSER_HOME",
+ "VK_VOLUME_MUTE",
+ "VK_VOLUME_DOWN",
+ "VK_VOLUME_UP",
+
+ "VK_MEDIA_NEXT_TRACK",
+ "VK_MEDIA_PREV_TRACK",
+ "VK_MEDIA_STOP",
+ "VK_MEDIA_PLAY_PAUSE",
+ "VK_LAUNCH_MAIL",
+ "VK_LAUNCH_MEDIA_SELECT",
+ "VK_LAUNCH_APP1",
+ "VK_LAUNCH_APP2",
+ "0xB8",
+ "0xB9",
+ "VK_OEM_1",
+ "VK_OEM_PLUS",
+ "VK_OEM_COMMA",
+ "VK_OEM_MINUS",
+ "VK_OEM_PERIOD",
+ "VK_OEM_2",
+
+ "VK_OEM_3",
+ "VK_ABNT_C1",
+ "VK_ABNT_C2",
+ "0xC3",
+ "0xC4",
+ "0xC5",
+ "0xC6",
+ "0xC7",
+ "0xC8",
+ "0xC9",
+ "0xCA",
+ "0xCB",
+ "0xCC",
+ "0xCD",
+ "0xCE",
+ "0xCF",
+
+ "0xD0",
+ "0xD1",
+ "0xD2",
+ "0xD3",
+ "0xD4",
+ "0xD5",
+ "0xD6",
+ "0xD7",
+ "0xD8",
+ "0xD9",
+ "0xDA",
+ "VK_OEM_4",
+ "VK_OEM_5",
+ "VK_OEM_6",
+ "VK_OEM_7",
+ "VK_OEM_8",
+
+ "0xE0",
+ "VK_OEM_AX",
+ "VK_OEM_102",
+ "VK_ICO_HELP",
+ "VK_ICO_00",
+ "VK_PROCESSKEY",
+ "VK_ICO_CLEAR",
+ "VK_PACKET",
+ "0xE8",
+ "VK_OEM_RESET",
+ "VK_OEM_JUMP",
+ "VK_OEM_PA1",
+ "VK_OEM_PA2",
+ "VK_OEM_PA3",
+ "VK_OEM_WSCTRL",
+ "VK_OEM_CUSEL",
+
+ "VK_OEM_ATTN",
+ "VK_OEM_FINISH",
+ "VK_OEM_COPY",
+ "VK_OEM_AUTO",
+ "VK_OEM_ENLW",
+ "VK_OEM_BACKTAB",
+ "VK_ATTN",
+ "VK_CRSEL",
+ "VK_EXSEL",
+ "VK_EREOF",
+ "VK_PLAY",
+ "VK_ZOOM",
+ "VK_NONAME",
+ "VK_PA1",
+ "VK_OEM_CLEAR",
+ "0xFF"};
+
+static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100,
+ "The virtual key name must be defined just 256 keys");
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static const nsCString GetCharacterCodeName(WPARAM aCharCode) {
+ switch (aCharCode) {
+ case 0x0000:
+ return "NULL (0x0000)"_ns;
+ case 0x0008:
+ return "BACKSPACE (0x0008)"_ns;
+ case 0x0009:
+ return "CHARACTER TABULATION (0x0009)"_ns;
+ case 0x000A:
+ return "LINE FEED (0x000A)"_ns;
+ case 0x000B:
+ return "LINE TABULATION (0x000B)"_ns;
+ case 0x000C:
+ return "FORM FEED (0x000C)"_ns;
+ case 0x000D:
+ return "CARRIAGE RETURN (0x000D)"_ns;
+ case 0x0018:
+ return "CANCEL (0x0018)"_ns;
+ case 0x001B:
+ return "ESCAPE (0x001B)"_ns;
+ case 0x0020:
+ return "SPACE (0x0020)"_ns;
+ case 0x007F:
+ return "DELETE (0x007F)"_ns;
+ case 0x00A0:
+ return "NO-BREAK SPACE (0x00A0)"_ns;
+ case 0x00AD:
+ return "SOFT HYPHEN (0x00AD)"_ns;
+ case 0x2000:
+ return "EN QUAD (0x2000)"_ns;
+ case 0x2001:
+ return "EM QUAD (0x2001)"_ns;
+ case 0x2002:
+ return "EN SPACE (0x2002)"_ns;
+ case 0x2003:
+ return "EM SPACE (0x2003)"_ns;
+ case 0x2004:
+ return "THREE-PER-EM SPACE (0x2004)"_ns;
+ case 0x2005:
+ return "FOUR-PER-EM SPACE (0x2005)"_ns;
+ case 0x2006:
+ return "SIX-PER-EM SPACE (0x2006)"_ns;
+ case 0x2007:
+ return "FIGURE SPACE (0x2007)"_ns;
+ case 0x2008:
+ return "PUNCTUATION SPACE (0x2008)"_ns;
+ case 0x2009:
+ return "THIN SPACE (0x2009)"_ns;
+ case 0x200A:
+ return "HAIR SPACE (0x200A)"_ns;
+ case 0x200B:
+ return "ZERO WIDTH SPACE (0x200B)"_ns;
+ case 0x200C:
+ return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
+ case 0x200D:
+ return "ZERO WIDTH JOINER (0x200D)"_ns;
+ case 0x200E:
+ return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
+ case 0x200F:
+ return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
+ case 0x2029:
+ return "PARAGRAPH SEPARATOR (0x2029)"_ns;
+ case 0x202A:
+ return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
+ case 0x202B:
+ return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
+ case 0x202D:
+ return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
+ case 0x202E:
+ return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
+ case 0x202F:
+ return "NARROW NO-BREAK SPACE (0x202F)"_ns;
+ case 0x205F:
+ return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
+ case 0x2060:
+ return "WORD JOINER (0x2060)"_ns;
+ case 0x2066:
+ return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
+ case 0x2067:
+ return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
+ case 0x3000:
+ return "IDEOGRAPHIC SPACE (0x3000)"_ns;
+ case 0xFEFF:
+ return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
+ default: {
+ if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) {
+ return nsPrintfCString("control (0x%04zX)", aCharCode);
+ }
+ if (NS_IS_HIGH_SURROGATE(aCharCode)) {
+ return nsPrintfCString("high surrogate (0x%04zX)", aCharCode);
+ }
+ if (NS_IS_LOW_SURROGATE(aCharCode)) {
+ return nsPrintfCString("low surrogate (0x%04zX)", aCharCode);
+ }
+ return IS_IN_BMP(aCharCode)
+ ? nsPrintfCString(
+ "'%s' (0x%04zX)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
+ aCharCode)
+ : nsPrintfCString(
+ "'%s' (0x%08zX)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(),
+ aCharCode);
+ }
+ }
+}
+
+static const nsCString GetKeyLocationName(uint32_t aLocation) {
+ switch (aLocation) {
+ case eKeyLocationLeft:
+ return "KEY_LOCATION_LEFT"_ns;
+ case eKeyLocationRight:
+ return "KEY_LOCATION_RIGHT"_ns;
+ case eKeyLocationStandard:
+ return "KEY_LOCATION_STANDARD"_ns;
+ case eKeyLocationNumpad:
+ return "KEY_LOCATION_NUMPAD"_ns;
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString GetCharacterCodeNames(const char16_t* aChars,
+ uint32_t aLength) {
+ if (!aLength) {
+ return ""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+static const nsCString GetCharacterCodeNames(
+ const UniCharsAndModifiers& aUniCharsAndModifiers) {
+ if (aUniCharsAndModifiers.IsEmpty()) {
+ return ""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString {
+ public:
+ explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) {
+ if (!aShiftState) {
+ AssignLiteral("none");
+ return;
+ }
+ if (aShiftState & VirtualKey::STATE_SHIFT) {
+ AssignLiteral("Shift");
+ aShiftState &= ~VirtualKey::STATE_SHIFT;
+ }
+ if (aShiftState & VirtualKey::STATE_CONTROL) {
+ MaybeAppendSeparator();
+ AssignLiteral("Ctrl");
+ aShiftState &= ~VirtualKey::STATE_CONTROL;
+ }
+ if (aShiftState & VirtualKey::STATE_ALT) {
+ MaybeAppendSeparator();
+ AssignLiteral("Alt");
+ aShiftState &= ~VirtualKey::STATE_ALT;
+ }
+ if (aShiftState & VirtualKey::STATE_CAPSLOCK) {
+ MaybeAppendSeparator();
+ AssignLiteral("CapsLock");
+ aShiftState &= ~VirtualKey::STATE_CAPSLOCK;
+ }
+ MOZ_ASSERT(!aShiftState);
+ }
+
+ private:
+ void MaybeAppendSeparator() {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+static const nsCString GetMessageName(UINT aMessage) {
+ switch (aMessage) {
+ case WM_NULL:
+ return "WM_NULL"_ns;
+ case WM_KEYDOWN:
+ return "WM_KEYDOWN"_ns;
+ case WM_KEYUP:
+ return "WM_KEYUP"_ns;
+ case WM_SYSKEYDOWN:
+ return "WM_SYSKEYDOWN"_ns;
+ case WM_SYSKEYUP:
+ return "WM_SYSKEYUP"_ns;
+ case WM_CHAR:
+ return "WM_CHAR"_ns;
+ case WM_UNICHAR:
+ return "WM_UNICHAR"_ns;
+ case WM_SYSCHAR:
+ return "WM_SYSCHAR"_ns;
+ case WM_DEADCHAR:
+ return "WM_DEADCHAR"_ns;
+ case WM_SYSDEADCHAR:
+ return "WM_SYSDEADCHAR"_ns;
+ case WM_APPCOMMAND:
+ return "WM_APPCOMMAND"_ns;
+ case WM_QUIT:
+ return "WM_QUIT"_ns;
+ default:
+ return nsPrintfCString("Unknown Message (0x%04X)", aMessage);
+ }
+}
+
+static const nsCString GetVirtualKeyCodeName(WPARAM aVK) {
+ if (aVK >= ArrayLength(kVirtualKeyName)) {
+ return nsPrintfCString("Invalid (0x%08zX)", aVK);
+ }
+ return nsCString(kVirtualKeyName[aVK]);
+}
+
+static const nsCString GetAppCommandName(WPARAM aCommand) {
+ switch (aCommand) {
+ case APPCOMMAND_BASS_BOOST:
+ return "APPCOMMAND_BASS_BOOST"_ns;
+ case APPCOMMAND_BASS_DOWN:
+ return "APPCOMMAND_BASS_DOWN"_ns;
+ case APPCOMMAND_BASS_UP:
+ return "APPCOMMAND_BASS_UP"_ns;
+ case APPCOMMAND_BROWSER_BACKWARD:
+ return "APPCOMMAND_BROWSER_BACKWARD"_ns;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ return "APPCOMMAND_BROWSER_FAVORITES"_ns;
+ case APPCOMMAND_BROWSER_FORWARD:
+ return "APPCOMMAND_BROWSER_FORWARD"_ns;
+ case APPCOMMAND_BROWSER_HOME:
+ return "APPCOMMAND_BROWSER_HOME"_ns;
+ case APPCOMMAND_BROWSER_REFRESH:
+ return "APPCOMMAND_BROWSER_REFRESH"_ns;
+ case APPCOMMAND_BROWSER_SEARCH:
+ return "APPCOMMAND_BROWSER_SEARCH"_ns;
+ case APPCOMMAND_BROWSER_STOP:
+ return "APPCOMMAND_BROWSER_STOP"_ns;
+ case APPCOMMAND_CLOSE:
+ return "APPCOMMAND_CLOSE"_ns;
+ case APPCOMMAND_COPY:
+ return "APPCOMMAND_COPY"_ns;
+ case APPCOMMAND_CORRECTION_LIST:
+ return "APPCOMMAND_CORRECTION_LIST"_ns;
+ case APPCOMMAND_CUT:
+ return "APPCOMMAND_CUT"_ns;
+ case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
+ return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns;
+ case APPCOMMAND_FIND:
+ return "APPCOMMAND_FIND"_ns;
+ case APPCOMMAND_FORWARD_MAIL:
+ return "APPCOMMAND_FORWARD_MAIL"_ns;
+ case APPCOMMAND_HELP:
+ return "APPCOMMAND_HELP"_ns;
+ case APPCOMMAND_LAUNCH_APP1:
+ return "APPCOMMAND_LAUNCH_APP1"_ns;
+ case APPCOMMAND_LAUNCH_APP2:
+ return "APPCOMMAND_LAUNCH_APP2"_ns;
+ case APPCOMMAND_LAUNCH_MAIL:
+ return "APPCOMMAND_LAUNCH_MAIL"_ns;
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns;
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN:
+ return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns;
+ case APPCOMMAND_MEDIA_CHANNEL_UP:
+ return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns;
+ case APPCOMMAND_MEDIA_FAST_FORWARD:
+ return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ return "APPCOMMAND_MEDIA_NEXTTRACK"_ns;
+ case APPCOMMAND_MEDIA_PAUSE:
+ return "APPCOMMAND_MEDIA_PAUSE"_ns;
+ case APPCOMMAND_MEDIA_PLAY:
+ return "APPCOMMAND_MEDIA_PLAY"_ns;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns;
+ case APPCOMMAND_MEDIA_RECORD:
+ return "APPCOMMAND_MEDIA_RECORD"_ns;
+ case APPCOMMAND_MEDIA_REWIND:
+ return "APPCOMMAND_MEDIA_REWIND"_ns;
+ case APPCOMMAND_MEDIA_STOP:
+ return "APPCOMMAND_MEDIA_STOP"_ns;
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE:
+ return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
+ return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
+ return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns;
+ case APPCOMMAND_MICROPHONE_VOLUME_UP:
+ return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns;
+ case APPCOMMAND_NEW:
+ return "APPCOMMAND_NEW"_ns;
+ case APPCOMMAND_OPEN:
+ return "APPCOMMAND_OPEN"_ns;
+ case APPCOMMAND_PASTE:
+ return "APPCOMMAND_PASTE"_ns;
+ case APPCOMMAND_PRINT:
+ return "APPCOMMAND_PRINT"_ns;
+ case APPCOMMAND_REDO:
+ return "APPCOMMAND_REDO"_ns;
+ case APPCOMMAND_REPLY_TO_MAIL:
+ return "APPCOMMAND_REPLY_TO_MAIL"_ns;
+ case APPCOMMAND_SAVE:
+ return "APPCOMMAND_SAVE"_ns;
+ case APPCOMMAND_SEND_MAIL:
+ return "APPCOMMAND_SEND_MAIL"_ns;
+ case APPCOMMAND_SPELL_CHECK:
+ return "APPCOMMAND_SPELL_CHECK"_ns;
+ case APPCOMMAND_TREBLE_DOWN:
+ return "APPCOMMAND_TREBLE_DOWN"_ns;
+ case APPCOMMAND_TREBLE_UP:
+ return "APPCOMMAND_TREBLE_UP"_ns;
+ case APPCOMMAND_UNDO:
+ return "APPCOMMAND_UNDO"_ns;
+ case APPCOMMAND_VOLUME_DOWN:
+ return "APPCOMMAND_VOLUME_DOWN"_ns;
+ case APPCOMMAND_VOLUME_MUTE:
+ return "APPCOMMAND_VOLUME_MUTE"_ns;
+ case APPCOMMAND_VOLUME_UP:
+ return "APPCOMMAND_VOLUME_UP"_ns;
+ default:
+ return nsPrintfCString("Unknown app command (0x%08zX)", aCommand);
+ }
+}
+
+static const nsCString GetAppCommandDeviceName(LPARAM aDevice) {
+ switch (aDevice) {
+ case FAPPCOMMAND_KEY:
+ return "FAPPCOMMAND_KEY"_ns;
+ case FAPPCOMMAND_MOUSE:
+ return "FAPPCOMMAND_MOUSE"_ns;
+ case FAPPCOMMAND_OEM:
+ return "FAPPCOMMAND_OEM"_ns;
+ default:
+ return nsPrintfCString("Unknown app command device (0x%04" PRIXLPTR ")",
+ aDevice);
+ }
+};
+
+class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString {
+ public:
+ explicit GetAppCommandKeysName(WPARAM aKeys) {
+ if (aKeys & MK_CONTROL) {
+ AppendLiteral("MK_CONTROL");
+ aKeys &= ~MK_CONTROL;
+ }
+ if (aKeys & MK_LBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_LBUTTON");
+ aKeys &= ~MK_LBUTTON;
+ }
+ if (aKeys & MK_MBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_MBUTTON");
+ aKeys &= ~MK_MBUTTON;
+ }
+ if (aKeys & MK_RBUTTON) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_RBUTTON");
+ aKeys &= ~MK_RBUTTON;
+ }
+ if (aKeys & MK_SHIFT) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_SHIFT");
+ aKeys &= ~MK_SHIFT;
+ }
+ if (aKeys & MK_XBUTTON1) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_XBUTTON1");
+ aKeys &= ~MK_XBUTTON1;
+ }
+ if (aKeys & MK_XBUTTON2) {
+ MaybeAppendSeparator();
+ AppendLiteral("MK_XBUTTON2");
+ aKeys &= ~MK_XBUTTON2;
+ }
+ if (aKeys) {
+ MaybeAppendSeparator();
+ AppendPrintf("Unknown Flags (0x%04zX)", aKeys);
+ }
+ if (IsEmpty()) {
+ AssignLiteral("none (0x0000)");
+ }
+ }
+
+ private:
+ void MaybeAppendSeparator() {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+static const nsCString ToString(const MSG& aMSG) {
+ nsCString result;
+ result.AssignLiteral("{ message=");
+ result.Append(GetMessageName(aMSG.message).get());
+ result.AppendLiteral(", ");
+ switch (aMSG.message) {
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ result.AppendPrintf(
+ "virtual keycode=%s, repeat count=%" PRIdLPTR
+ ", "
+ "scancode=0x%02X, extended key=%s, "
+ "context code=%s, previous key state=%s, "
+ "transition state=%s",
+ GetVirtualKeyCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
+ WinUtils::GetScanCode(aMSG.lParam),
+ GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
+ GetBoolName((aMSG.lParam & (1 << 29)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 30)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 31)) != 0));
+ break;
+ case WM_CHAR:
+ case WM_DEADCHAR:
+ case WM_SYSCHAR:
+ case WM_SYSDEADCHAR:
+ result.AppendPrintf(
+ "character code=%s, repeat count=%" PRIdLPTR
+ ", "
+ "scancode=0x%02X, extended key=%s, "
+ "context code=%s, previous key state=%s, "
+ "transition state=%s",
+ GetCharacterCodeName(aMSG.wParam).get(), aMSG.lParam & 0xFFFF,
+ WinUtils::GetScanCode(aMSG.lParam),
+ GetBoolName(WinUtils::IsExtendedScanCode(aMSG.lParam)),
+ GetBoolName((aMSG.lParam & (1 << 29)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 30)) != 0),
+ GetBoolName((aMSG.lParam & (1 << 31)) != 0));
+ break;
+ case WM_APPCOMMAND:
+ result.AppendPrintf(
+ "window handle=0x%zx, app command=%s, device=%s, dwKeys=%s",
+ aMSG.wParam,
+ GetAppCommandName(GET_APPCOMMAND_LPARAM(aMSG.lParam)).get(),
+ GetAppCommandDeviceName(GET_DEVICE_LPARAM(aMSG.lParam)).get(),
+ GetAppCommandKeysName(GET_KEYSTATE_LPARAM(aMSG.lParam)).get());
+ break;
+ default:
+ result.AppendPrintf("wParam=%zu, lParam=%" PRIdLPTR, aMSG.wParam,
+ aMSG.lParam);
+ break;
+ }
+ result.AppendPrintf(", hwnd=0x%p", aMSG.hwnd);
+ return result;
+}
+
+static const nsCString ToString(
+ const UniCharsAndModifiers& aUniCharsAndModifiers) {
+ if (aUniCharsAndModifiers.IsEmpty()) {
+ return "{}"_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("{ ");
+ result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0)));
+ for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) {
+ if (aUniCharsAndModifiers.ModifiersAt(i - 1) !=
+ aUniCharsAndModifiers.ModifiersAt(i)) {
+ result.AppendLiteral(" [");
+ result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0)));
+ result.AppendLiteral("]");
+ }
+ result.AppendLiteral(", ");
+ result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i)));
+ }
+ result.AppendLiteral(" [");
+ uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1;
+ result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex)));
+ result.AppendLiteral("] }");
+ return result;
+}
+
+const nsCString ToString(const ModifierKeyState& aModifierKeyState) {
+ nsCString result;
+ result.AssignLiteral("{ ");
+ result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get());
+ result.AppendLiteral(" }");
+ return result;
+}
+
+// Unique id counter associated with a keydown / keypress events. Used in
+// identifing keypress events for removal from async event dispatch queue
+// in metrofx after preventDefault is called on keydown events.
+static uint32_t sUniqueKeyEventId = 0;
+
+/*****************************************************************************
+ * mozilla::widget::ModifierKeyState
+ *****************************************************************************/
+
+ModifierKeyState::ModifierKeyState() { Update(); }
+
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers)
+ : mModifiers(aModifiers) {
+ MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
+ "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
+ "if MODIFIER_ALTGRAPH is set");
+}
+
+void ModifierKeyState::Update() {
+ mModifiers = 0;
+ if (IS_VK_DOWN(VK_SHIFT)) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ // If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only
+ // MODIFIER_ALTGRAPH should be set. Otherwise, i.e., if both Ctrl and Alt
+ // keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT
+ // keys should be set separately.
+ if (IS_VK_DOWN(VK_RMENU) && KeyboardLayout::GetInstance()->HasAltGr()) {
+ mModifiers |= MODIFIER_ALTGRAPH;
+ } else {
+ if (IS_VK_DOWN(VK_CONTROL)) {
+ mModifiers |= MODIFIER_CONTROL;
+ }
+ if (IS_VK_DOWN(VK_MENU)) {
+ mModifiers |= MODIFIER_ALT;
+ }
+ }
+ if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) {
+ mModifiers |= MODIFIER_META;
+ }
+ if (::GetKeyState(VK_CAPITAL) & 1) {
+ mModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (::GetKeyState(VK_NUMLOCK) & 1) {
+ mModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (::GetKeyState(VK_SCROLL) & 1) {
+ mModifiers |= MODIFIER_SCROLLLOCK;
+ }
+}
+
+void ModifierKeyState::Unset(Modifiers aRemovingModifiers) {
+ mModifiers &= ~aRemovingModifiers;
+}
+
+void ModifierKeyState::Set(Modifiers aAddingModifiers) {
+ mModifiers |= aAddingModifiers;
+ MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()),
+ "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set "
+ "if MODIFIER_ALTGRAPH is set");
+}
+
+void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const {
+ aInputEvent.mModifiers = mModifiers;
+
+ switch (aInputEvent.mClass) {
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eDragEventClass:
+ case eSimpleGestureEventClass:
+ InitMouseEvent(aInputEvent);
+ break;
+ default:
+ break;
+ }
+}
+
+void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const {
+ NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass ||
+ aMouseEvent.mClass == eWheelEventClass ||
+ aMouseEvent.mClass == eDragEventClass ||
+ aMouseEvent.mClass == eSimpleGestureEventClass,
+ "called with non-mouse event");
+
+ WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase();
+ mouseEvent.mButtons = 0;
+ if (::GetKeyState(VK_LBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (::GetKeyState(VK_RBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (::GetKeyState(VK_MBUTTON) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON1) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON2) < 0) {
+ mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag;
+ }
+}
+
+bool ModifierKeyState::IsShift() const {
+ return (mModifiers & MODIFIER_SHIFT) != 0;
+}
+
+bool ModifierKeyState::IsControl() const {
+ return (mModifiers & MODIFIER_CONTROL) != 0;
+}
+
+bool ModifierKeyState::IsAlt() const {
+ return (mModifiers & MODIFIER_ALT) != 0;
+}
+
+bool ModifierKeyState::IsWin() const {
+ return (mModifiers & MODIFIER_META) != 0;
+}
+
+bool ModifierKeyState::MaybeMatchShortcutKey() const {
+ // If Windows key is pressed, even if both Ctrl key and Alt key are pressed,
+ // it's possible to match a shortcut key.
+ if (IsWin()) {
+ return true;
+ }
+ // Otherwise, when both Ctrl key and Alt key are pressed, it shouldn't be
+ // a shortcut key for Windows since it means pressing AltGr key on
+ // some keyboard layouts.
+ if (IsControl() ^ IsAlt()) {
+ return true;
+ }
+ // If no modifier key is active except a lockable modifier nor Shift key,
+ // the key shouldn't match any shortcut keys (there are Space and
+ // Shift+Space, though, let's ignore these special case...).
+ return false;
+}
+
+bool ModifierKeyState::IsCapsLocked() const {
+ return (mModifiers & MODIFIER_CAPSLOCK) != 0;
+}
+
+bool ModifierKeyState::IsNumLocked() const {
+ return (mModifiers & MODIFIER_NUMLOCK) != 0;
+}
+
+bool ModifierKeyState::IsScrollLocked() const {
+ return (mModifiers & MODIFIER_SCROLLLOCK) != 0;
+}
+
+/*****************************************************************************
+ * mozilla::widget::UniCharsAndModifiers
+ *****************************************************************************/
+
+void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) {
+ mChars.Append(aUniChar);
+ mModifiers.AppendElement(aModifiers);
+}
+
+void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) {
+ for (size_t i = 0; i < Length(); i++) {
+ mModifiers[i] = aModifiers;
+ }
+}
+
+void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith(
+ const UniCharsAndModifiers& aOther) {
+ if (!BeginsWith(aOther)) {
+ return;
+ }
+ for (size_t i = 0; i < aOther.Length(); ++i) {
+ mModifiers[i] = aOther.mModifiers[i];
+ }
+}
+
+bool UniCharsAndModifiers::UniCharsEqual(
+ const UniCharsAndModifiers& aOther) const {
+ return mChars.Equals(aOther.mChars);
+}
+
+bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual(
+ const UniCharsAndModifiers& aOther) const {
+ return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator);
+}
+
+bool UniCharsAndModifiers::BeginsWith(
+ const UniCharsAndModifiers& aOther) const {
+ return StringBeginsWith(mChars, aOther.mChars);
+}
+
+UniCharsAndModifiers& UniCharsAndModifiers::operator+=(
+ const UniCharsAndModifiers& aOther) {
+ mChars.Append(aOther.mChars);
+ mModifiers.AppendElements(aOther.mModifiers);
+ return *this;
+}
+
+UniCharsAndModifiers UniCharsAndModifiers::operator+(
+ const UniCharsAndModifiers& aOther) const {
+ UniCharsAndModifiers result(*this);
+ result += aOther;
+ return result;
+}
+
+/*****************************************************************************
+ * mozilla::widget::VirtualKey
+ *****************************************************************************/
+
+// static
+VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) {
+ ShiftState state = 0;
+ if (aModifiers & MODIFIER_SHIFT) {
+ state |= STATE_SHIFT;
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ state |= STATE_ALTGRAPH;
+ } else {
+ if (aModifiers & MODIFIER_CONTROL) {
+ state |= STATE_CONTROL;
+ }
+ if (aModifiers & MODIFIER_ALT) {
+ state |= STATE_ALT;
+ }
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ state |= STATE_CAPSLOCK;
+ }
+ return state;
+}
+
+// static
+Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) {
+ Modifiers modifiers = 0;
+ if (aShiftState & STATE_SHIFT) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aShiftState & STATE_ALTGRAPH) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ } else {
+ if (aShiftState & STATE_CONTROL) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aShiftState & STATE_ALT) {
+ modifiers |= MODIFIER_ALT;
+ }
+ }
+ if (aShiftState & STATE_CAPSLOCK) {
+ modifiers |= MODIFIER_CAPSLOCK;
+ }
+ return modifiers;
+}
+
+const DeadKeyTable* VirtualKey::MatchingDeadKeyTable(
+ const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
+ if (!mIsDeadKey) {
+ return nullptr;
+ }
+
+ for (ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ if (!IsDeadKey(shiftState)) {
+ continue;
+ }
+ const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table;
+ if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) {
+ return dkt;
+ }
+ }
+
+ return nullptr;
+}
+
+void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
+ uint32_t aNumOfChars) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+
+ SetDeadKey(aShiftState, false);
+
+ for (uint32_t index = 0; index < aNumOfChars; index++) {
+ // Ignore legacy non-printable control characters
+ mShiftStates[aShiftState].Normal.Chars[index] =
+ (aChars[index] >= 0x20) ? aChars[index] : 0;
+ }
+
+ uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
+ for (uint32_t index = aNumOfChars; index < len; index++) {
+ mShiftStates[aShiftState].Normal.Chars[index] = 0;
+ }
+}
+
+void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+
+ SetDeadKey(aShiftState, true);
+
+ mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
+ mShiftStates[aShiftState].DeadKey.Table = nullptr;
+}
+
+UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const {
+ UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
+
+ const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
+ if (!(kShiftStateIndex & STATE_CONTROL_ALT)) {
+ // If neither Alt nor Ctrl key is pressed, just return stored data
+ // for the key.
+ return result;
+ }
+
+ if (result.IsEmpty()) {
+ // If Alt and/or Control are pressed and the key produces no
+ // character, return characters which is produced by the key without
+ // Alt and Control, and return given modifiers as is.
+ result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
+ result.FillModifiers(ShiftStateToModifiers(aShiftState));
+ return result;
+ }
+
+ if (IsAltGrIndex(kShiftStateIndex)) {
+ // If AltGr or both Ctrl and Alt are pressed and the key produces
+ // character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL
+ // since TextEditor won't handle eKeyPress event whose mModifiers
+ // has MODIFIER_ALT or MODIFIER_CONTROL. Additionally, we need to
+ // use MODIFIER_ALTGRAPH when a key produces character(s) with
+ // AltGr or both Ctrl and Alt on Windows. See following spec issue:
+ // <https://github.com/w3c/uievents/issues/147>
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ finalModifiers |= MODIFIER_ALTGRAPH;
+ result.FillModifiers(finalModifiers);
+ return result;
+ }
+
+ // Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s),
+ // check if different character(s) is produced by the key without Alt/Ctrl.
+ // If it produces different character, we need to consume the Alt and
+ // Ctrl modifier for TextEditor. Otherwise, the key does not produces the
+ // character actually. So, keep setting Alt and Ctrl modifiers.
+ UniCharsAndModifiers unmodifiedReslt =
+ GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT);
+ if (!result.UniCharsEqual(unmodifiedReslt)) {
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ }
+ return result;
+}
+
+UniCharsAndModifiers VirtualKey::GetNativeUniChars(
+ ShiftState aShiftState) const {
+ const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState);
+ UniCharsAndModifiers result;
+ Modifiers modifiers = ShiftStateToModifiers(aShiftState);
+ if (IsDeadKey(aShiftState)) {
+ result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers);
+ return result;
+ }
+
+ uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars);
+ for (uint32_t i = 0;
+ i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) {
+ result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers);
+ }
+ return result;
+}
+
+// static
+void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) {
+ if (aShiftState & STATE_SHIFT) {
+ aKbdState[VK_SHIFT] |= 0x80;
+ } else {
+ aKbdState[VK_SHIFT] &= ~0x80;
+ aKbdState[VK_LSHIFT] &= ~0x80;
+ aKbdState[VK_RSHIFT] &= ~0x80;
+ }
+
+ if (aShiftState & STATE_ALTGRAPH) {
+ aKbdState[VK_CONTROL] |= 0x80;
+ aKbdState[VK_LCONTROL] |= 0x80;
+ aKbdState[VK_RCONTROL] &= ~0x80;
+ aKbdState[VK_MENU] |= 0x80;
+ aKbdState[VK_LMENU] &= ~0x80;
+ aKbdState[VK_RMENU] |= 0x80;
+ } else {
+ if (aShiftState & STATE_CONTROL) {
+ aKbdState[VK_CONTROL] |= 0x80;
+ } else {
+ aKbdState[VK_CONTROL] &= ~0x80;
+ aKbdState[VK_LCONTROL] &= ~0x80;
+ aKbdState[VK_RCONTROL] &= ~0x80;
+ }
+
+ if (aShiftState & STATE_ALT) {
+ aKbdState[VK_MENU] |= 0x80;
+ } else {
+ aKbdState[VK_MENU] &= ~0x80;
+ aKbdState[VK_LMENU] &= ~0x80;
+ aKbdState[VK_RMENU] &= ~0x80;
+ }
+ }
+
+ if (aShiftState & STATE_CAPSLOCK) {
+ aKbdState[VK_CAPITAL] |= 0x01;
+ } else {
+ aKbdState[VK_CAPITAL] &= ~0x01;
+ }
+}
+
+/*****************************************************************************
+ * mozilla::widget::NativeKey
+ *****************************************************************************/
+
+uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0;
+NativeKey* NativeKey::sLatestInstance = nullptr;
+const MSG NativeKey::sEmptyMSG = {};
+MSG NativeKey::sLastKeyOrCharMSG = {};
+MSG NativeKey::sLastKeyMSG = {};
+char16_t NativeKey::sPendingHighSurrogate = 0;
+
+NativeKey::NativeKey(nsWindow* aWidget, const MSG& aMessage,
+ const ModifierKeyState& aModKeyState,
+ HKL aOverrideKeyboardLayout,
+ nsTArray<FakeCharMsg>* aFakeCharMsgs)
+ : mLastInstance(sLatestInstance),
+ mRemovingMsg(sEmptyMSG),
+ mReceivedMsg(sEmptyMSG),
+ mWidget(aWidget),
+ mDispatcher(aWidget->GetTextEventDispatcher()),
+ mMsg(aMessage),
+ mFocusedWndBeforeDispatch(::GetFocus()),
+ mDOMKeyCode(0),
+ mKeyNameIndex(KEY_NAME_INDEX_Unidentified),
+ mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN),
+ mModKeyState(aModKeyState),
+ mVirtualKeyCode(0),
+ mOriginalVirtualKeyCode(0),
+ mShiftedLatinChar(0),
+ mUnshiftedLatinChar(0),
+ mScanCode(0),
+ mIsExtended(false),
+ mIsRepeat(false),
+ mIsDeadKey(false),
+ mIsPrintableKey(false),
+ mIsSkippableInRemoteProcess(false),
+ mCharMessageHasGone(false),
+ mCanIgnoreModifierStateAtKeyPress(true),
+ mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ? aFakeCharMsgs
+ : nullptr) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::NativeKey(aWidget=0x%p { GetWindowHandle()=0x%p }, "
+ "aMessage=%s, aModKeyState=%s), sLatestInstance=0x%p",
+ this, aWidget, aWidget->GetWindowHandle(), ToString(aMessage).get(),
+ ToString(aModKeyState).get(), sLatestInstance));
+
+ MOZ_ASSERT(aWidget);
+ MOZ_ASSERT(mDispatcher);
+ sLatestInstance = this;
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mKeyboardLayout = KeyboardLayout::GetLayout();
+ if (aOverrideKeyboardLayout && mKeyboardLayout != aOverrideKeyboardLayout) {
+ keyboardLayout->OverrideLayout(aOverrideKeyboardLayout);
+ mKeyboardLayout = keyboardLayout->GetLoadedLayout();
+ MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout);
+ mIsOverridingKeyboardLayout = true;
+ } else {
+ mIsOverridingKeyboardLayout = false;
+ sLastKeyOrCharMSG = aMessage;
+ }
+
+ if (mMsg.message == WM_APPCOMMAND) {
+ InitWithAppCommand();
+ } else {
+ InitWithKeyOrChar();
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%p, "
+ "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, "
+ "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, "
+ "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, "
+ "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, "
+ "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, "
+ "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, "
+ "mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, "
+ "mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, "
+ "mIsOverridingKeyboardLayout=%s",
+ this, mKeyboardLayout, mFocusedWndBeforeDispatch,
+ GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(),
+ ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(),
+ GetVirtualKeyCodeName(mVirtualKeyCode).get(),
+ GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(),
+ ToString(mCommittedCharsAndModifiers).get(),
+ ToString(mInputtingStringAndModifiers).get(),
+ ToString(mShiftedString).get(), ToString(mUnshiftedString).get(),
+ GetCharacterCodeName(mShiftedLatinChar).get(),
+ GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode,
+ GetBoolName(mIsExtended), GetBoolName(mIsRepeat),
+ GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey),
+ GetBoolName(mIsSkippableInRemoteProcess),
+ GetBoolName(mCharMessageHasGone),
+ GetBoolName(mIsOverridingKeyboardLayout)));
+}
+
+void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) {
+ mIsSkippableInRemoteProcess = false;
+
+ if (!mIsRepeat) {
+ // If the message is not repeated key message, the event should be always
+ // handled in remote process even if it's too old.
+ return;
+ }
+
+ // Keyboard utilities may send us some generated messages and such messages
+ // may be marked as "repeated", e.g., SendInput() calls with
+ // KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP. However, key sequence
+ // comes from such utilities may be really important. For example, utilities
+ // may send WM_KEYDOWN for VK_BACK to remove previous character and send
+ // WM_KEYDOWN for VK_PACKET to insert a composite character. Therefore, we
+ // should check if current message and previous key message are caused by
+ // same physical key. If not, the message may be generated by such
+ // utility.
+ // XXX With this approach, if VK_BACK messages are generated with known
+ // scancode, we cannot distinguish whether coming VK_BACK message is
+ // actually repeated by the auto-repeat feature. Currently, we need
+ // this hack only for "SinhalaTamil IME" and fortunately, it generates
+ // VK_BACK messages with odd scancode. So, we don't need to handle
+ // VK_BACK specially at least for now.
+
+ if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) {
+ // If current event is not caused by physical key operation, it may be
+ // caused by a keyboard utility. If so, the event shouldn't be ignored by
+ // BrowserChild since it want to insert the character, delete a character or
+ // move caret.
+ return;
+ }
+
+ if (mOriginalVirtualKeyCode == VK_PACKET) {
+ // If the message is VK_PACKET, that means that a keyboard utility
+ // tries to insert a character.
+ return;
+ }
+
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ // However, some keyboard layouts may send some keyboard messages with
+ // activating the bit. If we dispatch repeated keyboard events, they
+ // may be ignored by BrowserChild due to performance reason. So, we need
+ // to check if actually a physical key is repeated by the auto-repeat
+ // feature.
+ switch (aLastKeyMSG.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ if (aLastKeyMSG.wParam == VK_PACKET) {
+ // If the last message was VK_PACKET, that means that a keyboard
+ // utility tried to insert a character. So, current message is
+ // not repeated key event of the previous event.
+ return;
+ }
+ // Let's check whether current message and previous message are
+ // caused by same physical key.
+ mIsSkippableInRemoteProcess =
+ mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) &&
+ mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam);
+ return;
+ default:
+ // If previous message is not a keydown, this must not be generated
+ // by the auto-repeat feature.
+ return;
+ }
+ case WM_APPCOMMAND:
+ MOZ_ASSERT_UNREACHABLE(
+ "WM_APPCOMMAND should be handled in "
+ "InitWithAppCommand()");
+ return;
+ default:
+ // keyup message shouldn't be repeated by the auto-repeat feature.
+ return;
+ }
+}
+
+void NativeKey::InitWithKeyOrChar() {
+ MSG lastKeyMSG = sLastKeyMSG;
+ char16_t pendingHighSurrogate = sPendingHighSurrogate;
+ mScanCode = WinUtils::GetScanCode(mMsg.lParam);
+ mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam);
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ sPendingHighSurrogate = 0;
+ [[fallthrough]];
+ case WM_KEYUP:
+ case WM_SYSKEYUP: {
+ // Modify sLastKeyMSG now since retrieving following char messages may
+ // cause sending another key message if odd tool hooks GetMessage(),
+ // PeekMessage().
+ sLastKeyMSG = mMsg;
+
+ // Note that we don't need to compute raw virtual keycode here even when
+ // it's VK_PROCESS (i.e., already handled by IME) because we need to
+ // export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process.
+ mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam);
+
+ // If the key message is sent from other application like a11y tools, the
+ // scancode value might not be set proper value. Then, probably the value
+ // is 0.
+ // NOTE: If the virtual keycode can be caused by both non-extended key
+ // and extended key, the API returns the non-extended key's
+ // scancode. E.g., VK_LEFT causes "4" key on numpad.
+ if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) {
+ uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam);
+ if (scanCodeEx) {
+ mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
+ uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
+ mIsExtended = (extended == 0xE0) || (extended == 0xE1);
+ }
+ }
+
+ // Most keys are not distinguished as left or right keys.
+ bool isLeftRightDistinguishedKey = false;
+
+ // mOriginalVirtualKeyCode must not distinguish left or right of
+ // Shift, Control or Alt.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LSHIFT:
+ case VK_RSHIFT:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_SHIFT;
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LCONTROL:
+ case VK_RCONTROL:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_CONTROL;
+ isLeftRightDistinguishedKey = true;
+ break;
+ case VK_LMENU:
+ case VK_RMENU:
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ mOriginalVirtualKeyCode = VK_MENU;
+ isLeftRightDistinguishedKey = true;
+ break;
+ }
+
+ // If virtual keycode (left-right distinguished keycode) is already
+ // computed, we don't need to do anymore.
+ if (mVirtualKeyCode) {
+ break;
+ }
+
+ // If the keycode doesn't have LR distinguished keycode, we just set
+ // mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute
+ // it from MapVirtualKeyEx() because the scan code might be wrong if
+ // the message is sent/posted by other application. Then, we will compute
+ // unexpected keycode from the scan code.
+ if (!isLeftRightDistinguishedKey) {
+ break;
+ }
+
+ NS_ASSERTION(!mVirtualKeyCode,
+ "mVirtualKeyCode has been computed already");
+
+ // Otherwise, compute the virtual keycode with MapVirtualKeyEx().
+ mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx();
+
+ // Following code shouldn't be used now because we compute scancode value
+ // if we detect that the sender doesn't set proper scancode.
+ // However, the detection might fail. Therefore, let's keep using this.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_CONTROL:
+ if (mVirtualKeyCode != VK_LCONTROL &&
+ mVirtualKeyCode != VK_RCONTROL) {
+ mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL;
+ }
+ break;
+ case VK_MENU:
+ if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) {
+ mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU;
+ }
+ break;
+ case VK_SHIFT:
+ if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) {
+ // Neither left shift nor right shift is an extended key,
+ // let's use VK_LSHIFT for unknown mapping.
+ mVirtualKeyCode = VK_LSHIFT;
+ }
+ break;
+ default:
+ MOZ_CRASH("Unsupported mOriginalVirtualKeyCode");
+ }
+ break;
+ }
+ case WM_CHAR:
+ case WM_UNICHAR:
+ case WM_SYSCHAR:
+ sPendingHighSurrogate = 0;
+ // If there is another instance and it is trying to remove a char message
+ // from the queue, this message should be handled in the old instance.
+ if (IsAnotherInstanceRemovingCharMessage()) {
+ // XXX Do we need to make mReceivedMsg an array?
+ MOZ_ASSERT(IsEmptyMSG(mLastInstance->mReceivedMsg));
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), WARNING, detecting another "
+ "instance is trying to remove a char message, so, this instance "
+ "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, "
+ "mReceivedMsg=%s",
+ this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(),
+ ToString(mLastInstance->mReceivedMsg).get()));
+ mLastInstance->mReceivedMsg = mMsg;
+ return;
+ }
+
+ // NOTE: If other applications like a11y tools sends WM_*CHAR without
+ // scancode, we cannot compute virtual keycode. I.e., with such
+ // applications, we cannot generate proper KeyboardEvent.code value.
+
+ mVirtualKeyCode = mOriginalVirtualKeyCode =
+ ComputeVirtualKeyCodeFromScanCodeEx();
+ NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
+ break;
+ default: {
+ MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message);
+ break;
+ }
+ }
+
+ if (!mVirtualKeyCode) {
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ }
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mDOMKeyCode =
+ keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode);
+ // Be aware, keyboard utilities can change non-printable keys to printable
+ // keys. In such case, we should make the key value as a printable key.
+ // FYI: IsFollowedByPrintableCharMessage() returns true only when it's
+ // handling a keydown message.
+ mKeyNameIndex =
+ IsFollowedByPrintableCharMessage()
+ ? KEY_NAME_INDEX_USE_STRING
+ : keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode);
+ mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
+ GetScanCodeWithExtendedFlag());
+
+ // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key
+ // combination is not reserved by the system, let's consume it now.
+ // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET
+ // if the message is WM_KEYUP because we don't have preceding
+ // WM_CHAR message.
+ // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events
+ // for a Unicode character in non-BMP because its key value looks
+ // broken and not good thing for our editor if only one keydown or
+ // keypress event's default is prevented. I guess, we should store
+ // key message information globally and we should wait following
+ // WM_KEYDOWN if following WM_CHAR is a part of a Unicode character.
+ if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) &&
+ !IsReservedBySystem()) {
+ MSG charMsg;
+ while (GetFollowingCharMessage(charMsg)) {
+ // Although, got message shouldn't be WM_NULL in desktop apps,
+ // we should keep checking this. FYI: This was added for Metrofox.
+ if (charMsg.message == WM_NULL) {
+ continue;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::InitWithKeyOrChar(), removed char message, %s",
+ this, ToString(charMsg).get()));
+ Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
+ mFollowingCharMsgs.AppendElement(charMsg);
+ }
+ if (mFollowingCharMsgs.Length() == 1) {
+ // If we receive a keydown message for a high-surrogate, a low-surrogate
+ // keydown message **will** and should follow it. We cannot translate the
+ // following WM_KEYDOWN message for the low-surrogate right now since
+ // it's not yet queued into the message queue yet. Therefore, we need to
+ // wait next one to dispatch keypress event with setting its `.key` value
+ // to a surrogate pair rather than setting it to a lone surrogate.
+ // FYI: This may happen with typing a non-BMP character on the touch
+ // keyboard on Windows 10 or later except when an IME is installed. (If
+ // IME is installed, composition is used instead.)
+ if (IS_HIGH_SURROGATE(mFollowingCharMsgs[0].wParam)) {
+ if (pendingHighSurrogate) {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), there is pending "
+ "high surrogate input, but received another high surrogate "
+ "input. The previous one is discarded",
+ this));
+ }
+ sPendingHighSurrogate = mFollowingCharMsgs[0].wParam;
+ mFollowingCharMsgs.Clear();
+ } else if (IS_LOW_SURROGATE(mFollowingCharMsgs[0].wParam)) {
+ // If we stopped dispathing a keypress event for a preceding
+ // high-surrogate, treat this keydown (for a low-surrogate) as
+ // introducing both the high surrogate and the low surrogate.
+ if (pendingHighSurrogate) {
+ MSG charMsg = mFollowingCharMsgs[0];
+ mFollowingCharMsgs[0].wParam = pendingHighSurrogate;
+ mFollowingCharMsgs.AppendElement(std::move(charMsg));
+ } else {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), there is no pending high "
+ "surrogate input, but received lone low surrogate input",
+ this));
+ }
+ } else {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), there is pending "
+ "high surrogate input, but received non-surrogate input. "
+ "The high surrogate input is discarded",
+ this));
+ }
+ } else if (!mFollowingCharMsgs.IsEmpty()) {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyOrChar(), there is pending "
+ "high surrogate input, but received 2 or more character input. "
+ "The high surrogate input is discarded",
+ this));
+ }
+ }
+
+ keyboardLayout->InitNativeKey(*this);
+
+ // Now, we can know if the key produces character(s) or a dead key with
+ // AltGraph modifier. When user emulates AltGr key press with pressing
+ // both Ctrl and Alt and the key produces character(s) or a dead key, we
+ // need to replace Control and Alt state with AltGraph if the keyboard
+ // layout has AltGr key.
+ // Note that if Ctrl and/or Alt are pressed (not to emulate to press AltGr),
+ // we need to set actual modifiers to eKeyDown and eKeyUp.
+ if (MaybeEmulatingAltGraph() &&
+ (mCommittedCharsAndModifiers.IsProducingCharsWithAltGr() ||
+ mKeyNameIndex == KEY_NAME_INDEX_Dead)) {
+ mModKeyState.Unset(MODIFIER_CONTROL | MODIFIER_ALT);
+ mModKeyState.Set(MODIFIER_ALTGRAPH);
+ }
+
+ mIsDeadKey =
+ (IsFollowedByDeadCharMessage() ||
+ keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
+ mIsPrintableKey = mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+ KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+ // The repeat count in mMsg.lParam isn't useful to check whether the event
+ // is caused by the auto-repeat feature because it's not incremented even
+ // if it's repeated twice or more (i.e., always 1). Therefore, we need to
+ // check previous key state (31th bit) instead. If it's 1, the key was down
+ // before the message was sent.
+ mIsRepeat = (mMsg.lParam & (1 << 30)) != 0;
+ InitIsSkippableForKeyOrChar(lastKeyMSG);
+
+ if (IsKeyDownMessage()) {
+ // Compute some strings which may be inputted by the key with various
+ // modifier state if this key event won't cause text input actually.
+ // They will be used for setting mAlternativeCharCodes in the callback
+ // method which will be called by TextEventDispatcher.
+ if (!IsFollowedByPrintableCharMessage()) {
+ ComputeInputtingStringWithKeyboardLayout();
+ }
+ // Remove odd char messages if there are.
+ RemoveFollowingOddCharMessages();
+ }
+}
+
+void NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages() {
+ mCommittedCharsAndModifiers.Clear();
+ // This should cause inputting text in focused editor. However, it
+ // ignores keypress events whose altKey or ctrlKey is true.
+ // Therefore, we need to remove these modifier state here.
+ Modifiers modifiers = mModKeyState.GetModifiers();
+ if (IsFollowedByPrintableCharMessage()) {
+ modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ if (MaybeEmulatingAltGraph()) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ }
+ }
+ // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved
+ // at same time.
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ // Ignore non-printable char messages.
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ continue;
+ }
+ char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam);
+ mCommittedCharsAndModifiers.Append(ch, modifiers);
+ }
+}
+
+NativeKey::~NativeKey() {
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::~NativeKey(), destroyed", this));
+ if (mIsOverridingKeyboardLayout) {
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ keyboardLayout->RestoreLayout();
+ }
+ sLatestInstance = mLastInstance;
+}
+
+void NativeKey::InitWithAppCommand() {
+ if (GET_DEVICE_LPARAM(mMsg.lParam) != FAPPCOMMAND_KEY) {
+ return;
+ }
+
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ switch (GET_APPCOMMAND_LPARAM(mMsg.lParam)) {
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+#define NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX(aAppCommand, aKeyNameIndex) \
+ case aAppCommand: \
+ mKeyNameIndex = aKeyNameIndex; \
+ break;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_APPCOMMAND_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ mKeyNameIndex = KEY_NAME_INDEX_Unidentified;
+ }
+
+ // Guess the virtual keycode which caused this message.
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_BACK;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FORWARD;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_REFRESH;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_STOP;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_SEARCH;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_FAVORITES;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_BROWSER_HOME;
+ break;
+ case APPCOMMAND_VOLUME_MUTE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_MUTE;
+ break;
+ case APPCOMMAND_VOLUME_DOWN:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_DOWN;
+ break;
+ case APPCOMMAND_VOLUME_UP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_VOLUME_UP;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_NEXT_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PREV_TRACK;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_STOP;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_MEDIA_PLAY_PAUSE;
+ break;
+ case APPCOMMAND_LAUNCH_MAIL:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MAIL;
+ break;
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_MEDIA_SELECT;
+ break;
+ case APPCOMMAND_LAUNCH_APP1:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP1;
+ break;
+ case APPCOMMAND_LAUNCH_APP2:
+ mVirtualKeyCode = mOriginalVirtualKeyCode = VK_LAUNCH_APP2;
+ break;
+ default:
+ return;
+ }
+
+ uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mVirtualKeyCode);
+ mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF);
+ uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8);
+ mIsExtended = (extended == 0xE0) || (extended == 0xE1);
+ mDOMKeyCode = KeyboardLayout::GetInstance()->ConvertNativeKeyCodeToDOMKeyCode(
+ mOriginalVirtualKeyCode);
+ mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex(
+ GetScanCodeWithExtendedFlag());
+ // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust
+ // the result of GetKeyboardState(). Otherwise, we dispatch both
+ // keydown and keyup events from WM_APPCOMMAND handler. Therefore,
+ // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive
+ // a pair of DOM keydown and keyup events. I.e., KeyboardEvent.repeat
+ // should be never true of such keys.
+ // XXX Isn't the key state always true? If the key press caused this
+ // WM_APPCOMMAND, that means it's pressed at that time.
+ if (mVirtualKeyCode) {
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ ::GetKeyboardState(kbdState);
+ mIsSkippableInRemoteProcess = mIsRepeat = !!kbdState[mVirtualKeyCode];
+ }
+}
+
+bool NativeKey::MaybeEmulatingAltGraph() const {
+ return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr();
+}
+
+// static
+bool NativeKey::IsControlChar(char16_t aChar) {
+ static const char16_t U_SPACE = 0x20;
+ static const char16_t U_DELETE = 0x7F;
+ return aChar < U_SPACE || aChar == U_DELETE;
+}
+
+bool NativeKey::IsFollowedByDeadCharMessage() const {
+ if (mFollowingCharMsgs.IsEmpty()) {
+ return false;
+ }
+ return IsDeadCharMessage(mFollowingCharMsgs[0]);
+}
+
+bool NativeKey::IsFollowedByPrintableCharMessage() const {
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const {
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool NativeKey::IsReservedBySystem() const {
+ // Alt+Space key is handled by OS, we shouldn't touch it.
+ if (mModKeyState.IsAlt() && !mModKeyState.IsControl() &&
+ mVirtualKeyCode == VK_SPACE) {
+ return true;
+ }
+
+ // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the
+ // window. Although, we don't prevent to close the window but the key
+ // event shouldn't be exposed to the web.
+
+ return false;
+}
+
+bool NativeKey::IsIMEDoingKakuteiUndo() const {
+ // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG:
+ // ---------------------------------------------------------------------------
+ // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK
+ // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0)
+ // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF)
+ // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1)
+ // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001)
+ // ---------------------------------------------------------------------------
+ // This doesn't match usual key message pattern such as:
+ // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP
+ // See following bugs for the detail.
+ // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese)
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English)
+ MSG startCompositionMsg, compositionMsg, charMsg;
+ return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd,
+ WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION,
+ WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) &&
+ WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ startCompositionMsg.wParam == 0x0 &&
+ startCompositionMsg.lParam == 0x0 && compositionMsg.wParam == 0x0 &&
+ compositionMsg.lParam == 0x1BF && charMsg.wParam == VK_BACK &&
+ charMsg.lParam == 0x1 &&
+ startCompositionMsg.time <= compositionMsg.time &&
+ compositionMsg.time <= charMsg.time;
+}
+
+void NativeKey::RemoveFollowingOddCharMessages() {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // If the keydown message is synthesized for automated tests, there is
+ // nothing to do here.
+ if (mFakeCharMsgs) {
+ return;
+ }
+
+ // If there are some following char messages before another key message,
+ // there is nothing to do here.
+ if (!mFollowingCharMsgs.IsEmpty()) {
+ return;
+ }
+
+ // If the handling key isn't Backspace, there is nothing to do here.
+ if (mOriginalVirtualKeyCode != VK_BACK) {
+ return;
+ }
+
+ // If we don't see the odd message pattern, there is nothing to do here.
+ if (!IsIMEDoingKakuteiUndo()) {
+ return;
+ }
+
+ // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both
+ // of them are Japanese IME).
+ MSG msg;
+ while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR,
+ PM_REMOVE | PM_NOYIELD)) {
+ if (msg.message != WM_CHAR) {
+ MOZ_RELEASE_ASSERT(msg.message == WM_NULL,
+ "Unexpected message was removed");
+ continue;
+ }
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::RemoveFollowingOddCharMessages(), removed odd char "
+ "message, %s",
+ this, ToString(msg).get()));
+ mRemovedOddCharMsgs.AppendElement(msg);
+ }
+}
+
+UINT NativeKey::GetScanCodeWithExtendedFlag() const {
+ if (!mIsExtended) {
+ return mScanCode;
+ }
+ return (0xE000 | mScanCode);
+}
+
+uint32_t NativeKey::GetKeyLocation() const {
+ switch (mVirtualKeyCode) {
+ case VK_LSHIFT:
+ case VK_LCONTROL:
+ case VK_LMENU:
+ case VK_LWIN:
+ return eKeyLocationLeft;
+
+ case VK_RSHIFT:
+ case VK_RCONTROL:
+ case VK_RMENU:
+ case VK_RWIN:
+ return eKeyLocationRight;
+
+ case VK_RETURN:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return !mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad;
+
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_END:
+ case VK_DOWN:
+ case VK_NEXT:
+ case VK_LEFT:
+ case VK_CLEAR:
+ case VK_RIGHT:
+ case VK_HOME:
+ case VK_UP:
+ case VK_PRIOR:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad;
+
+ // NumLock key isn't included due to IE9's behavior.
+ case VK_NUMPAD0:
+ case VK_NUMPAD1:
+ case VK_NUMPAD2:
+ case VK_NUMPAD3:
+ case VK_NUMPAD4:
+ case VK_NUMPAD5:
+ case VK_NUMPAD6:
+ case VK_NUMPAD7:
+ case VK_NUMPAD8:
+ case VK_NUMPAD9:
+ case VK_DECIMAL:
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ case VK_ADD:
+ // Separator key of Brazilian keyboard or JIS keyboard for Mac
+ case VK_ABNT_C2:
+ return eKeyLocationNumpad;
+
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ NS_WARNING("Failed to decide the key location?");
+ [[fallthrough]];
+
+ default:
+ return eKeyLocationStandard;
+ }
+}
+
+uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCode() const {
+ return static_cast<uint8_t>(
+ ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout));
+}
+
+uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const {
+ // MapVirtualKeyEx() has been improved for supporting extended keys since
+ // Vista. When we call it for mapping a scancode of an extended key and
+ // a virtual keycode, we need to add 0xE000 to the scancode.
+ return static_cast<uint8_t>(::MapVirtualKeyEx(
+ GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, mKeyboardLayout));
+}
+
+uint16_t NativeKey::ComputeScanCodeExFromVirtualKeyCode(
+ UINT aVirtualKeyCode) const {
+ return static_cast<uint16_t>(
+ ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC_EX, mKeyboardLayout));
+}
+
+char16_t NativeKey::ComputeUnicharFromScanCode() const {
+ return static_cast<char16_t>(::MapVirtualKeyEx(
+ ComputeVirtualKeyCodeFromScanCode(), MAPVK_VK_TO_CHAR, mKeyboardLayout));
+}
+
+nsEventStatus NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const {
+ return InitKeyEvent(aKeyEvent, mModKeyState);
+}
+
+nsEventStatus NativeKey::InitKeyEvent(
+ WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState) const {
+ if (mWidget->Destroyed()) {
+ MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget");
+ }
+
+ LayoutDeviceIntPoint point(0, 0);
+ mWidget->InitEvent(aKeyEvent, &point);
+
+ switch (aKeyEvent.mMessage) {
+ case eKeyDown:
+ // If it was followed by a char message but it was consumed by somebody,
+ // we should mark it as consumed because somebody must have handled it
+ // and we should prevent to do "double action" for the key operation.
+ // However, for compatibility with older version and other browsers,
+ // we should dispatch the events even in the web content.
+ if (mCharMessageHasGone) {
+ aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
+ }
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Unique id for this keydown event and its associated keypress.
+ sUniqueKeyEventId++;
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ case eKeyUp:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Set defaultPrevented of the key event if the VK_MENU is not a system
+ // key release, so that the menu bar does not trigger. This helps avoid
+ // triggering the menu bar for ALT key accelerators used in assistive
+ // technologies such as Window-Eyes and ZoomText or for switching open
+ // state of IME. On the other hand, we should dispatch the events even
+ // in the web content for compatibility with older version and other
+ // browsers.
+ if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
+ aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow);
+ }
+ break;
+ case eKeyPress:
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should be consumed above");
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ default:
+ MOZ_CRASH("Invalid event message");
+ }
+
+ aKeyEvent.mIsRepeat = mIsRepeat;
+ aKeyEvent.mMaybeSkippableInRemoteProcess = mIsSkippableInRemoteProcess;
+ aKeyEvent.mKeyNameIndex = mKeyNameIndex;
+ if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString();
+ }
+ aKeyEvent.mCodeNameIndex = mCodeNameIndex;
+ MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mLocation = GetKeyLocation();
+ aModKeyState.InitInputEvent(aKeyEvent);
+
+ KeyboardLayout::NotifyIdleServiceOfUserActivity();
+
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::InitKeyEvent(), initialized, aKeyEvent={ "
+ "mMessage=%s, mKeyNameIndex=%s, mKeyValue=\"%s\", mCodeNameIndex=%s, "
+ "mKeyCode=%s, mLocation=%s, mModifiers=%s, DefaultPrevented()=%s }",
+ this, ToChar(aKeyEvent.mMessage),
+ ToString(aKeyEvent.mKeyNameIndex).get(),
+ NS_ConvertUTF16toUTF8(aKeyEvent.mKeyValue).get(),
+ ToString(aKeyEvent.mCodeNameIndex).get(),
+ GetDOMKeyCodeName(aKeyEvent.mKeyCode).get(),
+ GetKeyLocationName(aKeyEvent.mLocation).get(),
+ GetModifiersName(aKeyEvent.mModifiers).get(),
+ GetBoolName(aKeyEvent.DefaultPrevented())));
+
+ return aKeyEvent.DefaultPrevented() ? nsEventStatus_eConsumeNoDefault
+ : nsEventStatus_eIgnore;
+}
+
+bool NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const {
+ RefPtr<nsAtom> command;
+ switch (aEventCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ command = nsGkAtoms::Back;
+ break;
+ case APPCOMMAND_BROWSER_FORWARD:
+ command = nsGkAtoms::Forward;
+ break;
+ case APPCOMMAND_BROWSER_REFRESH:
+ command = nsGkAtoms::Reload;
+ break;
+ case APPCOMMAND_BROWSER_STOP:
+ command = nsGkAtoms::Stop;
+ break;
+ case APPCOMMAND_BROWSER_SEARCH:
+ command = nsGkAtoms::Search;
+ break;
+ case APPCOMMAND_BROWSER_FAVORITES:
+ command = nsGkAtoms::Bookmarks;
+ break;
+ case APPCOMMAND_BROWSER_HOME:
+ command = nsGkAtoms::Home;
+ break;
+ case APPCOMMAND_CLOSE:
+ command = nsGkAtoms::Close;
+ break;
+ case APPCOMMAND_FIND:
+ command = nsGkAtoms::Find;
+ break;
+ case APPCOMMAND_HELP:
+ command = nsGkAtoms::Help;
+ break;
+ case APPCOMMAND_NEW:
+ command = nsGkAtoms::New;
+ break;
+ case APPCOMMAND_OPEN:
+ command = nsGkAtoms::Open;
+ break;
+ case APPCOMMAND_PRINT:
+ command = nsGkAtoms::Print;
+ break;
+ case APPCOMMAND_SAVE:
+ command = nsGkAtoms::Save;
+ break;
+ case APPCOMMAND_FORWARD_MAIL:
+ command = nsGkAtoms::ForwardMail;
+ break;
+ case APPCOMMAND_REPLY_TO_MAIL:
+ command = nsGkAtoms::ReplyToMail;
+ break;
+ case APPCOMMAND_SEND_MAIL:
+ command = nsGkAtoms::SendMail;
+ break;
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ command = nsGkAtoms::NextTrack;
+ break;
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ command = nsGkAtoms::PreviousTrack;
+ break;
+ case APPCOMMAND_MEDIA_STOP:
+ command = nsGkAtoms::MediaStop;
+ break;
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ command = nsGkAtoms::PlayPause;
+ break;
+ default:
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command "
+ "event",
+ this));
+ return false;
+ }
+ WidgetCommandEvent appCommandEvent(true, command, mWidget);
+
+ mWidget->InitEvent(appCommandEvent);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatching "
+ "%s app command event...",
+ this, nsAtomCString(command).get()));
+ bool ok =
+ mWidget->DispatchWindowEvent(appCommandEvent) || mWidget->Destroyed();
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatched app command event, "
+ "result=%s, mWidget->Destroyed()=%s",
+ this, GetBoolName(ok), GetBoolName(mWidget->Destroyed())));
+ return ok;
+}
+
+bool NativeKey::HandleAppCommandMessage() const {
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::HandleAppCommandMessage(), WARNING, not handled "
+ "due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ // NOTE: Typical behavior of WM_APPCOMMAND caused by key is, WM_APPCOMMAND
+ // message is _sent_ first. Then, the DefaultWndProc will _post_
+ // WM_KEYDOWN message and WM_KEYUP message if the keycode for the
+ // command is available (i.e., mVirtualKeyCode is not 0).
+
+ // NOTE: IntelliType (Microsoft's keyboard utility software) always consumes
+ // WM_KEYDOWN and WM_KEYUP.
+
+ // Let's dispatch keydown message before our chrome handles the command
+ // when the message is caused by a keypress. This behavior makes handling
+ // WM_APPCOMMAND be a default action of the keydown event. This means that
+ // web applications can handle multimedia keys and prevent our default action.
+ // This allow web applications to provide better UX for multimedia keyboard
+ // users.
+ bool dispatchKeyEvent = (GET_DEVICE_LPARAM(mMsg.lParam) == FAPPCOMMAND_KEY);
+ if (dispatchKeyEvent) {
+ // If a plug-in window has focus but it didn't consume the message, our
+ // window receive WM_APPCOMMAND message. In this case, we shouldn't
+ // dispatch KeyboardEvents because an event handler may access the
+ // plug-in process synchronously.
+ dispatchKeyEvent =
+ WinUtils::IsOurProcessWindow(reinterpret_cast<HWND>(mMsg.wParam));
+ }
+
+ bool consumed = false;
+
+ if (dispatchKeyEvent) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keydown "
+ "event...",
+ this));
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), tries to dispatch "
+ "keydown event...",
+ this));
+ // NOTE: If the keydown event is consumed by web contents, we shouldn't
+ // continue to handle the command.
+ if (!mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+ const_cast<NativeKey*>(this))) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event isn't "
+ "dispatched",
+ this));
+ // If keyboard event wasn't fired, there must be composition.
+ // So, we don't need to dispatch a command event.
+ return true;
+ }
+ consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event was "
+ "dispatched, consumed=%s",
+ this, GetBoolName(consumed)));
+ sDispatchedKeyOfAppCommand = mVirtualKeyCode;
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ }
+
+ // Dispatch a command event or a content command event if the command is
+ // supported.
+ if (!consumed) {
+ uint32_t appCommand = GET_APPCOMMAND_LPARAM(mMsg.lParam);
+ EventMessage contentCommandMessage = eVoidEvent;
+ switch (appCommand) {
+ case APPCOMMAND_BROWSER_BACKWARD:
+ case APPCOMMAND_BROWSER_FORWARD:
+ case APPCOMMAND_BROWSER_REFRESH:
+ case APPCOMMAND_BROWSER_STOP:
+ case APPCOMMAND_BROWSER_SEARCH:
+ case APPCOMMAND_BROWSER_FAVORITES:
+ case APPCOMMAND_BROWSER_HOME:
+ case APPCOMMAND_CLOSE:
+ case APPCOMMAND_FIND:
+ case APPCOMMAND_HELP:
+ case APPCOMMAND_NEW:
+ case APPCOMMAND_OPEN:
+ case APPCOMMAND_PRINT:
+ case APPCOMMAND_SAVE:
+ case APPCOMMAND_FORWARD_MAIL:
+ case APPCOMMAND_REPLY_TO_MAIL:
+ case APPCOMMAND_SEND_MAIL:
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ case APPCOMMAND_MEDIA_STOP:
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ // We shouldn't consume the message always because if we don't handle
+ // the message, the sender (typically, utility of keyboard or mouse)
+ // may send other key messages which indicate well known shortcut key.
+ consumed = DispatchCommandEvent(appCommand);
+ break;
+
+ // Use content command for following commands:
+ case APPCOMMAND_COPY:
+ contentCommandMessage = eContentCommandCopy;
+ break;
+ case APPCOMMAND_CUT:
+ contentCommandMessage = eContentCommandCut;
+ break;
+ case APPCOMMAND_PASTE:
+ contentCommandMessage = eContentCommandPaste;
+ break;
+ case APPCOMMAND_REDO:
+ contentCommandMessage = eContentCommandRedo;
+ break;
+ case APPCOMMAND_UNDO:
+ contentCommandMessage = eContentCommandUndo;
+ break;
+ }
+
+ if (contentCommandMessage) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ WidgetContentCommandEvent contentCommandEvent(true, contentCommandMessage,
+ mWidget);
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...",
+ this, ToChar(contentCommandMessage)));
+ mWidget->DispatchWindowEvent(contentCommandEvent);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event",
+ this, ToChar(contentCommandMessage)));
+ consumed = true;
+
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget",
+ this, ToChar(contentCommandMessage)));
+ return true;
+ }
+ } else {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), doesn't dispatch "
+ "content "
+ "command event",
+ this));
+ }
+ }
+
+ // Dispatch a keyup event if the command is caused by pressing a key and
+ // the key isn't mapped to a virtual keycode.
+ if (dispatchKeyEvent && !mVirtualKeyCode) {
+ MOZ_ASSERT(!mWidget->Destroyed());
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keyup "
+ "event...",
+ this));
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching keyup "
+ "event...",
+ this));
+ // NOTE: Ignore if the keyup event is consumed because keyup event
+ // represents just a physical key event state change.
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
+ const_cast<NativeKey*>(this));
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event",
+ this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keyup event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ }
+
+ return consumed;
+}
+
+bool NativeKey::HandleKeyDownMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if (sDispatchedKeyOfAppCommand &&
+ sDispatchedKeyOfAppCommand == mOriginalVirtualKeyCode) {
+ // The multimedia key event has already been dispatch from
+ // HandleAppCommandMessage().
+ sDispatchedKeyOfAppCommand = 0;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event due to already dispatched from HandleAppCommandMessage(), ",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return true;
+ }
+
+ if (IsReservedBySystem()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event because the key combination is reserved by the system",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ if (sPendingHighSurrogate) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keydown "
+ "event because the key introduced only a high surrotate, so we "
+ "should wait the following low surrogate input",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyDownMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ if (RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ return false;
+ }
+
+ bool defaultPrevented = false;
+ if (mFakeCharMsgs ||
+ !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::HandleKeyDownMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext());
+
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyDownMessage(), initializing keydown "
+ "event...",
+ this));
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState);
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...",
+ this));
+ bool dispatched = mDispatcher->DispatchKeyboardEvent(
+ eKeyDown, keydownEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (!dispatched) {
+ // If the keydown event wasn't fired, there must be composition.
+ // we don't need to do anything anymore.
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because keydown event isn't dispatched actually",
+ this));
+ return false;
+ }
+ defaultPrevented = status == nsEventStatus_eConsumeNoDefault;
+
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), keydown event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatched keydown event, "
+ "dispatched=%s, defaultPrevented=%s",
+ this, GetBoolName(dispatched), GetBoolName(defaultPrevented)));
+
+ // If IMC wasn't associated to the window but is associated it now (i.e.,
+ // focus is moved from a non-editable editor to an editor by keydown
+ // event handler), WM_CHAR and WM_SYSCHAR shouldn't cause first character
+ // inputting if IME is opened. But then, we should redirect the native
+ // keydown message to IME.
+ // However, note that if focus has been already moved to another
+ // application, we shouldn't redirect the message to it because the keydown
+ // message is processed by us, so, nobody shouldn't process it.
+ HWND focusedWnd = ::GetFocus();
+ if (!defaultPrevented && !mFakeCharMsgs && focusedWnd && !isIMEEnabled &&
+ WinUtils::IsIMEEnabled(mWidget->GetInputContext())) {
+ RedirectedKeyDownMessageManager::RemoveNextCharMessage(focusedWnd);
+
+ INPUT keyinput;
+ keyinput.type = INPUT_KEYBOARD;
+ keyinput.ki.wVk = mOriginalVirtualKeyCode;
+ keyinput.ki.wScan = mScanCode;
+ keyinput.ki.dwFlags = KEYEVENTF_SCANCODE;
+ if (mIsExtended) {
+ keyinput.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+ }
+ keyinput.ki.time = 0;
+ keyinput.ki.dwExtraInfo = 0;
+
+ RedirectedKeyDownMessageManager::WillRedirect(mMsg, defaultPrevented);
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...",
+ this, ToString(mMsg).get()));
+
+ ::SendInput(1, &keyinput, sizeof(keyinput));
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirected %s", this,
+ ToString(mMsg).get()));
+
+ // Return here. We shouldn't dispatch keypress event for this WM_KEYDOWN.
+ // If it's needed, it will be dispatched after next (redirected)
+ // WM_KEYDOWN.
+ return true;
+ }
+ } else {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), received a redirected %s",
+ this, ToString(mMsg).get()));
+
+ defaultPrevented = RedirectedKeyDownMessageManager::DefaultPrevented();
+ // If this is redirected keydown message, we have dispatched the keydown
+ // event already.
+ if (aEventDispatched) {
+ *aEventDispatched = true;
+ }
+ }
+
+ RedirectedKeyDownMessageManager::Forget();
+
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ // If the key was processed by IME and didn't cause WM_(SYS)CHAR messages, we
+ // shouldn't dispatch keypress event.
+ if (mOriginalVirtualKeyCode == VK_PROCESSKEY &&
+ !IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because the key was already handled by IME, "
+ "defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (defaultPrevented) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because preceding keydown event was consumed",
+ this));
+ return true;
+ }
+
+ MOZ_ASSERT(!mCharMessageHasGone,
+ "If following char message was consumed by somebody, "
+ "keydown event should have been consumed before dispatch");
+
+ // If mCommittedCharsAndModifiers was initialized with following char
+ // messages, we should dispatch keypress events with its information.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events with retrieved char messages...",
+ this));
+ return DispatchKeyPressEventsWithRetrievedCharMessages();
+ }
+
+ // If we won't be getting a WM_CHAR, WM_SYSCHAR or WM_DEADCHAR, synthesize a
+ // keypress for almost all keys
+ if (NeedsToHandleWithoutFollowingCharMessages()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events...",
+ this));
+ return DispatchKeyPressEventsWithoutCharMessage();
+ }
+
+ // If WM_KEYDOWN of VK_PACKET isn't followed by WM_CHAR, we don't need to
+ // dispatch keypress events.
+ if (mVirtualKeyCode == VK_PACKET) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key is VK_PACKET and there are no char messages",
+ this));
+ return false;
+ }
+
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin() && mIsPrintableKey) {
+ // If this is simple KeyDown event but next message is not WM_CHAR,
+ // this event may not input text, so we should ignore this event.
+ // See bug 314130.
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key event is simple printable key's event but not "
+ "followed "
+ "by char messages",
+ this));
+ return false;
+ }
+
+ if (mIsDeadKey) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event "
+ "because the key is a dead key and not followed by char messages",
+ this));
+ return false;
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events due to no following char messages...",
+ this));
+ return DispatchKeyPressEventsWithoutCharMessage();
+}
+
+bool NativeKey::HandleCharMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsCharOrSysCharMessage(mMsg));
+ return HandleCharMessage(mMsg, aEventDispatched);
+}
+
+bool NativeKey::HandleCharMessage(const MSG& aCharMsg,
+ bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg));
+ MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message));
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ if ((IsCharOrSysCharMessage(mMsg) || IsEnterKeyPressCharMessage(mMsg)) &&
+ IsAnotherInstanceRemovingCharMessage()) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, does nothing because "
+ "the message should be handled in another instance removing this "
+ "message",
+ this));
+ // Consume this for now because it will be handled by another instance.
+ return true;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyPress event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because the key combination is reserved by the system",
+ this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::HandleCharMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ // When a control key is inputted by a key, it should be handled without
+ // WM_*CHAR messages at receiving WM_*KEYDOWN message. So, when we receive
+ // WM_*CHAR message directly, we see a control character here.
+ // Note that when the char is '\r', it means that the char message should
+ // cause "Enter" keypress event for inserting a line break.
+ if (IsControlCharMessage(aCharMsg) && !IsEnterKeyPressCharMessage(aCharMsg)) {
+ // In this case, we don't need to dispatch eKeyPress event because:
+ // 1. We're the only browser which dispatches "keypress" event for
+ // non-printable characters (Although, both Chrome and Edge dispatch
+ // "keypress" event for some keys accidentally. For example, "IntlRo"
+ // key with Ctrl of Japanese keyboard layout).
+ // 2. Currently, we may handle shortcut keys with "keydown" event if
+ // it's reserved or something. So, we shouldn't dispatch "keypress"
+ // event without it.
+ // Note that this does NOT mean we stop dispatching eKeyPress event for
+ // key presses causes a control character when Ctrl is pressed. In such
+ // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress
+ // instead of this method.
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), doesn't dispatch keypress "
+ "event because received a control character input without WM_KEYDOWN",
+ this));
+ return false;
+ }
+
+ // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without
+ // preceding WM_KEYDOWN, we should should dispatch composition
+ // events instead of eKeyPress because they are not caused by
+ // actual keyboard operation.
+
+ // First, handle normal text input or non-printable key case here.
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ if (IsEnterKeyPressCharMessage(aCharMsg)) {
+ keypressEvent.mKeyCode = NS_VK_RETURN;
+ } else {
+ keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
+ }
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::HandleCharMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::HandleCharMessage(), initializing keypress "
+ "event...",
+ this));
+
+ ModifierKeyState modKeyState(mModKeyState);
+ // When AltGr is pressed, both Alt and Ctrl are active. However, when they
+ // are active, TextEditor won't treat the keypress event as inputting a
+ // character. Therefore, when AltGr is pressed and the key tries to input
+ // a character, let's set them to false.
+ if (modKeyState.IsControl() && modKeyState.IsAlt() &&
+ IsPrintableCharMessage(aCharMsg)) {
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatching keypress event...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), keypress event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), dispatched keypress event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const {
+ MOZ_ASSERT(IsKeyUpMessage());
+
+ if (aEventDispatched) {
+ *aEventDispatched = false;
+ }
+
+ // If the key combinations is reserved by the system, we shouldn't dispatch
+ // eKeyUp event for it and passes the message to next wndproc.
+ if (IsReservedBySystem()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup "
+ "event because the key combination is reserved by the system",
+ this));
+ return false;
+ }
+
+ if (sPendingHighSurrogate) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup "
+ "event because the key introduced only a high surrotate, so we "
+ "should wait the following low surrogate input",
+ this));
+ return false;
+ }
+
+ // If the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::HandleKeyUpMessage(), WARNING, not handled due to "
+ "destroyed the widget",
+ this));
+ return false;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::HandleKeyUpMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...",
+ this));
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...",
+ this));
+ bool dispatched = mDispatcher->DispatchKeyboardEvent(
+ eKeyUp, keyupEvent, status, const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), keyup event caused "
+ "destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatched keyup event, "
+ "dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::NeedsToHandleWithoutFollowingCharMessages() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ // If the key combination is reserved by the system, the caller shouldn't
+ // do anything with following WM_*CHAR messages. So, let's return true here.
+ if (IsReservedBySystem()) {
+ return true;
+ }
+
+ // If the keydown message is generated for inputting some Unicode characters
+ // via SendInput() API, we need to handle it only with WM_*CHAR messages.
+ if (mVirtualKeyCode == VK_PACKET) {
+ return false;
+ }
+
+ // If following char message is for a control character, it should be handled
+ // without WM_CHAR message. This is typically Ctrl + [a-z].
+ if (mFollowingCharMsgs.Length() == 1 &&
+ IsControlCharMessage(mFollowingCharMsgs[0])) {
+ return true;
+ }
+
+ // If keydown message is followed by WM_CHAR or WM_SYSCHAR whose wParam isn't
+ // a control character, we should dispatch keypress event with the char
+ // message even with any modifier state.
+ if (IsFollowedByPrintableCharOrSysCharMessage()) {
+ return false;
+ }
+
+ // If any modifier keys which may cause printable keys becoming non-printable
+ // are not pressed, we don't need special handling for the key.
+ // Note that if the key does not produce a character with AltGr and when
+ // AltGr key is pressed, we don't need to dispatch eKeyPress event for it
+ // because AltGr shouldn't be used for a modifier for a shortcut without
+ // Ctrl, Alt or Win. That means that we should treat it in same path for
+ // Shift key.
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
+ !mModKeyState.IsWin()) {
+ return false;
+ }
+
+ // If the key event causes dead key event, we don't need to dispatch keypress
+ // event.
+ if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) {
+ return false;
+ }
+
+ // Even if the key is a printable key, it might cause non-printable character
+ // input with modifier key(s).
+ return mIsPrintableKey;
+}
+
+static nsCString GetResultOfInSendMessageEx() {
+ DWORD ret = ::InSendMessageEx(nullptr);
+ if (!ret) {
+ return "ISMEX_NOSEND"_ns;
+ }
+ nsCString result;
+ if (ret & ISMEX_CALLBACK) {
+ result = "ISMEX_CALLBACK";
+ }
+ if (ret & ISMEX_NOTIFY) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_NOTIFY";
+ }
+ if (ret & ISMEX_REPLIED) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_REPLIED";
+ }
+ if (ret & ISMEX_SEND) {
+ if (!result.IsEmpty()) {
+ result += " | ";
+ }
+ result += "ISMEX_SEND";
+ }
+ return result;
+}
+
+bool NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1,
+ const MSG& aCharMsg2) const {
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ static const LPARAM kScanCodeMask = 0x00FF0000;
+ return aCharMsg1.message == aCharMsg2.message &&
+ aCharMsg1.wParam == aCharMsg2.wParam &&
+ (aCharMsg1.lParam & ~kScanCodeMask) ==
+ (aCharMsg2.lParam & ~kScanCodeMask);
+}
+
+bool NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1,
+ const MSG& aKeyOrCharMsg2) const {
+ if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) ||
+ NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) {
+ return false;
+ }
+ return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) &&
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) ==
+ WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam);
+}
+
+bool NativeKey::GetFollowingCharMessage(MSG& aCharMsg) {
+ MOZ_ASSERT(IsKeyDownMessage());
+
+ aCharMsg.message = WM_NULL;
+
+ if (mFakeCharMsgs) {
+ for (size_t i = 0; i < mFakeCharMsgs->Length(); i++) {
+ FakeCharMsg& fakeCharMsg = mFakeCharMsgs->ElementAt(i);
+ if (fakeCharMsg.mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsg.GetCharMsg(mMsg.hwnd);
+ fakeCharMsg.mConsumed = true;
+ if (!IsCharMessage(charMsg)) {
+ return false;
+ }
+ aCharMsg = charMsg;
+ return true;
+ }
+ return false;
+ }
+
+ // If next key message is not char message, we should give up to find a
+ // related char message for the handling keydown event for now.
+ // Note that it's possible other applications may send other key message
+ // after we call TranslateMessage(). That may cause PeekMessage() failing
+ // to get char message for the handling keydown message.
+ MSG nextKeyMsg;
+ if (!WinUtils::PeekMessage(&nextKeyMsg, mMsg.hwnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) ||
+ !IsCharMessage(nextKeyMsg)) {
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), there are no char "
+ "messages",
+ this));
+ return false;
+ }
+ const MSG kFoundCharMsg = nextKeyMsg;
+
+ AutoRestore<MSG> saveLastRemovingMsg(mRemovingMsg);
+ mRemovingMsg = nextKeyMsg;
+
+ mReceivedMsg = sEmptyMSG;
+ AutoRestore<MSG> ensureToClearRecivedMsg(mReceivedMsg);
+
+ // On Metrofox, PeekMessage() sometimes returns WM_NULL even if we specify
+ // the message range. So, if it returns WM_NULL, we should retry to get
+ // the following char message it was found above.
+ for (uint32_t i = 0; i < 50; i++) {
+ MSG removedMsg, nextKeyMsgInAllWindows;
+ bool doCrash = false;
+ if (!WinUtils::PeekMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message,
+ nextKeyMsg.message, PM_REMOVE | PM_NOYIELD)) {
+ // We meets unexpected case. We should collect the message queue state
+ // and crash for reporting the bug.
+ doCrash = true;
+
+ // If another instance was created for the removing message during trying
+ // to remove a char message, the instance didn't handle it for preventing
+ // recursive handling. So, let's handle it in this instance.
+ if (!IsEmptyMSG(mReceivedMsg)) {
+ // If focus is moved to different window, we shouldn't handle it on
+ // the widget. Let's discard it for now.
+ if (mReceivedMsg.hwnd != nextKeyMsg.hwnd) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's for "
+ "different window, mReceivedMsg=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ // There might still exist char messages, the loop of calling
+ // this method should be continued.
+ aCharMsg.message = WM_NULL;
+ return true;
+ }
+ // Even if the received message is different from what we tried to
+ // remove from the queue, let's take the received message as a part of
+ // the result of this key sequence.
+ if (mReceivedMsg.message != nextKeyMsg.message ||
+ mReceivedMsg.wParam != nextKeyMsg.wParam ||
+ mReceivedMsg.lParam != nextKeyMsg.lParam) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, received a "
+ "char message during removing it from the queue, but it's "
+ "differnt from what trying to remove from the queue, "
+ "aCharMsg=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ } else {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to "
+ "retrieve next char message via another instance, aCharMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(mReceivedMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ }
+ aCharMsg = mReceivedMsg;
+ return true;
+ }
+
+ // The char message is redirected to different thread's window by focus
+ // move or something or just cancelled by external application.
+ if (!WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but it's already gone from all message "
+ "queues, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true; // XXX should return false in this case
+ }
+ // The next key message is redirected to different window created by our
+ // thread, we should do nothing because we must not have focus.
+ if (nextKeyMsgInAllWindows.hwnd != mMsg.hwnd) {
+ aCharMsg = nextKeyMsgInAllWindows;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but found in another message queue, "
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ return true;
+ }
+ // If next key message becomes non-char message, this key operation
+ // may have already been consumed or canceled.
+ if (!IsCharMessage(nextKeyMsgInAllWindows)) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes non-char "
+ "message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but different key message,
+ // we should treat current key operation is consumed or canceled and
+ // next char message should be handled as an orphan char message later.
+ if (!IsSamePhysicalKeyMessage(nextKeyMsgInAllWindows, kFoundCharMsg)) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message and next key message becomes differnt "
+ "key's "
+ "char message, nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, "
+ "kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ // If next key message is still a char message but the message is changed,
+ // we should retry to remove the new message with PeekMessage() again.
+ if (nextKeyMsgInAllWindows.message != nextKeyMsg.message) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message due to message change, let's retry to "
+ "remove the message with newly found char message, "
+ "nextKeyMsgInAllWindows=%s, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsgInAllWindows).get(),
+ ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ nextKeyMsg = nextKeyMsgInAllWindows;
+ continue;
+ }
+ // If there is still existing a char message caused by same physical key
+ // in the queue but PeekMessage(PM_REMOVE) failed to remove it from the
+ // queue, it might be possible that the odd keyboard layout or utility
+ // hooks only PeekMessage(PM_NOREMOVE) and GetMessage(). So, let's try
+ // remove the char message with GetMessage() again.
+ // FYI: The wParam might be different from the found message, but it's
+ // okay because we assume that odd keyboard layouts return actual
+ // inputting character at removing the char message.
+ if (WinUtils::GetMessage(&removedMsg, mMsg.hwnd, nextKeyMsg.message,
+ nextKeyMsg.message)) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but succeeded with GetMessage(), "
+ "removedMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(kFoundCharMsg).get()));
+ // Cancel to crash, but we need to check the removed message value.
+ doCrash = false;
+ }
+ // If we've already removed some WM_NULL messages from the queue and
+ // the found message has already gone from the queue, let's treat the key
+ // as inputting no characters and already consumed.
+ else if (i > 0) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, but removed %d WM_NULL messages",
+ this, i));
+ // If the key is a printable key or a control key but tried to input
+ // a character, mark mCharMessageHasGone true for handling the keydown
+ // event as inputting empty string.
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target "
+ "message to remove, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ }
+
+ if (doCrash) {
+ nsPrintfCString info(
+ "\nPeekMessage() failed to remove char message! "
+ "\nActive keyboard layout=0x%p (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nWM_NULL has been removed: %d, "
+ "\nNext key message in all windows: %s, "
+ "time=%ld, ",
+ KeyboardLayout::GetInstance()->GetLoadedLayout(),
+ KeyboardLayout::GetInstance()->GetLoadedLayoutName().get(),
+ ToString(mMsg).get(), GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get(), i,
+ ToString(nextKeyMsgInAllWindows).get(), nextKeyMsgInAllWindows.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MSG nextMsg;
+ if (WinUtils::PeekMessage(&nextMsg, 0, 0, 0, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext message in all windows: %s, time=%ld",
+ ToString(nextMsg).get(), nextMsg.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ "\nThere is no message in any window"_ns);
+ }
+
+ MOZ_CRASH("We lost the following char message");
+ }
+
+ // We're still not sure why ::PeekMessage() may return WM_NULL even with
+ // its first message and its last message are same message. However,
+ // at developing Metrofox, we met this case even with usual keyboard
+ // layouts. So, it might be possible in desktop application or it really
+ // occurs with some odd keyboard layouts which perhaps hook API.
+ if (removedMsg.message == WM_NULL) {
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message, instead, removed WM_NULL message, "
+ "removedMsg=%s",
+ this, ToString(removedMsg).get()));
+ // Check if there is the message which we're trying to remove.
+ MSG newNextKeyMsg;
+ if (!WinUtils::PeekMessage(&newNextKeyMsg, mMsg.hwnd, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ // If there is no key message, we should mark this keydown as consumed
+ // because the key operation may have already been handled or canceled.
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ if (!IsCharMessage(newNextKeyMsg)) {
+ // If next key message becomes a non-char message, we should mark this
+ // keydown as consumed because the key operation may have already been
+ // handled or canceled.
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, failed to "
+ "remove a char message because it's gone during removing it from "
+ "the queue, nextKeyMsg=%s, newNextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(nextKeyMsg).get(), ToString(newNextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ MOZ_ASSERT(!mCharMessageHasGone);
+ mFollowingCharMsgs.Clear();
+ mCharMessageHasGone = true;
+ return false;
+ }
+ MOZ_LOG(
+ gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), there is the message "
+ "which is being tried to be removed from the queue, trying again...",
+ this));
+ continue;
+ }
+
+ // Typically, this case occurs with WM_DEADCHAR. If the removed message's
+ // wParam becomes 0, that means that the key event shouldn't cause text
+ // input. So, let's ignore the strange char message.
+ if (removedMsg.message == nextKeyMsg.message && !removedMsg.wParam) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message's wParam is 0, "
+ "removedMsg=%s",
+ this, ToString(removedMsg).get()));
+ return false;
+ }
+
+ // This is normal case.
+ if (MayBeSameCharMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::GetFollowingCharMessage(), succeeded to retrieve "
+ "next char message, aCharMsg=%s",
+ this, ToString(aCharMsg).get()));
+ return true;
+ }
+
+ // Even if removed message is different char message from the found char
+ // message, when the scan code is same, we can assume that the message
+ // is overwritten by somebody who hooks API. See bug 1336028 comment 0 for
+ // the possible scenarios.
+ if (IsCharMessage(removedMsg) &&
+ IsSamePhysicalKeyMessage(removedMsg, nextKeyMsg)) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message except their scancode, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // When found message's wParam is 0 and its scancode is 0xFF, we may remove
+ // usual char message actually. In such case, we should use the removed
+ // char message.
+ if (IsCharMessage(removedMsg) && !nextKeyMsg.wParam &&
+ WinUtils::GetScanCode(nextKeyMsg.lParam) == 0xFF) {
+ aCharMsg = removedMsg;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::GetFollowingCharMessage(), WARNING, succeeded to "
+ "remove a char message, but the removed message was changed from "
+ "the found message but the found message was odd, so, ignoring the "
+ "odd found message and respecting the removed message, aCharMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(aCharMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ return true;
+ }
+
+ // NOTE: Although, we don't know when this case occurs, the scan code value
+ // in lParam may be changed from 0 to something. The changed value
+ // is different from the scan code of handling keydown message.
+ MOZ_LOG(
+ gKeyLog, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed message "
+ "is really different from what we have already found, removedMsg=%s, "
+ "nextKeyMsg=%s, kFoundCharMsg=%s",
+ this, ToString(removedMsg).get(), ToString(nextKeyMsg).get(),
+ ToString(kFoundCharMsg).get()));
+ nsPrintfCString info(
+ "\nPeekMessage() removed unexpcted char message! "
+ "\nActive keyboard layout=0x%p (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nRemoved message: %s, ",
+ KeyboardLayout::GetInstance()->GetLoadedLayout(),
+ KeyboardLayout::GetInstance()->GetLoadedLayoutName().get(),
+ ToString(mMsg).get(), GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get(), ToString(removedMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ // What's the next key message?
+ MSG nextKeyMsgAfter;
+ if (WinUtils::PeekMessage(&nextKeyMsgAfter, mMsg.hwnd, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info(
+ "\nNext key message after unexpected char message "
+ "removed: %s, ",
+ ToString(nextKeyMsgAfter).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ nsLiteralCString("\nThere is no key message after unexpected char "
+ "message removed, "));
+ }
+ // Another window has a key message?
+ if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST,
+ WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) {
+ nsPrintfCString info("\nNext key message in all windows: %s.",
+ ToString(nextKeyMsgInAllWindows).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ "\nThere is no key message in any windows."_ns);
+ }
+
+ MOZ_CRASH("PeekMessage() removed unexpected message");
+ }
+ MOZ_LOG(
+ gKeyLog, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages "
+ "are all WM_NULL, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ nsPrintfCString info(
+ "\nWe lost following char message! "
+ "\nActive keyboard layout=0x%p (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, \n"
+ "Found message: %s, removed a lot of WM_NULL",
+ KeyboardLayout::GetInstance()->GetLoadedLayout(),
+ KeyboardLayout::GetInstance()->GetLoadedLayoutName().get(),
+ ToString(mMsg).get(), GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ MOZ_CRASH("We lost the following char message");
+ return false;
+}
+
+void NativeKey::ComputeInputtingStringWithKeyboardLayout() {
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+
+ if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) ||
+ mCharMessageHasGone) {
+ mInputtingStringAndModifiers = mCommittedCharsAndModifiers;
+ } else {
+ mInputtingStringAndModifiers.Clear();
+ }
+ mShiftedString.Clear();
+ mUnshiftedString.Clear();
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+
+ // XXX How about when Win key is pressed?
+ if (!mModKeyState.IsControl() && !mModKeyState.IsAlt()) {
+ return;
+ }
+
+ // If user is inputting a Unicode character with typing Alt + Numpad
+ // keys, we shouldn't set alternative key codes because the key event
+ // shouldn't match with a mnemonic. However, we should set only
+ // mUnshiftedString for keeping traditional behavior at WM_SYSKEYDOWN.
+ // FYI: I guess that it's okay that mUnshiftedString stays empty. So,
+ // if its value breaks something, you must be able to just return here.
+ if (MaybeTypingUnicodeScalarValue()) {
+ if (!mCommittedCharsAndModifiers.IsEmpty()) {
+ MOZ_ASSERT(mMsg.message == WM_SYSKEYDOWN);
+ char16_t num = mCommittedCharsAndModifiers.CharAt(0);
+ MOZ_ASSERT(num >= '0' && num <= '9');
+ mUnshiftedString.Append(num, MODIFIER_NONE);
+ return;
+ }
+ // If user presses a function key without NumLock or 3rd party utility
+ // synthesizes a function key on numpad, we should handle it as-is because
+ // the user's intention may be performing `Alt` + foo.
+ MOZ_ASSERT(!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode));
+ return;
+ }
+
+ ModifierKeyState capsLockState(mModKeyState.GetModifiers() &
+ MODIFIER_CAPSLOCK);
+
+ mUnshiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+ capsLockState.Set(MODIFIER_SHIFT);
+ mShiftedString =
+ keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState);
+
+ // The current keyboard cannot input alphabets or numerics,
+ // we should append them for Shortcut/Access keys.
+ // E.g., for Cyrillic keyboard layout.
+ capsLockState.Unset(MODIFIER_SHIFT);
+ WidgetUtils::GetLatinCharCodeForKeyCode(
+ mDOMKeyCode, capsLockState.GetModifiers(), &mUnshiftedLatinChar,
+ &mShiftedLatinChar);
+
+ // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z].
+ if (mShiftedLatinChar) {
+ // If the produced characters of the key on current keyboard layout
+ // are same as computed Latin characters, we shouldn't append the
+ // Latin characters to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) &&
+ mShiftedLatinChar == mShiftedString.CharAt(0)) {
+ mShiftedLatinChar = mUnshiftedLatinChar = 0;
+ }
+ } else if (mUnshiftedLatinChar) {
+ // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce
+ // alphabet character. At that time, the character may be produced
+ // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT
+ // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without
+ // Shift key but with Shift key, it produces '%'.
+ // If the mUnshiftedLatinChar is produced by the key on current
+ // keyboard layout, we shouldn't append it to alternativeCharCode.
+ if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) ||
+ mUnshiftedLatinChar == mShiftedString.CharAt(0)) {
+ mUnshiftedLatinChar = 0;
+ }
+ }
+
+ if (!mModKeyState.IsControl()) {
+ return;
+ }
+
+ // If the mCharCode is not ASCII character, we should replace the
+ // mCharCode with ASCII character only when Ctrl is pressed.
+ // But don't replace the mCharCode when the mCharCode is not same as
+ // unmodified characters. In such case, Ctrl is sometimes used for a
+ // part of character inputting key combination like Shift.
+ uint32_t ch =
+ mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar;
+ if (!ch) {
+ return;
+ }
+ if (mInputtingStringAndModifiers.IsEmpty() ||
+ mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual(
+ mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) {
+ mInputtingStringAndModifiers.Clear();
+ mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers());
+ }
+}
+
+bool NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "FAILED due to BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "initializing keypress event...",
+ this));
+ ModifierKeyState modKeyState(mModKeyState);
+ if (mCanIgnoreModifierStateAtKeyPress && IsFollowedByPrintableCharMessage()) {
+ // If eKeyPress event should cause inputting text in focused editor,
+ // we need to remove Alt and Ctrl state.
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // We don't need to send char message here if there are two or more retrieved
+ // messages because we need to set each message to each eKeyPress event.
+ bool needsCallback = mFollowingCharMsgs.Length() > 1;
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatching keypress event(s)...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this), needsCallback);
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "keypress event(s) caused destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "dispatched keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+bool NativeKey::DispatchKeyPressEventsWithoutCharMessage() const {
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!mIsDeadKey || !mCommittedCharsAndModifiers.IsEmpty());
+ MOZ_ASSERT(!mWidget->Destroyed());
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "FAILED due "
+ "to BeginNativeInputTransaction() failure",
+ this));
+ return true;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ if (mInputtingStringAndModifiers.IsEmpty() && mShiftedString.IsEmpty() &&
+ mUnshiftedString.IsEmpty()) {
+ keypressEvent.mKeyCode = mDOMKeyCode;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "initializing "
+ "keypress event...",
+ this));
+ nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "dispatching "
+ "keypress event(s)...",
+ this));
+ bool dispatched = mDispatcher->MaybeDispatchKeypressEvents(
+ keypressEvent, status, const_cast<NativeKey*>(this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "keypress event(s) caused destroying the widget",
+ this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), dispatched "
+ "keypress event(s), dispatched=%s, consumed=%s",
+ this, GetBoolName(dispatched), GetBoolName(consumed)));
+ return consumed;
+}
+
+void NativeKey::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndex) {
+ // If it's an eKeyPress event and it's generated from retrieved char message,
+ // we need to set raw message information for plugins.
+ if (aKeyboardEvent.mMessage == eKeyPress &&
+ IsFollowedByPrintableCharOrSysCharMessage()) {
+ MOZ_RELEASE_ASSERT(aIndex < mCommittedCharsAndModifiers.Length());
+ uint32_t foundPrintableCharMessages = 0;
+ for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+ if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) {
+ // XXX Should we dispatch a plugin event for WM_*DEADCHAR messages and
+ // WM_CHAR with a control character here? But we're not sure
+ // how can we create such message queue (i.e., WM_CHAR or
+ // WM_SYSCHAR with a printable character and such message are
+ // generated by a keydown). So, let's ignore such case until
+ // we'd get some bug reports.
+ MOZ_LOG(gKeyLog, LogLevel::Warning,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, "
+ "ignoring %zuth message due to non-printable char message, %s",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ continue;
+ }
+ if (foundPrintableCharMessages++ == aIndex) {
+ // Found message which caused the eKeyPress event.
+ break;
+ }
+ }
+ // Set modifier state from mCommittedCharsAndModifiers because some of them
+ // might be different. For example, Shift key was pressed at inputting
+ // dead char but Shift key was released before inputting next character.
+ if (mCanIgnoreModifierStateAtKeyPress) {
+ ModifierKeyState modKeyState(mModKeyState);
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(mCommittedCharsAndModifiers.ModifiersAt(aIndex));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ }
+ size_t longestLength =
+ std::max(mInputtingStringAndModifiers.Length(),
+ std::max(mShiftedString.Length(), mUnshiftedString.Length()));
+ size_t skipUniChars = longestLength - mInputtingStringAndModifiers.Length();
+ size_t skipShiftedChars = longestLength - mShiftedString.Length();
+ size_t skipUnshiftedChars = longestLength - mUnshiftedString.Length();
+ if (aIndex >= longestLength) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), does nothing for %uth "
+ "%s event",
+ this, aIndex + 1, ToChar(aKeyboardEvent.mMessage)));
+ return;
+ }
+
+ // Check if aKeyboardEvent is the last event for a key press.
+ // So, if it's not an eKeyPress event, it's always the last event.
+ // Otherwise, check if the index is the last character of
+ // mCommittedCharsAndModifiers.
+ bool isLastIndex = aKeyboardEvent.mMessage != eKeyPress ||
+ mCommittedCharsAndModifiers.IsEmpty() ||
+ mCommittedCharsAndModifiers.Length() - 1 == aIndex;
+
+ nsTArray<AlternativeCharCode>& altArray =
+ aKeyboardEvent.mAlternativeCharCodes;
+
+ // Set charCode and adjust modifier state for every eKeyPress event.
+ // This is not necessary for the other keyboard events because the other
+ // keyboard events shouldn't have non-zero charCode value and should have
+ // current modifier state.
+ if (aKeyboardEvent.mMessage == eKeyPress && skipUniChars <= aIndex) {
+ // XXX Modifying modifier state of aKeyboardEvent is illegal, but no way
+ // to set different modifier state per keypress event except this
+ // hack. Note that ideally, dead key should cause composition events
+ // instead of keypress events, though.
+ if (aIndex - skipUniChars < mInputtingStringAndModifiers.Length()) {
+ ModifierKeyState modKeyState(mModKeyState);
+ // If key in combination with Alt and/or Ctrl produces a different
+ // character than without them then do not report these flags
+ // because it is separate keyboard layout shift state. If dead-key
+ // and base character does not produce a valid composite character
+ // then both produced dead-key character and following base
+ // character may have different modifier flags, too.
+ modKeyState.Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT |
+ MODIFIER_ALTGRAPH | MODIFIER_CAPSLOCK);
+ modKeyState.Set(
+ mInputtingStringAndModifiers.ModifiersAt(aIndex - skipUniChars));
+ modKeyState.InitInputEvent(aKeyboardEvent);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth modifier state to %s",
+ this, aIndex + 1, ToString(modKeyState).get()));
+ }
+ uint16_t uniChar =
+ mInputtingStringAndModifiers.CharAt(aIndex - skipUniChars);
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ aKeyboardEvent.SetCharCode(uniChar);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), "
+ "setting %uth charCode to %s",
+ this, aIndex + 1, GetCharacterCodeName(uniChar).get()));
+ }
+
+ // We need to append alterntaive charCode values:
+ // - if the event is eKeyPress, we need to append for the index because
+ // eKeyPress event is dispatched for every character inputted by a
+ // key press.
+ // - if the event is not eKeyPress, we need to append for all characters
+ // inputted by the key press because the other keyboard events (e.g.,
+ // eKeyDown are eKeyUp) are fired only once for a key press.
+ size_t count;
+ if (aKeyboardEvent.mMessage == eKeyPress) {
+ // Basically, append alternative charCode values only for the index.
+ count = 1;
+ // However, if it's the last eKeyPress event but different shift state
+ // can input longer string, the last eKeyPress event should have all
+ // remaining alternative charCode values.
+ if (isLastIndex) {
+ count = longestLength - aIndex;
+ }
+ } else {
+ count = longestLength;
+ }
+ for (size_t i = 0; i < count; ++i) {
+ uint16_t shiftedChar = 0, unshiftedChar = 0;
+ if (skipShiftedChars <= aIndex + i) {
+ shiftedChar = mShiftedString.CharAt(aIndex + i - skipShiftedChars);
+ }
+ if (skipUnshiftedChars <= aIndex + i) {
+ unshiftedChar = mUnshiftedString.CharAt(aIndex + i - skipUnshiftedChars);
+ }
+
+ if (shiftedChar || unshiftedChar) {
+ AlternativeCharCode chars(unshiftedChar, shiftedChar);
+ altArray.AppendElement(chars);
+ }
+
+ if (!isLastIndex) {
+ continue;
+ }
+
+ if (mUnshiftedLatinChar || mShiftedLatinChar) {
+ AlternativeCharCode chars(mUnshiftedLatinChar, mShiftedLatinChar);
+ altArray.AppendElement(chars);
+ }
+
+ // Typically, following virtual keycodes are used for a key which can
+ // input the character. However, these keycodes are also used for
+ // other keys on some keyboard layout. E.g., in spite of Shift+'1'
+ // inputs '+' on Thai keyboard layout, a key which is at '=/+'
+ // key on ANSI keyboard layout is VK_OEM_PLUS. Native applications
+ // handle it as '+' key if Ctrl key is pressed.
+ char16_t charForOEMKeyCode = 0;
+ switch (mVirtualKeyCode) {
+ case VK_OEM_PLUS:
+ charForOEMKeyCode = '+';
+ break;
+ case VK_OEM_COMMA:
+ charForOEMKeyCode = ',';
+ break;
+ case VK_OEM_MINUS:
+ charForOEMKeyCode = '-';
+ break;
+ case VK_OEM_PERIOD:
+ charForOEMKeyCode = '.';
+ break;
+ }
+ if (charForOEMKeyCode && charForOEMKeyCode != mUnshiftedString.CharAt(0) &&
+ charForOEMKeyCode != mShiftedString.CharAt(0) &&
+ charForOEMKeyCode != mUnshiftedLatinChar &&
+ charForOEMKeyCode != mShiftedLatinChar) {
+ AlternativeCharCode OEMChars(charForOEMKeyCode, charForOEMKeyCode);
+ altArray.AppendElement(OEMChars);
+ }
+ }
+}
+
+/*****************************************************************************
+ * mozilla::widget::KeyboardLayout
+ *****************************************************************************/
+
+KeyboardLayout* KeyboardLayout::sInstance = nullptr;
+StaticRefPtr<nsIUserIdleServiceInternal> KeyboardLayout::sIdleService;
+
+// static
+KeyboardLayout* KeyboardLayout::GetInstance() {
+ if (!sInstance) {
+ sInstance = new KeyboardLayout();
+ }
+ return sInstance;
+}
+
+// static
+void KeyboardLayout::Shutdown() {
+ delete sInstance;
+ sInstance = nullptr;
+ sIdleService = nullptr;
+}
+
+// static
+void KeyboardLayout::NotifyIdleServiceOfUserActivity() {
+ if (!sIdleService) {
+ sIdleService = nsCOMPtr<nsIUserIdleServiceInternal>(
+ do_GetService("@mozilla.org/widget/useridleservice;1"))
+ .forget();
+ if (NS_WARN_IF(!sIdleService)) {
+ return;
+ }
+ }
+ sIdleService->ResetIdleTimeOut(0);
+}
+
+KeyboardLayout::KeyboardLayout() {
+ mDeadKeyTableListHead = nullptr;
+ // A dead key sequence should be made from up to 5 keys. Therefore, 4 is
+ // enough and makes sense because the item is uint8_t.
+ // (Although, even if it's possible to be 6 keys or more in a sequence,
+ // this array will be re-allocated).
+ mActiveDeadKeys.SetCapacity(4);
+ mDeadKeyShiftStates.SetCapacity(4);
+
+ // If we put it off to load active keyboard layout when first needed, we need
+ // to load it at instanciation. That makes us save the cost of a call of
+ // GetKeyboardLayout() API.
+ if (StaticPrefs::ui_key_layout_load_when_first_needed()) {
+ OnLayoutChange(::GetKeyboardLayout(0));
+ }
+}
+
+KeyboardLayout::~KeyboardLayout() { ReleaseDeadKeyTables(); }
+
+bool KeyboardLayout::IsPrintableCharKey(uint8_t aVirtualKey) {
+ return GetKeyIndex(aVirtualKey) >= 0;
+}
+
+WORD KeyboardLayout::ComputeScanCodeForVirtualKeyCode(
+ uint8_t aVirtualKeyCode) const {
+ return static_cast<WORD>(::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC,
+ KeyboardLayout::GetLayout()));
+}
+
+bool KeyboardLayout::IsDeadKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const {
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+
+ // XXX KeyboardLayout class doesn't support unusual keyboard layout which
+ // maps some function keys as dead keys.
+ if (virtualKeyIndex < 0) {
+ return false;
+ }
+
+ return mVirtualKeys[virtualKeyIndex].IsDeadKey(
+ VirtualKey::ModifiersToShiftState(aModKeyState.GetModifiers()));
+}
+
+bool KeyboardLayout::IsSysKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const {
+ // If Alt key is not pressed, it's never a system key combination.
+ // Additionally, if Ctrl key is pressed, it's never a system key combination
+ // too.
+ // FYI: Windows logo key state won't affect if it's a system key.
+ if (!aModKeyState.IsAlt() || aModKeyState.IsControl()) {
+ return false;
+ }
+
+ int32_t virtualKeyIndex = GetKeyIndex(aVirtualKey);
+ if (virtualKeyIndex < 0) {
+ return true;
+ }
+
+ UniCharsAndModifiers inputCharsAndModifiers =
+ GetUniCharsAndModifiers(aVirtualKey, aModKeyState);
+ if (inputCharsAndModifiers.IsEmpty()) {
+ return true;
+ }
+
+ // If the Alt key state isn't consumed, that means that the key with Alt
+ // doesn't cause text input. So, the combination is a system key.
+ return !!(inputCharsAndModifiers.ModifiersAt(0) & MODIFIER_ALT);
+}
+
+void KeyboardLayout::InitNativeKey(NativeKey& aNativeKey) {
+ if (mIsPendingToRestoreKeyboardLayout) {
+ LoadLayout(::GetKeyboardLayout(0));
+ }
+
+ // If the aNativeKey is initialized with WM_CHAR, the key information
+ // should be discarded because mKeyValue should have the string to be
+ // inputted.
+ if (aNativeKey.mMsg.message == WM_CHAR) {
+ char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam);
+ // But don't set key value as printable key if the character is a control
+ // character such as 0x0D at pressing Enter key.
+ if (!NativeKey::IsControlChar(ch)) {
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ Modifiers modifiers =
+ aNativeKey.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
+ return;
+ }
+ }
+
+ // If the aNativeKey is in a sequence to input a Unicode character with
+ // Alt + numpad keys, we should just set the number as the inputting charcter.
+ // Note that we should compute the key value from the virtual key code
+ // because they may be mapped to alphabets, but they should be treated as
+ // Alt + [0-9] even by web apps.
+ // However, we shouldn't touch the key value if given virtual key code is
+ // not a printable key because it may be synthesized by 3rd party utility
+ // or just NumLock is unlocked and user tries to use shortcut key. In the
+ // latter case, we cannot solve the conflict issue with Alt+foo shortcut key
+ // and inputting a Unicode scalar value like reported to bug 1606655, though,
+ // I have no better idea. Perhaps, `Alt` shouldn't be used for shortcut key
+ // combination on Windows.
+ if (aNativeKey.MaybeTypingUnicodeScalarValue() &&
+ KeyboardLayout::IsPrintableCharKey(aNativeKey.mVirtualKeyCode)) {
+ // If the key code value is mapped to a Numpad key, let's compute the key
+ // value with it. This is same behavior as Chrome. In strictly speaking,
+ // I think that the else block's computation is better because it seems
+ // that Windows does not refer virtual key code value, but we should avoid
+ // web-compat issues.
+ char16_t num = '0';
+ if (aNativeKey.mVirtualKeyCode >= VK_NUMPAD0 &&
+ aNativeKey.mVirtualKeyCode <= VK_NUMPAD9) {
+ num = '0' + aNativeKey.mVirtualKeyCode - VK_NUMPAD0;
+ }
+ // Otherwise, let's use fake key value for making never match with
+ // mnemonic.
+ else {
+ switch (aNativeKey.mScanCode) {
+ case 0x0052: // Numpad0
+ num = '0';
+ break;
+ case 0x004F: // Numpad1
+ num = '1';
+ break;
+ case 0x0050: // Numpad2
+ num = '2';
+ break;
+ case 0x0051: // Numpad3
+ num = '3';
+ break;
+ case 0x004B: // Numpad4
+ num = '4';
+ break;
+ case 0x004C: // Numpad5
+ num = '5';
+ break;
+ case 0x004D: // Numpad6
+ num = '6';
+ break;
+ case 0x0047: // Numpad7
+ num = '7';
+ break;
+ case 0x0048: // Numpad8
+ num = '8';
+ break;
+ case 0x0049: // Numpad9
+ num = '9';
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "IsTypingUnicodeScalarValue() must have returned true for wrong "
+ "scancode");
+ break;
+ }
+ }
+ aNativeKey.mCommittedCharsAndModifiers.Append(num,
+ aNativeKey.GetModifiers());
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ return;
+ }
+
+ // When it's followed by non-dead char message(s) for printable character(s),
+ // aNativeKey should dispatch eKeyPress events for them rather than
+ // information from keyboard layout because respecting WM_(SYS)CHAR messages
+ // guarantees that we can always input characters which is expected by
+ // the user even if the user uses odd keyboard layout.
+ // Or, when it was followed by non-dead char message for a printable character
+ // but it's gone at removing the message from the queue, let's treat it
+ // as a key inputting empty string.
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() ||
+ aNativeKey.mCharMessageHasGone) {
+ MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg));
+ if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) {
+ // Initialize mCommittedCharsAndModifiers with following char messages.
+ aNativeKey.InitCommittedCharsAndModifiersWithFollowingCharMessages();
+ MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty());
+
+ // Currently, we are doing a ugly hack to keypress events to cause
+ // inputting character even if Ctrl or Alt key is pressed, that is, we
+ // remove Ctrl and Alt modifier state from keypress event. However, for
+ // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes
+ // keypress event whose ctrlKey is true. For preventing this problem,
+ // we should mark as not removable if Ctrl or Alt key does not cause
+ // changing inputting character.
+ if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) &&
+ (aNativeKey.IsControl() ^ aNativeKey.IsAlt())) {
+ ModifierKeyState state = aNativeKey.ModifierKeyStateRef();
+ state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ UniCharsAndModifiers charsWithoutModifier =
+ GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), state);
+ aNativeKey.mCanIgnoreModifierStateAtKeyPress =
+ !charsWithoutModifier.UniCharsEqual(
+ aNativeKey.mCommittedCharsAndModifiers);
+ }
+ } else {
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ }
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+
+ // If it's not in dead key sequence, we don't need to do anymore here.
+ if (!IsInDeadKeySequence()) {
+ return;
+ }
+
+ // If it's in dead key sequence and dead char is inputted as is, we need to
+ // set the previous modifier state which is stored when preceding dead key
+ // is pressed.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers.OverwriteModifiersIfBeginsWith(
+ deadChars);
+ // Finish the dead key sequence.
+ DeactivateDeadKeyState();
+ return;
+ }
+
+ // If it's a dead key, aNativeKey will be initialized by
+ // MaybeInitNativeKeyAsDeadKey().
+ if (MaybeInitNativeKeyAsDeadKey(aNativeKey)) {
+ return;
+ }
+
+ // If the key is not a usual printable key, KeyboardLayout class assume that
+ // it's not cause dead char nor printable char. Therefore, there are nothing
+ // to do here fore such keys (e.g., function keys).
+ // However, this should keep dead key state even if non-printable key is
+ // pressed during a dead key sequence.
+ if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) {
+ return;
+ }
+
+ MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET,
+ "At handling VK_PACKET, we shouldn't refer keyboard layout");
+ MOZ_ASSERT(
+ aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING,
+ "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING");
+
+ // If it's in dead key handling and the pressed key causes a composite
+ // character, aNativeKey will be initialized by
+ // MaybeInitNativeKeyWithCompositeChar().
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) {
+ return;
+ }
+
+ UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
+
+ // If the key press isn't related to any dead keys, initialize aNativeKey
+ // with the characters which should be caused by the key.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers = baseChars;
+ return;
+ }
+
+ // If the key doesn't cause a composite character with preceding dead key,
+ // initialize aNativeKey with the dead-key character followed by current
+ // key's character.
+ UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
+ aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+}
+
+bool KeyboardLayout::MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey) {
+ // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
+ if (!IsInDeadKeySequence() && !IsDeadKey(aNativeKey)) {
+ return false;
+ }
+
+ // When keydown message is followed by a dead char message, it should be
+ // initialized as dead key.
+ bool isDeadKeyDownEvent =
+ aNativeKey.IsKeyDownMessage() && aNativeKey.IsFollowedByDeadCharMessage();
+
+ // When keyup message is received, let's check if it's one of preceding
+ // dead keys because keydown message order and keyup message order may be
+ // different.
+ bool isDeadKeyUpEvent =
+ !aNativeKey.IsKeyDownMessage() &&
+ mActiveDeadKeys.Contains(aNativeKey.GenericVirtualKeyCode());
+
+ if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
+ ActivateDeadKeyState(aNativeKey);
+ // Any dead key events don't generate characters. So, a dead key should
+ // cause only keydown event and keyup event whose KeyboardEvent.key
+ // values are "Dead".
+ aNativeKey.mCommittedCharsAndModifiers.Clear();
+ aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ return true;
+ }
+
+ // At keydown message handling, we need to forget the first dead key
+ // because there is no guarantee coming WM_KEYUP for the second dead
+ // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing
+ // another dead key before releasing current key. Therefore, we can
+ // set only a character for current key for keyup event.
+ if (!IsInDeadKeySequence()) {
+ aNativeKey.mCommittedCharsAndModifiers =
+ GetUniCharsAndModifiers(aNativeKey);
+ return true;
+ }
+
+ // When non-printable key event comes during a dead key sequence, that must
+ // be a modifier key event. So, such events shouldn't be handled as a part
+ // of the dead key sequence.
+ if (!IsDeadKey(aNativeKey)) {
+ return false;
+ }
+
+ // FYI: Following code may run when the user doesn't input text actually
+ // but the key sequence is a dead key sequence. For example,
+ // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this
+ // complicated code for now because this runs really rarely.
+
+ // Dead key followed by another dead key may cause a composed character
+ // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
+ if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) {
+ return true;
+ }
+
+ // Otherwise, dead key followed by another dead key causes inputting both
+ // character.
+ UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
+ UniCharsAndModifiers newChars = GetUniCharsAndModifiers(aNativeKey);
+ // But keypress events should be fired for each committed character.
+ aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars;
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+bool KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
+ NativeKey& aNativeKey) {
+ if (!IsInDeadKeySequence()) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
+ return false;
+ }
+
+ UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey);
+ if (baseChars.IsEmpty() || !baseChars.CharAt(0)) {
+ return false;
+ }
+
+ char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0));
+ if (!compositeChar) {
+ return false;
+ }
+
+ // Active dead-key and base character does produce exactly one composite
+ // character.
+ aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar,
+ baseChars.ModifiersAt(0));
+ if (aNativeKey.IsKeyDownMessage()) {
+ DeactivateDeadKeyState();
+ }
+ return true;
+}
+
+UniCharsAndModifiers KeyboardLayout::GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const {
+ UniCharsAndModifiers result;
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return result;
+ }
+ return mVirtualKeys[key].GetUniChars(aShiftState);
+}
+
+UniCharsAndModifiers KeyboardLayout::GetDeadUniCharsAndModifiers() const {
+ MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length());
+
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return UniCharsAndModifiers();
+ }
+
+ UniCharsAndModifiers result;
+ for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) {
+ result +=
+ GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]);
+ }
+ return result;
+}
+
+char16_t KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const {
+ if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
+ return 0;
+ }
+ // XXX Currently, we don't support computing a composite character with
+ // two or more dead keys since it needs big table for supporting
+ // long chained dead keys. However, this should be a minor bug
+ // because this runs only when the latest keydown event does not cause
+ // WM_(SYS)CHAR messages. So, when user wants to input a character,
+ // this path never runs.
+ if (mActiveDeadKeys.Length() > 1) {
+ return 0;
+ }
+ int32_t key = GetKeyIndex(mActiveDeadKeys[0]);
+ if (key < 0) {
+ return 0;
+ }
+ return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar);
+}
+
+static bool IsValidKeyboardLayoutsChild(const nsAString& aChildName) {
+ if (aChildName.Length() != 8) {
+ return false;
+ }
+ for (size_t i = 0; i < aChildName.Length(); i++) {
+ if ((aChildName[i] >= '0' && aChildName[i] <= '9') ||
+ (aChildName[i] >= 'a' && aChildName[i] <= 'f') ||
+ (aChildName[i] >= 'A' && aChildName[i] <= 'F')) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+// static
+nsCString KeyboardLayout::GetLayoutName(HKL aLayout) {
+ constexpr auto kKeyboardLayouts =
+ u"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\"_ns;
+ uint16_t language = reinterpret_cast<uintptr_t>(aLayout) & 0xFFFF;
+ uint16_t layout = (reinterpret_cast<uintptr_t>(aLayout) >> 16) & 0xFFFF;
+ // If the layout is less than 0xA000XXXX (normal keyboard layout for the
+ // language) or 0xEYYYXXXX (IMM-IME), we can retrieve its name simply.
+ if (layout < 0xA000 || (layout & 0xF000) == 0xE000) {
+ nsAutoString key(kKeyboardLayouts);
+ key.AppendPrintf("%08" PRIXPTR, layout < 0xA000
+ ? layout
+ : reinterpret_cast<uintptr_t>(aLayout));
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinRegistry::GetString(
+ HKEY_LOCAL_MACHINE, key, u"Layout Text"_ns, buf,
+ WinRegistry::kLegacyWinUtilsStringFlags))) {
+ return "No name or too long name"_ns;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+
+ if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+ nsCString result;
+ result.AppendPrintf("Odd HKL: 0x%08" PRIXPTR,
+ reinterpret_cast<uintptr_t>(aLayout));
+ return result;
+ }
+
+ // Otherwise, we need to walk the registry under "Keyboard Layouts".
+ WinRegistry::Key regKey(HKEY_LOCAL_MACHINE, kKeyboardLayouts,
+ WinRegistry::KeyMode::Read);
+ if (NS_WARN_IF(!regKey)) {
+ return ""_ns;
+ }
+ uint32_t childCount = regKey.GetChildCount();
+ if (NS_WARN_IF(!childCount)) {
+ return ""_ns;
+ }
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString childName;
+ if (NS_WARN_IF(!regKey.GetChildName(i, childName)) ||
+ !IsValidKeyboardLayoutsChild(childName)) {
+ continue;
+ }
+ nsresult rv = NS_OK;
+ uint32_t childNum = static_cast<uint32_t>(childName.ToInteger64(&rv, 16));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ // Ignore normal keyboard layouts for each language.
+ if (childNum <= 0xFFFF) {
+ continue;
+ }
+ // If it doesn't start with 'A' nor 'a', language should be matched.
+ if ((childNum & 0xFFFF) != language &&
+ (childNum & 0xF0000000) != 0xA0000000) {
+ continue;
+ }
+ // Then, the child should have "Layout Id" which is "YYY" of 0xFYYYXXXX.
+ nsAutoString key(kKeyboardLayouts);
+ key += childName;
+ WinRegistry::Key regKey(HKEY_LOCAL_MACHINE, key,
+ WinRegistry::KeyMode::QueryValue);
+ if (NS_WARN_IF(!regKey)) {
+ continue;
+ }
+ wchar_t buf[256];
+ if (NS_WARN_IF(!regKey.GetValueAsString(
+ u"Layout Id"_ns, buf, WinRegistry::kLegacyWinUtilsStringFlags))) {
+ continue;
+ }
+ uint16_t layoutId = wcstol(buf, nullptr, 16);
+ if (layoutId != (layout & 0x0FFF)) {
+ continue;
+ }
+ if (NS_WARN_IF(!regKey.GetValueAsString(
+ u"Layout Text"_ns, buf, WinRegistry::kLegacyWinUtilsStringFlags))) {
+ continue;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+ return ""_ns;
+}
+
+void KeyboardLayout::LoadLayout(HKL aLayout) {
+ mIsPendingToRestoreKeyboardLayout = false;
+
+ if (mKeyboardLayout == aLayout) {
+ return;
+ }
+
+ mKeyboardLayout = aLayout;
+ mHasAltGr = false;
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("KeyboardLayout::LoadLayout(aLayout=0x%p (%s))", aLayout,
+ GetLayoutName(aLayout).get()));
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ BYTE originalKbdState[256];
+ // Bitfield with all shift states that have at least one dead-key.
+ uint16_t shiftStatesWithDeadKeys = 0;
+ // Bitfield with all shift states that produce any possible dead-key base
+ // characters.
+ uint16_t shiftStatesWithBaseChars = 0;
+
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+
+ ReleaseDeadKeyTables();
+
+ ::GetKeyboardState(originalKbdState);
+
+ // For each shift state gather all printable characters that are produced
+ // for normal case when no any dead-key is active.
+
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ VirtualKey::FillKbdState(kbdState, shiftState);
+ bool isAltGr = VirtualKey::IsAltGrIndex(shiftState);
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki < 0) {
+ continue;
+ }
+ NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index");
+ char16_t uniChars[5];
+ int32_t ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars,
+ ArrayLength(uniChars), 0, mKeyboardLayout);
+ // dead-key
+ if (ret < 0) {
+ shiftStatesWithDeadKeys |= (1 << shiftState);
+ // Repeat dead-key to deactivate it and get its character
+ // representation.
+ char16_t deadChar[2];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar,
+ ArrayLength(deadChar), 0, mKeyboardLayout);
+ NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character");
+ mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]);
+
+ MOZ_LOG(gKeyLog, LogLevel::Verbose,
+ (" %s (%d): DeadChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeNames(deadChar, 1).get(), ret));
+ } else {
+ if (ret == 1) {
+ // dead-key can pair only with exactly one base character.
+ shiftStatesWithBaseChars |= (1 << shiftState);
+ }
+ mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret);
+ MOZ_LOG(gKeyLog, LogLevel::Verbose,
+ (" %s (%d): NormalChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeNames(uniChars, ret).get(), ret));
+ }
+
+ // If the key inputs at least one character with AltGr modifier,
+ // check if AltGr changes inputting character. If it does, mark
+ // this keyboard layout has AltGr modifier actually.
+ if (!mHasAltGr && ret > 0 && isAltGr &&
+ mVirtualKeys[vki].IsChangedByAltGr(shiftState)) {
+ mHasAltGr = true;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" Found a key (%s) changed by AltGr: %s -> %s (%s) (ret=%d)",
+ kVirtualKeyName[virtualKey],
+ GetCharacterCodeNames(
+ mVirtualKeys[vki].GetNativeUniChars(
+ shiftState - VirtualKey::ShiftStateIndex::eAltGr))
+ .get(),
+ GetCharacterCodeNames(
+ mVirtualKeys[vki].GetNativeUniChars(shiftState))
+ .get(),
+ GetShiftStateName(shiftState).get(), ret));
+ }
+ }
+ }
+
+ // Now process each dead-key to find all its base characters and resulting
+ // composite characters.
+ for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(shiftStatesWithDeadKeys & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) {
+ AutoTArray<DeadKeyEntry, 256> deadKeyArray;
+ uint32_t n = GetDeadKeyCombinations(
+ virtualKey, kbdState, shiftStatesWithBaseChars, deadKeyArray);
+ const DeadKeyTable* dkt =
+ mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray.Elements(), n);
+ if (!dkt) {
+ dkt = AddDeadKeyTable(deadKeyArray.Elements(), n);
+ }
+ mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt);
+ }
+ }
+ }
+
+ ::SetKeyboardState(originalKbdState);
+
+ if (MOZ_LOG_TEST(gKeyLog, LogLevel::Verbose)) {
+ static const UINT kExtendedScanCode[] = {0x0000, 0xE000};
+ static const UINT kMapType = MAPVK_VSC_TO_VK_EX;
+ MOZ_LOG(gKeyLog, LogLevel::Verbose,
+ ("Logging virtual keycode values for scancode (0x%p)...",
+ mKeyboardLayout));
+ for (uint32_t i = 0; i < ArrayLength(kExtendedScanCode); i++) {
+ for (uint32_t j = 1; j <= 0xFF; j++) {
+ UINT scanCode = kExtendedScanCode[i] + j;
+ UINT virtualKeyCode =
+ ::MapVirtualKeyEx(scanCode, kMapType, mKeyboardLayout);
+ MOZ_LOG(gKeyLog, LogLevel::Verbose,
+ ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
+ }
+ }
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" AltGr key is %s in %s", mHasAltGr ? "found" : "not found",
+ GetLayoutName(aLayout).get()));
+}
+
+inline int32_t KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey) {
+ // Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed
+ // to produce visible representation:
+ // 0x20 - VK_SPACE ' '
+ // 0x30..0x39 '0'..'9'
+ // 0x41..0x5A 'A'..'Z'
+ // 0x60..0x69 '0'..'9' on numpad
+ // 0x6A - VK_MULTIPLY '*' on numpad
+ // 0x6B - VK_ADD '+' on numpad
+ // 0x6D - VK_SUBTRACT '-' on numpad
+ // 0x6E - VK_DECIMAL '.' on numpad
+ // 0x6F - VK_DIVIDE '/' on numpad
+ // 0x6E - VK_DECIMAL '.'
+ // 0xBA - VK_OEM_1 ';:' for US
+ // 0xBB - VK_OEM_PLUS '+' any country
+ // 0xBC - VK_OEM_COMMA ',' any country
+ // 0xBD - VK_OEM_MINUS '-' any country
+ // 0xBE - VK_OEM_PERIOD '.' any country
+ // 0xBF - VK_OEM_2 '/?' for US
+ // 0xC0 - VK_OEM_3 '`~' for US
+ // 0xC1 - VK_ABNT_C1 '/?' for Brazilian
+ // 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac)
+ // 0xDB - VK_OEM_4 '[{' for US
+ // 0xDC - VK_OEM_5 '\|' for US
+ // 0xDD - VK_OEM_6 ']}' for US
+ // 0xDE - VK_OEM_7 ''"' for US
+ // 0xDF - VK_OEM_8
+ // 0xE1 - no name
+ // 0xE2 - VK_OEM_102 '\_' for JIS
+ // 0xE3 - no name
+ // 0xE4 - no name
+
+ static const int8_t xlat[256] = {
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ //-----------------------------------------------------------------------
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10
+ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30
+ -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0
+ 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0
+ -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0
+ };
+
+ return xlat[aVirtualKey];
+}
+
+const DeadKeyTable* KeyboardLayout::AddDeadKeyTable(
+ const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) {
+ DeadKeyTableListEntry* next = mDeadKeyTableListHead;
+
+ const size_t bytes = offsetof(DeadKeyTableListEntry, data) +
+ DeadKeyTable::SizeInBytes(aEntries);
+ uint8_t* p = new uint8_t[bytes];
+
+ mDeadKeyTableListHead = reinterpret_cast<DeadKeyTableListEntry*>(p);
+ mDeadKeyTableListHead->next = next;
+
+ DeadKeyTable* dkt =
+ reinterpret_cast<DeadKeyTable*>(mDeadKeyTableListHead->data);
+
+ dkt->Init(aDeadKeyArray, aEntries);
+
+ return dkt;
+}
+
+void KeyboardLayout::ReleaseDeadKeyTables() {
+ while (mDeadKeyTableListHead) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(mDeadKeyTableListHead);
+ mDeadKeyTableListHead = mDeadKeyTableListHead->next;
+
+ delete[] p;
+ }
+}
+
+bool KeyboardLayout::EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState) {
+ int32_t ret;
+ do {
+ char16_t dummyChars[5];
+ ret =
+ ::ToUnicodeEx(aDeadKey, 0, (PBYTE)aDeadKeyKbdState, (LPWSTR)dummyChars,
+ ArrayLength(dummyChars), 0, mKeyboardLayout);
+ // returned values:
+ // <0 - Dead key state is active. The keyboard driver will wait for next
+ // character.
+ // 1 - Previous pressed key was a valid base character that produced
+ // exactly one composite character.
+ // >1 - Previous pressed key does not produce any composite characters.
+ // Return dead-key character followed by base character(s).
+ } while ((ret < 0) != aIsActive);
+
+ return (ret < 0);
+}
+
+void KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey) {
+ // Dead-key state should be activated at keydown.
+ if (!aNativeKey.IsKeyDownMessage()) {
+ return;
+ }
+
+ mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
+ mDeadKeyShiftStates.AppendElement(aNativeKey.GetShiftState());
+}
+
+void KeyboardLayout::DeactivateDeadKeyState() {
+ if (mActiveDeadKeys.IsEmpty()) {
+ return;
+ }
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ // Assume that the last dead key can finish dead key sequence.
+ VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement());
+ EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState);
+ mActiveDeadKeys.Clear();
+ mDeadKeyShiftStates.Clear();
+}
+
+bool KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar,
+ char16_t aCompositeChar,
+ nsTArray<DeadKeyEntry>& aDeadKeyArray) {
+ auto dke = DeadKeyEntry(aBaseChar, aCompositeChar);
+ for (uint32_t index = 0; index < aDeadKeyArray.Length(); index++) {
+ if (aDeadKeyArray[index] == dke) {
+ return false;
+ }
+ }
+
+ aDeadKeyArray.AppendElement(dke);
+
+ return true;
+}
+
+uint32_t KeyboardLayout::GetDeadKeyCombinations(
+ uint8_t aDeadKey, const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars, nsTArray<DeadKeyEntry>& aDeadKeyArray) {
+ bool deadKeyActive = false;
+ uint32_t entries = 0;
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ for (uint32_t shiftState = 0; shiftState < 16; shiftState++) {
+ if (!(aShiftStatesWithBaseChars & (1 << shiftState))) {
+ continue;
+ }
+
+ VirtualKey::FillKbdState(kbdState, shiftState);
+
+ for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) {
+ int32_t vki = GetKeyIndex(virtualKey);
+ // Dead-key can pair only with such key that produces exactly one base
+ // character.
+ if (vki >= 0 &&
+ mVirtualKeys[vki].GetNativeUniChars(shiftState).Length() == 1) {
+ // Ensure dead-key is in active state, when it swallows entered
+ // character and waits for the next pressed key.
+ if (!deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(true, aDeadKey, aDeadKeyKbdState);
+ }
+
+ // Depending on the character the followed the dead-key, the keyboard
+ // driver can produce one composite character, or a dead-key character
+ // followed by a second character.
+ char16_t compositeChars[5];
+ int32_t ret =
+ ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)compositeChars,
+ ArrayLength(compositeChars), 0, mKeyboardLayout);
+ switch (ret) {
+ case 0:
+ // This key combination does not produce any characters. The
+ // dead-key is still in active state.
+ break;
+ case 1: {
+ char16_t baseChars[5];
+ ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)baseChars,
+ ArrayLength(baseChars), 0, mKeyboardLayout);
+ if (entries < aDeadKeyArray.Capacity()) {
+ switch (ret) {
+ case 1:
+ // Exactly one composite character produced. Now, when
+ // dead-key is not active, repeat the last character one more
+ // time to determine the base character.
+ if (AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray)) {
+ entries++;
+ }
+ deadKeyActive = false;
+ break;
+ case -1: {
+ // If pressing another dead-key produces different character,
+ // we should register the dead-key entry with first character
+ // produced by current key.
+
+ // First inactivate the dead-key state completely.
+ deadKeyActive =
+ EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ if (NS_WARN_IF(deadKeyActive)) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ (" failed to deactivating the dead-key state..."));
+ break;
+ }
+ for (int32_t i = 0; i < 5; ++i) {
+ ret = ::ToUnicodeEx(
+ virtualKey, 0, kbdState, (LPWSTR)baseChars,
+ ArrayLength(baseChars), 0, mKeyboardLayout);
+ if (ret >= 0) {
+ break;
+ }
+ }
+ if (ret > 0 &&
+ AddDeadKeyEntry(baseChars[0], compositeChars[0],
+ aDeadKeyArray)) {
+ entries++;
+ }
+ // Inactivate dead-key state for current virtual keycode.
+ EnsureDeadKeyActive(false, virtualKey, kbdState);
+ break;
+ }
+ default:
+ NS_WARNING("File a bug for this dead-key handling!");
+ deadKeyActive = false;
+ break;
+ }
+ }
+ MOZ_LOG(
+ gKeyLog, LogLevel::Verbose,
+ (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ GetCharacterCodeNames(compositeChars, 1).get(),
+ ret <= 0
+ ? "''"
+ : GetCharacterCodeNames(baseChars, std::min(ret, 5)).get(),
+ ret));
+ break;
+ }
+ default:
+ // 1. Unexpected dead-key. Dead-key chaining is not supported.
+ // 2. More than one character generated. This is not a valid
+ // dead-key and base character combination.
+ deadKeyActive = false;
+ MOZ_LOG(
+ gKeyLog, LogLevel::Verbose,
+ (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ ret <= 0
+ ? "''"
+ : GetCharacterCodeNames(compositeChars, std::min(ret, 5))
+ .get(),
+ ret));
+ break;
+ }
+ }
+ }
+ }
+
+ if (deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ }
+
+ aDeadKeyArray.Sort();
+
+ return entries;
+}
+
+uint32_t KeyboardLayout::ConvertNativeKeyCodeToDOMKeyCode(
+ UINT aNativeKeyCode) const {
+ // Alphabet or Numeric or Numpad or Function keys
+ if ((aNativeKeyCode >= 0x30 && aNativeKeyCode <= 0x39) ||
+ (aNativeKeyCode >= 0x41 && aNativeKeyCode <= 0x5A) ||
+ (aNativeKeyCode >= 0x60 && aNativeKeyCode <= 0x87)) {
+ return static_cast<uint32_t>(aNativeKeyCode);
+ }
+ switch (aNativeKeyCode) {
+ // Following keycodes are same as our DOM keycodes
+ case VK_CANCEL:
+ case VK_BACK:
+ case VK_TAB:
+ case VK_CLEAR:
+ case VK_RETURN:
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU: // Alt
+ case VK_PAUSE:
+ case VK_CAPITAL: // CAPS LOCK
+ case VK_KANA: // same as VK_HANGUL
+ case VK_JUNJA:
+ case VK_FINAL:
+ case VK_HANJA: // same as VK_KANJI
+ case VK_ESCAPE:
+ case VK_CONVERT:
+ case VK_NONCONVERT:
+ case VK_ACCEPT:
+ case VK_MODECHANGE:
+ case VK_SPACE:
+ case VK_PRIOR: // PAGE UP
+ case VK_NEXT: // PAGE DOWN
+ case VK_END:
+ case VK_HOME:
+ case VK_LEFT:
+ case VK_UP:
+ case VK_RIGHT:
+ case VK_DOWN:
+ case VK_SELECT:
+ case VK_PRINT:
+ case VK_EXECUTE:
+ case VK_SNAPSHOT:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_APPS: // Context Menu
+ case VK_SLEEP:
+ case VK_NUMLOCK:
+ case VK_SCROLL: // SCROLL LOCK
+ case VK_ATTN: // Attension key of IBM midrange computers, e.g., AS/400
+ case VK_CRSEL: // Cursor Selection
+ case VK_EXSEL: // Extend Selection
+ case VK_EREOF: // Erase EOF key of IBM 3270 keyboard layout
+ case VK_PLAY:
+ case VK_ZOOM:
+ case VK_PA1: // PA1 key of IBM 3270 keyboard layout
+ return uint32_t(aNativeKeyCode);
+
+ case VK_HELP:
+ return NS_VK_HELP;
+
+ // Windows key should be mapped to a Win keycode
+ // They should be able to be distinguished by DOM3 KeyboardEvent.location
+ case VK_LWIN:
+ case VK_RWIN:
+ return NS_VK_WIN;
+
+ case VK_VOLUME_MUTE:
+ return NS_VK_VOLUME_MUTE;
+ case VK_VOLUME_DOWN:
+ return NS_VK_VOLUME_DOWN;
+ case VK_VOLUME_UP:
+ return NS_VK_VOLUME_UP;
+
+ case VK_LSHIFT:
+ case VK_RSHIFT:
+ return NS_VK_SHIFT;
+
+ case VK_LCONTROL:
+ case VK_RCONTROL:
+ return NS_VK_CONTROL;
+
+ // Note that even if the key is AltGr, we should return NS_VK_ALT for
+ // compatibility with both older Gecko and the other browsers.
+ case VK_LMENU:
+ case VK_RMENU:
+ return NS_VK_ALT;
+
+ // Following keycodes are not defined in our DOM keycodes.
+ case VK_BROWSER_BACK:
+ case VK_BROWSER_FORWARD:
+ case VK_BROWSER_REFRESH:
+ case VK_BROWSER_STOP:
+ case VK_BROWSER_SEARCH:
+ case VK_BROWSER_FAVORITES:
+ case VK_BROWSER_HOME:
+ case VK_MEDIA_NEXT_TRACK:
+ case VK_MEDIA_PREV_TRACK:
+ case VK_MEDIA_STOP:
+ case VK_MEDIA_PLAY_PAUSE:
+ case VK_LAUNCH_MAIL:
+ case VK_LAUNCH_MEDIA_SELECT:
+ case VK_LAUNCH_APP1:
+ case VK_LAUNCH_APP2:
+ return 0;
+
+ // Following OEM specific virtual keycodes should pass through DOM keyCode
+ // for compatibility with the other browsers on Windows.
+
+ // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS.
+ case VK_OEM_FJ_JISHO:
+ case VK_OEM_FJ_MASSHOU:
+ case VK_OEM_FJ_TOUROKU:
+ case VK_OEM_FJ_LOYA:
+ case VK_OEM_FJ_ROYA:
+ // Not sure what means "ICO".
+ case VK_ICO_HELP:
+ case VK_ICO_00:
+ case VK_ICO_CLEAR:
+ // Following OEM specific virtual keycodes are defined for Nokia/Ericsson.
+ case VK_OEM_RESET:
+ case VK_OEM_JUMP:
+ case VK_OEM_PA1:
+ case VK_OEM_PA2:
+ case VK_OEM_PA3:
+ case VK_OEM_WSCTRL:
+ case VK_OEM_CUSEL:
+ case VK_OEM_ATTN:
+ case VK_OEM_FINISH:
+ case VK_OEM_COPY:
+ case VK_OEM_AUTO:
+ case VK_OEM_ENLW:
+ case VK_OEM_BACKTAB:
+ // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though
+ // DOM keyCode like other OEM specific virtual keycodes.
+ case VK_OEM_CLEAR:
+ return uint32_t(aNativeKeyCode);
+
+ // 0xE1 is an OEM specific virtual keycode. However, the value is already
+ // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode
+ // cannot pass through DOM keyCode.
+ case 0xE1:
+ return 0;
+
+ // Following keycodes are OEM keys which are keycodes for non-alphabet and
+ // non-numeric keys, we should compute each keycode of them from unshifted
+ // character which is inputted by each key. But if the unshifted character
+ // is not an ASCII character but shifted character is an ASCII character,
+ // we should refer it.
+ case VK_OEM_1:
+ case VK_OEM_PLUS:
+ case VK_OEM_COMMA:
+ case VK_OEM_MINUS:
+ case VK_OEM_PERIOD:
+ case VK_OEM_2:
+ case VK_OEM_3:
+ case VK_OEM_4:
+ case VK_OEM_5:
+ case VK_OEM_6:
+ case VK_OEM_7:
+ case VK_OEM_8:
+ case VK_OEM_102:
+ case VK_ABNT_C1: {
+ NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode),
+ "The key must be printable");
+ ModifierKeyState modKeyState(0);
+ UniCharsAndModifiers uniChars =
+ GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' ||
+ uniChars.CharAt(0) > 0x7F) {
+ modKeyState.Set(MODIFIER_SHIFT);
+ uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState);
+ if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' ||
+ uniChars.CharAt(0) > 0x7F) {
+ // In this case, we've returned 0 in this case for long time because
+ // we decided that we should avoid setting same keyCode value to 2 or
+ // more keys since active keyboard layout may have a key to input the
+ // punctuation with different key. However, setting keyCode to 0
+ // makes some web applications which are aware of neither
+ // KeyboardEvent.key nor KeyboardEvent.code not work with Firefox
+ // when user selects non-ASCII capable keyboard layout such as
+ // Russian and Thai layout. So, let's decide keyCode value with
+ // major keyboard layout's key which causes the OEM keycode.
+ // Actually, this maps same keyCode value to 2 keys on Russian
+ // keyboard layout. "Period" key causes VK_OEM_PERIOD but inputs
+ // Yu of Cyrillic and "Slash" key causes VK_OEM_2 (same as US
+ // keyboard layout) but inputs "." (period of ASCII). Therefore,
+ // we return DOM_VK_PERIOD which is same as VK_OEM_PERIOD for
+ // "Period" key. On the other hand, we use same keyCode value for
+ // "Slash" key too because it inputs ".".
+ CodeNameIndex code;
+ switch (aNativeKeyCode) {
+ case VK_OEM_1:
+ code = CODE_NAME_INDEX_Semicolon;
+ break;
+ case VK_OEM_PLUS:
+ code = CODE_NAME_INDEX_Equal;
+ break;
+ case VK_OEM_COMMA:
+ code = CODE_NAME_INDEX_Comma;
+ break;
+ case VK_OEM_MINUS:
+ code = CODE_NAME_INDEX_Minus;
+ break;
+ case VK_OEM_PERIOD:
+ code = CODE_NAME_INDEX_Period;
+ break;
+ case VK_OEM_2:
+ code = CODE_NAME_INDEX_Slash;
+ break;
+ case VK_OEM_3:
+ code = CODE_NAME_INDEX_Backquote;
+ break;
+ case VK_OEM_4:
+ code = CODE_NAME_INDEX_BracketLeft;
+ break;
+ case VK_OEM_5:
+ code = CODE_NAME_INDEX_Backslash;
+ break;
+ case VK_OEM_6:
+ code = CODE_NAME_INDEX_BracketRight;
+ break;
+ case VK_OEM_7:
+ code = CODE_NAME_INDEX_Quote;
+ break;
+ case VK_OEM_8:
+ // Use keyCode value for "Backquote" key on UK keyboard layout.
+ code = CODE_NAME_INDEX_Backquote;
+ break;
+ case VK_OEM_102:
+ // Use keyCode value for "IntlBackslash" key.
+ code = CODE_NAME_INDEX_IntlBackslash;
+ break;
+ case VK_ABNT_C1: // "/" of ABNT.
+ // Use keyCode value for "IntlBackslash" key on ABNT keyboard
+ // layout.
+ code = CODE_NAME_INDEX_IntlBackslash;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Handle all OEM keycode values");
+ return 0;
+ }
+ return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
+ }
+ }
+ return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0));
+ }
+
+ // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already
+ // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore,
+ // We should keep consistency between Gecko on all platforms rather than
+ // with other browsers since a lot of keyCode values are already different
+ // between browsers.
+ case VK_ABNT_C2:
+ return NS_VK_SEPARATOR;
+
+ // VK_PROCESSKEY means IME already consumed the key event.
+ case VK_PROCESSKEY:
+ return NS_VK_PROCESSKEY;
+ // VK_PACKET is generated by SendInput() API, we don't need to
+ // care this message as key event.
+ case VK_PACKET:
+ return 0;
+ // If a key is not mapped to a virtual keycode, 0xFF is used.
+ case 0xFF:
+ NS_WARNING("The key is failed to be converted to a virtual keycode");
+ return 0;
+ }
+#ifdef DEBUG
+ nsPrintfCString warning(
+ "Unknown virtual keycode (0x%08X), please check the "
+ "latest MSDN document, there may be some new "
+ "keycodes we've never known.",
+ aNativeKeyCode);
+ NS_WARNING(warning.get());
+#endif
+ return 0;
+}
+
+KeyNameIndex KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex(
+ uint8_t aVirtualKey) const {
+ if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) {
+ return KEY_NAME_INDEX_USE_STRING;
+ }
+
+ // If the keyboard layout has AltGr and AltRight key is pressed,
+ // return AltGraph.
+ if (aVirtualKey == VK_RMENU && HasAltGr()) {
+ return KEY_NAME_INDEX_AltGraph;
+ }
+
+ switch (aVirtualKey) {
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ HKL layout = KeyboardLayout::GetLayout();
+ WORD langID = LOWORD(static_cast<HKL>(layout));
+ WORD primaryLangID = PRIMARYLANGID(langID);
+
+ if (primaryLangID == LANG_JAPANESE) {
+ switch (aVirtualKey) {
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, \
+ aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_JAPANESE_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+ } else if (primaryLangID == LANG_KOREAN) {
+ switch (aVirtualKey) {
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_KOREAN_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+ }
+
+ switch (aVirtualKey) {
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+#define NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_OTHER_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex KeyboardLayout::ConvertScanCodeToCodeNameIndex(UINT aScanCode) {
+ switch (aScanCode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+nsresult KeyboardLayout::SynthesizeNativeKeyEvent(
+ nsWindow* aWidget, int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters) {
+ UINT keyboardLayoutListCount = ::GetKeyboardLayoutList(0, nullptr);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "One keyboard layout must be installed at least");
+ HKL keyboardLayoutListBuff[50];
+ HKL* keyboardLayoutList = keyboardLayoutListCount < 50
+ ? keyboardLayoutListBuff
+ : new HKL[keyboardLayoutListCount];
+ keyboardLayoutListCount =
+ ::GetKeyboardLayoutList(keyboardLayoutListCount, keyboardLayoutList);
+ NS_ASSERTION(keyboardLayoutListCount > 0,
+ "Failed to get all keyboard layouts installed on the system");
+
+ nsPrintfCString layoutName("%08x", aNativeKeyboardLayout);
+ HKL loadedLayout = LoadKeyboardLayoutA(layoutName.get(), KLF_NOTELLSHELL);
+ if (loadedLayout == nullptr) {
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete[] keyboardLayoutList;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Setup clean key state and load desired layout
+ BYTE originalKbdState[256];
+ ::GetKeyboardState(originalKbdState);
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ // This changes the state of the keyboard for the current thread only,
+ // and we'll restore it soon, so this should be OK.
+ ::SetKeyboardState(kbdState);
+
+ OverrideLayout(loadedLayout);
+
+ bool isAltGrKeyPress = false;
+ if (aModifierFlags & nsIWidget::ALTGRAPH) {
+ if (!HasAltGr()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // AltGr emulates ControlLeft key press and AltRight key press.
+ // So, we should remove those flags from aModifierFlags before
+ // calling WinUtils::SetupKeyModifiersSequence() to create correct
+ // key sequence.
+ // FYI: We don't support both ControlLeft and AltRight (AltGr) are
+ // pressed at the same time unless synthesizing key is
+ // VK_LCONTROL.
+ aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::ALT_R);
+ }
+
+ uint8_t argumentKeySpecific = 0;
+ switch (aNativeKeyCode & 0xFF) {
+ case VK_SHIFT:
+ aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R);
+ argumentKeySpecific = VK_LSHIFT;
+ break;
+ case VK_LSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_RSHIFT:
+ aModifierFlags &= ~nsIWidget::SHIFT_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT;
+ break;
+ case VK_CONTROL:
+ aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R);
+ argumentKeySpecific = VK_LCONTROL;
+ break;
+ case VK_LCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_RCONTROL:
+ aModifierFlags &= ~nsIWidget::CTRL_R;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL;
+ break;
+ case VK_MENU:
+ aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R);
+ argumentKeySpecific = VK_LMENU;
+ break;
+ case VK_LMENU:
+ aModifierFlags &= ~nsIWidget::ALT_L;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ break;
+ case VK_RMENU:
+ aModifierFlags &= ~(nsIWidget::ALT_R | nsIWidget::ALTGRAPH);
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ // If AltRight key is AltGr in the keyboard layout, let's use
+ // SetupKeyModifiersSequence() to emulate the native behavior
+ // since the same event order between keydown and keyup makes
+ // the following code complicated.
+ if (HasAltGr()) {
+ isAltGrKeyPress = true;
+ aModifierFlags &= ~nsIWidget::CTRL_L;
+ aModifierFlags |= nsIWidget::ALTGRAPH;
+ }
+ break;
+ case VK_CAPITAL:
+ aModifierFlags &= ~nsIWidget::CAPS_LOCK;
+ argumentKeySpecific = VK_CAPITAL;
+ break;
+ case VK_NUMLOCK:
+ aModifierFlags &= ~nsIWidget::NUM_LOCK;
+ argumentKeySpecific = VK_NUMLOCK;
+ break;
+ }
+
+ AutoTArray<KeyPair, 10> keySequence;
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYDOWN);
+ if (!isAltGrKeyPress) {
+ keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
+ }
+
+ // Simulate the pressing of each modifier key and then the real key
+ // FYI: Each NativeKey instance here doesn't need to override keyboard layout
+ // since this method overrides and restores the keyboard layout.
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ uint16_t scanCode = keySequence[i].mScanCode;
+ kbdState[key] = 0x81; // key is down and toggled on if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0x81;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ // When AltGr key is pressed, both ControlLeft and AltRight cause
+ // WM_KEYDOWN messages.
+ bool makeSysKeyMsg =
+ !(aModifierFlags & nsIWidget::ALTGRAPH) && IsSysKey(key, modKeyState);
+ MSG keyDownMsg =
+ WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN, key,
+ lParam, aWidget->GetWindowHandle());
+ if (i == keySequence.Length() - 1) {
+ bool makeDeadCharMsg =
+ (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty());
+ nsAutoString chars(aCharacters);
+ if (makeDeadCharMsg) {
+ UniCharsAndModifiers deadChars =
+ GetUniCharsAndModifiers(key, modKeyState);
+ chars = deadChars.ToString();
+ NS_ASSERTION(chars.Length() == 1,
+ "Dead char must be only one character");
+ }
+ if (chars.IsEmpty()) {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ } else {
+ AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs;
+ for (uint32_t j = 0; j < chars.Length(); j++) {
+ NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement();
+ fakeCharMsg->mCharCode = chars.CharAt(j);
+ fakeCharMsg->mScanCode = scanCode;
+ fakeCharMsg->mIsSysKey = makeSysKeyMsg;
+ fakeCharMsg->mIsDeadKey = makeDeadCharMsg;
+ }
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs);
+ bool dispatched;
+ nativeKey.HandleKeyDownMessage(&dispatched);
+ // If some char messages are not consumed, let's emulate the widget
+ // receiving the message directly.
+ for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) {
+ if (fakeCharMsgs[j].mConsumed) {
+ continue;
+ }
+ MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, charMsg, modKeyState);
+ nativeKey.HandleCharMessage(charMsg);
+ }
+ }
+ } else {
+ NativeKey nativeKey(aWidget, keyDownMsg, modKeyState);
+ nativeKey.HandleKeyDownMessage();
+ }
+ }
+
+ keySequence.Clear();
+ if (!isAltGrKeyPress) {
+ keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific));
+ }
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYUP);
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ uint16_t scanCode = keySequence[i].mScanCode;
+ kbdState[key] = 0; // key is up and toggled off if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0;
+ }
+ ::SetKeyboardState(kbdState);
+ ModifierKeyState modKeyState;
+ // If scan code isn't specified explicitly, let's compute it with current
+ // keyboard layout.
+ if (!scanCode) {
+ scanCode =
+ ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key);
+ }
+ LPARAM lParam = static_cast<LPARAM>(scanCode << 16);
+ // If the scan code is for an extended key, set extended key flag.
+ if ((scanCode & 0xFF00) == 0xE000) {
+ lParam |= 0x1000000;
+ }
+ // Don't use WM_SYSKEYUP for Alt keyup.
+ // NOTE: When AltGr was pressed, ControlLeft causes WM_SYSKEYUP normally.
+ bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU;
+ MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP,
+ key, lParam, aWidget->GetWindowHandle());
+ NativeKey nativeKey(aWidget, keyUpMsg, modKeyState);
+ nativeKey.HandleKeyUpMessage();
+ }
+
+ // Restore old key state and layout
+ ::SetKeyboardState(originalKbdState);
+ RestoreLayout();
+
+ // Don't unload the layout if it's installed actually.
+ for (uint32_t i = 0; i < keyboardLayoutListCount; i++) {
+ if (keyboardLayoutList[i] == loadedLayout) {
+ loadedLayout = 0;
+ break;
+ }
+ }
+ if (keyboardLayoutListBuff != keyboardLayoutList) {
+ delete[] keyboardLayoutList;
+ }
+ if (loadedLayout) {
+ ::UnloadKeyboardLayout(loadedLayout);
+ }
+ return NS_OK;
+}
+
+/*****************************************************************************
+ * mozilla::widget::DeadKeyTable
+ *****************************************************************************/
+
+char16_t DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const {
+ // Dead-key table is sorted by BaseChar in ascending order.
+ // Usually they are too small to use binary search.
+
+ for (uint32_t index = 0; index < mEntries; index++) {
+ if (mTable[index].BaseChar == aBaseChar) {
+ return mTable[index].CompositeChar;
+ }
+ if (mTable[index].BaseChar > aBaseChar) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * mozilla::widget::RedirectedKeyDownMessage
+ *****************************************************************************/
+
+MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg;
+bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false;
+
+// static
+bool RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) {
+ return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) &&
+ (sRedirectedKeyDownMsg.message == aMsg.message &&
+ WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) ==
+ WinUtils::GetScanCode(aMsg.lParam));
+}
+
+// static
+void RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) {
+ MSG msg;
+ if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST,
+ PM_NOREMOVE | PM_NOYIELD) &&
+ (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) {
+ WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message,
+ PM_REMOVE | PM_NOYIELD);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h
new file mode 100644
index 0000000000..5d430dbea0
--- /dev/null
+++ b/widget/windows/KeyboardLayout.h
@@ -0,0 +1,1156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 KeyboardLayout_h__
+#define KeyboardLayout_h__
+
+#include "mozilla/RefPtr.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/widget/WinMessages.h"
+#include "mozilla/widget/WinModifierKeyState.h"
+#include <windows.h>
+
+#define NS_NUM_OF_KEYS 70
+
+#define VK_OEM_1 0xBA // ';:' for US
+#define VK_OEM_PLUS 0xBB // '+' any country
+#define VK_OEM_COMMA 0xBC
+#define VK_OEM_MINUS 0xBD // '-' any country
+#define VK_OEM_PERIOD 0xBE
+#define VK_OEM_2 0xBF
+#define VK_OEM_3 0xC0
+// '/?' for Brazilian (ABNT)
+#define VK_ABNT_C1 0xC1
+// Separator in Numpad for Brazilian (ABNT) or JIS keyboard for Mac.
+#define VK_ABNT_C2 0xC2
+#define VK_OEM_4 0xDB
+#define VK_OEM_5 0xDC
+#define VK_OEM_6 0xDD
+#define VK_OEM_7 0xDE
+#define VK_OEM_8 0xDF
+#define VK_OEM_102 0xE2
+#define VK_OEM_CLEAR 0xFE
+
+class nsIUserIdleServiceInternal;
+
+namespace mozilla {
+namespace widget {
+
+enum ScanCode : uint16_t {
+ eCapsLock = 0x003A,
+ eNumLock = 0xE045,
+ eShiftLeft = 0x002A,
+ eShiftRight = 0x0036,
+ eControlLeft = 0x001D,
+ eControlRight = 0xE01D,
+ eAltLeft = 0x0038,
+ eAltRight = 0xE038,
+};
+
+// 0: nsIWidget's native modifier flag
+// 1: Virtual keycode which does not distinguish whether left or right location.
+// 2: Virtual keycode which distinguishes whether left or right location.
+// 3: Scan code.
+static const uint32_t sModifierKeyMap[][4] = {
+ {nsIWidget::CAPS_LOCK, VK_CAPITAL, 0, ScanCode::eCapsLock},
+ {nsIWidget::NUM_LOCK, VK_NUMLOCK, 0, ScanCode::eNumLock},
+ {nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT, ScanCode::eShiftLeft},
+ {nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT, ScanCode::eShiftRight},
+ {nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft},
+ {nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL, ScanCode::eControlRight},
+ {nsIWidget::ALT_L, VK_MENU, VK_LMENU, ScanCode::eAltLeft},
+ {nsIWidget::ALT_R, VK_MENU, VK_RMENU, ScanCode::eAltRight}};
+
+class KeyboardLayout;
+
+class MOZ_STACK_CLASS UniCharsAndModifiers final {
+ public:
+ UniCharsAndModifiers() {}
+ UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const;
+ UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther);
+
+ /**
+ * Append a pair of unicode character and the final modifier.
+ */
+ void Append(char16_t aUniChar, Modifiers aModifiers);
+ void Clear() {
+ mChars.Truncate();
+ mModifiers.Clear();
+ }
+ bool IsEmpty() const {
+ MOZ_ASSERT(mChars.Length() == mModifiers.Length());
+ return mChars.IsEmpty();
+ }
+
+ char16_t CharAt(size_t aIndex) const {
+ MOZ_ASSERT(aIndex < Length());
+ return mChars[aIndex];
+ }
+ Modifiers ModifiersAt(size_t aIndex) const {
+ MOZ_ASSERT(aIndex < Length());
+ return mModifiers[aIndex];
+ }
+ size_t Length() const {
+ MOZ_ASSERT(mChars.Length() == mModifiers.Length());
+ return mChars.Length();
+ }
+
+ bool IsProducingCharsWithAltGr() const {
+ return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0;
+ }
+
+ void FillModifiers(Modifiers aModifiers);
+ /**
+ * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between
+ * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars.
+ */
+ void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther);
+
+ bool UniCharsEqual(const UniCharsAndModifiers& aOther) const;
+ bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const;
+ bool BeginsWith(const UniCharsAndModifiers& aOther) const;
+
+ const nsString& ToString() const { return mChars; }
+
+ private:
+ nsAutoString mChars;
+ // 5 is enough number for normal keyboard layout handling. On Windows,
+ // a dead key sequence may cause inputting up to 5 characters per key press.
+ CopyableAutoTArray<Modifiers, 5> mModifiers;
+};
+
+struct DeadKeyEntry {
+ char16_t BaseChar;
+ char16_t CompositeChar;
+
+ DeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar)
+ : BaseChar(aBaseChar), CompositeChar(aCompositeChar) {}
+
+ bool operator<(const DeadKeyEntry& aOther) const {
+ return this->BaseChar < aOther.BaseChar;
+ }
+
+ bool operator==(const DeadKeyEntry& aOther) const {
+ return this->BaseChar == aOther.BaseChar;
+ }
+};
+
+class DeadKeyTable {
+ friend class KeyboardLayout;
+
+ uint16_t mEntries;
+ // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as
+ // required. It is the only way to create new DeadKeyTable instances.
+ DeadKeyEntry mTable[1];
+
+ void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) {
+ mEntries = aEntries;
+ memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry));
+ }
+
+ static uint32_t SizeInBytes(uint32_t aEntries) {
+ return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry);
+ }
+
+ public:
+ uint32_t Entries() const { return mEntries; }
+
+ bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const {
+ return (mEntries == aEntries &&
+ !memcmp(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry)));
+ }
+
+ char16_t GetCompositeChar(char16_t aBaseChar) const;
+};
+
+class VirtualKey {
+ public:
+ enum ShiftStateIndex : uint8_t {
+ // 0 - Normal
+ eNormal = 0,
+ // 1 - Shift
+ eShift,
+ // 2 - Control
+ eControl,
+ // 3 - Control + Shift
+ eControlShift,
+ // 4 - Alt
+ eAlt,
+ // 5 - Alt + Shift
+ eAltShift,
+ // 6 - Alt + Control (AltGr)
+ eAltGr,
+ // 7 - Alt + Control + Shift (AltGr + Shift)
+ eAltGrShift,
+ // 8 - CapsLock
+ eWithCapsLock,
+ // 9 - CapsLock + Shift
+ eShiftWithCapsLock,
+ // 10 - CapsLock + Control
+ eControlWithCapsLock,
+ // 11 - CapsLock + Control + Shift
+ eControlShiftWithCapsLock,
+ // 12 - CapsLock + Alt
+ eAltWithCapsLock,
+ // 13 - CapsLock + Alt + Shift
+ eAltShiftWithCapsLock,
+ // 14 - CapsLock + Alt + Control (CapsLock + AltGr)
+ eAltGrWithCapsLock,
+ // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift)
+ eAltGrShiftWithCapsLock,
+ };
+
+ enum ShiftStateFlag {
+ STATE_SHIFT = 0x01,
+ STATE_CONTROL = 0x02,
+ STATE_ALT = 0x04,
+ STATE_CAPSLOCK = 0x08,
+ // ShiftState needs to have AltGr state separately since this is necessary
+ // for lossless conversion with Modifiers.
+ STATE_ALTGRAPH = 0x80,
+ // Useful to remove or check Ctrl and Alt flags.
+ STATE_CONTROL_ALT = STATE_CONTROL | STATE_ALT,
+ };
+
+ typedef uint8_t ShiftState;
+
+ static ShiftState ModifiersToShiftState(Modifiers aModifiers);
+ static ShiftState ModifierKeyStateToShiftState(
+ const ModifierKeyState& aModKeyState) {
+ return ModifiersToShiftState(aModKeyState.GetModifiers());
+ }
+ static Modifiers ShiftStateToModifiers(ShiftState aShiftState);
+ static bool IsAltGrIndex(uint8_t aIndex) {
+ return (aIndex & STATE_CONTROL_ALT) == STATE_CONTROL_ALT;
+ }
+
+ private:
+ union KeyShiftState {
+ struct {
+ char16_t Chars[4];
+ } Normal;
+ struct {
+ const DeadKeyTable* Table;
+ char16_t DeadChar;
+ } DeadKey;
+ };
+
+ KeyShiftState mShiftStates[16];
+ uint16_t mIsDeadKey;
+
+ static uint8_t ToShiftStateIndex(ShiftState aShiftState) {
+ if (!(aShiftState & STATE_ALTGRAPH)) {
+ MOZ_ASSERT(aShiftState <= eAltGrShiftWithCapsLock);
+ return static_cast<uint8_t>(aShiftState);
+ }
+ uint8_t index = aShiftState & ~STATE_ALTGRAPH;
+ index |= (STATE_ALT | STATE_CONTROL);
+ MOZ_ASSERT(index <= eAltGrShiftWithCapsLock);
+ return index;
+ }
+
+ void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey) {
+ if (aIsDeadKey) {
+ mIsDeadKey |= 1 << ToShiftStateIndex(aShiftState);
+ } else {
+ mIsDeadKey &= ~(1 << ToShiftStateIndex(aShiftState));
+ }
+ }
+
+ public:
+ static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
+
+ bool IsDeadKey(ShiftState aShiftState) const {
+ return (mIsDeadKey & (1 << ToShiftStateIndex(aShiftState))) != 0;
+ }
+
+ /**
+ * IsChangedByAltGr() is useful to check if a key with AltGr produces
+ * different character(s) from the key without AltGr.
+ * Note that this is designed for checking if a keyboard layout has AltGr
+ * key. So, this result may not exactly correct for the key since it's
+ * okay to fails in some edge cases when we check all keys which produce
+ * character(s) in a layout.
+ */
+ bool IsChangedByAltGr(ShiftState aShiftState) const {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+ MOZ_ASSERT(IsAltGrIndex(aShiftState));
+ MOZ_ASSERT(IsDeadKey(aShiftState) ||
+ mShiftStates[aShiftState].Normal.Chars[0]);
+ const ShiftState kShiftStateWithoutAltGr =
+ aShiftState - ShiftStateIndex::eAltGr;
+ if (IsDeadKey(aShiftState) != IsDeadKey(kShiftStateWithoutAltGr)) {
+ return false;
+ }
+ if (IsDeadKey(aShiftState)) {
+ return mShiftStates[aShiftState].DeadKey.DeadChar !=
+ mShiftStates[kShiftStateWithoutAltGr].DeadKey.DeadChar;
+ }
+ for (size_t i = 0; i < 4; i++) {
+ if (mShiftStates[aShiftState].Normal.Chars[i] !=
+ mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) {
+ return true;
+ }
+ if (!mShiftStates[aShiftState].Normal.Chars[i] &&
+ !mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ void AttachDeadKeyTable(ShiftState aShiftState,
+ const DeadKeyTable* aDeadKeyTable) {
+ MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState));
+ mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable;
+ }
+
+ void SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
+ uint32_t aNumOfChars);
+ void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar);
+ const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries) const;
+ inline char16_t GetCompositeChar(ShiftState aShiftState,
+ char16_t aBaseChar) const {
+ return mShiftStates[ToShiftStateIndex(aShiftState)]
+ .DeadKey.Table->GetCompositeChar(aBaseChar);
+ }
+
+ char16_t GetCompositeChar(const ModifierKeyState& aModKeyState,
+ char16_t aBaseChar) const {
+ return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState),
+ aBaseChar);
+ }
+
+ /**
+ * GetNativeUniChars() returns character(s) which is produced by the
+ * key with given modifiers. This does NOT return proper MODIFIER_ALTGRAPH
+ * state because this is raw accessor of the database of this key.
+ */
+ UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetNativeUniChars(
+ const ModifierKeyState& aModKeyState) const {
+ return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+
+ /**
+ * GetUniChars() returns characters and modifiers which are not consumed
+ * to input the character.
+ * For example, if you specify Ctrl key but the key produces no character
+ * with Ctrl, this returns character(s) which is produced by the key
+ * without Ctrl. So, the result is useful to decide KeyboardEvent.key
+ * value.
+ * Another example is, if you specify Ctrl key and the key produces
+ * different character(s) from the case without Ctrl key, this returns
+ * the character(s) *without* MODIFIER_CONTROL. This modifier information
+ * is useful for eKeyPress since TextEditor does not treat eKeyPress events
+ * whose modifier includes MODIFIER_ALT and/or MODIFIER_CONTROL.
+ *
+ * @param aShiftState Modifiers which you want to retrieve
+ * KeyboardEvent.key value for the key with.
+ * If AltGr key is pressed, this should include
+ * STATE_ALTGRAPH and should NOT include
+ * STATE_ALT nor STATE_CONTROL.
+ * If both Alt and Ctrl are pressed to emulate
+ * AltGr, this should include both STATE_ALT and
+ * STATE_CONTROL but should NOT include
+ * MODIFIER_ALTGRAPH.
+ * Then, this returns proper modifiers when
+ * this key produces no character with AltGr.
+ */
+ UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const {
+ return GetUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+};
+
+class MOZ_STACK_CLASS NativeKey final {
+ friend class KeyboardLayout;
+
+ public:
+ struct FakeCharMsg {
+ UINT mCharCode;
+ UINT mScanCode;
+ bool mIsSysKey;
+ bool mIsDeadKey;
+ bool mConsumed;
+
+ FakeCharMsg()
+ : mCharCode(0),
+ mScanCode(0),
+ mIsSysKey(false),
+ mIsDeadKey(false),
+ mConsumed(false) {}
+
+ MSG GetCharMsg(HWND aWnd) const {
+ MSG msg;
+ msg.hwnd = aWnd;
+ msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR
+ : mIsDeadKey ? WM_DEADCHAR
+ : mIsSysKey ? WM_SYSCHAR
+ : WM_CHAR;
+ msg.wParam = static_cast<WPARAM>(mCharCode);
+ msg.lParam = static_cast<LPARAM>(mScanCode << 16);
+ msg.time = 0;
+ msg.pt.x = msg.pt.y = 0;
+ return msg;
+ }
+ };
+
+ NativeKey(nsWindow* aWidget, const MSG& aMessage,
+ const ModifierKeyState& aModKeyState,
+ HKL aOverrideKeyboardLayout = 0,
+ nsTArray<FakeCharMsg>* aFakeCharMsgs = nullptr);
+
+ ~NativeKey();
+
+ /**
+ * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be
+ * initialized with WM_KEYDOWN or WM_SYSKEYDOWN.
+ * Returns true if dispatched keydown event or keypress event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be
+ * initialized with them.
+ * Returns true if dispatched keypress event is consumed. Otherwise, false.
+ */
+ bool HandleCharMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles keyup message. Returns true if the event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const;
+
+ /**
+ * Handles WM_APPCOMMAND message. Returns true if the event is consumed.
+ * Otherwise, false.
+ */
+ bool HandleAppCommandMessage() const;
+
+ /**
+ * Callback of TextEventDispatcherListener::WillDispatchKeyboardEvent().
+ * This method sets alternative char codes of aKeyboardEvent.
+ */
+ void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndex);
+
+ /**
+ * Returns true if aChar is a control character which shouldn't be inputted
+ * into focused text editor.
+ */
+ static bool IsControlChar(char16_t aChar);
+
+ bool IsShift() const { return mModKeyState.IsShift(); }
+ bool IsControl() const { return mModKeyState.IsControl(); }
+ bool IsAlt() const { return mModKeyState.IsAlt(); }
+ bool MaybeEmulatingAltGraph() const;
+ Modifiers GetModifiers() const { return mModKeyState.GetModifiers(); }
+ const ModifierKeyState& ModifierKeyStateRef() const { return mModKeyState; }
+ VirtualKey::ShiftState GetShiftState() const {
+ return VirtualKey::ModifierKeyStateToShiftState(mModKeyState);
+ }
+
+ /**
+ * GenericVirtualKeyCode() returns virtual keycode which cannot distinguish
+ * position of modifier keys. E.g., VK_CONTROL for both ControlLeft and
+ * ControlRight.
+ */
+ uint8_t GenericVirtualKeyCode() const { return mOriginalVirtualKeyCode; }
+
+ /**
+ * SpecificVirtualKeyCode() returns virtual keycode which can distinguish
+ * position of modifier keys. E.g., returns VK_LCONTROL or VK_RCONTROL
+ * instead of VK_CONTROL. If the key message is synthesized with not
+ * enough information, this prefers left position's keycode.
+ */
+ uint8_t SpecificVirtualKeyCode() const { return mVirtualKeyCode; }
+
+ private:
+ NativeKey* mLastInstance;
+ // mRemovingMsg is set at removing a char message from
+ // GetFollowingCharMessage().
+ MSG mRemovingMsg;
+ // mReceivedMsg is set when another instance starts to handle the message
+ // unexpectedly.
+ MSG mReceivedMsg;
+ RefPtr<nsWindow> mWidget;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ HKL mKeyboardLayout;
+ MSG mMsg;
+ // mFollowingCharMsgs stores WM_CHAR, WM_SYSCHAR, WM_DEADCHAR or
+ // WM_SYSDEADCHAR message which follows WM_KEYDOWN.
+ // Note that the stored messaged are already removed from the queue.
+ // FYI: 5 is enough number for usual keyboard layout handling. On Windows,
+ // a dead key sequence may cause inputting up to 5 characters per key press.
+ AutoTArray<MSG, 5> mFollowingCharMsgs;
+ // mRemovedOddCharMsgs stores WM_CHAR messages which are caused by ATOK or
+ // WXG (they are Japanese IME) when the user tries to do "Kakutei-undo"
+ // (it means "undo the last commit").
+ nsTArray<MSG> mRemovedOddCharMsgs;
+ // If dispatching eKeyDown or eKeyPress event causes focus change,
+ // the instance shouldn't handle remaning char messages. For checking it,
+ // this should store first focused window.
+ HWND mFocusedWndBeforeDispatch;
+
+ uint32_t mDOMKeyCode;
+ KeyNameIndex mKeyNameIndex;
+ CodeNameIndex mCodeNameIndex;
+
+ ModifierKeyState mModKeyState;
+
+ // mVirtualKeyCode distinguishes left key or right key of modifier key.
+ uint8_t mVirtualKeyCode;
+ // mOriginalVirtualKeyCode doesn't distinguish left key or right key of
+ // modifier key. However, if the given keycode is VK_PROCESS, it's resolved
+ // to a keycode before it's handled by IME.
+ uint8_t mOriginalVirtualKeyCode;
+
+ // mCommittedChars indicates the inputted characters which is committed by
+ // the key. If dead key fail to composite a character, mCommittedChars
+ // indicates both the dead characters and the base characters.
+ UniCharsAndModifiers mCommittedCharsAndModifiers;
+
+ // Following strings are computed by
+ // ComputeInputtingStringWithKeyboardLayout() which is typically called
+ // before dispatching keydown event.
+ // mInputtingStringAndModifiers's string is the string to be
+ // inputted into the focused editor and its modifier state is proper
+ // modifier state for inputting the string into the editor.
+ UniCharsAndModifiers mInputtingStringAndModifiers;
+ // mShiftedString is the string to be inputted into the editor with
+ // current modifier state with active shift state.
+ UniCharsAndModifiers mShiftedString;
+ // mUnshiftedString is the string to be inputted into the editor with
+ // current modifier state without shift state.
+ UniCharsAndModifiers mUnshiftedString;
+ // Following integers are computed by
+ // ComputeInputtingStringWithKeyboardLayout() which is typically called
+ // before dispatching keydown event. The meaning of these values is same
+ // as charCode.
+ uint32_t mShiftedLatinChar;
+ uint32_t mUnshiftedLatinChar;
+
+ WORD mScanCode;
+ bool mIsExtended;
+ // mIsRepeat is true if the key message is caused by the auto-repeat
+ // feature.
+ bool mIsRepeat;
+ bool mIsDeadKey;
+ // mIsPrintableKey is true if the key may be a printable key without
+ // any modifier keys. Otherwise, false.
+ // Please note that the event may not cause any text input even if this
+ // is true. E.g., it might be dead key state or Ctrl key may be pressed.
+ bool mIsPrintableKey;
+ // mIsSkippableInRemoteProcess is false if the key event shouldn't be
+ // skipped in the remote process even if it's too old event.
+ bool mIsSkippableInRemoteProcess;
+ // mCharMessageHasGone is true if the message is a keydown message and
+ // it's followed by at least one char message but it's gone at removing
+ // from the queue. This could occur if PeekMessage() or something is
+ // hooked by odd tool.
+ bool mCharMessageHasGone;
+ // mIsOverridingKeyboardLayout is true if the instance temporarily overriding
+ // keyboard layout with specified by the constructor.
+ bool mIsOverridingKeyboardLayout;
+ // mCanIgnoreModifierStateAtKeyPress is true if it's allowed to remove
+ // Ctrl or Alt modifier state at dispatching eKeyPress.
+ bool mCanIgnoreModifierStateAtKeyPress;
+
+ nsTArray<FakeCharMsg>* mFakeCharMsgs;
+
+ // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed
+ // virtual keycode is set to this. Even if we consume WM_APPCOMMAND message,
+ // Windows may send WM_KEYDOWN and WM_KEYUP message for them.
+ // At that time, we should not dispatch key events for them.
+ static uint8_t sDispatchedKeyOfAppCommand;
+
+ NativeKey() {
+ MOZ_CRASH("The default constructor of NativeKey isn't available");
+ }
+
+ void InitWithAppCommand();
+ void InitWithKeyOrChar();
+
+ /**
+ * InitIsSkippableForKeyOrChar() initializes mIsSkippableInRemoteProcess with
+ * mIsRepeat and previous key message information. So, this must be called
+ * after mIsRepeat is initialized.
+ */
+ void InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG);
+
+ /**
+ * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes
+ * mCommittedCharsAndModifiers with mFollowingCharMsgs and mModKeyState.
+ * If mFollowingCharMsgs includes non-printable char messages, they are
+ * ignored (skipped).
+ */
+ void InitCommittedCharsAndModifiersWithFollowingCharMessages();
+
+ UINT GetScanCodeWithExtendedFlag() const;
+
+ // The result is one of eKeyLocation*.
+ uint32_t GetKeyLocation() const;
+
+ /**
+ * RemoveFollowingOddCharMessages() removes odd WM_CHAR messages from the
+ * queue when IsIMEDoingKakuteiUndo() returns true.
+ */
+ void RemoveFollowingOddCharMessages();
+
+ /**
+ * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes
+ * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this
+ * returns true, the caller needs to be careful for processing the messages.
+ */
+ bool IsIMEDoingKakuteiUndo() const;
+
+ /**
+ * This returns true if user types a number key in numpad with Alt key
+ * to input a Unicode character from its scalar value.
+ * Note that inputting Unicode scalar value is available without NumLock.
+ * Therefore, this returns true even if user presses a function key on
+ * numpad without NumLock, but that may be intended to perform a shortcut
+ * key like Alt + Home.
+ */
+ bool MaybeTypingUnicodeScalarValue() const {
+ return !mIsExtended && IsSysKeyDownOrKeyUpMessage() && IsAlt() &&
+ !IsControl() && !IsShift() &&
+ ((mScanCode >= 0x004F && mScanCode <= 0x0052) || // Numpad0-3
+ (mScanCode >= 0x004B && mScanCode <= 0x004D) || // Numpad4-6
+ (mScanCode >= 0x0047 && mScanCode <= 0x0049)); // Numpad7-9
+ }
+
+ bool IsKeyDownMessage() const {
+ return mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN;
+ }
+ bool IsKeyUpMessage() const {
+ return mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP;
+ }
+ bool IsSysKeyDownOrKeyUpMessage() const {
+ return mMsg.message == WM_SYSKEYDOWN || mMsg.message == WM_SYSKEYUP;
+ }
+ bool IsCharOrSysCharMessage(const MSG& aMSG) const {
+ return IsCharOrSysCharMessage(aMSG.message);
+ }
+ bool IsCharOrSysCharMessage(UINT aMessage) const {
+ return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR);
+ }
+ bool IsCharMessage(const MSG& aMSG) const {
+ return IsCharMessage(aMSG.message);
+ }
+ bool IsCharMessage(UINT aMessage) const {
+ return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage));
+ }
+ bool IsDeadCharMessage(const MSG& aMSG) const {
+ return IsDeadCharMessage(aMSG.message);
+ }
+ bool IsDeadCharMessage(UINT aMessage) const {
+ return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR);
+ }
+ bool IsSysCharMessage(const MSG& aMSG) const {
+ return IsSysCharMessage(aMSG.message);
+ }
+ bool IsSysCharMessage(UINT aMessage) const {
+ return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR);
+ }
+ bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const;
+ bool IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1,
+ const MSG& aKeyOrCharMsg2) const;
+ bool IsFollowedByPrintableCharMessage() const;
+ bool IsFollowedByPrintableCharOrSysCharMessage() const;
+ bool IsFollowedByDeadCharMessage() const;
+ bool IsPrintableCharMessage(const MSG& aMSG) const {
+ return aMSG.message == WM_CHAR &&
+ !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+ bool IsEnterKeyPressCharMessage(const MSG& aMSG) const {
+ return aMSG.message == WM_CHAR && aMSG.wParam == '\r';
+ }
+ bool IsPrintableCharOrSysCharMessage(const MSG& aMSG) const {
+ return IsCharOrSysCharMessage(aMSG) &&
+ !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+ bool IsControlCharMessage(const MSG& aMSG) const {
+ return IsCharMessage(aMSG.message) &&
+ IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+
+ /**
+ * IsReservedBySystem() returns true if the key combination is reserved by
+ * the system. Even if it's consumed by web apps, the message should be
+ * sent to next wndproc.
+ */
+ bool IsReservedBySystem() const;
+
+ /**
+ * GetFollowingCharMessage() returns following char message of handling
+ * keydown event. If the message is found, this method returns true.
+ * Otherwise, returns false.
+ *
+ * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its
+ * hwnd may be different window.
+ */
+ bool GetFollowingCharMessage(MSG& aCharMsg);
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK.
+ */
+ uint8_t ComputeVirtualKeyCodeFromScanCode() const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX.
+ */
+ uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC.
+ */
+ uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const;
+
+ /**
+ * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR.
+ */
+ char16_t ComputeUnicharFromScanCode() const;
+
+ /**
+ * Initializes the aKeyEvent with the information stored in the instance.
+ */
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState) const;
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const;
+
+ /**
+ * Dispatches a command event for aEventCommand.
+ * Returns true if the event is consumed. Otherwise, false.
+ */
+ bool DispatchCommandEvent(uint32_t aEventCommand) const;
+
+ /**
+ * DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress
+ * event(s) with retrieved char messages.
+ */
+ bool DispatchKeyPressEventsWithRetrievedCharMessages() const;
+
+ /**
+ * DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s)
+ * without char messages. So, this should be used only when there are no
+ * following char messages.
+ */
+ bool DispatchKeyPressEventsWithoutCharMessage() const;
+
+ /**
+ * Checkes whether the key event down message is handled without following
+ * WM_CHAR messages. For example, if following WM_CHAR message indicates
+ * control character input, the WM_CHAR message is unclear whether it's
+ * caused by a printable key with Ctrl or just a function key such as Enter
+ * or Backspace.
+ */
+ bool NeedsToHandleWithoutFollowingCharMessages() const;
+
+ /**
+ * ComputeInputtingStringWithKeyboardLayout() computes string to be inputted
+ * with the key and the modifier state, without shift state and with shift
+ * state.
+ */
+ void ComputeInputtingStringWithKeyboardLayout();
+
+ /**
+ * IsFocusedWindowChanged() returns true if focused window is changed
+ * after the instance is created.
+ */
+ bool IsFocusedWindowChanged() const {
+ return mFocusedWndBeforeDispatch != ::GetFocus();
+ }
+
+ /**
+ * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be
+ * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them.
+ * Returns true if dispatched keypress event is consumed. Otherwise, false.
+ */
+ bool HandleCharMessage(const MSG& aCharMsg,
+ bool* aEventDispatched = nullptr) const;
+
+ // Calls of PeekMessage() from NativeKey might cause nested message handling
+ // due to (perhaps) odd API hook. NativeKey should do nothing if given
+ // message is tried to be retrieved by another instance.
+
+ /**
+ * sLatestInstacne is a pointer to the newest instance of NativeKey which is
+ * handling a key or char message(s).
+ */
+ static NativeKey* sLatestInstance;
+
+ static const MSG sEmptyMSG;
+
+ static MSG sLastKeyOrCharMSG;
+
+ static MSG sLastKeyMSG;
+
+ // Set to non-zero if we receive a WM_KEYDOWN message which introduces only
+ // a high surrogate. Then, it'll be cleared when next keydown or char message
+ // is received.
+ static char16_t sPendingHighSurrogate;
+
+ static bool IsEmptyMSG(const MSG& aMSG) {
+ return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG));
+ }
+
+ bool IsAnotherInstanceRemovingCharMessage() const {
+ return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg);
+ }
+
+ public:
+ /**
+ * Returns last key or char MSG. If no MSG has been received yet, the result
+ * is empty MSG (i.e., .message is WM_NULL).
+ */
+ static const MSG& LastKeyOrCharMSG() { return sLastKeyOrCharMSG; }
+};
+
+class KeyboardLayout {
+ public:
+ static KeyboardLayout* GetInstance();
+ static void Shutdown();
+
+ /**
+ * GetLayout() returns a keyboard layout which has already been loaded in the
+ * singleton instance or active keyboard layout.
+ */
+ static HKL GetLayout() {
+ if (!sInstance || sInstance->mIsPendingToRestoreKeyboardLayout) {
+ return ::GetKeyboardLayout(0);
+ }
+ return sInstance->mKeyboardLayout;
+ }
+
+ /**
+ * GetLoadedLayout() returns a keyboard layout which was loaded in the
+ * singleton instance. This may be different from the active keyboard layout
+ * on the system if we override the keyboard layout for synthesizing native
+ * key events for tests.
+ */
+ HKL GetLoadedLayout() { return mKeyboardLayout; }
+
+ /**
+ * GetLoadedLayoutName() returns the name of the loaded keyboard layout in the
+ * singleton instance.
+ */
+ nsCString GetLoadedLayoutName() {
+ return KeyboardLayout::GetLayoutName(mKeyboardLayout);
+ }
+
+ static void NotifyIdleServiceOfUserActivity();
+
+ static bool IsPrintableCharKey(uint8_t aVirtualKey);
+
+ /**
+ * HasAltGr() returns true if the keyboard layout's AltRight key is AltGr
+ * key.
+ */
+ bool HasAltGr() const { return mHasAltGr; }
+
+ /**
+ * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState.
+ * This method isn't stateful.
+ */
+ bool IsDeadKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+ bool IsDeadKey(const NativeKey& aNativeKey) const {
+ return IsDeadKey(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.ModifierKeyStateRef());
+ }
+
+ /**
+ * IsInDeadKeySequence() returns true when it's in a dead key sequence.
+ * It starts when a dead key is down and ends when another key down causes
+ * inactivating the dead key state.
+ */
+ bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); }
+
+ /**
+ * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
+ * or WM_SYS*CHAR messages.
+ */
+ bool IsSysKey(uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+ bool IsSysKey(const NativeKey& aNativeKey) const {
+ return IsSysKey(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.ModifierKeyStateRef());
+ }
+
+ /**
+ * GetUniCharsAndModifiers() returns characters which are inputted by
+ * aVirtualKey with aModKeyState. This method isn't stateful.
+ * Note that if the combination causes text input, the result's Ctrl and
+ * Alt key state are never active.
+ */
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const {
+ VirtualKey::ShiftState shiftState =
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
+ return GetUniCharsAndModifiers(aVirtualKey, shiftState);
+ }
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ const NativeKey& aNativeKey) const {
+ return GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(),
+ aNativeKey.GetShiftState());
+ }
+
+ /**
+ * OnLayoutChange() must be called before the first keydown message is
+ * received. LoadLayout() changes the keyboard state, that causes breaking
+ * dead key state. Therefore, we need to load the layout before the first
+ * keydown message.
+ */
+ void OnLayoutChange(HKL aKeyboardLayout) {
+ MOZ_ASSERT(!mIsOverridden);
+ LoadLayout(aKeyboardLayout);
+ }
+
+ /**
+ * OverrideLayout() loads the specified keyboard layout.
+ */
+ void OverrideLayout(HKL aLayout) {
+ mIsOverridden = true;
+ LoadLayout(aLayout);
+ }
+
+ /**
+ * RestoreLayout() loads the current keyboard layout of the thread.
+ */
+ void RestoreLayout() {
+ mIsOverridden = false;
+ mIsPendingToRestoreKeyboardLayout = true;
+ }
+
+ uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const;
+
+ /**
+ * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for
+ * non-printable keys (except some special keys like space key).
+ */
+ KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const;
+
+ /**
+ * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for
+ * the given scan code. aScanCode can be over 0xE000 since this method
+ * doesn't use Windows API.
+ */
+ static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode);
+
+ /**
+ * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC.
+ */
+ WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const;
+
+ /**
+ * Implementation of nsIWidget::SynthesizeNativeKeyEvent().
+ */
+ nsresult SynthesizeNativeKeyEvent(nsWindow* aWidget,
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+ private:
+ KeyboardLayout();
+ ~KeyboardLayout();
+
+ static KeyboardLayout* sInstance;
+ static StaticRefPtr<nsIUserIdleServiceInternal> sIdleService;
+
+ struct DeadKeyTableListEntry {
+ DeadKeyTableListEntry* next;
+ uint8_t data[1];
+ };
+
+ HKL mKeyboardLayout = nullptr;
+
+ VirtualKey mVirtualKeys[NS_NUM_OF_KEYS] = {};
+ DeadKeyTableListEntry* mDeadKeyTableListHead = nullptr;
+ // When mActiveDeadKeys is empty, it's not in dead key sequence.
+ // Otherwise, it contains virtual keycodes which are pressed in current
+ // dead key sequence.
+ nsTArray<uint8_t> mActiveDeadKeys;
+ // mDeadKeyShiftStates is always same length as mActiveDeadKeys.
+ // This stores shift states at pressing each dead key stored in
+ // mActiveDeadKeys.
+ nsTArray<VirtualKey::ShiftState> mDeadKeyShiftStates;
+
+ bool mIsOverridden = false;
+ bool mIsPendingToRestoreKeyboardLayout = false;
+ bool mHasAltGr = false;
+
+ static inline int32_t GetKeyIndex(uint8_t aVirtualKey);
+ static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar,
+ nsTArray<DeadKeyEntry>& aDeadKeyArray);
+ bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState);
+ uint32_t GetDeadKeyCombinations(uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars,
+ nsTArray<DeadKeyEntry>& aDeadKeyArray);
+ /**
+ * Activates or deactivates dead key state.
+ */
+ void ActivateDeadKeyState(const NativeKey& aNativeKey);
+ void DeactivateDeadKeyState();
+
+ const DeadKeyTable* AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries);
+ void ReleaseDeadKeyTables();
+
+ /**
+ * Loads the specified keyboard layout. This method always clear the dead key
+ * state.
+ */
+ void LoadLayout(HKL aLayout);
+
+ /**
+ * Gets the keyboard layout name of aLayout. Be careful, this may be too
+ * slow to call at handling user input.
+ */
+ static nsCString GetLayoutName(HKL aLayout);
+
+ /**
+ * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or
+ * WM_KEYUP. This method is stateful. This saves current dead key state at
+ * WM_KEYDOWN. Additionally, computes current inputted character(s) and set
+ * them to the aNativeKey.
+ */
+ void InitNativeKey(NativeKey& aNativeKey);
+
+ /**
+ * MaybeInitNativeKeyAsDeadKey() initializes aNativeKey only when aNativeKey
+ * is a dead key's event.
+ * When it's not in a dead key sequence, this activates the dead key state.
+ * When it's in a dead key sequence, this initializes aNativeKey with a
+ * composite character or a preceding dead char and a dead char which should
+ * be caused by aNativeKey.
+ * Returns true when this initializes aNativeKey. Otherwise, false.
+ */
+ bool MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey);
+
+ /**
+ * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with
+ * proper composite character when dead key produces a composite character.
+ * Otherwise, just returns false.
+ */
+ bool MaybeInitNativeKeyWithCompositeChar(NativeKey& aNativeKey);
+
+ /**
+ * See the comment of GetUniCharsAndModifiers() below.
+ */
+ UniCharsAndModifiers GetUniCharsAndModifiers(
+ uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const;
+
+ /**
+ * GetDeadUniCharsAndModifiers() returns dead chars which are stored in
+ * current dead key sequence. So, this is stateful.
+ */
+ UniCharsAndModifiers GetDeadUniCharsAndModifiers() const;
+
+ /**
+ * GetCompositeChar() returns a composite character with dead character
+ * caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character
+ * (aBaseChar).
+ * If the combination of the dead character and the base character doesn't
+ * cause a composite character, this returns 0.
+ */
+ char16_t GetCompositeChar(char16_t aBaseChar) const;
+
+ // NativeKey class should access InitNativeKey() directly, but it shouldn't
+ // be available outside of NativeKey. So, let's make NativeKey a friend
+ // class of this.
+ friend class NativeKey;
+};
+
+class RedirectedKeyDownMessageManager {
+ public:
+ /*
+ * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is
+ * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent()
+ * prevents to dispatch eKeyDown event because it has been dispatched
+ * before the message was redirected. However, in some cases, WM_*KEYDOWN
+ * message handler may not handle actually. Then, the message handler needs
+ * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR
+ * message for the redirected keydown message. AutoFlusher class is a helper
+ * class for doing it. This must be created in the stack.
+ */
+ class MOZ_STACK_CLASS AutoFlusher final {
+ public:
+ AutoFlusher(nsWindow* aWidget, const MSG& aMsg)
+ : mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)),
+ mWidget(aWidget),
+ mMsg(aMsg) {}
+
+ ~AutoFlusher() {
+ if (mCancel) {
+ return;
+ }
+ // Prevent unnecessary keypress event
+ if (!mWidget->Destroyed()) {
+ RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd);
+ }
+ // Foreget the redirected message
+ RedirectedKeyDownMessageManager::Forget();
+ }
+
+ void Cancel() { mCancel = true; }
+
+ private:
+ bool mCancel;
+ RefPtr<nsWindow> mWidget;
+ const MSG& mMsg;
+ };
+
+ static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented) {
+ sRedirectedKeyDownMsg = aMsg;
+ sDefaultPreventedOfRedirectedMsg = aDefualtPrevented;
+ }
+
+ static void Forget() { sRedirectedKeyDownMsg.message = WM_NULL; }
+
+ static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; }
+ static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; }
+
+ static bool IsRedirectedMessage(const MSG& aMsg);
+
+ /**
+ * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM
+ * message handler. If there is no WM_(SYS)CHAR message for it, this
+ * method does nothing.
+ * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is
+ * called in message loop. So, WM_(SYS)KEYDOWN message should have
+ * WM_(SYS)CHAR message in the queue if the keydown event causes character
+ * input.
+ */
+ static void RemoveNextCharMessage(HWND aWnd);
+
+ private:
+ // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which
+ // is reirected with SendInput() API by
+ // widget::NativeKey::DispatchKeyDownAndKeyPressEvent()
+ static MSG sRedirectedKeyDownMsg;
+ static bool sDefaultPreventedOfRedirectedMsg;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/LSPAnnotator.cpp b/widget/windows/LSPAnnotator.cpp
new file mode 100644
index 0000000000..ab3a768d66
--- /dev/null
+++ b/widget/windows/LSPAnnotator.cpp
@@ -0,0 +1,135 @@
+/* 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/. */
+
+/**
+ * LSPs are evil little bits of code that are allowed to inject into our
+ * networking stack by Windows. Once they have wormed into our process
+ * they gnaw at our innards until we crash. Here we force the buggers
+ * into the light by recording them in our crash information.
+ * We do the enumeration on a thread because I'm concerned about startup perf
+ * on machines with several LSPs.
+ */
+
+#include "nsICrashReporter.h"
+#include "nsISupportsImpl.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsWindowsHelpers.h"
+#include <windows.h>
+#include <rpc.h>
+#include <ws2spi.h>
+
+namespace mozilla {
+namespace crashreporter {
+
+class LSPAnnotationGatherer : public Runnable {
+ ~LSPAnnotationGatherer() {}
+
+ public:
+ LSPAnnotationGatherer() : Runnable("crashreporter::LSPAnnotationGatherer") {}
+ NS_DECL_NSIRUNNABLE
+
+ void Annotate();
+ nsCString mString;
+};
+
+void LSPAnnotationGatherer::Annotate() {
+ nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ bool enabled;
+ if (cr && NS_SUCCEEDED(cr->GetCrashReporterEnabled(&enabled)) && enabled) {
+ cr->AnnotateCrashReport("Winsock_LSP"_ns, mString);
+ }
+}
+
+NS_IMETHODIMP
+LSPAnnotationGatherer::Run() {
+ DWORD size = 0;
+ int err;
+ // Get the size of the buffer we need
+ if (SOCKET_ERROR != WSCEnumProtocols(nullptr, nullptr, &size, &err) ||
+ err != WSAENOBUFS) {
+ // Er, what?
+ MOZ_ASSERT_UNREACHABLE(
+ "WSCEnumProtocols succeeded when it should have "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ auto byteArray = MakeUnique<char[]>(size);
+ WSAPROTOCOL_INFOW* providers =
+ reinterpret_cast<WSAPROTOCOL_INFOW*>(byteArray.get());
+
+ int n = WSCEnumProtocols(nullptr, providers, &size, &err);
+ if (n == SOCKET_ERROR) {
+ // Lame. We provided the right size buffer; we'll just give up now.
+ NS_WARNING("Could not get LSP list");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString str;
+ for (int i = 0; i < n; i++) {
+ AppendUTF16toUTF8(nsDependentString(providers[i].szProtocol), str);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iVersion);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iAddressFamily);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iSocketType);
+ str.AppendLiteral(" : ");
+ str.AppendInt(providers[i].iProtocol);
+ str.AppendLiteral(" : ");
+ str.AppendPrintf("0x%lx", providers[i].dwServiceFlags1);
+ str.AppendLiteral(" : ");
+ str.AppendPrintf("0x%lx", providers[i].dwProviderFlags);
+ str.AppendLiteral(" : ");
+
+ wchar_t path[MAX_PATH];
+ int pathLen = MAX_PATH;
+ if (!WSCGetProviderPath(&providers[i].ProviderId, path, &pathLen, &err)) {
+ AppendUTF16toUTF8(nsDependentString(path), str);
+ }
+
+ str.AppendLiteral(" : ");
+ // Call WSCGetProviderInfo to obtain the category flags for this provider.
+ // When present, these flags inform Windows as to which order to chain the
+ // providers.
+ DWORD categoryInfo;
+ size_t categoryInfoSize = sizeof(categoryInfo);
+ if (!WSCGetProviderInfo(&providers[i].ProviderId, ProviderInfoLspCategories,
+ (PBYTE)&categoryInfo, &categoryInfoSize, 0, &err)) {
+ str.AppendPrintf("0x%lx", categoryInfo);
+ }
+
+ str.AppendLiteral(" : ");
+ if (providers[i].ProtocolChain.ChainLen <= BASE_PROTOCOL) {
+ // If we're dealing with a catalog entry that identifies an individual
+ // base or layer provider, log its provider GUID.
+ RPC_CSTR provIdStr = nullptr;
+ if (UuidToStringA(&providers[i].ProviderId, &provIdStr) == RPC_S_OK) {
+ str.Append(reinterpret_cast<char*>(provIdStr));
+ RpcStringFreeA(&provIdStr);
+ }
+ }
+
+ if (i + 1 != n) {
+ str.AppendLiteral(" \n ");
+ }
+ }
+
+ mString = str;
+ NS_DispatchToMainThread(
+ NewRunnableMethod("crashreporter::LSPAnnotationGatherer::Annotate", this,
+ &LSPAnnotationGatherer::Annotate));
+ return NS_OK;
+}
+
+void LSPAnnotate() {
+ nsCOMPtr<nsIRunnable> runnable(new LSPAnnotationGatherer());
+ NS_DispatchBackgroundTask(runnable.forget());
+}
+
+} // namespace crashreporter
+} // namespace mozilla
diff --git a/widget/windows/LegacyJumpListBuilder.cpp b/widget/windows/LegacyJumpListBuilder.cpp
new file mode 100644
index 0000000000..fbfe10f64b
--- /dev/null
+++ b/widget/windows/LegacyJumpListBuilder.cpp
@@ -0,0 +1,647 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "LegacyJumpListBuilder.h"
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsArrayUtils.h"
+#include "nsWidgetsCID.h"
+#include "WinTaskbar.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsIObserverService.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/mscom/ApartmentRegion.h"
+#include "mozilla/mscom/EnsureMTA.h"
+
+#include <shellapi.h>
+#include "WinUtils.h"
+
+using mozilla::dom::Promise;
+
+// The amount of time, in milliseconds, that our IO thread will stay alive after
+// the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+namespace mozilla {
+namespace widget {
+
+// defined in WinTaskbar.cpp
+extern const wchar_t* gMozillaJumpListIDGeneric;
+
+Atomic<bool> LegacyJumpListBuilder::sBuildingList(false);
+const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
+
+NS_IMPL_ISUPPORTS(LegacyJumpListBuilder, nsILegacyJumpListBuilder, nsIObserver)
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
+#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
+
+namespace detail {
+
+class DoneCommitListBuildCallback final : public nsIRunnable {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ public:
+ DoneCommitListBuildCallback(nsILegacyJumpListCommittedCallback* aCallback,
+ LegacyJumpListBuilder* aBuilder)
+ : mCallback(aCallback), mBuilder(aBuilder), mResult(false) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mCallback) {
+ Unused << mCallback->Done(mResult);
+ }
+ // Ensure we are releasing on the main thread.
+ Destroy();
+ return NS_OK;
+ }
+
+ void SetResult(bool aResult) { mResult = aResult; }
+
+ private:
+ ~DoneCommitListBuildCallback() {
+ // Destructor does not always call on the main thread.
+ MOZ_ASSERT(!mCallback);
+ MOZ_ASSERT(!mBuilder);
+ }
+
+ void Destroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback = nullptr;
+ mBuilder = nullptr;
+ }
+
+ // These two references MUST be released on the main thread.
+ RefPtr<nsILegacyJumpListCommittedCallback> mCallback;
+ RefPtr<LegacyJumpListBuilder> mBuilder;
+ bool mResult;
+};
+
+NS_IMPL_ISUPPORTS(DoneCommitListBuildCallback, nsIRunnable);
+
+} // namespace detail
+
+LegacyJumpListBuilder::LegacyJumpListBuilder()
+ : mMaxItems(0),
+ mHasCommit(false),
+ mMonitor("LegacyJumpListBuilderMonitor") {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Instantiate mJumpListMgr in the multithreaded apartment so that proxied
+ // calls on that object do not need to interact with the main thread's message
+ // pump.
+ mscom::EnsureMTA([&]() {
+ RefPtr<ICustomDestinationList> jumpListMgr;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(jumpListMgr));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ // Since we are accessing mJumpListMgr across different threads
+ // (ie, different apartments), mJumpListMgr must be an agile reference.
+ mJumpListMgr = mscom::AgileReference(jumpListMgr);
+ });
+
+ if (!mJumpListMgr) {
+ return;
+ }
+
+ // Make a lazy thread for any IO
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List",
+ LazyIdleThread::ManualShutdown);
+ Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
+ observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
+ }
+}
+
+LegacyJumpListBuilder::~LegacyJumpListBuilder() {
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::SetAppUserModelID(
+ const nsAString& aAppUserModelId) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mAppUserModelId.Assign(aAppUserModelId);
+ // MSIX packages explicitly do not support setting the appid from within
+ // the app, as it is set in the package manifest instead.
+ if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
+ jumpListMgr->SetAppID(mAppUserModelId.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::GetAvailable(int16_t* aAvailable) {
+ *aAvailable = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (mJumpListMgr) *aAvailable = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::GetIsListCommitted(bool* aCommit) {
+ *aCommit = mHasCommit;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::GetMaxListItems(int16_t* aMaxItems) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ *aMaxItems = 0;
+
+ if (sBuildingList) {
+ *aMaxItems = mMaxItems;
+ return NS_OK;
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ IObjectArray* objArray;
+ if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ *aMaxItems = mMaxItems;
+
+ if (objArray) objArray->Release();
+
+ jumpListMgr->AbortList();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::InitListBuild(JSContext* aCx,
+ Promise** aPromise) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>(
+ "InitListBuild", this, &LegacyJumpListBuilder::DoInitListBuild,
+ promise);
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+void LegacyJumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ HRESULT hr = E_UNEXPECTED;
+ auto errorHandler = MakeScopeExit([&aPromise, &hr]() {
+ if (SUCCEEDED(hr)) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildReject", [promise = std::move(aPromise)]() {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ }));
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return;
+ }
+
+ nsTArray<nsString> urisToRemove;
+ RefPtr<IObjectArray> objArray;
+ hr = jumpListMgr->BeginList(
+ &mMaxItems,
+ IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ // The returned objArray of removed items are for manually removed items.
+ // This does not return items which are removed because they were previously
+ // part of the jump list but are no longer part of the jump list.
+ sBuildingList = true;
+ RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildResolve", [urisToRemove = std::move(urisToRemove),
+ promise = std::move(aPromise)]() {
+ promise->MaybeResolve(urisToRemove);
+ }));
+}
+
+// Ensures that we have no old ICO files left in the jump list cache
+nsresult LegacyJumpListBuilder::RemoveIconCacheForAllItems() {
+ // Construct the path of our jump list cache
+ nsCOMPtr<nsIFile> jumpListCacheDir;
+ nsresult rv =
+ NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = jumpListCacheDir->AppendNative(
+ nsDependentCString(mozilla::widget::FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ nsCOMPtr<nsIFile> currFile;
+ if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
+ break;
+
+ nsAutoString path;
+ if (NS_FAILED(currFile->GetPath(path))) continue;
+
+ if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
+
+ // We found an ICO file that exists, so we should remove it
+ currFile->Remove(false);
+ }
+ } while (true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::AddListToBuild(int16_t aCatType,
+ nsIArray* items,
+ const nsAString& catName,
+ bool* _retval) {
+ nsresult rv;
+
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ switch (aCatType) {
+ case nsILegacyJumpListBuilder::JUMPLIST_CATEGORY_TASKS: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Build the list
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsILegacyJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ // Check for separators
+ if (IsSeparator(item)) {
+ RefPtr<IShellLinkW> link;
+ rv = LegacyJumpListSeparator::GetSeparator(link);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ continue;
+ }
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = LegacyJumpListShortcut::GetShellLink(item, link, mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AddUserTasks(pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+ return NS_OK;
+ } break;
+ case nsILegacyJumpListBuilder::JUMPLIST_CATEGORY_RECENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsILegacyJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsILegacyJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ if (catName.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsILegacyJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) continue;
+ switch (type) {
+ case nsILegacyJumpListItem::JUMPLIST_ITEM_SEPARATOR: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = LegacyJumpListSeparator::GetSeparator(shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsILegacyJumpListItem::JUMPLIST_ITEM_LINK: {
+ RefPtr<IShellItem2> shellItem;
+ rv = LegacyJumpListLink::GetShellItem(item, shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsILegacyJumpListItem::JUMPLIST_ITEM_SHORTCUT: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = LegacyJumpListShortcut::GetShellLink(item, shellItem,
+ mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ }
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AppendCategory(
+ reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+
+ // Get rid of the old icons
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+ } break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::AbortListBuild() {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ jumpListMgr->AbortList();
+ sBuildingList = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::CommitListBuild(
+ nsILegacyJumpListCommittedCallback* aCallback) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ // Also holds a strong reference to this to prevent use-after-free.
+ RefPtr<detail::DoneCommitListBuildCallback> callback =
+ new detail::DoneCommitListBuildCallback(aCallback, this);
+
+ // The builder has a strong reference in the callback already, so we do not
+ // need to do it for this runnable again.
+ RefPtr<nsIRunnable> event =
+ NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>(
+ "LegacyJumpListBuilder::DoCommitListBuild", this,
+ &LegacyJumpListBuilder::DoCommitListBuild, std::move(callback));
+ Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void LegacyJumpListBuilder::DoCommitListBuild(
+ RefPtr<detail::DoneCommitListBuildCallback> aCallback) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+ MOZ_ASSERT(aCallback);
+
+ HRESULT hr = E_UNEXPECTED;
+ auto onExit = MakeScopeExit([&hr, &aCallback]() {
+ // XXX We might want some specific error data here.
+ aCallback->SetResult(SUCCEEDED(hr));
+ Unused << NS_DispatchToMainThread(aCallback);
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return;
+ }
+
+ hr = jumpListMgr->CommitList();
+ sBuildingList = false;
+
+ if (SUCCEEDED(hr)) {
+ mHasCommit = true;
+ }
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::DeleteActiveList(bool* _retval) {
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr.Resolve();
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (SUCCEEDED(jumpListMgr->DeleteList(mAppUserModelId.get()))) {
+ *_retval = true;
+ }
+
+ return NS_OK;
+}
+
+/* internal */
+
+bool LegacyJumpListBuilder::IsSeparator(nsCOMPtr<nsILegacyJumpListItem>& item) {
+ int16_t type;
+ item->GetType(&type);
+ if (NS_FAILED(item->GetType(&type))) return false;
+
+ if (type == nsILegacyJumpListItem::JUMPLIST_ITEM_SEPARATOR) return true;
+ return false;
+}
+
+// RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
+// avoid unnecessary extra XPCOM incantations. For each object in the input
+// array, if it's an IShellLinkW, this deletes the cached icon and adds the
+// url param to a list of URLs to be removed from the places database.
+void LegacyJumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
+ IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Early return here just in case some versions of Windows don't populate this
+ if (!aObjArray) {
+ return;
+ }
+
+ uint32_t count = 0;
+ aObjArray->GetCount(&count);
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ RefPtr<IShellLinkW> pLink;
+
+ if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
+ static_cast<void**>(getter_AddRefs(pLink))))) {
+ continue;
+ }
+
+ wchar_t buf[MAX_PATH];
+ HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist && numArgs > 0) {
+ nsString spec(arglist[0]);
+ aURISpecs.AppendElement(std::move(spec));
+ ::LocalFree(arglist);
+ }
+ }
+
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ nsDependentString spec(buf);
+ DeleteIconFromDisk(spec);
+ }
+ }
+}
+
+void LegacyJumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ icoFile->Remove(false);
+ }
+}
+
+NS_IMETHODIMP LegacyJumpListBuilder::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ NS_ENSURE_ARG_POINTER(aTopic);
+ if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
+ }
+ mIOThread->Shutdown();
+ // Clear out mJumpListMgr, as MSCOM services won't be available soon.
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ mJumpListMgr = nullptr;
+ } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
+ nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
+ bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
+ if (!enabled) {
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
+ // Delete JumpListCache icons from Disk, if any.
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/LegacyJumpListBuilder.h b/widget/windows/LegacyJumpListBuilder.h
new file mode 100644
index 0000000000..1d96773c47
--- /dev/null
+++ b/widget/windows/LegacyJumpListBuilder.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __LegacyJumpListBuilder_h__
+#define __LegacyJumpListBuilder_h__
+
+#include <windows.h>
+
+// Needed for various com interfaces
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "nsString.h"
+
+#include "nsILegacyJumpListBuilder.h"
+#include "nsILegacyJumpListItem.h"
+#include "LegacyJumpListItem.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/mscom/AgileReference.h"
+#include "mozilla/ReentrantMonitor.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace detail {
+class DoneCommitListBuildCallback;
+} // namespace detail
+
+class LegacyJumpListBuilder : public nsILegacyJumpListBuilder,
+ public nsIObserver {
+ virtual ~LegacyJumpListBuilder();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILEGACYJUMPLISTBUILDER
+ NS_DECL_NSIOBSERVER
+
+ LegacyJumpListBuilder();
+
+ protected:
+ static Atomic<bool> sBuildingList;
+
+ private:
+ mscom::AgileReference<ICustomDestinationList> mJumpListMgr
+ MOZ_GUARDED_BY(mMonitor);
+ uint32_t mMaxItems MOZ_GUARDED_BY(mMonitor);
+ bool mHasCommit;
+ RefPtr<LazyIdleThread> mIOThread;
+ ReentrantMonitor mMonitor;
+ nsString mAppUserModelId;
+
+ bool IsSeparator(nsCOMPtr<nsILegacyJumpListItem>& item);
+ void RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray* aObjArray,
+ nsTArray<nsString>& aURISpecs);
+ void DeleteIconFromDisk(const nsAString& aPath);
+ nsresult RemoveIconCacheForAllItems();
+ void DoCommitListBuild(RefPtr<detail::DoneCommitListBuildCallback> aCallback);
+ void DoInitListBuild(RefPtr<dom::Promise>&& aPromise);
+
+ friend class WinTaskbar;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __LegacyJumpListBuilder_h__ */
diff --git a/widget/windows/LegacyJumpListItem.cpp b/widget/windows/LegacyJumpListItem.cpp
new file mode 100644
index 0000000000..3ffddf11f9
--- /dev/null
+++ b/widget/windows/LegacyJumpListItem.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "LegacyJumpListItem.h"
+
+#include <shellapi.h>
+#include <propvarutil.h>
+#include <propkey.h>
+
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsCExternalHandlerService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Preferences.h"
+#include "LegacyJumpListBuilder.h"
+#include "WinUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+// ISUPPORTS Impl's
+NS_IMPL_ISUPPORTS(LegacyJumpListItem, nsILegacyJumpListItem)
+
+NS_INTERFACE_MAP_BEGIN(LegacyJumpListSeparator)
+ NS_INTERFACE_MAP_ENTRY(nsILegacyJumpListSeparator)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsILegacyJumpListItem,
+ LegacyJumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LegacyJumpListItemBase)
+NS_INTERFACE_MAP_END
+NS_IMPL_ADDREF(LegacyJumpListSeparator)
+NS_IMPL_RELEASE(LegacyJumpListSeparator)
+
+NS_INTERFACE_MAP_BEGIN(LegacyJumpListLink)
+ NS_INTERFACE_MAP_ENTRY(nsILegacyJumpListLink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsILegacyJumpListItem,
+ LegacyJumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LegacyJumpListItemBase)
+NS_INTERFACE_MAP_END
+NS_IMPL_ADDREF(LegacyJumpListLink)
+NS_IMPL_RELEASE(LegacyJumpListLink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyJumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY(nsILegacyJumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsILegacyJumpListItem,
+ LegacyJumpListItemBase)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsILegacyJumpListShortcut)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyJumpListShortcut)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyJumpListShortcut)
+NS_IMPL_CYCLE_COLLECTION(LegacyJumpListShortcut, mHandlerApp)
+
+NS_IMETHODIMP LegacyJumpListItemBase::GetType(int16_t* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mItemType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListItemBase::Equals(nsILegacyJumpListItem* aItem,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ *aResult = false;
+
+ int16_t theType = nsILegacyJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ *aResult = true;
+
+ return NS_OK;
+}
+
+/* link impl. */
+
+NS_IMETHODIMP LegacyJumpListLink::GetUri(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = mURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListLink::SetUri(nsIURI* aURI) {
+ mURI = aURI;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListLink::SetUriTitle(const nsAString& aUriTitle) {
+ mUriTitle.Assign(aUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListLink::GetUriTitle(nsAString& aUriTitle) {
+ aUriTitle.Assign(mUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListLink::Equals(nsILegacyJumpListItem* aItem,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsILegacyJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ nsCOMPtr<nsILegacyJumpListLink> link = do_QueryInterface(aItem, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check the titles
+ nsAutoString title;
+ link->GetUriTitle(title);
+ if (!mUriTitle.Equals(title)) return NS_OK;
+
+ // Call the internal object's equals() method to check.
+ nsCOMPtr<nsIURI> theUri;
+ bool equals = false;
+ if (NS_SUCCEEDED(link->GetUri(getter_AddRefs(theUri)))) {
+ if (!theUri) {
+ if (!mURI) *aResult = true;
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(theUri->Equals(mURI, &equals)) && equals) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* shortcut impl. */
+
+NS_IMETHODIMP LegacyJumpListShortcut::GetApp(nsILocalHandlerApp** aApp) {
+ NS_IF_ADDREF(*aApp = mHandlerApp);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::SetApp(nsILocalHandlerApp* aApp) {
+ mHandlerApp = aApp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::GetIconIndex(int32_t* aIconIndex) {
+ NS_ENSURE_ARG_POINTER(aIconIndex);
+
+ *aIconIndex = mIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::SetIconIndex(int32_t aIconIndex) {
+ mIconIndex = aIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::GetFaviconPageUri(
+ nsIURI** aFaviconPageURI) {
+ NS_IF_ADDREF(*aFaviconPageURI = mFaviconPageURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::SetFaviconPageUri(
+ nsIURI* aFaviconPageURI) {
+ mFaviconPageURI = aFaviconPageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP LegacyJumpListShortcut::Equals(nsILegacyJumpListItem* aItem,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsILegacyJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType) return NS_OK;
+
+ nsCOMPtr<nsILegacyJumpListShortcut> shortcut = do_QueryInterface(aItem, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check the icon index
+ // int32_t idx;
+ // shortcut->GetIconIndex(&idx);
+ // if (mIconIndex != idx)
+ // return NS_OK;
+ // No need to check the icon page URI either
+
+ // Call the internal object's equals() method to check.
+ nsCOMPtr<nsILocalHandlerApp> theApp;
+ bool equals = false;
+ if (NS_SUCCEEDED(shortcut->GetApp(getter_AddRefs(theApp)))) {
+ if (!theApp) {
+ if (!mHandlerApp) *aResult = true;
+ return NS_OK;
+ }
+ if (NS_SUCCEEDED(theApp->Equals(mHandlerApp, &equals)) && equals) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+/* internal helpers */
+
+// (static) Creates a ShellLink that encapsulate a separator.
+nsresult LegacyJumpListSeparator::GetSeparator(
+ RefPtr<IShellLinkW>& aShellLink) {
+ HRESULT hr;
+ IShellLinkW* psl;
+
+ // Create a IShellLink.
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (LPVOID*)&psl);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ IPropertyStore* pPropStore = nullptr;
+ hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromBoolean(TRUE, &pv);
+
+ pPropStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+
+ aShellLink = dont_AddRef(psl);
+
+ return NS_OK;
+}
+
+// (static) Creates a ShellLink that encapsulate a shortcut to local apps.
+nsresult LegacyJumpListShortcut::GetShellLink(
+ nsCOMPtr<nsILegacyJumpListItem>& item, RefPtr<IShellLinkW>& aShellLink,
+ RefPtr<LazyIdleThread>& aIOThread) {
+ HRESULT hr;
+ IShellLinkW* psl;
+ nsresult rv;
+
+ // Shell links:
+ // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx
+ // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
+
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
+
+ if (type != nsILegacyJumpListItem::JUMPLIST_ITEM_SHORTCUT)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsILegacyJumpListShortcut> shortcut = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocalHandlerApp> handlerApp;
+ rv = shortcut->GetApp(getter_AddRefs(handlerApp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a IShellLink
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, (LPVOID*)&psl);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Retrieve the app path, title, description and optional command line args.
+ nsAutoString appPath, appTitle, appDescription, appArgs;
+ int32_t appIconIndex = 0;
+
+ // Path
+ nsCOMPtr<nsIFile> executable;
+ handlerApp->GetExecutable(getter_AddRefs(executable));
+
+ rv = executable->GetPath(appPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Command line parameters
+ uint32_t count = 0;
+ handlerApp->GetParameterCount(&count);
+ for (uint32_t idx = 0; idx < count; idx++) {
+ if (idx > 0) appArgs.Append(' ');
+ nsAutoString param;
+ rv = handlerApp->GetParameter(idx, param);
+ if (NS_FAILED(rv)) return rv;
+ appArgs.Append(param);
+ }
+
+ handlerApp->GetName(appTitle);
+ handlerApp->GetDetailedDescription(appDescription);
+
+ bool useUriIcon = false; // if we want to use the URI icon
+ bool usedUriIcon = false; // if we did use the URI icon
+ shortcut->GetIconIndex(&appIconIndex);
+
+ nsCOMPtr<nsIURI> iconUri;
+ rv = shortcut->GetFaviconPageUri(getter_AddRefs(iconUri));
+ if (NS_SUCCEEDED(rv) && iconUri) {
+ useUriIcon = true;
+ }
+
+ // Store the title of the app
+ if (appTitle.Length() > 0) {
+ IPropertyStore* pPropStore = nullptr;
+ hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromString(appTitle.get(), &pv);
+
+ pPropStore->SetValue(PKEY_Title, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+ }
+
+ // Store the rest of the params
+ psl->SetPath(appPath.get());
+ psl->SetDescription(appDescription.get());
+ psl->SetArguments(appArgs.get());
+
+ if (useUriIcon) {
+ nsString icoFilePath;
+ rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile(
+ iconUri, icoFilePath, aIOThread, false);
+ if (NS_SUCCEEDED(rv)) {
+ // Always use the first icon in the ICO file
+ // our encoded icon only has 1 resource
+ psl->SetIconLocation(icoFilePath.get(), 0);
+ usedUriIcon = true;
+ }
+ }
+
+ // We didn't use an ICO via URI so fall back to the app icon
+ if (!usedUriIcon) {
+ psl->SetIconLocation(appPath.get(), appIconIndex);
+ }
+
+ aShellLink = dont_AddRef(psl);
+
+ return NS_OK;
+}
+
+// If successful fills in the aSame parameter
+// aSame will be true if the path is in our icon cache
+static nsresult IsPathInOurIconCache(
+ nsCOMPtr<nsILegacyJumpListShortcut>& aShortcut, wchar_t* aPath,
+ bool* aSame) {
+ NS_ENSURE_ARG_POINTER(aPath);
+ NS_ENSURE_ARG_POINTER(aSame);
+
+ *aSame = false;
+
+ // Construct the path of our jump list cache
+ nsCOMPtr<nsIFile> jumpListCache;
+ nsresult rv =
+ NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = jumpListCache->AppendNative(
+ nsDependentCString(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString jumpListCachePath;
+ rv = jumpListCache->GetPath(jumpListCachePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> passedInFile =
+ do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(passedInFile, NS_ERROR_FAILURE);
+ nsAutoString passedInPath(aPath);
+ rv = passedInFile->InitWithPath(passedInPath);
+ nsCOMPtr<nsIFile> passedInParentFile;
+ passedInFile->GetParent(getter_AddRefs(passedInParentFile));
+ nsAutoString passedInParentPath;
+ rv = jumpListCache->GetPath(passedInParentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSame = jumpListCachePath.Equals(passedInParentPath);
+ return NS_OK;
+}
+
+// (static) For a given IShellLink, create and return a populated
+// nsILegacyJumpListShortcut.
+nsresult LegacyJumpListShortcut::GetJumpListShortcut(
+ IShellLinkW* pLink, nsCOMPtr<nsILegacyJumpListShortcut>& aShortcut) {
+ NS_ENSURE_ARG_POINTER(pLink);
+
+ nsresult rv;
+ HRESULT hres;
+
+ nsCOMPtr<nsILocalHandlerApp> handlerApp =
+ do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t buf[MAX_PATH];
+
+ // Path
+ hres = pLink->GetPath(buf, MAX_PATH, nullptr, SLGP_UNCPRIORITY);
+ if (FAILED(hres)) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIFile> file;
+ nsDependentString filepath(buf);
+ rv = NS_NewLocalFile(filepath, false, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = handlerApp->SetExecutable(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parameters
+ hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+ int32_t idx;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist) {
+ for (idx = 0; idx < numArgs; idx++) {
+ // szArglist[i] is null terminated
+ nsDependentString arg(arglist[idx]);
+ handlerApp->AppendParameter(arg);
+ }
+ ::LocalFree(arglist);
+ }
+ }
+
+ rv = aShortcut->SetApp(handlerApp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Icon index or file location
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ // XXX How do we handle converting local files to images here? Do we need
+ // to?
+ aShortcut->SetIconIndex(iconIdx);
+
+ // Obtain the local profile directory and construct the output icon file
+ // path We only set the Icon Uri if we're sure it was from our icon cache.
+ bool isInOurCache;
+ if (NS_SUCCEEDED(IsPathInOurIconCache(aShortcut, buf, &isInOurCache)) &&
+ isInOurCache) {
+ nsCOMPtr<nsIURI> iconUri;
+ nsAutoString path(buf);
+ rv = NS_NewURI(getter_AddRefs(iconUri), path);
+ if (NS_SUCCEEDED(rv)) {
+ aShortcut->SetFaviconPageUri(iconUri);
+ }
+ }
+ }
+
+ // Do we need the title and description? Probably not since handler app
+ // doesn't compare these in equals.
+
+ return NS_OK;
+}
+
+// (static) ShellItems are used to encapsulate links to things. We currently
+// only support URI links, but more support could be added, such as local file
+// and directory links.
+nsresult LegacyJumpListLink::GetShellItem(nsCOMPtr<nsILegacyJumpListItem>& item,
+ RefPtr<IShellItem2>& aShellItem) {
+ IShellItem2* psi = nullptr;
+ nsresult rv;
+
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
+
+ if (type != nsILegacyJumpListItem::JUMPLIST_ITEM_LINK)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsILegacyJumpListLink> link = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = link->GetUri(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the IShellItem
+ if (FAILED(SHCreateItemFromParsingName(NS_ConvertASCIItoUTF16(spec).get(),
+ nullptr, IID_PPV_ARGS(&psi)))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Set the title
+ nsAutoString linkTitle;
+ link->GetUriTitle(linkTitle);
+
+ IPropertyStore* pPropStore = nullptr;
+ HRESULT hres = psi->GetPropertyStore(GPS_DEFAULT, IID_IPropertyStore,
+ (void**)&pPropStore);
+ if (FAILED(hres)) return NS_ERROR_UNEXPECTED;
+
+ PROPVARIANT pv;
+ InitPropVariantFromString(linkTitle.get(), &pv);
+
+ // May fail due to shell item access permissions.
+ pPropStore->SetValue(PKEY_ItemName, pv);
+ pPropStore->Commit();
+ pPropStore->Release();
+
+ PropVariantClear(&pv);
+
+ aShellItem = dont_AddRef(psi);
+
+ return NS_OK;
+}
+
+// (static) For a given IShellItem, create and return a populated
+// nsILegacyJumpListLink.
+nsresult LegacyJumpListLink::GetJumpListLink(
+ IShellItem* pItem, nsCOMPtr<nsILegacyJumpListLink>& aLink) {
+ NS_ENSURE_ARG_POINTER(pItem);
+
+ // We assume for now these are URI links, but through properties we could
+ // query and create other types.
+ nsresult rv;
+ LPWSTR lpstrName = nullptr;
+
+ if (SUCCEEDED(pItem->GetDisplayName(SIGDN_URL, &lpstrName))) {
+ nsCOMPtr<nsIURI> uri;
+ nsAutoString spec(lpstrName);
+
+ rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(spec));
+ if (NS_FAILED(rv)) return NS_ERROR_INVALID_ARG;
+
+ aLink->SetUri(uri);
+
+ ::CoTaskMemFree(lpstrName);
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/LegacyJumpListItem.h b/widget/windows/LegacyJumpListItem.h
new file mode 100644
index 0000000000..bcef82d349
--- /dev/null
+++ b/widget/windows/LegacyJumpListItem.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __LegacyJumpListItem_h__
+#define __LegacyJumpListItem_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsILegacyJumpListItem.h" // defines nsILegacyJumpListItem
+#include "nsIMIMEInfo.h" // defines nsILocalHandlerApp
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsICryptoHash.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace widget {
+
+class LegacyJumpListItemBase : public nsILegacyJumpListItem {
+ public:
+ LegacyJumpListItemBase()
+ : mItemType(nsILegacyJumpListItem::JUMPLIST_ITEM_EMPTY) {}
+
+ explicit LegacyJumpListItemBase(int32_t type) : mItemType(type) {}
+
+ NS_DECL_NSILEGACYJUMPLISTITEM
+
+ static const char kJumpListCacheDir[];
+
+ protected:
+ virtual ~LegacyJumpListItemBase() {}
+
+ short Type() { return mItemType; }
+ short mItemType;
+};
+
+class LegacyJumpListItem : public LegacyJumpListItemBase {
+ ~LegacyJumpListItem() {}
+
+ public:
+ using LegacyJumpListItemBase::LegacyJumpListItemBase;
+
+ NS_DECL_ISUPPORTS
+};
+
+class LegacyJumpListSeparator : public LegacyJumpListItemBase,
+ public nsILegacyJumpListSeparator {
+ ~LegacyJumpListSeparator() {}
+
+ public:
+ LegacyJumpListSeparator()
+ : LegacyJumpListItemBase(nsILegacyJumpListItem::JUMPLIST_ITEM_SEPARATOR) {
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSILEGACYJUMPLISTITEM(LegacyJumpListItemBase::)
+
+ static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink);
+};
+
+class LegacyJumpListLink : public LegacyJumpListItemBase,
+ public nsILegacyJumpListLink {
+ ~LegacyJumpListLink() {}
+
+ public:
+ LegacyJumpListLink()
+ : LegacyJumpListItemBase(nsILegacyJumpListItem::JUMPLIST_ITEM_LINK) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD GetType(int16_t* aType) override {
+ return LegacyJumpListItemBase::GetType(aType);
+ }
+ NS_IMETHOD Equals(nsILegacyJumpListItem* item, bool* _retval) override;
+ NS_DECL_NSILEGACYJUMPLISTLINK
+
+ static nsresult GetShellItem(nsCOMPtr<nsILegacyJumpListItem>& item,
+ RefPtr<IShellItem2>& aShellItem);
+ static nsresult GetJumpListLink(IShellItem* pItem,
+ nsCOMPtr<nsILegacyJumpListLink>& aLink);
+
+ protected:
+ nsString mUriTitle;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsICryptoHash> mCryptoHash;
+};
+
+class LegacyJumpListShortcut : public LegacyJumpListItemBase,
+ public nsILegacyJumpListShortcut {
+ ~LegacyJumpListShortcut() {}
+
+ public:
+ LegacyJumpListShortcut()
+ : LegacyJumpListItemBase(nsILegacyJumpListItem::JUMPLIST_ITEM_SHORTCUT) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(LegacyJumpListShortcut,
+ LegacyJumpListItemBase)
+ NS_IMETHOD GetType(int16_t* aType) override {
+ return LegacyJumpListItemBase::GetType(aType);
+ }
+ NS_IMETHOD Equals(nsILegacyJumpListItem* item, bool* _retval) override;
+ NS_DECL_NSILEGACYJUMPLISTSHORTCUT
+
+ static nsresult GetShellLink(nsCOMPtr<nsILegacyJumpListItem>& item,
+ RefPtr<IShellLinkW>& aShellLink,
+ RefPtr<LazyIdleThread>& aIOThread);
+ static nsresult GetJumpListShortcut(
+ IShellLinkW* pLink, nsCOMPtr<nsILegacyJumpListShortcut>& aShortcut);
+ static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile);
+
+ protected:
+ int32_t mIconIndex;
+ nsCOMPtr<nsIURI> mFaviconPageURI;
+ nsCOMPtr<nsILocalHandlerApp> mHandlerApp;
+
+ bool ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __LegacyJumpListItem_h__ */
diff --git a/widget/windows/MediaKeysEventSourceFactory.cpp b/widget/windows/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..525aab19c6
--- /dev/null
+++ b/widget/windows/MediaKeysEventSourceFactory.cpp
@@ -0,0 +1,20 @@
+/* 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/. */
+
+#include "MediaKeysEventSourceFactory.h"
+#include "WindowsSMTCProvider.h"
+
+namespace mozilla {
+namespace widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+#ifndef __MINGW32__
+ return new WindowsSMTCProvider();
+#else
+ return nullptr; // MinGW doesn't support the required Windows 8.1+ APIs
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKInputPaneManager.cpp b/widget/windows/OSKInputPaneManager.cpp
new file mode 100644
index 0000000000..293a84cd28
--- /dev/null
+++ b/widget/windows/OSKInputPaneManager.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#define NTDDI_VERSION NTDDI_WIN10_RS1
+
+#include "OSKInputPaneManager.h"
+#include "nsDebug.h"
+
+#ifndef __MINGW32__
+# include <inputpaneinterop.h>
+# include <windows.ui.viewmanagement.h>
+# include <wrl.h>
+
+using namespace ABI::Windows::UI::ViewManagement;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+#endif
+
+namespace mozilla {
+namespace widget {
+
+#ifndef __MINGW32__
+static ComPtr<IInputPane2> GetInputPane(HWND aHwnd) {
+ ComPtr<IInputPaneInterop> inputPaneInterop;
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_InputPane).Get(),
+ IID_PPV_ARGS(&inputPaneInterop));
+ if (NS_WARN_IF(FAILED(hr))) {
+ return nullptr;
+ }
+
+ ComPtr<IInputPane> inputPane;
+ hr = inputPaneInterop->GetForWindow(aHwnd, IID_PPV_ARGS(&inputPane));
+ if (NS_WARN_IF(FAILED(hr))) {
+ return nullptr;
+ }
+
+ ComPtr<IInputPane2> inputPane2;
+ inputPane.As(&inputPane2);
+ return inputPane2;
+}
+
+# ifdef DEBUG
+static bool IsInputPaneVisible(ComPtr<IInputPane2>& aInputPane2) {
+ ComPtr<IInputPaneControl> inputPaneControl;
+ aInputPane2.As(&inputPaneControl);
+ if (NS_WARN_IF(!inputPaneControl)) {
+ return false;
+ }
+ boolean visible = false;
+ inputPaneControl->get_Visible(&visible);
+ return visible;
+}
+
+static bool IsForegroundApp() {
+ HWND foregroundWnd = ::GetForegroundWindow();
+ if (!foregroundWnd) {
+ return false;
+ }
+ DWORD pid;
+ ::GetWindowThreadProcessId(foregroundWnd, &pid);
+ return pid == ::GetCurrentProcessId();
+}
+# endif
+#endif
+
+// static
+void OSKInputPaneManager::ShowOnScreenKeyboard(HWND aWnd) {
+#ifndef __MINGW32__
+ ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd);
+ if (!inputPane2) {
+ return;
+ }
+ boolean result;
+ inputPane2->TryShow(&result);
+ NS_WARNING_ASSERTION(
+ result || !IsForegroundApp() || IsInputPaneVisible(inputPane2),
+ "IInputPane2::TryShow is failure");
+#endif
+}
+
+// static
+void OSKInputPaneManager::DismissOnScreenKeyboard(HWND aWnd) {
+#ifndef __MINGW32__
+ ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd);
+ if (!inputPane2) {
+ return;
+ }
+ boolean result;
+ inputPane2->TryHide(&result);
+ NS_WARNING_ASSERTION(
+ result || !IsForegroundApp() || !IsInputPaneVisible(inputPane2),
+ "IInputPane2::TryHide is failure");
+#endif
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKInputPaneManager.h b/widget/windows/OSKInputPaneManager.h
new file mode 100644
index 0000000000..421a5f1e02
--- /dev/null
+++ b/widget/windows/OSKInputPaneManager.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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_OSKInputPaneManager_h
+#define mozilla_widget_OSKInputPaneManager_h
+
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class OSKInputPaneManager final {
+ public:
+ static void ShowOnScreenKeyboard(HWND aHwnd);
+ static void DismissOnScreenKeyboard(HWND aHwnd);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_OSKInputPaneManager_h
diff --git a/widget/windows/OSKTabTipManager.cpp b/widget/windows/OSKTabTipManager.cpp
new file mode 100644
index 0000000000..b7b06c08d1
--- /dev/null
+++ b/widget/windows/OSKTabTipManager.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "OSKTabTipManager.h"
+
+#include "mozilla/Preferences.h"
+#include "nsDebug.h"
+#include "mozilla/widget/WinRegistry.h"
+
+#include <shellapi.h>
+#include <shlobj.h>
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Get the HWND for the on-screen keyboard, if it's up. Only
+ * allowed for Windows 8 and higher.
+ */
+static HWND GetOnScreenKeyboardWindow() {
+ const wchar_t kOSKClassName[] = L"IPTip_Main_Window";
+ HWND osk = ::FindWindowW(kOSKClassName, nullptr);
+ if (::IsWindow(osk) && ::IsWindowEnabled(osk) && ::IsWindowVisible(osk)) {
+ return osk;
+ }
+ return nullptr;
+}
+
+// static
+void OSKTabTipManager::ShowOnScreenKeyboard() {
+ const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
+
+ if (GetOnScreenKeyboardWindow()) {
+ return;
+ }
+
+ nsAutoString cachedPath;
+ nsresult result = Preferences::GetString(kOskPathPrefName, cachedPath);
+ if (NS_FAILED(result) || cachedPath.IsEmpty()) {
+ wchar_t path[MAX_PATH];
+ // The path to TabTip.exe is defined at the following registry key.
+ // This is pulled out of the 64-bit registry hive directly.
+ constexpr auto kRegKeyName =
+ u"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32"_ns;
+ if (!WinRegistry::GetString(HKEY_LOCAL_MACHINE, kRegKeyName, u""_ns, path,
+ WinRegistry::kLegacyWinUtilsStringFlags)) {
+ return;
+ }
+
+ std::wstring wstrpath(path);
+ // The path provided by the registry will often contain
+ // %CommonProgramFiles%, which will need to be replaced if it is present.
+ size_t commonProgramFilesOffset = wstrpath.find(L"%CommonProgramFiles%");
+ if (commonProgramFilesOffset != std::wstring::npos) {
+ // The path read from the registry contains the %CommonProgramFiles%
+ // environment variable prefix. On 64 bit Windows the
+ // SHGetKnownFolderPath function returns the common program files path
+ // with the X86 suffix for the FOLDERID_ProgramFilesCommon value.
+ // To get the correct path to TabTip.exe we first read the environment
+ // variable CommonProgramW6432 which points to the desired common
+ // files path. Failing that we fallback to the SHGetKnownFolderPath API.
+ // We then replace the %CommonProgramFiles% value with the actual common
+ // files path found in the process.
+ std::wstring commonProgramFilesPath;
+ std::vector<wchar_t> commonProgramFilesPathW6432;
+ DWORD bufferSize =
+ ::GetEnvironmentVariableW(L"CommonProgramW6432", nullptr, 0);
+ if (bufferSize) {
+ commonProgramFilesPathW6432.resize(bufferSize);
+ ::GetEnvironmentVariableW(L"CommonProgramW6432",
+ commonProgramFilesPathW6432.data(),
+ bufferSize);
+ commonProgramFilesPath =
+ std::wstring(commonProgramFilesPathW6432.data());
+ } else {
+ PWSTR path = nullptr;
+ HRESULT hres = SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0,
+ nullptr, &path);
+ if (FAILED(hres) || !path) {
+ return;
+ }
+ commonProgramFilesPath =
+ static_cast<const wchar_t*>(nsDependentString(path).get());
+ ::CoTaskMemFree(path);
+ }
+ wstrpath.replace(commonProgramFilesOffset,
+ wcslen(L"%CommonProgramFiles%"), commonProgramFilesPath);
+ }
+
+ cachedPath.Assign(wstrpath.data());
+ Preferences::SetString(kOskPathPrefName, cachedPath);
+ }
+
+ const char16_t* cachedPathPtr;
+ cachedPath.GetData(&cachedPathPtr);
+ ShellExecuteW(nullptr, L"", char16ptr_t(cachedPathPtr), nullptr, nullptr,
+ SW_SHOW);
+}
+
+// static
+void OSKTabTipManager::DismissOnScreenKeyboard() {
+ HWND osk = GetOnScreenKeyboardWindow();
+ if (osk) {
+ ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKTabTipManager.h b/widget/windows/OSKTabTipManager.h
new file mode 100644
index 0000000000..231403375b
--- /dev/null
+++ b/widget/windows/OSKTabTipManager.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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 OSKTabTipManager_h
+#define OSKTabTipManager_h
+
+namespace mozilla {
+namespace widget {
+
+class OSKTabTipManager final {
+ public:
+ static void ShowOnScreenKeyboard();
+ static void DismissOnScreenKeyboard();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // OSKTabTipManager_h
diff --git a/widget/windows/OSKVRManager.cpp b/widget/windows/OSKVRManager.cpp
new file mode 100644
index 0000000000..158a0f3561
--- /dev/null
+++ b/widget/windows/OSKVRManager.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "OSKVRManager.h"
+
+#include "FxRWindowManager.h"
+#include "VRShMem.h"
+#include "moz_external_vr.h"
+
+namespace mozilla {
+namespace widget {
+
+// static
+void OSKVRManager::ShowOnScreenKeyboard() {
+#ifdef NIGHTLY_BUILD
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+ mozilla::gfx::VRFxEventState::FOCUS);
+#endif // NIGHTLY_BUILD
+}
+
+// static
+void OSKVRManager::DismissOnScreenKeyboard() {
+#ifdef NIGHTLY_BUILD
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+ mozilla::gfx::VRFxEventState::BLUR);
+#endif // NIGHTLY_BUILD
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/OSKVRManager.h b/widget/windows/OSKVRManager.h
new file mode 100644
index 0000000000..7c5a1afa07
--- /dev/null
+++ b/widget/windows/OSKVRManager.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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 OSKVRManager_h
+#define OSKVRManager_h
+
+namespace mozilla {
+namespace widget {
+
+class OSKVRManager final {
+ public:
+ static void ShowOnScreenKeyboard();
+ static void DismissOnScreenKeyboard();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // OSKVRManager_h
diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..89ea575a47
--- /dev/null
+++ b/widget/windows/PCompositorWidget.ipdl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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/. */
+
+include protocol PCompositorBridge;
+
+include "mozilla/dom/TabMessageUtils.h";
+include "mozilla/widget/WidgetMessageUtils.h";
+
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
+using mozilla::widget::TransparencyMode from "nsIWidget.h";
+using nsSizeMode from "nsIWidget.h";
+
+namespace mozilla {
+namespace widget {
+
+struct RemoteBackbufferHandles {
+ FileDescriptor fileMapping;
+ FileDescriptor requestReadyEvent;
+ FileDescriptor responseReadyEvent;
+};
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ sync Initialize(RemoteBackbufferHandles aRemoteHandles);
+
+ sync EnterPresentLock();
+ sync LeavePresentLock();
+ async UpdateTransparency(TransparencyMode aMode);
+ async NotifyVisibilityUpdated(nsSizeMode aSizeMode, bool aIsFullyOccluded);
+ sync ClearTransparentWindow();
+ async __delete__();
+
+child:
+ async ObserveVsync();
+ async UnobserveVsync();
+ async UpdateCompositorWnd(WindowsHandle aCompositorWnd, WindowsHandle aParentWnd)
+ returns (bool success);
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..67dd92b802
--- /dev/null
+++ b/widget/windows/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* 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/. */
+
+include "mozilla/dom/TabMessageUtils.h";
+include "mozilla/widget/WidgetMessageUtils.h";
+
+include HeadlessWidgetTypes;
+
+using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
+using nsSizeMode from "nsIWidgetListener.h";
+using mozilla::widget::TransparencyMode from "nsIWidget.h";
+
+namespace mozilla {
+namespace widget {
+
+struct WinCompositorWidgetInitData
+{
+ WindowsHandle hWnd;
+ uintptr_t widgetKey;
+ TransparencyMode transparencyMode;
+ nsSizeMode sizeMode;
+};
+
+union CompositorWidgetInitData
+{
+ WinCompositorWidgetInitData;
+ HeadlessCompositorWidgetInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/RemoteBackbuffer.cpp b/widget/windows/RemoteBackbuffer.cpp
new file mode 100644
index 0000000000..56a8bae0fe
--- /dev/null
+++ b/widget/windows/RemoteBackbuffer.cpp
@@ -0,0 +1,713 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "RemoteBackbuffer.h"
+#include "GeckoProfiler.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Span.h"
+#include "mozilla/gfx/Point.h"
+#include "WinUtils.h"
+#include <algorithm>
+#include <type_traits>
+
+namespace mozilla {
+namespace widget {
+namespace remote_backbuffer {
+
+// This number can be adjusted as a time-memory tradeoff
+constexpr uint8_t kMaxDirtyRects = 8;
+
+struct IpcSafeRect {
+ explicit IpcSafeRect(const gfx::IntRect& aRect)
+ : x(aRect.x), y(aRect.y), width(aRect.width), height(aRect.height) {}
+ int32_t x;
+ int32_t y;
+ int32_t width;
+ int32_t height;
+};
+
+enum class ResponseResult {
+ Unknown,
+ Error,
+ BorrowSuccess,
+ BorrowSameBuffer,
+ PresentSuccess
+};
+
+enum class SharedDataType {
+ BorrowRequest,
+ BorrowRequestAllowSameBuffer,
+ BorrowResponse,
+ PresentRequest,
+ PresentResponse
+};
+
+struct BorrowResponseData {
+ ResponseResult result;
+ int32_t width;
+ int32_t height;
+ HANDLE fileMapping;
+};
+
+struct PresentRequestData {
+ uint8_t lenDirtyRects;
+ IpcSafeRect dirtyRects[kMaxDirtyRects];
+};
+
+struct PresentResponseData {
+ ResponseResult result;
+};
+
+struct SharedData {
+ SharedDataType dataType;
+ union {
+ BorrowResponseData borrowResponse;
+ PresentRequestData presentRequest;
+ PresentResponseData presentResponse;
+ } data;
+};
+
+static_assert(std::is_trivially_copyable<SharedData>::value &&
+ std::is_standard_layout<SharedData>::value,
+ "SharedData must be safe to pass over IPC boundaries");
+
+class SharedImage {
+ public:
+ SharedImage()
+ : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {}
+
+ ~SharedImage() {
+ if (mPixelData) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+ }
+
+ bool Initialize(int32_t aWidth, int32_t aHeight) {
+ MOZ_ASSERT(aWidth > 0);
+ MOZ_ASSERT(aHeight > 0);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ DWORD bufferSize = static_cast<DWORD>(mHeight * GetStride());
+
+ mFileMapping = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE,
+ 0 /*sizeHigh*/, bufferSize, nullptr /*name*/);
+ if (!mFileMapping) {
+ return false;
+ }
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr);
+
+ return true;
+ }
+
+ bool InitializeRemote(int32_t aWidth, int32_t aHeight, HANDLE aFileMapping) {
+ MOZ_ASSERT(aWidth > 0);
+ MOZ_ASSERT(aHeight > 0);
+ MOZ_ASSERT(aFileMapping);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mFileMapping = aFileMapping;
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr);
+
+ return true;
+ }
+
+ HBITMAP CreateDIBSection() {
+ BITMAPINFO bitmapInfo = {};
+ bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
+ bitmapInfo.bmiHeader.biWidth = mWidth;
+ bitmapInfo.bmiHeader.biHeight = -mHeight;
+ bitmapInfo.bmiHeader.biPlanes = 1;
+ bitmapInfo.bmiHeader.biBitCount = 32;
+ bitmapInfo.bmiHeader.biCompression = BI_RGB;
+ void* dummy = nullptr;
+ return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo,
+ DIB_RGB_COLORS, &dummy, mFileMapping,
+ 0 /*offset*/);
+ }
+
+ HANDLE CreateRemoteFileMapping(HANDLE aTargetProcess) {
+ MOZ_ASSERT(aTargetProcess);
+
+ HANDLE fileMapping = nullptr;
+ if (!::DuplicateHandle(GetCurrentProcess(), mFileMapping, aTargetProcess,
+ &fileMapping, 0 /*desiredAccess*/,
+ FALSE /*inheritHandle*/, DUPLICATE_SAME_ACCESS)) {
+ return nullptr;
+ }
+ return fileMapping;
+ }
+
+ already_AddRefed<gfx::DrawTarget> CreateDrawTarget() {
+ return gfx::Factory::CreateDrawTargetForData(
+ gfx::BackendType::CAIRO, mPixelData, gfx::IntSize(mWidth, mHeight),
+ GetStride(), gfx::SurfaceFormat::B8G8R8A8);
+ }
+
+ void CopyPixelsFrom(const SharedImage& other) {
+ const unsigned char* src = other.mPixelData;
+ unsigned char* dst = mPixelData;
+
+ int32_t width = std::min(mWidth, other.mWidth);
+ int32_t height = std::min(mHeight, other.mHeight);
+
+ for (int32_t row = 0; row < height; ++row) {
+ memcpy(dst, src, static_cast<uint32_t>(width * kBytesPerPixel));
+ src += other.GetStride();
+ dst += GetStride();
+ }
+ }
+
+ int32_t GetWidth() { return mWidth; }
+
+ int32_t GetHeight() { return mHeight; }
+
+ SharedImage(const SharedImage&) = delete;
+ SharedImage(SharedImage&&) = delete;
+ SharedImage& operator=(const SharedImage&) = delete;
+ SharedImage& operator=(SharedImage&&) = delete;
+
+ private:
+ static constexpr int32_t kBytesPerPixel = 4;
+
+ int32_t GetStride() const {
+ // DIB requires 32-bit row alignment
+ return (((mWidth * kBytesPerPixel) + 3) / 4) * 4;
+ }
+
+ int32_t mWidth;
+ int32_t mHeight;
+ HANDLE mFileMapping;
+ unsigned char* mPixelData;
+};
+
+class PresentableSharedImage {
+ public:
+ PresentableSharedImage()
+ : mSharedImage(),
+ mDeviceContext(nullptr),
+ mDIBSection(nullptr),
+ mSavedObject(nullptr) {}
+
+ ~PresentableSharedImage() {
+ if (mSavedObject) {
+ MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext, mSavedObject));
+ }
+
+ if (mDIBSection) {
+ MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection));
+ }
+
+ if (mDeviceContext) {
+ MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext));
+ }
+ }
+
+ bool Initialize(int32_t aWidth, int32_t aHeight) {
+ if (!mSharedImage.Initialize(aWidth, aHeight)) {
+ return false;
+ }
+
+ mDeviceContext = ::CreateCompatibleDC(nullptr);
+ if (!mDeviceContext) {
+ return false;
+ }
+
+ mDIBSection = mSharedImage.CreateDIBSection();
+ if (!mDIBSection) {
+ return false;
+ }
+
+ mSavedObject = ::SelectObject(mDeviceContext, mDIBSection);
+ if (!mSavedObject) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool PresentToWindow(HWND aWindowHandle, TransparencyMode aTransparencyMode,
+ Span<const IpcSafeRect> aDirtyRects) {
+ if (aTransparencyMode == TransparencyMode::Transparent) {
+ // If our window is a child window or a child-of-a-child, the window
+ // that needs to be updated is the top level ancestor of the tree
+ HWND topLevelWindow = WinUtils::GetTopLevelHWND(aWindowHandle, true);
+ MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow, GWL_EXSTYLE) &
+ WS_EX_LAYERED);
+
+ BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ POINT srcPos = {0, 0};
+ RECT clientRect = {};
+ if (!::GetClientRect(aWindowHandle, &clientRect)) {
+ return false;
+ }
+ MOZ_ASSERT(clientRect.left == 0);
+ MOZ_ASSERT(clientRect.top == 0);
+ int32_t width = clientRect.right;
+ int32_t height = clientRect.bottom;
+ SIZE winSize = {width, height};
+ // Window resize could cause the client area to be different than
+ // mSharedImage's size. If the client area doesn't match,
+ // PresentToWindow() returns false without calling UpdateLayeredWindow().
+ // Another call to UpdateLayeredWindow() will follow shortly, since the
+ // resize will eventually force the backbuffer to repaint itself again.
+ // When client area is larger than mSharedImage's size,
+ // UpdateLayeredWindow() draws the window completely invisible. But it
+ // does not return false.
+ if (width != mSharedImage.GetWidth() ||
+ height != mSharedImage.GetHeight()) {
+ return false;
+ }
+
+ return !!::UpdateLayeredWindow(
+ topLevelWindow, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize,
+ mDeviceContext, &srcPos, 0 /*colorKey*/, &bf, ULW_ALPHA);
+ }
+
+ gfx::IntRect sharedImageRect{0, 0, mSharedImage.GetWidth(),
+ mSharedImage.GetHeight()};
+
+ bool result = true;
+
+ HDC windowDC = ::GetDC(aWindowHandle);
+ if (!windowDC) {
+ return false;
+ }
+
+ for (auto& ipcDirtyRect : aDirtyRects) {
+ gfx::IntRect dirtyRect{ipcDirtyRect.x, ipcDirtyRect.y, ipcDirtyRect.width,
+ ipcDirtyRect.height};
+ gfx::IntRect bltRect = dirtyRect.Intersect(sharedImageRect);
+
+ if (!::BitBlt(windowDC, bltRect.x /*dstX*/, bltRect.y /*dstY*/,
+ bltRect.width, bltRect.height, mDeviceContext,
+ bltRect.x /*srcX*/, bltRect.y /*srcY*/, SRCCOPY)) {
+ result = false;
+ break;
+ }
+ }
+
+ MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle, windowDC));
+
+ return result;
+ }
+
+ HANDLE CreateRemoteFileMapping(HANDLE aTargetProcess) {
+ return mSharedImage.CreateRemoteFileMapping(aTargetProcess);
+ }
+
+ already_AddRefed<gfx::DrawTarget> CreateDrawTarget() {
+ return mSharedImage.CreateDrawTarget();
+ }
+
+ void CopyPixelsFrom(const PresentableSharedImage& other) {
+ mSharedImage.CopyPixelsFrom(other.mSharedImage);
+ }
+
+ int32_t GetWidth() { return mSharedImage.GetWidth(); }
+
+ int32_t GetHeight() { return mSharedImage.GetHeight(); }
+
+ PresentableSharedImage(const PresentableSharedImage&) = delete;
+ PresentableSharedImage(PresentableSharedImage&&) = delete;
+ PresentableSharedImage& operator=(const PresentableSharedImage&) = delete;
+ PresentableSharedImage& operator=(PresentableSharedImage&&) = delete;
+
+ private:
+ SharedImage mSharedImage;
+ HDC mDeviceContext;
+ HBITMAP mDIBSection;
+ HGDIOBJ mSavedObject;
+};
+
+Provider::Provider()
+ : mWindowHandle(nullptr),
+ mTargetProcess(nullptr),
+ mFileMapping(nullptr),
+ mRequestReadyEvent(nullptr),
+ mResponseReadyEvent(nullptr),
+ mSharedDataPtr(nullptr),
+ mStopServiceThread(false),
+ mServiceThread(nullptr),
+ mBackbuffer() {}
+
+Provider::~Provider() {
+ mBackbuffer.reset();
+
+ if (mServiceThread) {
+ mStopServiceThread = true;
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ MOZ_ALWAYS_TRUE(PR_JoinThread(mServiceThread) == PR_SUCCESS);
+ }
+
+ if (mSharedDataPtr) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr));
+ }
+
+ if (mResponseReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent));
+ }
+
+ if (mRequestReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+
+ if (mTargetProcess) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mTargetProcess));
+ }
+}
+
+bool Provider::Initialize(HWND aWindowHandle, DWORD aTargetProcessId,
+ TransparencyMode aTransparencyMode) {
+ MOZ_ASSERT(aWindowHandle);
+ MOZ_ASSERT(aTargetProcessId);
+
+ mWindowHandle = aWindowHandle;
+
+ mTargetProcess = ::OpenProcess(PROCESS_DUP_HANDLE, FALSE /*inheritHandle*/,
+ aTargetProcessId);
+ if (!mTargetProcess) {
+ return false;
+ }
+
+ mFileMapping = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, 0 /*sizeHigh*/,
+ static_cast<DWORD>(sizeof(SharedData)), nullptr /*name*/);
+ if (!mFileMapping) {
+ return false;
+ }
+
+ mRequestReadyEvent =
+ ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/,
+ FALSE /*initialState*/, nullptr /*name*/);
+ if (!mRequestReadyEvent) {
+ return false;
+ }
+
+ mResponseReadyEvent =
+ ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/,
+ FALSE /*initialState*/, nullptr /*name*/);
+ if (!mResponseReadyEvent) {
+ return false;
+ }
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr);
+
+ mStopServiceThread = false;
+
+ // Use a raw NSPR OS-level thread here instead of nsThread because we are
+ // performing low-level synchronization across processes using Win32 Events,
+ // and nsThread is designed around an incompatible "in-process task queue"
+ // model
+ mServiceThread = PR_CreateThread(
+ PR_USER_THREAD, [](void* p) { static_cast<Provider*>(p)->ThreadMain(); },
+ this, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
+ 0 /*default stack size*/);
+ if (!mServiceThread) {
+ return false;
+ }
+
+ mTransparencyMode = uint32_t(aTransparencyMode);
+
+ return true;
+}
+
+Maybe<RemoteBackbufferHandles> Provider::CreateRemoteHandles() {
+ return Some(
+ RemoteBackbufferHandles(ipc::FileDescriptor(mFileMapping),
+ ipc::FileDescriptor(mRequestReadyEvent),
+ ipc::FileDescriptor(mResponseReadyEvent)));
+}
+
+void Provider::UpdateTransparencyMode(TransparencyMode aTransparencyMode) {
+ mTransparencyMode = uint32_t(aTransparencyMode);
+}
+
+void Provider::ThreadMain() {
+ AUTO_PROFILER_REGISTER_THREAD("RemoteBackbuffer");
+ NS_SetCurrentThreadName("RemoteBackbuffer");
+
+ while (true) {
+ {
+ AUTO_PROFILER_THREAD_SLEEP;
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+ }
+
+ if (mStopServiceThread) {
+ break;
+ }
+
+ switch (mSharedDataPtr->dataType) {
+ case SharedDataType::BorrowRequest:
+ case SharedDataType::BorrowRequestAllowSameBuffer: {
+ BorrowResponseData responseData = {};
+
+ HandleBorrowRequest(&responseData,
+ mSharedDataPtr->dataType ==
+ SharedDataType::BorrowRequestAllowSameBuffer);
+
+ mSharedDataPtr->dataType = SharedDataType::BorrowResponse;
+ mSharedDataPtr->data.borrowResponse = responseData;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent));
+
+ break;
+ }
+ case SharedDataType::PresentRequest: {
+ PresentRequestData requestData = mSharedDataPtr->data.presentRequest;
+ PresentResponseData responseData = {};
+
+ HandlePresentRequest(requestData, &responseData);
+
+ mSharedDataPtr->dataType = SharedDataType::PresentResponse;
+ mSharedDataPtr->data.presentResponse = responseData;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent));
+
+ break;
+ }
+ default:
+ break;
+ };
+ }
+}
+
+void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData,
+ bool aAllowSameBuffer) {
+ MOZ_ASSERT(aResponseData);
+
+ aResponseData->result = ResponseResult::Error;
+
+ RECT clientRect = {};
+ if (!::GetClientRect(mWindowHandle, &clientRect)) {
+ return;
+ }
+
+ MOZ_ASSERT(clientRect.left == 0);
+ MOZ_ASSERT(clientRect.top == 0);
+
+ int32_t width = clientRect.right ? clientRect.right : 1;
+ int32_t height = clientRect.bottom ? clientRect.bottom : 1;
+
+ bool needNewBackbuffer = !aAllowSameBuffer || !mBackbuffer ||
+ (mBackbuffer->GetWidth() != width) ||
+ (mBackbuffer->GetHeight() != height);
+
+ if (!needNewBackbuffer) {
+ aResponseData->result = ResponseResult::BorrowSameBuffer;
+ return;
+ }
+
+ auto newBackbuffer = std::make_unique<PresentableSharedImage>();
+ if (!newBackbuffer->Initialize(width, height)) {
+ return;
+ }
+
+ // Preserve the contents of the old backbuffer (if it exists)
+ if (mBackbuffer) {
+ newBackbuffer->CopyPixelsFrom(*mBackbuffer);
+ mBackbuffer.reset();
+ }
+
+ HANDLE remoteFileMapping =
+ newBackbuffer->CreateRemoteFileMapping(mTargetProcess);
+ if (!remoteFileMapping) {
+ return;
+ }
+
+ aResponseData->result = ResponseResult::BorrowSuccess;
+ aResponseData->width = width;
+ aResponseData->height = height;
+ aResponseData->fileMapping = remoteFileMapping;
+
+ mBackbuffer = std::move(newBackbuffer);
+}
+
+void Provider::HandlePresentRequest(const PresentRequestData& aRequestData,
+ PresentResponseData* aResponseData) {
+ MOZ_ASSERT(aResponseData);
+
+ Span rectSpan(aRequestData.dirtyRects, kMaxDirtyRects);
+
+ aResponseData->result = ResponseResult::Error;
+
+ if (!mBackbuffer) {
+ return;
+ }
+
+ if (!mBackbuffer->PresentToWindow(
+ mWindowHandle, GetTransparencyMode(),
+ rectSpan.First(aRequestData.lenDirtyRects))) {
+ return;
+ }
+
+ aResponseData->result = ResponseResult::PresentSuccess;
+}
+
+Client::Client()
+ : mFileMapping(nullptr),
+ mRequestReadyEvent(nullptr),
+ mResponseReadyEvent(nullptr),
+ mSharedDataPtr(nullptr),
+ mBackbuffer() {}
+
+Client::~Client() {
+ mBackbuffer.reset();
+
+ if (mSharedDataPtr) {
+ MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr));
+ }
+
+ if (mResponseReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent));
+ }
+
+ if (mRequestReadyEvent) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent));
+ }
+
+ if (mFileMapping) {
+ MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping));
+ }
+}
+
+bool Client::Initialize(const RemoteBackbufferHandles& aRemoteHandles) {
+ MOZ_ASSERT(aRemoteHandles.fileMapping().IsValid());
+ MOZ_ASSERT(aRemoteHandles.requestReadyEvent().IsValid());
+ MOZ_ASSERT(aRemoteHandles.responseReadyEvent().IsValid());
+
+ // FIXME: Due to PCompositorWidget using virtual Recv methods,
+ // RemoteBackbufferHandles is passed by const reference, and cannot have its
+ // signature customized, meaning that we need to clone the handles here.
+ //
+ // Once PCompositorWidget is migrated to use direct call semantics, the
+ // signature can be changed to accept RemoteBackbufferHandles by rvalue
+ // reference or value, and the DuplicateHandle calls here can be avoided.
+ mFileMapping = aRemoteHandles.fileMapping().ClonePlatformHandle().release();
+ mRequestReadyEvent =
+ aRemoteHandles.requestReadyEvent().ClonePlatformHandle().release();
+ mResponseReadyEvent =
+ aRemoteHandles.responseReadyEvent().ClonePlatformHandle().release();
+
+ void* mappedFilePtr =
+ ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
+ 0 /*offsetLow*/, 0 /*bytesToMap*/);
+ if (!mappedFilePtr) {
+ return false;
+ }
+
+ mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr);
+
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget> Client::BorrowDrawTarget() {
+ mSharedDataPtr->dataType = mBackbuffer
+ ? SharedDataType::BorrowRequestAllowSameBuffer
+ : SharedDataType::BorrowRequest;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+
+ if (mSharedDataPtr->dataType != SharedDataType::BorrowResponse) {
+ return nullptr;
+ }
+
+ BorrowResponseData responseData = mSharedDataPtr->data.borrowResponse;
+
+ if ((responseData.result != ResponseResult::BorrowSameBuffer) &&
+ (responseData.result != ResponseResult::BorrowSuccess)) {
+ return nullptr;
+ }
+
+ if (responseData.result == ResponseResult::BorrowSuccess) {
+ mBackbuffer.reset();
+
+ auto newBackbuffer = std::make_unique<SharedImage>();
+ if (!newBackbuffer->InitializeRemote(responseData.width,
+ responseData.height,
+ responseData.fileMapping)) {
+ return nullptr;
+ }
+
+ mBackbuffer = std::move(newBackbuffer);
+ }
+
+ MOZ_ASSERT(mBackbuffer);
+
+ return mBackbuffer->CreateDrawTarget();
+}
+
+bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion) {
+ mSharedDataPtr->dataType = SharedDataType::PresentRequest;
+
+ // Simplify the region until it has <= kMaxDirtyRects
+ aDirtyRegion.SimplifyOutward(kMaxDirtyRects);
+
+ Span rectSpan(mSharedDataPtr->data.presentRequest.dirtyRects, kMaxDirtyRects);
+
+ uint8_t rectIndex = 0;
+ for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ rectSpan[rectIndex] = IpcSafeRect(iter.Get());
+ ++rectIndex;
+ }
+
+ mSharedDataPtr->data.presentRequest.lenDirtyRects = rectIndex;
+
+ MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent));
+ MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) ==
+ WAIT_OBJECT_0);
+
+ if (mSharedDataPtr->dataType != SharedDataType::PresentResponse) {
+ return false;
+ }
+
+ if (mSharedDataPtr->data.presentResponse.result !=
+ ResponseResult::PresentSuccess) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace remote_backbuffer
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/RemoteBackbuffer.h b/widget/windows/RemoteBackbuffer.h
new file mode 100644
index 0000000000..5899e80984
--- /dev/null
+++ b/widget/windows/RemoteBackbuffer.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_RemoteBackbuffer_h
+#define widget_windows_RemoteBackbuffer_h
+
+#include "nsIWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/gfx/2D.h"
+#include "prthread.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+namespace remote_backbuffer {
+
+struct IpcRect;
+struct SharedData;
+struct BorrowResponseData;
+struct PresentRequestData;
+struct PresentResponseData;
+class SharedImage;
+class PresentableSharedImage;
+
+class Provider {
+ public:
+ Provider();
+ ~Provider();
+
+ bool Initialize(HWND aWindowHandle, DWORD aTargetProcessId,
+ TransparencyMode aTransparencyMode);
+
+ Maybe<RemoteBackbufferHandles> CreateRemoteHandles();
+
+ void UpdateTransparencyMode(TransparencyMode aTransparencyMode);
+
+ Provider(const Provider&) = delete;
+ Provider(Provider&&) = delete;
+ Provider& operator=(const Provider&) = delete;
+ Provider& operator=(Provider&&) = delete;
+
+ private:
+ void ThreadMain();
+
+ void HandleBorrowRequest(BorrowResponseData* aResponseData,
+ bool aAllowSameBuffer);
+ void HandlePresentRequest(const PresentRequestData& aRequestData,
+ PresentResponseData* aResponseData);
+
+ HWND mWindowHandle;
+ HANDLE mTargetProcess;
+ HANDLE mFileMapping;
+ HANDLE mRequestReadyEvent;
+ HANDLE mResponseReadyEvent;
+ SharedData* mSharedDataPtr;
+ bool mStopServiceThread;
+ PRThread* mServiceThread;
+ std::unique_ptr<PresentableSharedImage> mBackbuffer;
+ mozilla::Atomic<uint32_t, MemoryOrdering::Relaxed> mTransparencyMode;
+ TransparencyMode GetTransparencyMode() const {
+ return TransparencyMode(uint32_t(mTransparencyMode));
+ }
+};
+
+class Client {
+ public:
+ Client();
+ ~Client();
+
+ bool Initialize(const RemoteBackbufferHandles& aRemoteHandles);
+
+ already_AddRefed<gfx::DrawTarget> BorrowDrawTarget();
+ bool PresentDrawTarget(gfx::IntRegion aDirtyRegion);
+
+ Client(const Client&) = delete;
+ Client(Client&&) = delete;
+ Client& operator=(const Client&) = delete;
+ Client& operator=(Client&&) = delete;
+
+ private:
+ HANDLE mFileMapping;
+ HANDLE mRequestReadyEvent;
+ HANDLE mResponseReadyEvent;
+ SharedData* mSharedDataPtr;
+ std::unique_ptr<SharedImage> mBackbuffer;
+};
+
+} // namespace remote_backbuffer
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_RemoteBackbuffer_h
diff --git a/widget/windows/ScreenHelperWin.cpp b/widget/windows/ScreenHelperWin.cpp
new file mode 100644
index 0000000000..8a0ec3b608
--- /dev/null
+++ b/widget/windows/ScreenHelperWin.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "ScreenHelperWin.h"
+
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "WinUtils.h"
+
+static mozilla::LazyLogModule sScreenLog("WidgetScreen");
+
+namespace mozilla {
+namespace widget {
+
+static void GetDisplayInfo(const char16ptr_t aName,
+ hal::ScreenOrientation& aOrientation,
+ uint16_t& aAngle, bool& aIsPseudoDisplay,
+ uint32_t& aRefreshRate) {
+ DISPLAY_DEVICEW displayDevice = {.cb = sizeof(DISPLAY_DEVICEW)};
+
+ // XXX Is the pseudodisplay status really useful?
+ aIsPseudoDisplay =
+ EnumDisplayDevicesW(aName, 0, &displayDevice, 0) &&
+ (displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER);
+
+ DEVMODEW mode = {.dmSize = sizeof(DEVMODEW)};
+ if (!EnumDisplaySettingsW(aName, ENUM_CURRENT_SETTINGS, &mode)) {
+ return;
+ }
+ MOZ_ASSERT(mode.dmFields & DM_DISPLAYORIENTATION);
+
+ aRefreshRate = mode.dmDisplayFrequency;
+
+ // conver to default/natural size
+ if (mode.dmDisplayOrientation == DMDO_90 ||
+ mode.dmDisplayOrientation == DMDO_270) {
+ DWORD temp = mode.dmPelsHeight;
+ mode.dmPelsHeight = mode.dmPelsWidth;
+ mode.dmPelsWidth = temp;
+ }
+
+ bool defaultIsLandscape = mode.dmPelsWidth >= mode.dmPelsHeight;
+ switch (mode.dmDisplayOrientation) {
+ case DMDO_DEFAULT:
+ aOrientation = defaultIsLandscape
+ ? hal::ScreenOrientation::LandscapePrimary
+ : hal::ScreenOrientation::PortraitPrimary;
+ aAngle = 0;
+ break;
+ case DMDO_90:
+ aOrientation = defaultIsLandscape
+ ? hal::ScreenOrientation::PortraitPrimary
+ : hal::ScreenOrientation::LandscapeSecondary;
+ aAngle = 270;
+ break;
+ case DMDO_180:
+ aOrientation = defaultIsLandscape
+ ? hal::ScreenOrientation::LandscapeSecondary
+ : hal::ScreenOrientation::PortraitSecondary;
+ aAngle = 180;
+ break;
+ case DMDO_270:
+ aOrientation = defaultIsLandscape
+ ? hal::ScreenOrientation::PortraitSecondary
+ : hal::ScreenOrientation::LandscapePrimary;
+ aAngle = 90;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected angle");
+ break;
+ }
+}
+
+BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) {
+ auto screens = reinterpret_cast<nsTArray<RefPtr<Screen>>*>(ioParam);
+ BOOL success = FALSE;
+ MONITORINFOEX info;
+ info.cbSize = sizeof(MONITORINFOEX);
+ success = ::GetMonitorInfoW(aMon, &info);
+ if (!success) {
+ MOZ_LOG(sScreenLog, LogLevel::Error, ("GetMonitorInfoW failed"));
+ return TRUE; // continue the enumeration
+ }
+
+ double scale = WinUtils::LogToPhysFactor(aMon);
+ DesktopToLayoutDeviceScale contentsScaleFactor;
+ if (WinUtils::IsPerMonitorDPIAware()) {
+ contentsScaleFactor.scale = 1.0;
+ } else {
+ contentsScaleFactor.scale = scale;
+ }
+ CSSToLayoutDeviceScale defaultCssScaleFactor(scale);
+ LayoutDeviceIntRect rect(info.rcMonitor.left, info.rcMonitor.top,
+ info.rcMonitor.right - info.rcMonitor.left,
+ info.rcMonitor.bottom - info.rcMonitor.top);
+ LayoutDeviceIntRect availRect(info.rcWork.left, info.rcWork.top,
+ info.rcWork.right - info.rcWork.left,
+ info.rcWork.bottom - info.rcWork.top);
+
+ HDC hDC = CreateDC(nullptr, info.szDevice, nullptr, nullptr);
+ if (!hDC) {
+ MOZ_LOG(sScreenLog, LogLevel::Error, ("CollectMonitors CreateDC failed"));
+ return TRUE;
+ }
+ uint32_t pixelDepth = ::GetDeviceCaps(hDC, BITSPIXEL);
+ DeleteDC(hDC);
+ if (pixelDepth == 32) {
+ // If a device uses 32 bits per pixel, it's still only using 8 bits
+ // per color component, which is what our callers want to know.
+ // (Some devices report 32 and some devices report 24.)
+ pixelDepth = 24;
+ }
+
+ float dpi = WinUtils::MonitorDPI(aMon);
+
+ auto orientation = hal::ScreenOrientation::None;
+ uint16_t angle = 0;
+ bool isPseudoDisplay = false;
+ uint32_t refreshRate = 0;
+ GetDisplayInfo(info.szDevice, orientation, angle, isPseudoDisplay,
+ refreshRate);
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("New screen [%s (%s) %d %u %f %f %f %d %d %d]",
+ ToString(rect).c_str(), ToString(availRect).c_str(), pixelDepth,
+ refreshRate, contentsScaleFactor.scale, defaultCssScaleFactor.scale,
+ dpi, isPseudoDisplay, uint32_t(orientation), angle));
+ auto screen = MakeRefPtr<Screen>(
+ rect, availRect, pixelDepth, pixelDepth, refreshRate, contentsScaleFactor,
+ defaultCssScaleFactor, dpi, Screen::IsPseudoDisplay(isPseudoDisplay),
+ orientation, angle);
+ if (info.dwFlags & MONITORINFOF_PRIMARY) {
+ // The primary monitor must be the first element of the screen list.
+ screens->InsertElementAt(0, std::move(screen));
+ } else {
+ screens->AppendElement(std::move(screen));
+ }
+ return TRUE;
+}
+
+void ScreenHelperWin::RefreshScreens() {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
+
+ AutoTArray<RefPtr<Screen>, 4> screens;
+ BOOL result = ::EnumDisplayMonitors(
+ nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&screens);
+ if (!result) {
+ NS_WARNING("Unable to EnumDisplayMonitors");
+ }
+ ScreenManager::Refresh(std::move(screens));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ScreenHelperWin.h b/widget/windows/ScreenHelperWin.h
new file mode 100644
index 0000000000..275014232a
--- /dev/null
+++ b/widget/windows/ScreenHelperWin.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_windows_ScreenHelperWin_h
+#define mozilla_widget_windows_ScreenHelperWin_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperWin final : public ScreenManager::Helper {
+ public:
+ ScreenHelperWin(){};
+ ~ScreenHelperWin() override {}
+
+ static void RefreshScreens();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_windows_ScreenHelperWin_h
diff --git a/widget/windows/ShellHeaderOnlyUtils.h b/widget/windows/ShellHeaderOnlyUtils.h
new file mode 100644
index 0000000000..ea95077fb3
--- /dev/null
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ShellHeaderOnlyUtils_h
+#define mozilla_ShellHeaderOnlyUtils_h
+
+#if defined(LIBXUL) && !defined(UNICODE)
+# error \
+ "UNICODE not set - must be set to prevent compile failure in `comdef.h` due to us deleting `FormatMessage` when absent."
+#endif
+
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include <objbase.h>
+
+#include <exdisp.h>
+#include <shldisp.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <shobjidl.h>
+#include <shtypes.h>
+// NB: include this after shldisp.h so its macros do not conflict with COM
+// interfaces defined by shldisp.h
+#include <shellapi.h>
+#include <type_traits>
+
+#include <comdef.h>
+#include <comutil.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+/**
+ * Ask the current user's Desktop to ShellExecute on our behalf, thus causing
+ * the resulting launched process to inherit its security priviliges from
+ * Explorer instead of our process.
+ *
+ * This is useful in two scenarios, in particular:
+ * * We are running as an elevated user and we want to start something as the
+ * "normal" user;
+ * * We are starting a process that is incompatible with our process's
+ * process mitigation policies. By delegating to Explorer, the child process
+ * will not be affected by our process mitigations.
+ *
+ * Since this communication happens over DCOM, Explorer's COM DACL governs
+ * whether or not we can execute against it, thus avoiding privilege escalation.
+ */
+inline LauncherVoidResult ShellExecuteByExplorer(const _bstr_t& aPath,
+ const _variant_t& aArgs,
+ const _variant_t& aVerb,
+ const _variant_t& aWorkingDir,
+ const _variant_t& aShowCmd) {
+ // NB: Explorer may be a local server, not an inproc server
+ RefPtr<IShellWindows> shellWindows;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_ShellWindows, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
+ IID_IShellWindows, getter_AddRefs(shellWindows));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 1. Find the shell view for the desktop.
+ _variant_t loc(int(CSIDL_DESKTOP));
+ _variant_t empty;
+ long hwnd;
+ RefPtr<IDispatch> dispDesktop;
+ hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd,
+ SWFO_NEEDDISPATCH,
+ getter_AddRefs(dispDesktop));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ if (hr == S_FALSE) {
+ // The call succeeded but the window was not found.
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_NOT_FOUND);
+ }
+
+ RefPtr<IServiceProvider> servProv;
+ hr = dispDesktop->QueryInterface(IID_IServiceProvider,
+ getter_AddRefs(servProv));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellBrowser> browser;
+ hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser,
+ getter_AddRefs(browser));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellView> activeShellView;
+ hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 2. Get the automation object for the desktop.
+ RefPtr<IDispatch> dispView;
+ hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch,
+ getter_AddRefs(dispView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellFolderViewDual> folderView;
+ hr = dispView->QueryInterface(IID_IShellFolderViewDual,
+ getter_AddRefs(folderView));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // 3. Get the interface to IShellDispatch2
+ RefPtr<IDispatch> dispShell;
+ hr = folderView->get_Application(getter_AddRefs(dispShell));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ RefPtr<IShellDispatch2> shellDisp;
+ hr =
+ dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Passing the foreground privilege so that the shell can launch an
+ // application in the foreground. This fails with E_ACCESSDENIED if the
+ // current window is shown in the background. We keep a soft assert for
+ // the other failures to investigate how it happened.
+ hr = ::CoAllowSetForegroundWindow(shellDisp, nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr) || hr == E_ACCESSDENIED);
+
+ // shellapi.h macros interfere with the correct naming of the method being
+ // called on IShellDispatch2. Temporarily remove that definition.
+#if defined(ShellExecute)
+# define MOZ_REDEFINE_SHELLEXECUTE
+# undef ShellExecute
+#endif // defined(ShellExecute)
+
+ // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to execute.
+ hr = shellDisp->ShellExecute(aPath, aArgs, aWorkingDir, aVerb, aShowCmd);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Restore the macro that was removed prior to IShellDispatch2::ShellExecute
+#if defined(MOZ_REDEFINE_SHELLEXECUTE)
+# if defined(UNICODE)
+# define ShellExecute ShellExecuteW
+# else
+# define ShellExecute ShellExecuteA
+# endif
+# undef MOZ_REDEFINE_SHELLEXECUTE
+#endif // defined(MOZ_REDEFINE_SHELLEXECUTE)
+
+ return Ok();
+}
+
+using UniqueAbsolutePidl =
+ UniquePtr<std::remove_pointer_t<PIDLIST_ABSOLUTE>, CoTaskMemFreeDeleter>;
+
+inline LauncherResult<UniqueAbsolutePidl> ShellParseDisplayName(
+ const wchar_t* aPath) {
+ PIDLIST_ABSOLUTE rawAbsPidl = nullptr;
+ SFGAOF sfgao;
+ HRESULT hr = ::SHParseDisplayName(aPath, nullptr, &rawAbsPidl, 0, &sfgao);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ return UniqueAbsolutePidl(rawAbsPidl);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_ShellHeaderOnlyUtils_h
diff --git a/widget/windows/SystemStatusBar.cpp b/widget/windows/SystemStatusBar.cpp
new file mode 100644
index 0000000000..6cc5668bbe
--- /dev/null
+++ b/widget/windows/SystemStatusBar.cpp
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include <strsafe.h>
+#include "SystemStatusBar.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/widget/IconLoader.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "nsComputedDOMStyle.h"
+#include "nsIContentPolicy.h"
+#include "nsISupports.h"
+#include "nsMenuPopupFrame.h"
+#include "nsXULPopupManager.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+#include "nsWindowGfx.h"
+
+#include "shellapi.h"
+
+namespace mozilla::widget {
+
+using mozilla::LinkedListElement;
+using mozilla::dom::Element;
+
+class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
+ public IconLoader::Listener,
+ public nsISupports {
+ public:
+ explicit StatusBarEntry(Element* aMenu);
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+ nsresult Init();
+ void Destroy();
+
+ MOZ_CAN_RUN_SCRIPT LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp,
+ LPARAM lp);
+ const Element* GetMenu() { return mMenu; };
+
+ nsresult OnComplete(imgIContainer* aImage) override;
+
+ private:
+ ~StatusBarEntry();
+ RefPtr<mozilla::widget::IconLoader> mIconLoader;
+ // Effectively const but is cycle collected
+ MOZ_KNOWN_LIVE RefPtr<Element> mMenu;
+ NOTIFYICONDATAW mIconData;
+ boolean mInitted;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
+ tmp->Destroy();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
+
+StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
+ mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
+ /* hWnd */ 0,
+ /* uID */ 2,
+ /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
+ /* uCallbackMessage */ WM_USER,
+ /* hIcon */ 0,
+ /* szTip */ L"", // This is updated in Init()
+ /* dwState */ 0,
+ /* dwStateMask */ 0,
+ /* szInfo */ L"",
+ /* uVersion */ {NOTIFYICON_VERSION_4},
+ /* szInfoTitle */ L"",
+ /* dwInfoFlags */ 0};
+ MOZ_ASSERT(mMenu);
+}
+
+StatusBarEntry::~StatusBarEntry() {
+ if (!mInitted) {
+ return;
+ }
+ Destroy();
+ ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
+ VERIFY(::DestroyWindow(mIconData.hWnd));
+}
+
+void StatusBarEntry::Destroy() {
+ if (mIconLoader) {
+ mIconLoader->Destroy();
+ mIconLoader = nullptr;
+ }
+}
+
+nsresult StatusBarEntry::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr = mMenu->GetAttr(nsGkAtoms::image, imageURIString);
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> iconURI;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<const ComputedStyle> sc =
+ nsComputedDOMStyle::GetComputedStyle(mMenu);
+ if (!sc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ iconURI = sc->StyleList()->GetListStyleImageURI();
+ } else {
+ uint64_t dummy = 0;
+ nsContentPolicyType policyType;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
+ if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ mIconLoader = new IconLoader(this);
+
+ if (iconURI) {
+ rv = mIconLoader->LoadIcon(iconURI, mMenu);
+ }
+
+ HWND iconWindow;
+ NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
+ /* extended style */ 0,
+ /* className */ L"IconWindowClass",
+ /* title */ 0,
+ /* style */ WS_CAPTION,
+ /* x, y, cx, cy */ 0, 0, 0, 0,
+ /* parent */ 0,
+ /* menu */ 0,
+ /* instance */ 0,
+ /* create struct */ 0),
+ NS_ERROR_FAILURE);
+ ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
+
+ mIconData.hWnd = iconWindow;
+ mIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
+
+ nsAutoString labelAttr;
+ mMenu->GetAttr(nsGkAtoms::label, labelAttr);
+ const nsString& label = PromiseFlatString(labelAttr);
+
+ size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
+ wchar_t* tooltip = &(mIconData.szTip[0]);
+ ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
+
+ ::Shell_NotifyIconW(NIM_ADD, &mIconData);
+ ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
+
+ mInitted = true;
+ return NS_OK;
+}
+
+nsresult StatusBarEntry::OnComplete(imgIContainer* aImage) {
+ NS_ENSURE_ARG_POINTER(aImage);
+
+ RefPtr<StatusBarEntry> kungFuDeathGrip = this;
+
+ nsresult rv = nsWindowGfx::CreateIcon(
+ aImage, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &mIconData.hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
+
+ if (mIconData.hIcon) {
+ ::DestroyIcon(mIconData.hIcon);
+ mIconData.hIcon = nullptr;
+ }
+
+ // To simplify things, we won't react to CSS changes to update the icon
+ // with this implementation. We can get rid of the IconLoader at this point.
+ mIconLoader->Destroy();
+ mIconLoader = nullptr;
+ return NS_OK;
+}
+
+LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ if (msg == WM_USER &&
+ (LOWORD(lp) == NIN_SELECT || LOWORD(lp) == NIN_KEYSELECT ||
+ LOWORD(lp) == WM_CONTEXTMENU)) {
+ auto* menu = dom::XULButtonElement::FromNode(mMenu);
+ if (!menu) {
+ return TRUE;
+ }
+
+ nsMenuPopupFrame* popupFrame = menu->GetMenuPopup(FlushType::None);
+ if (NS_WARN_IF(!popupFrame)) {
+ return TRUE;
+ }
+
+ nsIWidget* widget = popupFrame->GetNearestWidget();
+ MOZ_DIAGNOSTIC_ASSERT(widget);
+ if (!widget) {
+ return TRUE;
+ }
+
+ HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ MOZ_DIAGNOSTIC_ASSERT(win);
+ if (!win) {
+ return TRUE;
+ }
+
+ if (LOWORD(lp) == NIN_KEYSELECT && ::GetForegroundWindow() == win) {
+ // When enter is pressed on the icon, the shell sends two NIN_KEYSELECT
+ // notifications. This might cause us to open two windows. To work around
+ // this, if we're already the foreground window (which happens below),
+ // ignore this notification.
+ return TRUE;
+ }
+
+ if (LOWORD(lp) != WM_CONTEXTMENU &&
+ mMenu->HasAttr(nsGkAtoms::contextmenu)) {
+ ::SetForegroundWindow(win);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULSystemStatusBarClick, nullptr,
+ WidgetMouseEvent::eReal);
+ RefPtr<nsPresContext> presContext = popupFrame->PresContext();
+ EventDispatcher::Dispatch(mMenu, presContext, &event, nullptr, &status);
+ return DefWindowProc(hWnd, msg, wp, lp);
+ }
+
+ nsPresContext* pc = popupFrame->PresContext();
+ const CSSIntPoint point = gfx::RoundedToInt(
+ LayoutDeviceIntPoint(GET_X_LPARAM(wp), GET_Y_LPARAM(wp)) /
+ pc->CSSToDevPixelScale());
+
+ // The menu that is being opened is a Gecko <xul:menu>, and the popup code
+ // that manages it expects that the window that the <xul:menu> belongs to
+ // will be in the foreground when it opens. If we don't do this, then if the
+ // icon is clicked when the window is _not_ in the foreground, then the
+ // opened menu will not be keyboard focusable, nor will it close on its own
+ // if the user clicks away from the menu (at least, not until the user
+ // focuses any window in the parent process).
+ ::SetForegroundWindow(win);
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ pm->ShowPopupAtScreen(popupFrame->GetContent()->AsElement(), point.x,
+ point.y, false, nullptr);
+ }
+
+ return DefWindowProc(hWnd, msg, wp, lp);
+}
+
+NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar)
+
+MOZ_CAN_RUN_SCRIPT static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg,
+ WPARAM wp, LPARAM lp) {
+ if (RefPtr<StatusBarEntry> entry =
+ (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA)) {
+ return entry->OnMessage(hWnd, msg, wp, lp);
+ }
+ return TRUE;
+}
+
+static StaticRefPtr<SystemStatusBar> sSingleton;
+
+SystemStatusBar& SystemStatusBar::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new SystemStatusBar();
+ ClearOnShutdown(&sSingleton);
+ }
+ return *sSingleton;
+}
+
+already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() {
+ RefPtr<SystemStatusBar> sm = &GetSingleton();
+ return sm.forget();
+}
+
+nsresult SystemStatusBar::Init() {
+ WNDCLASS classStruct = {/* style */ 0,
+ /* lpfnWndProc */ &WindowProc,
+ /* cbClsExtra */ 0,
+ /* cbWndExtra */ 0,
+ /* hInstance */ 0,
+ /* hIcon */ 0,
+ /* hCursor */ 0,
+ /* hbrBackground */ 0,
+ /* lpszMenuName */ 0,
+ /* lpszClassName */ L"IconWindowClass"};
+ NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::AddItem(Element* aElement) {
+ RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement);
+ nsresult rv = entry->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatusBarEntries.insertBack(entry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::RemoveItem(Element* aElement) {
+ for (StatusBarEntry* entry : mStatusBarEntries) {
+ if (entry->GetMenu() == aElement) {
+ entry->removeFrom(mStatusBarEntries);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace mozilla::widget
diff --git a/widget/windows/SystemStatusBar.h b/widget/windows/SystemStatusBar.h
new file mode 100644
index 0000000000..6afafd9d80
--- /dev/null
+++ b/widget/windows/SystemStatusBar.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_SystemStatusBar_h
+#define widget_windows_SystemStatusBar_h
+
+#include "nsISystemStatusBar.h"
+#include "mozilla/LinkedList.h"
+
+namespace mozilla::widget {
+class StatusBarEntry;
+
+class SystemStatusBar final : public nsISystemStatusBar {
+ public:
+ explicit SystemStatusBar() = default;
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+ static SystemStatusBar& GetSingleton();
+ static already_AddRefed<SystemStatusBar> GetAddRefedSingleton();
+
+ nsresult Init();
+
+ private:
+ ~SystemStatusBar() = default;
+ mozilla::LinkedList<RefPtr<StatusBarEntry>> mStatusBarEntries;
+};
+
+} // namespace mozilla::widget
+
+#endif // widget_windows_SystemStatusBar_h
diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp
new file mode 100644
index 0000000000..b3e3a164ae
--- /dev/null
+++ b/widget/windows/TSFTextStore.cpp
@@ -0,0 +1,7513 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#define INPUTSCOPE_INIT_GUID
+#define TEXTATTRS_INIT_GUID
+#include "TSFTextStore.h"
+
+#include <algorithm>
+#include <comutil.h> // for _bstr_t
+#include <oleauto.h> // for SysAllocString
+#include <olectl.h>
+#include "nscore.h"
+
+#include "IMMHandler.h"
+#include "KeyboardLayout.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/widget/WinRegistry.h"
+#include "nsWindow.h"
+#include "nsPrintfCString.h"
+
+// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
+// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+mozilla::LazyLogModule gIMELog("IMEHandler");
+
+// TODO: GUID_PROP_URL has not been declared in the SDK yet. We should drop the
+// `s` prefix after it's released by a new SDK and define it with #if.
+static const GUID sGUID_PROP_URL = {
+ 0xd5138268,
+ 0xa1bf,
+ 0x4308,
+ {0xbc, 0xbf, 0x2e, 0x73, 0x93, 0x98, 0xe2, 0x34}};
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * TSF related code should log its behavior even on release build especially
+ * in the interface methods.
+ *
+ * In interface methods, use LogLevel::Info.
+ * In internal methods, use LogLevel::Debug for logging normal behavior.
+ * For logging error, use LogLevel::Error.
+ *
+ * When an instance method is called, start with following text:
+ * "0x%p TSFFoo::Bar(", the 0x%p should be the "this" of the nsFoo.
+ * after that, start with:
+ * "0x%p TSFFoo::Bar("
+ * In an internal method, start with following text:
+ * "0x%p TSFFoo::Bar("
+ * When a static method is called, start with following text:
+ * "TSFFoo::Bar("
+ */
+
+enum class TextInputProcessorID {
+ // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP().
+ eNotComputed,
+
+ // Not a TIP. E.g., simple keyboard layout or IMM-IME.
+ eNone,
+
+ // Used for other TIPs, i.e., any TIPs which we don't support specifically.
+ eUnknown,
+
+ // TIP for Japanese.
+ eMicrosoftIMEForJapanese,
+ eMicrosoftOfficeIME2010ForJapanese,
+ eGoogleJapaneseInput,
+ eATOK2011,
+ eATOK2012,
+ eATOK2013,
+ eATOK2014,
+ eATOK2015,
+ eATOK2016,
+ eATOKUnknown,
+ eJapanist10,
+
+ // TIP for Traditional Chinese.
+ eMicrosoftBopomofo,
+ eMicrosoftChangJie,
+ eMicrosoftPhonetic,
+ eMicrosoftQuick,
+ eMicrosoftNewChangJie,
+ eMicrosoftNewPhonetic,
+ eMicrosoftNewQuick,
+ eFreeChangJie,
+
+ // TIP for Simplified Chinese.
+ eMicrosoftPinyin,
+ eMicrosoftPinyinNewExperienceInputStyle,
+ eMicrosoftWubi,
+
+ // TIP for Korean.
+ eMicrosoftIMEForKorean,
+ eMicrosoftOldHangul,
+
+ // Keyman Desktop, which can install various language keyboards.
+ eKeymanDesktop,
+};
+
+static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; }
+
+static void HandleSeparator(nsCString& aDesc) {
+ if (!aDesc.IsEmpty()) {
+ aDesc.AppendLiteral(" | ");
+ }
+}
+
+static const nsCString GetFindFlagName(DWORD aFindFlag) {
+ nsCString description;
+ if (!aFindFlag) {
+ description.AppendLiteral("no flags (0)");
+ return description;
+ }
+ if (aFindFlag & TS_ATTR_FIND_BACKWARDS) {
+ description.AppendLiteral("TS_ATTR_FIND_BACKWARDS");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET");
+ }
+ if (aFindFlag & TS_ATTR_FIND_UPDATESTART) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_UPDATESTART");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE");
+ }
+ if (aFindFlag & TS_ATTR_FIND_WANT_END) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_WANT_END");
+ }
+ if (aFindFlag & TS_ATTR_FIND_HIDDEN) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_ATTR_FIND_HIDDEN");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("Unknown (");
+ description.AppendInt(static_cast<uint32_t>(aFindFlag));
+ description.Append(')');
+ }
+ return description;
+}
+
+class GetACPFromPointFlagName : public nsAutoCString {
+ public:
+ explicit GetACPFromPointFlagName(DWORD aFlags) {
+ if (!aFlags) {
+ AppendLiteral("no flags (0)");
+ return;
+ }
+ if (aFlags & GXFPF_ROUND_NEAREST) {
+ AppendLiteral("GXFPF_ROUND_NEAREST");
+ aFlags &= ~GXFPF_ROUND_NEAREST;
+ }
+ if (aFlags & GXFPF_NEAREST) {
+ HandleSeparator(*this);
+ AppendLiteral("GXFPF_NEAREST");
+ aFlags &= ~GXFPF_NEAREST;
+ }
+ if (aFlags) {
+ HandleSeparator(*this);
+ AppendLiteral("Unknown(");
+ AppendInt(static_cast<uint32_t>(aFlags));
+ Append(')');
+ }
+ }
+ virtual ~GetACPFromPointFlagName() {}
+};
+
+static const char* GetFocusChangeName(
+ InputContextAction::FocusChange aFocusChange) {
+ switch (aFocusChange) {
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ return "FOCUS_NOT_CHANGED";
+ case InputContextAction::GOT_FOCUS:
+ return "GOT_FOCUS";
+ case InputContextAction::LOST_FOCUS:
+ return "LOST_FOCUS";
+ case InputContextAction::MENU_GOT_PSEUDO_FOCUS:
+ return "MENU_GOT_PSEUDO_FOCUS";
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ return "MENU_LOST_PSEUDO_FOCUS";
+ case InputContextAction::WIDGET_CREATED:
+ return "WIDGET_CREATED";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetCLSIDNameStr(REFCLSID aCLSID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromCLSID(aCLSID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsCString result;
+ result = NS_ConvertUTF16toUTF8(str);
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static nsCString GetGUIDNameStr(REFGUID aGUID) {
+ OLECHAR str[40];
+ int len = ::StringFromGUID2(aGUID, str, ArrayLength(str));
+ if (!len || !str[0]) {
+ return ""_ns;
+ }
+
+ return NS_ConvertUTF16toUTF8(str);
+}
+
+static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) {
+#define RETURN_GUID_NAME(aNamedGUID) \
+ if (IsEqualGUID(aGUID, aNamedGUID)) { \
+ return nsLiteralCString(#aNamedGUID); \
+ }
+
+ RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
+ RETURN_GUID_NAME(sGUID_PROP_URL)
+ RETURN_GUID_NAME(TSATTRID_OTHERS)
+ RETURN_GUID_NAME(TSATTRID_Font)
+ RETURN_GUID_NAME(TSATTRID_Font_FaceName)
+ RETURN_GUID_NAME(TSATTRID_Font_SizePts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Bold)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Italic)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Position)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Protected)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Weight)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Height)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Blink)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_Color)
+ RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor)
+ RETURN_GUID_NAME(TSATTRID_Text)
+ RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting)
+ RETURN_GUID_NAME(TSATTRID_Text_RightToLeft)
+ RETURN_GUID_NAME(TSATTRID_Text_Orientation)
+ RETURN_GUID_NAME(TSATTRID_Text_Language)
+ RETURN_GUID_NAME(TSATTRID_Text_ReadOnly)
+ RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center)
+ RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify)
+ RETURN_GUID_NAME(TSATTRID_Text_Link)
+ RETURN_GUID_NAME(TSATTRID_Text_Hyphenation)
+ RETURN_GUID_NAME(TSATTRID_Text_Para)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly)
+ RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple)
+ RETURN_GUID_NAME(TSATTRID_List)
+ RETURN_GUID_NAME(TSATTRID_List_LevelIndel)
+ RETURN_GUID_NAME(TSATTRID_List_Type)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Bullet)
+ RETURN_GUID_NAME(TSATTRID_List_Type_Arabic)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter)
+ RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman)
+ RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman)
+ RETURN_GUID_NAME(TSATTRID_App)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling)
+ RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar)
+
+#undef RETURN_GUID_NAME
+
+ return GetGUIDNameStr(aGUID);
+}
+
+static nsCString GetRIIDNameStr(REFIID aRIID) {
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromIID(aRIID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return ""_ns;
+ }
+
+ nsAutoString key(L"Interface\\");
+ key += str;
+
+ nsCString result;
+ wchar_t buf[256];
+ if (WinRegistry::GetString(HKEY_CLASSES_ROOT, key, u""_ns, buf,
+ WinRegistry::kLegacyWinUtilsStringFlags)) {
+ result = NS_ConvertUTF16toUTF8(buf);
+ } else {
+ result = NS_ConvertUTF16toUTF8(str);
+ }
+
+ ::CoTaskMemFree(str);
+ return result;
+}
+
+static const char* GetCommonReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case S_OK:
+ return "S_OK";
+ case E_ABORT:
+ return "E_ABORT";
+ case E_ACCESSDENIED:
+ return "E_ACCESSDENIED";
+ case E_FAIL:
+ return "E_FAIL";
+ case E_HANDLE:
+ return "E_HANDLE";
+ case E_INVALIDARG:
+ return "E_INVALIDARG";
+ case E_NOINTERFACE:
+ return "E_NOINTERFACE";
+ case E_NOTIMPL:
+ return "E_NOTIMPL";
+ case E_OUTOFMEMORY:
+ return "E_OUTOFMEMORY";
+ case E_POINTER:
+ return "E_POINTER";
+ case E_UNEXPECTED:
+ return "E_UNEXPECTED";
+ default:
+ return SUCCEEDED(aResult) ? "Succeeded" : "Failed";
+ }
+}
+
+static const char* GetTextStoreReturnValueName(HRESULT aResult) {
+ switch (aResult) {
+ case TS_E_FORMAT:
+ return "TS_E_FORMAT";
+ case TS_E_INVALIDPOINT:
+ return "TS_E_INVALIDPOINT";
+ case TS_E_INVALIDPOS:
+ return "TS_E_INVALIDPOS";
+ case TS_E_NOINTERFACE:
+ return "TS_E_NOINTERFACE";
+ case TS_E_NOLAYOUT:
+ return "TS_E_NOLAYOUT";
+ case TS_E_NOLOCK:
+ return "TS_E_NOLOCK";
+ case TS_E_NOOBJECT:
+ return "TS_E_NOOBJECT";
+ case TS_E_NOSELECTION:
+ return "TS_E_NOSELECTION";
+ case TS_E_NOSERVICE:
+ return "TS_E_NOSERVICE";
+ case TS_E_READONLY:
+ return "TS_E_READONLY";
+ case TS_E_SYNCHRONOUS:
+ return "TS_E_SYNCHRONOUS";
+ case TS_S_ASYNC:
+ return "TS_S_ASYNC";
+ default:
+ return GetCommonReturnValueName(aResult);
+ }
+}
+
+static const nsCString GetSinkMaskNameStr(DWORD aSinkMask) {
+ nsCString description;
+ if (aSinkMask & TS_AS_TEXT_CHANGE) {
+ description.AppendLiteral("TS_AS_TEXT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_SEL_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_SEL_CHANGE");
+ }
+ if (aSinkMask & TS_AS_LAYOUT_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_LAYOUT_CHANGE");
+ }
+ if (aSinkMask & TS_AS_ATTR_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_ATTR_CHANGE");
+ }
+ if (aSinkMask & TS_AS_STATUS_CHANGE) {
+ HandleSeparator(description);
+ description.AppendLiteral("TS_AS_STATUS_CHANGE");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const nsCString GetLockFlagNameStr(DWORD aLockFlags) {
+ nsCString description;
+ if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) {
+ description.AppendLiteral("TS_LF_READWRITE");
+ } else if (aLockFlags & TS_LF_READ) {
+ description.AppendLiteral("TS_LF_READ");
+ }
+ if (aLockFlags & TS_LF_SYNC) {
+ if (!description.IsEmpty()) {
+ description.AppendLiteral(" | ");
+ }
+ description.AppendLiteral("TS_LF_SYNC");
+ }
+ if (description.IsEmpty()) {
+ description.AppendLiteral("not-specified");
+ }
+ return description;
+}
+
+static const char* GetTextRunTypeName(TsRunType aRunType) {
+ switch (aRunType) {
+ case TS_RT_PLAIN:
+ return "TS_RT_PLAIN";
+ case TS_RT_HIDDEN:
+ return "TS_RT_HIDDEN";
+ case TS_RT_OPAQUE:
+ return "TS_RT_OPAQUE";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString GetColorName(const TF_DA_COLOR& aColor) {
+ switch (aColor.type) {
+ case TF_CT_NONE:
+ return "TF_CT_NONE"_ns;
+ case TF_CT_SYSCOLOR:
+ return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X",
+ static_cast<int32_t>(aColor.nIndex));
+ case TF_CT_COLORREF:
+ return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X",
+ static_cast<int32_t>(aColor.cr));
+ break;
+ default:
+ return nsPrintfCString("Unknown(%08X)",
+ static_cast<int32_t>(aColor.type));
+ }
+}
+
+static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) {
+ switch (aLineStyle) {
+ case TF_LS_NONE:
+ return "TF_LS_NONE"_ns;
+ case TF_LS_SOLID:
+ return "TF_LS_SOLID"_ns;
+ case TF_LS_DOT:
+ return "TF_LS_DOT"_ns;
+ case TF_LS_DASH:
+ return "TF_LS_DASH"_ns;
+ case TF_LS_SQUIGGLE:
+ return "TF_LS_SQUIGGLE"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle));
+ }
+ }
+}
+
+static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) {
+ switch (aAttr) {
+ case TF_ATTR_INPUT:
+ return "TF_ATTR_INPUT"_ns;
+ case TF_ATTR_TARGET_CONVERTED:
+ return "TF_ATTR_TARGET_CONVERTED"_ns;
+ case TF_ATTR_CONVERTED:
+ return "TF_ATTR_CONVERTED"_ns;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return "TF_ATTR_TARGET_NOTCONVERTED"_ns;
+ case TF_ATTR_INPUT_ERROR:
+ return "TF_ATTR_INPUT_ERROR"_ns;
+ case TF_ATTR_FIXEDCONVERTED:
+ return "TF_ATTR_FIXEDCONVERTED"_ns;
+ case TF_ATTR_OTHER:
+ return "TF_ATTR_OTHER"_ns;
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
+ }
+ }
+}
+
+static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) {
+ nsCString str;
+ str = "crText:{ ";
+ str += GetColorName(aDispAttr.crText);
+ str += " }, crBk:{ ";
+ str += GetColorName(aDispAttr.crBk);
+ str += " }, lsStyle: ";
+ str += GetLineStyleName(aDispAttr.lsStyle);
+ str += ", fBoldLine: ";
+ str += GetBoolName(aDispAttr.fBoldLine);
+ str += ", crLine:{ ";
+ str += GetColorName(aDispAttr.crLine);
+ str += " }, bAttr: ";
+ str += GetClauseAttrName(aDispAttr.bAttr);
+ return str;
+}
+
+static const char* GetMouseButtonName(int16_t aButton) {
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ return "LeftButton";
+ case MouseButton::eMiddle:
+ return "MiddleButton";
+ case MouseButton::eSecondary:
+ return "RightButton";
+ default:
+ return "UnknownButton";
+ }
+}
+
+#define ADD_SEPARATOR_IF_NECESSARY(aStr) \
+ if (!aStr.IsEmpty()) { \
+ aStr.AppendLiteral(", "); \
+ }
+
+static nsCString GetMouseButtonsName(int16_t aButtons) {
+ if (!aButtons) {
+ return "no buttons"_ns;
+ }
+ nsCString names;
+ if (aButtons & MouseButtonsFlag::ePrimaryFlag) {
+ names = "LeftButton";
+ }
+ if (aButtons & MouseButtonsFlag::eSecondaryFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "RightButton";
+ }
+ if (aButtons & MouseButtonsFlag::eMiddleFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "MiddleButton";
+ }
+ if (aButtons & MouseButtonsFlag::e4thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "4thButton";
+ }
+ if (aButtons & MouseButtonsFlag::e5thFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "5thButton";
+ }
+ return names;
+}
+
+static nsCString GetModifiersName(Modifiers aModifiers) {
+ if (aModifiers == MODIFIER_NONE) {
+ return "no modifiers"_ns;
+ }
+ nsCString names;
+ if (aModifiers & MODIFIER_ALT) {
+ names = NS_DOM_KEYNAME_ALT;
+ }
+ if (aModifiers & MODIFIER_ALTGRAPH) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_ALTGRAPH;
+ }
+ if (aModifiers & MODIFIER_CAPSLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CAPSLOCK;
+ }
+ if (aModifiers & MODIFIER_CONTROL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_CONTROL;
+ }
+ if (aModifiers & MODIFIER_FN) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FN;
+ }
+ if (aModifiers & MODIFIER_FNLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_FNLOCK;
+ }
+ if (aModifiers & MODIFIER_META) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_META;
+ }
+ if (aModifiers & MODIFIER_NUMLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_NUMLOCK;
+ }
+ if (aModifiers & MODIFIER_SCROLLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SCROLLLOCK;
+ }
+ if (aModifiers & MODIFIER_SHIFT) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SHIFT;
+ }
+ if (aModifiers & MODIFIER_SYMBOL) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOL;
+ }
+ if (aModifiers & MODIFIER_SYMBOLLOCK) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_SYMBOLLOCK;
+ }
+ return names;
+}
+
+class GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LR)");
+ return;
+ }
+ AssignLiteral("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 {
+ public:
+ explicit GetEscapedUTF8String(const nsAString& aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ explicit GetEscapedUTF8String(const char16ptr_t aString)
+ : NS_ConvertUTF16toUTF8(aString) {
+ Escape();
+ }
+ GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength)
+ : NS_ConvertUTF16toUTF8(aString, aLength) {
+ Escape();
+ }
+
+ private:
+ void Escape() {
+ ReplaceSubstring("\r", "\\r");
+ ReplaceSubstring("\n", "\\n");
+ ReplaceSubstring("\t", "\\t");
+ }
+};
+
+class GetInputScopeString : public nsAutoCString {
+ public:
+ explicit GetInputScopeString(const nsTArray<InputScope>& aList) {
+ for (InputScope inputScope : aList) {
+ if (!IsEmpty()) {
+ AppendLiteral(", ");
+ }
+ switch (inputScope) {
+ case IS_DEFAULT:
+ AppendLiteral("IS_DEFAULT");
+ break;
+ case IS_URL:
+ AppendLiteral("IS_URL");
+ break;
+ case IS_FILE_FULLFILEPATH:
+ AppendLiteral("IS_FILE_FULLFILEPATH");
+ break;
+ case IS_FILE_FILENAME:
+ AppendLiteral("IS_FILE_FILENAME");
+ break;
+ case IS_EMAIL_USERNAME:
+ AppendLiteral("IS_EMAIL_USERNAME");
+ break;
+ case IS_EMAIL_SMTPEMAILADDRESS:
+ AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS");
+ break;
+ case IS_LOGINNAME:
+ AppendLiteral("IS_LOGINNAME");
+ break;
+ case IS_PERSONALNAME_FULLNAME:
+ AppendLiteral("IS_PERSONALNAME_FULLNAME");
+ break;
+ case IS_PERSONALNAME_PREFIX:
+ AppendLiteral("IS_PERSONALNAME_PREFIX");
+ break;
+ case IS_PERSONALNAME_GIVENNAME:
+ AppendLiteral("IS_PERSONALNAME_GIVENNAME");
+ break;
+ case IS_PERSONALNAME_MIDDLENAME:
+ AppendLiteral("IS_PERSONALNAME_MIDDLENAME");
+ break;
+ case IS_PERSONALNAME_SURNAME:
+ AppendLiteral("IS_PERSONALNAME_SURNAME");
+ break;
+ case IS_PERSONALNAME_SUFFIX:
+ AppendLiteral("IS_PERSONALNAME_SUFFIX");
+ break;
+ case IS_ADDRESS_FULLPOSTALADDRESS:
+ AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS");
+ break;
+ case IS_ADDRESS_POSTALCODE:
+ AppendLiteral("IS_ADDRESS_POSTALCODE");
+ break;
+ case IS_ADDRESS_STREET:
+ AppendLiteral("IS_ADDRESS_STREET");
+ break;
+ case IS_ADDRESS_STATEORPROVINCE:
+ AppendLiteral("IS_ADDRESS_STATEORPROVINCE");
+ break;
+ case IS_ADDRESS_CITY:
+ AppendLiteral("IS_ADDRESS_CITY");
+ break;
+ case IS_ADDRESS_COUNTRYNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYNAME");
+ break;
+ case IS_ADDRESS_COUNTRYSHORTNAME:
+ AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME");
+ break;
+ case IS_CURRENCY_AMOUNTANDSYMBOL:
+ AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL");
+ break;
+ case IS_CURRENCY_AMOUNT:
+ AppendLiteral("IS_CURRENCY_AMOUNT");
+ break;
+ case IS_DATE_FULLDATE:
+ AppendLiteral("IS_DATE_FULLDATE");
+ break;
+ case IS_DATE_MONTH:
+ AppendLiteral("IS_DATE_MONTH");
+ break;
+ case IS_DATE_DAY:
+ AppendLiteral("IS_DATE_DAY");
+ break;
+ case IS_DATE_YEAR:
+ AppendLiteral("IS_DATE_YEAR");
+ break;
+ case IS_DATE_MONTHNAME:
+ AppendLiteral("IS_DATE_MONTHNAME");
+ break;
+ case IS_DATE_DAYNAME:
+ AppendLiteral("IS_DATE_DAYNAME");
+ break;
+ case IS_DIGITS:
+ AppendLiteral("IS_DIGITS");
+ break;
+ case IS_NUMBER:
+ AppendLiteral("IS_NUMBER");
+ break;
+ case IS_ONECHAR:
+ AppendLiteral("IS_ONECHAR");
+ break;
+ case IS_PASSWORD:
+ AppendLiteral("IS_PASSWORD");
+ break;
+ case IS_TELEPHONE_FULLTELEPHONENUMBER:
+ AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER");
+ break;
+ case IS_TELEPHONE_COUNTRYCODE:
+ AppendLiteral("IS_TELEPHONE_COUNTRYCODE");
+ break;
+ case IS_TELEPHONE_AREACODE:
+ AppendLiteral("IS_TELEPHONE_AREACODE");
+ break;
+ case IS_TELEPHONE_LOCALNUMBER:
+ AppendLiteral("IS_TELEPHONE_LOCALNUMBER");
+ break;
+ case IS_TIME_FULLTIME:
+ AppendLiteral("IS_TIME_FULLTIME");
+ break;
+ case IS_TIME_HOUR:
+ AppendLiteral("IS_TIME_HOUR");
+ break;
+ case IS_TIME_MINORSEC:
+ AppendLiteral("IS_TIME_MINORSEC");
+ break;
+ case IS_NUMBER_FULLWIDTH:
+ AppendLiteral("IS_NUMBER_FULLWIDTH");
+ break;
+ case IS_ALPHANUMERIC_HALFWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH");
+ break;
+ case IS_ALPHANUMERIC_FULLWIDTH:
+ AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH");
+ break;
+ case IS_CURRENCY_CHINESE:
+ AppendLiteral("IS_CURRENCY_CHINESE");
+ break;
+ case IS_BOPOMOFO:
+ AppendLiteral("IS_BOPOMOFO");
+ break;
+ case IS_HIRAGANA:
+ AppendLiteral("IS_HIRAGANA");
+ break;
+ case IS_KATAKANA_HALFWIDTH:
+ AppendLiteral("IS_KATAKANA_HALFWIDTH");
+ break;
+ case IS_KATAKANA_FULLWIDTH:
+ AppendLiteral("IS_KATAKANA_FULLWIDTH");
+ break;
+ case IS_HANJA:
+ AppendLiteral("IS_HANJA");
+ break;
+ case IS_PHRASELIST:
+ AppendLiteral("IS_PHRASELIST");
+ break;
+ case IS_REGULAREXPRESSION:
+ AppendLiteral("IS_REGULAREXPRESSION");
+ break;
+ case IS_SRGS:
+ AppendLiteral("IS_SRGS");
+ break;
+ case IS_XML:
+ AppendLiteral("IS_XML");
+ break;
+ case IS_PRIVATE:
+ AppendLiteral("IS_PRIVATE");
+ break;
+ default:
+ AppendPrintf("Unknown Value(%d)", inputScope);
+ break;
+ }
+ }
+ }
+};
+
+/******************************************************************/
+/* InputScopeImpl */
+/******************************************************************/
+
+class InputScopeImpl final : public ITfInputScope {
+ ~InputScopeImpl() {}
+
+ public:
+ explicit InputScopeImpl(const nsTArray<InputScope>& aList)
+ : mInputScopes(aList.Clone()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get()));
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl)
+
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) {
+ *ppv = static_cast<ITfInputScope*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) {
+ uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length());
+
+ InputScope* pScope =
+ (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count);
+ NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY);
+
+ if (mInputScopes.IsEmpty()) {
+ *pScope = IS_DEFAULT;
+ *pcCount = 1;
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ *pcCount = 0;
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ *(pScope + idx) = mInputScopes[idx];
+ (*pcCount)++;
+ }
+
+ *pprgInputScopes = pScope;
+ return S_OK;
+ }
+
+ STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) {
+ return E_NOTIMPL;
+ }
+ STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; }
+ STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; }
+ STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; }
+
+ private:
+ nsTArray<InputScope> mInputScopes;
+};
+
+/******************************************************************/
+/* TSFStaticSink */
+/******************************************************************/
+
+class TSFStaticSink final : public ITfInputProcessorProfileActivationSink {
+ public:
+ static TSFStaticSink* GetInstance() {
+ if (!sInstance) {
+ RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr();
+ if (NS_WARN_IF(!threadMgr)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no ThreadMgr instance"));
+ return nullptr;
+ }
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance due to no InputProcessorProfiles instance"));
+ return nullptr;
+ }
+ RefPtr<TSFStaticSink> staticSink = new TSFStaticSink();
+ if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) {
+ staticSink->Destroy();
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink "
+ "instance"));
+ return nullptr;
+ }
+ sInstance = staticSink.forget();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown() {
+ if (sInstance) {
+ sInstance->Destroy();
+ sInstance = nullptr;
+ }
+ }
+
+ bool Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles);
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if (IID_IUnknown == riid ||
+ IID_ITfInputProcessorProfileActivationSink == riid) {
+ *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+ return E_NOINTERFACE;
+ }
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink)
+
+ const nsString& GetActiveTIPKeyboardDescription() const {
+ return mActiveTIPKeyboardDescription;
+ }
+
+ static bool IsIMM_IMEActive() {
+ // Use IMM API until TSFStaticSink starts to work.
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return IsIMM_IME(::GetKeyboardLayout(0));
+ }
+ return sInstance->mIsIMM_IME;
+ }
+
+ static bool IsIMM_IME(HKL aHKL) {
+ return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0);
+ }
+
+ static bool IsTraditionalChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsTraditionalChineseInternal();
+ }
+ static bool IsSimplifiedChinese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsSimplifiedChineseInternal();
+ }
+ static bool IsJapanese() {
+ EnsureInstance();
+ return sInstance && sInstance->IsJapaneseInternal();
+ }
+ static bool IsKorean() {
+ EnsureInstance();
+ return sInstance && sInstance->IsKoreanInternal();
+ }
+
+ /**
+ * ActiveTIP() returns an ID for currently active TIP.
+ * Please note that this method is expensive due to needs a lot of GUID
+ * comparations if active language ID is one of CJKT. If you need to
+ * check TIPs for a specific language, you should check current language
+ * first.
+ */
+ static TextInputProcessorID ActiveTIP() {
+ EnsureInstance();
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return TextInputProcessorID::eUnknown;
+ }
+ sInstance->ComputeActiveTextInputProcessor();
+ if (NS_WARN_IF(sInstance->mActiveTIP ==
+ TextInputProcessorID::eNotComputed)) {
+ return TextInputProcessorID::eUnknown;
+ }
+ return sInstance->mActiveTIP;
+ }
+
+ static bool GetActiveTIPNameForTelemetry(nsAString& aName) {
+ if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) {
+ return false;
+ }
+ if (sInstance->mActiveTIPGUID == GUID_NULL) {
+ aName.Truncate();
+ aName.AppendPrintf("0x%04X", sInstance->mLangID);
+ return true;
+ }
+ // key should be "LocaleID|Description". Although GUID of the
+ // profile is unique key since description may be localized for system
+ // language, unfortunately, it's too long to record as key with its
+ // description. Therefore, we should record only the description with
+ // LocaleID because Microsoft IME may not include language information.
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ aName.Truncate();
+ aName.AppendPrintf("0x%04X|", sInstance->mLangID);
+ nsAutoString description;
+ description.Assign(sInstance->mActiveTIPKeyboardDescription);
+ static const uint32_t kMaxDescriptionLength = 72 - aName.Length();
+ if (description.Length() > kMaxDescriptionLength) {
+ if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) &&
+ NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) {
+ description.Truncate(kMaxDescriptionLength - 2);
+ } else {
+ description.Truncate(kMaxDescriptionLength - 1);
+ }
+ // U+2026 is "..."
+ description.Append(char16_t(0x2026));
+ }
+ aName.Append(description);
+ return true;
+ }
+
+ static bool IsMSChangJieOrMSQuickActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Traditional Chinese.
+ if (!IsTraditionalChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftQuick:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSPinyinOrMSWubiActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Simplified Chinese.
+ if (!IsSimplifiedChinese()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsMSJapaneseIMEActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+
+ static bool IsGoogleJapaneseInputActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if the language
+ // for current TIP is Japanese.
+ if (!IsJapanese()) {
+ return false;
+ }
+ return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput;
+ }
+
+ static bool IsATOKActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ return IsJapanese() && sInstance->IsATOKActiveInternal();
+ }
+
+ // Note that ATOK 2011 - 2016 refers native caret position for deciding its
+ // popup window position.
+ static bool IsATOKReferringNativeCaretActive() {
+ // ActiveTIP() is expensive if it hasn't computed active TIP yet.
+ // For avoiding unnecessary computation, we should check if active TIP is
+ // ATOK first since it's cheaper.
+ if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) {
+ return false;
+ }
+ switch (ActiveTIP()) {
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private:
+ static void EnsureInstance() {
+ if (!sInstance) {
+ RefPtr<TSFStaticSink> staticSink = GetInstance();
+ Unused << staticSink;
+ }
+ }
+
+ bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; }
+ bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; }
+ bool IsJapaneseInternal() const { return mLangID == 0x0411; }
+ bool IsKoreanInternal() const { return mLangID == 0x0412; }
+
+ bool IsATOKActiveInternal() {
+ EnsureInitActiveTIPKeyboard();
+ // FYI: Name of packaged ATOK includes the release year like "ATOK 2015".
+ // Name of ATOK Passport (subscription) equals "ATOK".
+ return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) ||
+ mActiveTIPKeyboardDescription.EqualsLiteral("ATOK");
+ }
+
+ void ComputeActiveTextInputProcessor() {
+ if (mActiveTIP != TextInputProcessorID::eNotComputed) {
+ return;
+ }
+
+ if (mActiveTIPGUID == GUID_NULL) {
+ mActiveTIP = TextInputProcessorID::eNone;
+ return;
+ }
+
+ // Comparing GUID is slow. So, we should use language information to
+ // reduce the comparing cost for TIP which is not we do not support
+ // specifically since they are always compared with all supported TIPs.
+ switch (mLangID) {
+ case 0x0404:
+ mActiveTIP = ComputeActiveTIPAsTraditionalChinese();
+ break;
+ case 0x0411:
+ mActiveTIP = ComputeActiveTIPAsJapanese();
+ break;
+ case 0x0412:
+ mActiveTIP = ComputeActiveTIPAsKorean();
+ break;
+ case 0x0804:
+ mActiveTIP = ComputeActiveTIPAsSimplifiedChinese();
+ break;
+ default:
+ mActiveTIP = TextInputProcessorID::eUnknown;
+ break;
+ }
+ // Special case for Keyman Desktop, it is available for any languages.
+ // Therefore, we need to check it only if we don't know the active TIP.
+ if (mActiveTIP != TextInputProcessorID::eUnknown) {
+ return;
+ }
+
+ // Note that keyboard layouts for Keyman assign its GUID on install
+ // randomly, but CLSID is constant in any environments.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7
+ // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37
+ // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7}
+ static constexpr CLSID kKeymanDesktop_CLSID = {
+ 0xFE0420F1,
+ 0x38D1,
+ 0x4B4C,
+ {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}};
+ if (mActiveTIPCLSID == kKeymanDesktop_CLSID) {
+ mActiveTIP = TextInputProcessorID::eKeymanDesktop;
+ }
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsJapanese() {
+ // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForJapaneseGUID = {
+ 0xA76C93D9,
+ 0x5523,
+ 0x4E90,
+ {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}};
+ if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForJapanese;
+ }
+ // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+ static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = {
+ 0x54EDCC94,
+ 0x1524,
+ 0x4BB1,
+ {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}};
+ if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) {
+ return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese;
+ }
+ // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D}
+ static constexpr GUID kGoogleJapaneseInputGUID = {
+ 0x773EB24E,
+ 0xCA1D,
+ 0x4B1B,
+ {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}};
+ if (mActiveTIPGUID == kGoogleJapaneseInputGUID) {
+ return TextInputProcessorID::eGoogleJapaneseInput;
+ }
+ // {F9C24A5C-8A53-499D-9572-93B2FF582115}
+ static const GUID kATOK2011GUID = {
+ 0xF9C24A5C,
+ 0x8A53,
+ 0x499D,
+ {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}};
+ if (mActiveTIPGUID == kATOK2011GUID) {
+ return TextInputProcessorID::eATOK2011;
+ }
+ // {1DE01562-F445-401B-B6C3-E5B18DB79461}
+ static constexpr GUID kATOK2012GUID = {
+ 0x1DE01562,
+ 0xF445,
+ 0x401B,
+ {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}};
+ if (mActiveTIPGUID == kATOK2012GUID) {
+ return TextInputProcessorID::eATOK2012;
+ }
+ // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
+ static constexpr GUID kATOK2013GUID = {
+ 0x3C4DB511,
+ 0x189A,
+ 0x4168,
+ {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}};
+ if (mActiveTIPGUID == kATOK2013GUID) {
+ return TextInputProcessorID::eATOK2013;
+ }
+ // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
+ static constexpr GUID kATOK2014GUID = {
+ 0x4EF33B79,
+ 0x6AA9,
+ 0x4271,
+ {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}};
+ if (mActiveTIPGUID == kATOK2014GUID) {
+ return TextInputProcessorID::eATOK2014;
+ }
+ // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
+ static constexpr GUID kATOK2015GUID = {
+ 0xEAB4DC00,
+ 0xCE2E,
+ 0x483D,
+ {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}};
+ if (mActiveTIPGUID == kATOK2015GUID) {
+ return TextInputProcessorID::eATOK2015;
+ }
+ // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
+ static constexpr GUID kATOK2016GUID = {
+ 0x0B557B4C,
+ 0x5740,
+ 0x4110,
+ {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}};
+ if (mActiveTIPGUID == kATOK2016GUID) {
+ return TextInputProcessorID::eATOK2016;
+ }
+
+ // * ATOK 2017
+ // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F}
+ // * ATOK Passport (confirmed with version 31.1.2)
+ // - {A38F2FD9-7199-45E1-841C-BE0313D8052F}
+
+ if (IsATOKActiveInternal()) {
+ return TextInputProcessorID::eATOKUnknown;
+ }
+
+ // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7}
+ static constexpr GUID kJapanist10GUID = {
+ 0xE6D66705,
+ 0x1EDA,
+ 0x4373,
+ {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}};
+ if (mActiveTIPGUID == kJapanist10GUID) {
+ return TextInputProcessorID::eJapanist10;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() {
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftBopomofoGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftBopomofoGUID) {
+ return TextInputProcessorID::eMicrosoftBopomofo;
+ }
+ // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10)
+ static const GUID kMicrosoftChangJieGUID = {
+ 0x4BDF9F03,
+ 0xC7D3,
+ 0x11D4,
+ {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftChangJie;
+ }
+ // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPhoneticGUID = {
+ 0x761309DE,
+ 0x317A,
+ 0x11D4,
+ {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftPhonetic;
+ }
+ // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftQuickGUID = {
+ 0x6024B45F,
+ 0x5C54,
+ 0x11D4,
+ {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftQuickGUID) {
+ return TextInputProcessorID::eMicrosoftQuick;
+ }
+ // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewChangJieGUID = {
+ 0xF3BA907A,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) {
+ return TextInputProcessorID::eMicrosoftNewChangJie;
+ }
+ // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewPhoneticGUID = {
+ 0xB2F9C502,
+ 0x1742,
+ 0x11D4,
+ {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) {
+ return TextInputProcessorID::eMicrosoftNewPhonetic;
+ }
+ // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftNewQuickGUID = {
+ 0x0B883BA0,
+ 0xC1C7,
+ 0x11D4,
+ {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftNewQuickGUID) {
+ return TextInputProcessorID::eMicrosoftNewQuick;
+ }
+
+ // NOTE: There are some other Traditional Chinese TIPs installed in Windows:
+ // * Chinese Traditional Array (version 6.0)
+ // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1)
+ // * Chinese Traditional DaYi (version 6.0)
+ // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1)
+
+ // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD}
+ static const GUID kFreeChangJieGUID = {
+ 0xB58630B5,
+ 0x0ED3,
+ 0x4335,
+ {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}};
+ if (mActiveTIPGUID == kFreeChangJieGUID) {
+ return TextInputProcessorID::eFreeChangJie;
+ }
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() {
+ // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor
+ // "Microsoft Pinyin New Experience Input Style" on Win7.
+ // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftPinyinGUID = {
+ 0xFA550B04,
+ 0x5AD7,
+ 0x411F,
+ {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}};
+ if (mActiveTIPGUID == kMicrosoftPinyinGUID) {
+ return TextInputProcessorID::eMicrosoftPinyin;
+ }
+
+ // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7)
+ static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = {
+ 0xF3BA9077,
+ 0x6C7E,
+ 0x11D4,
+ {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}};
+ if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) {
+ return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle;
+ }
+ // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftWubiGUID = {
+ 0x82590C13,
+ 0xF4DD,
+ 0x44F4,
+ {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}};
+ if (mActiveTIPGUID == kMicrosoftWubiGUID) {
+ return TextInputProcessorID::eMicrosoftWubi;
+ }
+ // NOTE: There are some other Simplified Chinese TIPs installed in Windows:
+ // * Chinese Simplified QuanPin (version 6.0)
+ // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1)
+ // * Chinese Simplified ZhengMa (version 6.0)
+ // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1)
+ // * Chinese Simplified ShuangPin (version 6.0)
+ // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1)
+ // * Microsoft Pinyin ABC Input Style
+ // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7)
+ return TextInputProcessorID::eUnknown;
+ }
+
+ TextInputProcessorID ComputeActiveTIPAsKorean() {
+ // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10)
+ static constexpr GUID kMicrosoftIMEForKoreanGUID = {
+ 0xB5FE1F02,
+ 0xD5F2,
+ 0x4445,
+ {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}};
+ if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) {
+ return TextInputProcessorID::eMicrosoftIMEForKorean;
+ }
+ // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10)
+ static constexpr GUID kMicrosoftOldHangulGUID = {
+ 0xB60AF051,
+ 0x257A,
+ 0x46BC,
+ {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}};
+ if (mActiveTIPGUID == kMicrosoftOldHangulGUID) {
+ return TextInputProcessorID::eMicrosoftOldHangul;
+ }
+
+ // NOTE: There is the other Korean TIP installed in Windows:
+ // * Microsoft IME 2010
+ // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7)
+
+ return TextInputProcessorID::eUnknown;
+ }
+
+ public: // ITfInputProcessorProfileActivationSink
+ STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL,
+ DWORD);
+
+ private:
+ TSFStaticSink();
+ virtual ~TSFStaticSink() {}
+
+ bool EnsureInitActiveTIPKeyboard();
+
+ void Destroy();
+
+ void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile, nsAString& aDescription);
+ bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile);
+
+ TextInputProcessorID mActiveTIP;
+
+ // Cookie of installing ITfInputProcessorProfileActivationSink
+ DWORD mIPProfileCookie;
+
+ LANGID mLangID;
+
+ // True if current IME is implemented with IMM.
+ bool mIsIMM_IME;
+ // True if OnActivated() is already called
+ bool mOnActivatedCalled;
+
+ RefPtr<ITfThreadMgr> mThreadMgr;
+ RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles;
+
+ // Active TIP keyboard's description. If active language profile isn't TIP,
+ // i.e., IMM-IME or just a keyboard layout, this is empty.
+ nsString mActiveTIPKeyboardDescription;
+
+ // Active TIP's GUID and CLSID
+ GUID mActiveTIPGUID;
+ CLSID mActiveTIPCLSID;
+
+ static StaticRefPtr<TSFStaticSink> sInstance;
+};
+
+StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
+
+TSFStaticSink::TSFStaticSink()
+ : mActiveTIP(TextInputProcessorID::eNotComputed),
+ mIPProfileCookie(TF_INVALID_COOKIE),
+ mLangID(0),
+ mIsIMM_IME(false),
+ mOnActivatedCalled(false),
+ mActiveTIPGUID(GUID_NULL) {}
+
+bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr,
+ ITfInputProcessorProfiles* aInputProcessorProfiles) {
+ MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles,
+ "TSFStaticSink::Init() must be called only once");
+
+ mThreadMgr = aThreadMgr;
+ mInputProcessorProfiles = aInputProcessorProfiles;
+
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
+ "instance (0x%08lX)",
+ this, hr));
+ return false;
+ }
+
+ // NOTE: On Vista or later, Windows let us know activate IME changed only
+ // with ITfInputProcessorProfileActivationSink.
+ hr = source->AdviseSink(
+ IID_ITfInputProcessorProfileActivationSink,
+ static_cast<ITfInputProcessorProfileActivationSink*>(this),
+ &mIPProfileCookie);
+ if (FAILED(hr) || mIPProfileCookie == TF_INVALID_COOKIE) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to install "
+ "ITfInputProcessorProfileActivationSink (0x%08lX)",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Init(), "
+ "mIPProfileCookie=0x%08lX",
+ this, mIPProfileCookie));
+ return true;
+}
+
+void TSFStaticSink::Destroy() {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Shutdown() "
+ "mIPProfileCookie=0x%08lX",
+ this, mIPProfileCookie));
+
+ if (mIPProfileCookie != TF_INVALID_COOKIE) {
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Shutdown() FAILED to get "
+ "ITfSource instance (0x%08lX)",
+ this, hr));
+ } else {
+ hr = source->UnadviseSink(mIPProfileCookie);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
+ "ITfInputProcessorProfileActivationSink (0x%08lX)",
+ this, hr));
+ }
+ }
+ }
+
+ mThreadMgr = nullptr;
+ mInputProcessorProfiles = nullptr;
+}
+
+STDMETHODIMP
+TSFStaticSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID rclsid,
+ REFGUID catid, REFGUID guidProfile, HKL hkl,
+ DWORD dwFlags) {
+ if ((dwFlags & TF_IPSINK_FLAG_ACTIVE) &&
+ (dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT ||
+ catid == GUID_TFCAT_TIP_KEYBOARD)) {
+ mOnActivatedCalled = true;
+ mActiveTIP = TextInputProcessorID::eNotComputed;
+ mActiveTIPGUID = guidProfile;
+ mActiveTIPCLSID = rclsid;
+ mLangID = langid & 0xFFFF;
+ mIsIMM_IME = IsIMM_IME(hkl);
+ GetTIPDescription(rclsid, langid, guidProfile,
+ mActiveTIPKeyboardDescription);
+ if (mActiveTIPGUID != GUID_NULL) {
+ // key should be "LocaleID|Description". Although GUID of the
+ // profile is unique key since description may be localized for system
+ // language, unfortunately, it's too long to record as key with its
+ // description. Therefore, we should record only the description with
+ // LocaleID because Microsoft IME may not include language information.
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ nsAutoString key;
+ TSFStaticSink::GetActiveTIPNameForTelemetry(key);
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key,
+ true);
+ }
+ // Notify IMEHandler of changing active keyboard layout.
+ IMEHandler::OnKeyboardLayoutChanged();
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08lX), "
+ "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%p, "
+ "dwFlags=0x%08lX (TF_IPSINK_FLAG_ACTIVE: %s)), mIsIMM_IME=%s, "
+ "mActiveTIPDescription=\"%s\"",
+ this,
+ dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR
+ ? "TF_PROFILETYPE_INPUTPROCESSOR"
+ : dwProfileType == TF_PROFILETYPE_KEYBOARDLAYOUT
+ ? "TF_PROFILETYPE_KEYBOARDLAYOUT"
+ : "Unknown",
+ dwProfileType, langid, GetCLSIDNameStr(rclsid).get(),
+ GetGUIDNameStr(catid).get(), GetGUIDNameStr(guidProfile).get(), hkl,
+ dwFlags, GetBoolName(dwFlags & TF_IPSINK_FLAG_ACTIVE),
+ GetBoolName(mIsIMM_IME),
+ NS_ConvertUTF16toUTF8(mActiveTIPKeyboardDescription).get()));
+ return S_OK;
+}
+
+bool TSFStaticSink::EnsureInitActiveTIPKeyboard() {
+ if (mOnActivatedCalled) {
+ return true;
+ }
+
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = mInputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get input processor profile manager, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active keyboard layout profile due to no active profile, "
+ "hr=0x%08lX",
+ this, hr));
+ // XXX Should we call OnActivated() with arguments like non-TIP in this
+ // case?
+ return false;
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active TIP keyboard, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), "
+ "calling OnActivated() manually...",
+ this));
+ OnActivated(profile.dwProfileType, profile.langid, profile.clsid,
+ profile.catid, profile.guidProfile, ::GetKeyboardLayout(0),
+ TF_IPSINK_FLAG_ACTIVE);
+ return true;
+}
+
+void TSFStaticSink::GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile,
+ nsAString& aDescription) {
+ aDescription.Truncate();
+
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return;
+ }
+
+ BSTR description = nullptr;
+ HRESULT hr = mInputProcessorProfiles->GetLanguageProfileDescription(
+ aTextService, aLangID, aProfile, &description);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
+ "due to GetLanguageProfileDescription() failure, hr=0x%08lX",
+ this, hr));
+ return;
+ }
+
+ if (description && description[0]) {
+ aDescription.Assign(description);
+ }
+ ::SysFreeString(description);
+}
+
+bool TSFStaticSink::IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile) {
+ if (aTextService == CLSID_NULL || aProfile == GUID_NULL) {
+ return false;
+ }
+
+ RefPtr<IEnumTfLanguageProfiles> enumLangProfiles;
+ HRESULT hr = mInputProcessorProfiles->EnumLanguageProfiles(
+ aLangID, getter_AddRefs(enumLangProfiles));
+ if (FAILED(hr) || !enumLangProfiles) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
+ "to get language profiles enumerator, hr=0x%08lX",
+ this, hr));
+ return false;
+ }
+
+ TF_LANGUAGEPROFILE profile;
+ ULONG fetch = 0;
+ while (SUCCEEDED(enumLangProfiles->Next(1, &profile, &fetch)) && fetch) {
+ // XXX We're not sure a profile is registered with two or more categories.
+ if (profile.clsid == aTextService && profile.guidProfile == aProfile &&
+ profile.catid == GUID_TFCAT_TIP_KEYBOARD) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/******************************************************************/
+/* TSFTextStore */
+/******************************************************************/
+
+StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr;
+StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump;
+StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr;
+StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr;
+StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr;
+StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose;
+StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
+StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
+StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
+StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+const MSG* TSFTextStore::sHandlingKeyMsg = nullptr;
+DWORD TSFTextStore::sClientId = 0;
+bool TSFTextStore::sIsKeyboardEventDispatched = false;
+
+#define TEXTSTORE_DEFAULT_VIEW (1)
+
+TSFTextStore::TSFTextStore()
+ : mEditCookie(0),
+ mSinkMask(0),
+ mLock(0),
+ mLockQueued(0),
+ mHandlingKeyMessage(0) {
+ // We hope that 5 or more actions don't occur at once.
+ mPendingActions.SetCapacity(5);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
+}
+
+TSFTextStore::~TSFTextStore() {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore instance is destroyed", this));
+}
+
+bool TSFTextStore::Init(nsWindow* aWidget, const InputContext& aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init(aWidget=0x%p)", this, aWidget));
+
+ if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
+ "destroyed widget",
+ this));
+ return false;
+ }
+
+ if (mDocumentMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to already initialized",
+ this));
+ return false;
+ }
+
+ mWidget = aWidget;
+ if (NS_WARN_IF(!mWidget)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget is nullptr ",
+ this));
+ return false;
+ }
+ mDispatcher = mWidget->GetTextEventDispatcher();
+ if (NS_WARN_IF(!mDispatcher)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget->GetTextEventDispatcher() failure",
+ this));
+ return false;
+ }
+
+ mInPrivateBrowsing = aContext.mInPrivateBrowsing;
+ SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputMode);
+
+ if (aContext.mURI) {
+ // We don't need the document URL if it fails, let's ignore the error.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, mDocumentURL);
+ }
+ }
+
+ // Create document manager
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> documentMgr;
+ HRESULT hr = threadMgr->CreateDocumentMgr(getter_AddRefs(documentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
+ "(0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr due to "
+ "TextStore being destroyed during calling "
+ "ITfThreadMgr::CreateDocumentMgr()",
+ this));
+ return false;
+ }
+ // Create context and add it to document manager
+ RefPtr<ITfContext> context;
+ hr = documentMgr->CreateContext(sClientId, 0,
+ static_cast<ITextStoreACP*>(this),
+ getter_AddRefs(context), &mEditCookie);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create the context "
+ "(0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling "
+ "ITfDocumentMgr::CreateContext()",
+ this));
+ return false;
+ }
+
+ hr = documentMgr->Push(context);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08lX)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfContext due to "
+ "TextStore being destroyed during calling ITfDocumentMgr::Push()",
+ this));
+ documentMgr->Pop(TF_POPF_ALL);
+ return false;
+ }
+
+ mDocumentMgr = documentMgr;
+ mContext = context;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init() succeeded: "
+ "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08lX",
+ this, mDocumentMgr.get(), mContext.get(), mEditCookie));
+
+ return true;
+}
+
+void TSFTextStore::Destroy() {
+ if (mBeingDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy(), mLock=%s, "
+ "mComposition=%s, mHandlingKeyMessage=%u",
+ this, GetLockFlagNameStr(mLock).get(),
+ ToString(mComposition).c_str(), mHandlingKeyMessage));
+
+ mDestroyed = true;
+
+ // Destroy native caret first because it's not directly related to TSF and
+ // there may be another textstore which gets focus. So, we should avoid
+ // to destroy caret after the new one recreates caret.
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ if (mLock) {
+ mPendingDestroy = true;
+ return;
+ }
+
+ AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed);
+ mBeingDestroyed = true;
+
+ // If there is composition, TSF keeps the composition even after the text
+ // store destroyed. So, we should clear the composition here.
+ if (mComposition.isSome()) {
+ CommitCompositionInternal(false);
+ }
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Destroy(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_DESTROY)...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnLayoutChange(TS_LC_DESTROY, TEXTSTORE_DEFAULT_VIEW);
+ }
+
+ // If this is called during handling a keydown or keyup message, we should
+ // put off to release TSF objects until it completely finishes since
+ // MS-IME for Japanese refers some objects without grabbing them.
+ if (!mHandlingKeyMessage) {
+ ReleaseTSFObjects();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy() succeeded", this));
+}
+
+void TSFTextStore::ReleaseTSFObjects() {
+ MOZ_ASSERT(!mHandlingKeyMessage);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
+
+ mDocumentURL.Truncate();
+ mContext = nullptr;
+ if (mDocumentMgr) {
+ RefPtr<ITfDocumentMgr> documentMgr = mDocumentMgr.forget();
+ documentMgr->Pop(TF_POPF_ALL);
+ }
+ mSink = nullptr;
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+
+ if (!mMouseTrackers.IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects(), "
+ "removing a mouse tracker...",
+ this));
+ mMouseTrackers.Clear();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects() completed", this));
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+ if ((IID_IUnknown == riid) || (IID_ITextStoreACP == riid)) {
+ *ppv = static_cast<ITextStoreACP*>(this);
+ } else if (IID_ITfContextOwnerCompositionSink == riid) {
+ *ppv = static_cast<ITfContextOwnerCompositionSink*>(this);
+ } else if (IID_ITfMouseTrackerACP == riid) {
+ *ppv = static_cast<ITfMouseTrackerACP*>(this);
+ }
+ if (*ppv) {
+ AddRef();
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInterface() FAILED, riid=%s", this,
+ GetRIIDNameStr(riid).get()));
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseSink(REFIID riid, IUnknown* punk, DWORD dwMask) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseSink(riid=%s, punk=0x%p, dwMask=%s), "
+ "mSink=0x%p, mSinkMask=%s",
+ this, GetRIIDNameStr(riid).get(), punk, GetSinkMaskNameStr(dwMask).get(),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get()));
+
+ if (!punk) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (IID_ITextStoreACPSink != riid) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "unsupported interface",
+ this));
+ return E_INVALIDARG; // means unsupported interface.
+ }
+
+ if (!mSink) {
+ // Install sink
+ punk->QueryInterface(IID_ITextStoreACPSink, getter_AddRefs(mSink));
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "punk not having the interface",
+ this));
+ return E_UNEXPECTED;
+ }
+ } else {
+ // If sink is already installed we check to see if they are the same
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ if (comparison1 != comparison2) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_ADVISELIMIT;
+ }
+ }
+ // Update mask either for a new sink or an existing sink
+ mSinkMask = dwMask;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseSink(IUnknown* punk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p", this, punk,
+ mSink.get()));
+
+ if (!punk) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "any sink not stored",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ // Get IUnknown from both sides for comparison
+ RefPtr<IUnknown> comparison1, comparison2;
+ punk->QueryInterface(IID_IUnknown, getter_AddRefs(comparison1));
+ mSink->QueryInterface(IID_IUnknown, getter_AddRefs(comparison2));
+ // Unadvise only if sinks are the same
+ if (comparison1 != comparison2) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to "
+ "the sink being different from the stored sink",
+ this));
+ return CONNECT_E_NOCONNECTION;
+ }
+ mSink = nullptr;
+ mSinkMask = 0;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestLock(DWORD dwLockFlags, HRESULT* phrSession) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock(dwLockFlags=%s, phrSession=0x%p), "
+ "mLock=%s, mDestroyed=%s",
+ this, GetLockFlagNameStr(dwLockFlags).get(), phrSession,
+ GetLockFlagNameStr(mLock).get(), GetBoolName(mDestroyed)));
+
+ if (!mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "any sink not stored",
+ this));
+ return E_FAIL;
+ }
+ if (mDestroyed &&
+ (mContentForTSF.isNothing() || mSelectionForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "being destroyed and no information of the contents",
+ this));
+ return E_FAIL;
+ }
+ if (!phrSession) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "null phrSession",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!mLock) {
+ // put on lock
+ mLock = dwLockFlags & (~TS_LF_SYNC);
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p Locking (%s) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ // Don't release this instance during this lock because this is called by
+ // TSF but they don't grab us during this call.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ *phrSession = sink->OnLockGranted(mLock);
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ while (mLockQueued) {
+ mLock = mLockQueued;
+ mLockQueued = 0;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+ ">>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ sink->OnLockGranted(mLock);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ }
+
+ // The document is now completely unlocked.
+ mLock = 0;
+
+ MaybeFlushPendingNotifications();
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() succeeded: *phrSession=%s",
+ this, GetTextStoreReturnValueName(*phrSession)));
+ return S_OK;
+ }
+
+ // only time when reentrant lock is allowed is when caller holds a
+ // read-only lock and is requesting an async write lock
+ if (IsReadLocked() && !IsReadWriteLocked() && IsReadWriteLock(dwLockFlags) &&
+ !(dwLockFlags & TS_LF_SYNC)) {
+ *phrSession = TS_S_ASYNC;
+ mLockQueued = dwLockFlags & (~TS_LF_SYNC);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() stores the request in the "
+ "queue, *phrSession=TS_S_ASYNC",
+ this));
+ return S_OK;
+ }
+
+ // no more locks allowed
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestLock() didn't allow to lock, "
+ "*phrSession=TS_E_SYNCHRONOUS",
+ this));
+ *phrSession = TS_E_SYNCHRONOUS;
+ return E_FAIL;
+}
+
+void TSFTextStore::DidLockGranted() {
+ if (IsReadWriteLocked()) {
+ // FreeCJ (TIP for Traditional Chinese) calls SetSelection() to set caret
+ // to the start of composition string and insert a full width space for
+ // a placeholder with a call of SetText(). After that, it calls
+ // OnUpdateComposition() without new range. Therefore, let's record the
+ // composition update information here.
+ CompleteLastActionIfStillIncomplete();
+
+ FlushPendingActions();
+ }
+
+ // If the widget has gone, we don't need to notify anything.
+ if (mDestroyed || !mWidget || mWidget->Destroyed()) {
+ mPendingSelectionChangeData.reset();
+ mHasReturnedNoLayoutError = false;
+ }
+}
+
+void TSFTextStore::DispatchEvent(WidgetGUIEvent& aEvent) {
+ if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
+ return;
+ }
+ // If the event isn't a query content event, the event may be handled
+ // asynchronously. So, we should put off to answer from GetTextExt() etc.
+ if (!aEvent.AsQueryContentEvent()) {
+ mDeferNotifyingTSFUntilNextUpdate = true;
+ }
+ mWidget->DispatchWindowEvent(aEvent);
+}
+
+void TSFTextStore::FlushPendingActions() {
+ if (!mWidget || mWidget->Destroyed()) {
+ // Note that don't clear mContentForTSF because TIP may try to commit
+ // composition with a document lock. In such case, TSFTextStore needs to
+ // behave as expected by TIP.
+ mPendingActions.Clear();
+ mPendingSelectionChangeData.reset();
+ mHasReturnedNoLayoutError = false;
+ return;
+ }
+
+ // Some TIP may request lock but does nothing during the lock. In such case,
+ // this should do nothing. For example, when MS-IME for Japanese is active
+ // and we're inactivating, this case occurs and causes different behavior
+ // from the other TIPs.
+ if (mPendingActions.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsWindow> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+ for (uint32_t i = 0; i < mPendingActions.Length(); i++) {
+ PendingAction& action = mPendingActions[i];
+ switch (action.mType) {
+ case PendingAction::Type::eKeyboardEvent:
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending KeyboardEvent(%s) due to already destroyed",
+ this,
+ action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp"));
+ }
+ MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN ||
+ action.mKeyMsg.message == WM_KEYUP);
+ DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg);
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ case PendingAction::Type::eCompositionStart: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionStart={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld }, mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending compositionstart due to already destroyed",
+ this));
+ break;
+ }
+
+ if (action.mAdjustSelection) {
+ // Select composition range so the new composition replaces the range
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ widget->InitEvent(selectionSet);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = false;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ }
+
+ // eCompositionStart always causes
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. Therefore, we should
+ // wait to clear mContentForTSF until it's notified.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionstart event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionstart event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ if (!widget || widget->Destroyed()) {
+ break;
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionUpdate: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionUpdate={ mData=\"%s\", "
+ "mRanges=0x%p, mRanges->Length()=%zu }",
+ this, GetEscapedUTF8String(action.mData).get(),
+ action.mRanges.get(),
+ action.mRanges ? action.mRanges->Length() : 0));
+
+ // eCompositionChange causes a DOM text event, the IME will be notified
+ // of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. In this case, we
+ // should not clear mContentForTSF until we notify the IME of the
+ // composition update.
+ mDeferClearingContentForTSF = true;
+
+ rv = mDispatcher->SetPendingComposition(action.mData, action.mRanges);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to setting pending composition... "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionchange event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionchange event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ // Be aware, the mWidget might already have been destroyed.
+ }
+ break;
+ }
+ case PendingAction::Type::eCompositionEnd: {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eCompositionEnd={ mData=\"%s\" }",
+ this, GetEscapedUTF8String(action.mData).get()));
+
+ // Dispatching eCompositionCommit causes a DOM text event, then,
+ // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED
+ // when focused content actually handles the event. For example,
+ // when focused content is in a remote process, it's sent when
+ // all dispatched composition events have been handled in the remote
+ // process. So, until then, we don't have newer content information.
+ // Therefore, we need to put off to clear mContentForTSF.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "dispatching compositioncommit event...",
+ this));
+ WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositioncommit event, "
+ "IsHandlingCompositionInContent()=%s",
+ this, GetBoolName(IsHandlingCompositionInContent())));
+ // XXX Is this right? If there is a composition in content,
+ // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED?
+ mDeferClearingContentForTSF = !IsHandlingCompositionInContent();
+ }
+ break;
+ }
+ case PendingAction::Type::eSetSelection: {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing Type::eSetSelection={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld, mSelectionReversed=%s }, "
+ "mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(action.mSelectionReversed), GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending selectionset due to already destroyed",
+ this));
+ break;
+ }
+
+ WidgetSelectionEvent selectionSet(true, eSetSelection, widget);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = action.mSelectionReversed;
+ selectionSet.mExpandToClusterBoundary =
+ TSFStaticSink::ActiveTIP() !=
+ TextInputProcessorID::eKeymanDesktop &&
+ StaticPrefs::
+ intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries();
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED due to eSetSelection failure",
+ this));
+ break;
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected action type");
+ }
+
+ if (widget && !widget->Destroyed()) {
+ continue;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "qutting since the mWidget has gone",
+ this));
+ break;
+ }
+ mPendingActions.Clear();
+}
+
+void TSFTextStore::MaybeFlushPendingNotifications() {
+ if (mDeferNotifyingTSF) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to initializing "
+ "something...",
+ this));
+ return;
+ }
+
+ if (IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being the "
+ "document locked...",
+ this));
+ return;
+ }
+
+ if (mDeferCommittingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(false)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(false);
+ } else if (mDeferCancellingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(true)...",
+ this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(true);
+ }
+
+ if (mDeferNotifyingTSFUntilNextUpdate) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being "
+ "dispatching events...",
+ this));
+ return;
+ }
+
+ if (mPendingDestroy) {
+ Destroy();
+ return;
+ }
+
+ if (mDestroyed) {
+ // If it's already been destroyed completely, this shouldn't notify TSF of
+ // anything anymore.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "does nothing because this has already destroyed completely...",
+ this));
+ return;
+ }
+
+ if (!mDeferClearingContentForTSF && mContentForTSF.isSome()) {
+ mContentForTSF.reset();
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "mContentForTSF is set to `Nothing`",
+ this));
+ }
+
+ // When there is no cached content, we can sync actual contents and TSF/TIP
+ // expecting contents.
+ RefPtr<TSFTextStore> kungFuDeathGrip = this;
+ Unused << kungFuDeathGrip;
+ if (mContentForTSF.isNothing()) {
+ if (mPendingTextChangeData.IsValid()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfTextChange()...",
+ this));
+ NotifyTSFOfTextChange();
+ }
+ if (mPendingSelectionChangeData.isSome()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfSelectionChange()...",
+ this));
+ NotifyTSFOfSelectionChange();
+ }
+ }
+
+ if (mHasReturnedNoLayoutError) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+ }
+}
+
+void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() {
+ // If we've already been destroyed, we cannot do anything.
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because it's already been destroyed",
+ this));
+ return;
+ }
+
+ // If we're not handling key message or we've already dispatched a keyboard
+ // event for the handling key message, we should do nothing anymore.
+ if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "does nothing because not necessary to dispatch keyboard event",
+ this));
+ return;
+ }
+
+ sIsKeyboardEventDispatched = true;
+ // If the document is locked, just adding the task to dispatching an event
+ // to the queue.
+ if (IsReadLocked()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "adding to dispatch a keyboard event into the queue...",
+ this));
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eKeyboardEvent;
+ memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG));
+ return;
+ }
+
+ // Otherwise, dispatch a keyboard event.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), "
+ "trying to dispatch a keyboard event...",
+ this));
+ DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg);
+}
+
+void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) {
+ MOZ_ASSERT(mWidget);
+ MOZ_ASSERT(!mWidget->Destroyed());
+ MOZ_ASSERT(!mDestroyed);
+
+ ModifierKeyState modKeyState;
+ MSG msg(aMsg);
+ msg.wParam = VK_PROCESSKEY;
+ NativeKey nativeKey(mWidget, msg, modKeyState);
+ switch (aMsg.message) {
+ case WM_KEYDOWN:
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyDown event...",
+ this));
+ nativeKey.HandleKeyDownMessage();
+ break;
+ case WM_KEYUP:
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "dispatching an eKeyUp event...",
+ this));
+ nativeKey.HandleKeyUpMessage();
+ break;
+ default:
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), "
+ "ERROR, it doesn't handle the message",
+ this));
+ break;
+ }
+}
+
+STDMETHODIMP
+TSFTextStore::GetStatus(TS_STATUS* pdcs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
+
+ if (!pdcs) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
+ return E_INVALIDARG;
+ }
+ // We manage on-screen keyboard by own.
+ pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE;
+ // we use a "flat" text model for TSF support so no hidden text
+ pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch,
+ LONG* pacpResultStart, LONG* pacpResultEnd) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
+ "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
+ this, acpTestStart, acpTestEnd, cch, pacpResultStart, pacpResultEnd));
+
+ if (!pacpResultStart || !pacpResultEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "the null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "wrong argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // XXX need to adjust to cluster boundary
+ // Assume we are given good offsets for now
+ if (mComposition.isNothing() &&
+ ((StaticPrefs::
+ intl_tsf_hack_ms_traditional_chinese_query_insert_result() &&
+ TSFStaticSink::IsMSChangJieOrMSQuickActive()) ||
+ (StaticPrefs::
+ intl_tsf_hack_ms_simplified_chinese_query_insert_result() &&
+ TSFStaticSink::IsMSPinyinOrMSWubiActive()))) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::QueryInsert() WARNING using different "
+ "result for the TIP",
+ this));
+ // Chinese TIPs of Microsoft assume that QueryInsert() returns selected
+ // range which should be removed.
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestEnd;
+ } else {
+ *pacpResultStart = acpTestStart;
+ *pacpResultEnd = acpTestStart + cch;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert() succeeded: "
+ "*pacpResultStart=%ld, *pacpResultEnd=%ld)",
+ this, *pacpResultStart, *pacpResultEnd));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetSelection(ULONG ulIndex, ULONG ulCount,
+ TS_SELECTION_ACP* pSelection, ULONG* pcFetched) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection(ulIndex=%lu, ulCount=%lu, "
+ "pSelection=0x%p, pcFetched=0x%p)",
+ this, ulIndex, ulCount, pSelection, pcFetched));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to not locked", this));
+ return TS_E_NOLOCK;
+ }
+ if (!ulCount || !pSelection || !pcFetched) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pcFetched = 0;
+
+ if (ulIndex != static_cast<ULONG>(TS_DEFAULT_SELECTION) && ulIndex != 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "unsupported selection",
+ this));
+ return TS_E_NOSELECTION;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ if (DoNotReturnErrorFromGetSelection()) {
+ *pSelection = Selection::EmptyACP();
+ *pcFetched = 1;
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() returns fake selection range "
+ "for avoiding a crash in TSF, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (!selectionForTSF->HasRange()) {
+ *pSelection = Selection::EmptyACP();
+ *pcFetched = 0;
+ return TS_E_NOSELECTION;
+ }
+ *pSelection = selectionForTSF->ACPRef();
+ *pcFetched = 1;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s",
+ this, mozilla::ToString(*pSelection).c_str()));
+ return S_OK;
+}
+
+// static
+bool TSFTextStore::DoNotReturnErrorFromGetSelection() {
+ // There is a crash bug of TSF if we return error from GetSelection().
+ // That was introduced in Anniversary Update (build 14393, see bug 1312302)
+ // TODO: We should avoid to run this hack on fixed builds. When we get
+ // exact build number, we should get back here.
+ static bool sTSFMayCrashIfGetSelectionReturnsError =
+ IsWin10AnniversaryUpdateOrLater();
+ return sTSFMayCrashIfGetSelectionReturnsError;
+}
+
+Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() {
+ // This should be called when the document is locked or the content hasn't
+ // been abandoned yet.
+ if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing",
+ this, GetBoolName(IsReadLocked())));
+ return mContentForTSF;
+ }
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "SelectionForTSF() failure",
+ this));
+ mContentForTSF.reset();
+ return mContentForTSF;
+ }
+
+ if (mContentForTSF.isNothing()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mIsInitializingContentForTSF,
+ "TSFTextStore::ContentForTSF() shouldn't be called recursively");
+
+ AutoNotifyingTSFBatch deferNotifyingTSF(*this);
+ AutoRestore<bool> saveInitializingContetTSF(mIsInitializingContentForTSF);
+ mIsInitializingContentForTSF = true;
+
+ nsString text; // Don't use auto string for avoiding to copy long string.
+ if (NS_WARN_IF(!GetCurrentText(text))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to "
+ "GetCurrentText() failure",
+ this));
+ return mContentForTSF;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mContentForTSF.isNothing(),
+ "How was it initialized recursively?");
+ mContentForTSF.reset(); // For avoiding crash in release channel
+ mContentForTSF.emplace(*this, text);
+ // Basically, the cached content which is expected by TSF/TIP should be
+ // cleared after active composition is committed or the document lock is
+ // unlocked. However, in e10s mode, content will be modified
+ // asynchronously. In such case, mDeferClearingContentForTSF may be
+ // true until whole dispatched events are handled by the focused editor.
+ mDeferClearingContentForTSF = false;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this,
+ mozilla::ToString(mContentForTSF).c_str()));
+
+ return mContentForTSF;
+}
+
+bool TSFTextStore::CanAccessActualContentDirectly() const {
+ if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) {
+ return true;
+ }
+
+ // If the cached content has been changed by something except composition,
+ // the content cache may be different from actual content.
+ if (mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ return false;
+ }
+
+ // If the cached selection isn't changed, cached content and actual content
+ // should be same.
+ if (mPendingSelectionChangeData.isNothing()) {
+ return true;
+ }
+
+ return mSelectionForTSF->EqualsExceptDirection(*mPendingSelectionChangeData);
+}
+
+bool TSFTextStore::GetCurrentText(nsAString& aTextContent) {
+ if (mContentForTSF.isSome()) {
+ aTextContent = mContentForTSF->TextRef();
+ return true;
+ }
+
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mWidget && !mWidget->Destroyed());
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetCurrentText(): "
+ "retrieving text from the content...",
+ this));
+
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mWidget);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mWidget->InitEvent(queryTextContentEvent);
+ DispatchEvent(queryTextContentEvent);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
+ "eQueryTextContent failure",
+ this));
+ aTextContent.Truncate();
+ return false;
+ }
+
+ aTextContent = queryTextContentEvent.mReply->DataRef();
+ return true;
+}
+
+Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() {
+ if (mSelectionForTSF.isNothing()) {
+ MOZ_ASSERT(!mDestroyed);
+ // If the window has never been available, we should crash since working
+ // with broken values may make TIP confused.
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget");
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mIsInitializingSelectionForTSF,
+ "TSFTextStore::SelectionForTSF() shouldn't be called recursively");
+
+ AutoNotifyingTSFBatch deferNotifyingTSF(*this);
+ AutoRestore<bool> saveInitializingSelectionForTSF(
+ mIsInitializingSelectionForTSF);
+ mIsInitializingSelectionForTSF = true;
+
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ mWidget);
+ mWidget->InitEvent(querySelectedTextEvent);
+ DispatchEvent(querySelectedTextEvent);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ return mSelectionForTSF;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(mSelectionForTSF.isNothing(),
+ "How was it initialized recursively?");
+ mSelectionForTSF = Some(Selection(querySelectedTextEvent));
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SelectionForTSF() succeeded, "
+ "mSelectionForTSF=%s",
+ this, ToString(mSelectionForTSF).c_str()));
+
+ return mSelectionForTSF;
+}
+
+static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) {
+ RefPtr<ITfRangeACP> rangeACP;
+ aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP));
+ NS_ENSURE_TRUE(rangeACP, E_FAIL);
+ return rangeACP->GetExtent(aStart, aLength);
+}
+
+static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) {
+ switch (aDisplayAttr.bAttr) {
+ case TF_ATTR_TARGET_CONVERTED:
+ return TextRangeType::eSelectedClause;
+ case TF_ATTR_CONVERTED:
+ return TextRangeType::eConvertedClause;
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ return TextRangeType::eRawClause;
+ }
+}
+
+HRESULT
+TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange,
+ TF_DISPLAYATTRIBUTE* aResult) {
+ NS_ENSURE_TRUE(aAttrProperty, E_FAIL);
+ NS_ENSURE_TRUE(aRange, E_FAIL);
+ NS_ENSURE_TRUE(aResult, E_FAIL);
+
+ HRESULT hr;
+
+ if (MOZ_LOG_TEST(gIMELog, LogLevel::Debug)) {
+ LONG start = 0, length = 0;
+ hr = GetRangeExtent(aRange, &start, &length);
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute(): "
+ "GetDisplayAttribute range=%ld-%ld (hr=%s)",
+ this, start - mComposition->StartOffset(),
+ start - mComposition->StartOffset() + length,
+ GetCommonReturnValueName(hr)));
+ }
+
+ VARIANT propValue;
+ ::VariantInit(&propValue);
+ hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() failed",
+ this));
+ return hr;
+ }
+ if (VT_I4 != propValue.vt) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() returns non-VT_I4 value",
+ this));
+ ::VariantClear(&propValue);
+ return E_FAIL;
+ }
+
+ RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr();
+ if (NS_WARN_IF(!categoryMgr)) {
+ return E_FAIL;
+ }
+ GUID guid;
+ hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
+ ::VariantClear(&propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfCategoryMgr::GetGUID() failed",
+ this));
+ return hr;
+ }
+
+ RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr();
+ if (NS_WARN_IF(!displayAttrMgr)) {
+ return E_FAIL;
+ }
+ RefPtr<ITfDisplayAttributeInfo> info;
+ hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
+ nullptr);
+ if (FAILED(hr) || !info) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ hr = info->GetAttributeInfo(aResult);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeInfo::GetAttributeInfo() failed",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
+ "Result={ %s }",
+ this, GetDisplayAttrStr(*aResult).get()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary("
+ "aRangeNew=0x%p), mComposition=%s",
+ this, aRangeNew, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ HRESULT hr;
+ RefPtr<ITfCompositionView> pComposition(mComposition->GetView());
+ RefPtr<ITfRange> composingRange(aRangeNew);
+ if (!composingRange) {
+ hr = pComposition->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ }
+
+ // Get starting offset of the composition
+ LONG compStart = 0, compLength = 0;
+ hr = GetRangeExtent(composingRange, &compStart, &compLength);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ if (mComposition->StartOffset() == compStart &&
+ mComposition->Length() == compLength) {
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
+ "restaring composition because of compostion range is changed "
+ "(range=%ld-%ld, mComposition=%s)",
+ this, compStart, compStart + compLength,
+ ToString(mComposition).c_str()));
+
+ // If the queried composition length is different from the length
+ // of our composition string, OnUpdateComposition is being called
+ // because a part of the original composition was committed.
+ hr = RestartComposition(*mComposition, pComposition, composingRange);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to RestartComposition() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded", this));
+ return S_OK;
+}
+
+HRESULT TSFTextStore::RestartComposition(Composition& aCurrentComposition,
+ ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+
+ LONG newStart, newLength;
+ HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
+ LONG newEnd = newStart + newLength;
+
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ // If the new range has no overlap with the crrent range, we just commit
+ // the composition and restart new composition with the new range but
+ // current selection range should be preserved.
+ if (newStart >= aCurrentComposition.EndOffset() ||
+ newEnd <= aCurrentComposition.StartOffset()) {
+ RecordCompositionEndAction();
+ RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
+ "aNewRange=0x%p { newStart=%ld, newLength=%ld }), "
+ "aCurrentComposition=%s, "
+ "selectionForTSF=%s",
+ this, aCompositionView, aNewRange, newStart, newLength,
+ ToString(aCurrentComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ // If the new range has an overlap with the current one, we should not commit
+ // the whole current range to avoid creating an odd undo transaction.
+ // I.e., the overlapped range which is being composed should not appear in
+ // undo transaction.
+
+ // Backup current composition data and selection data.
+ Composition oldComposition = aCurrentComposition;
+ Selection oldSelection = *selectionForTSF;
+
+ // Commit only the part of composition.
+ LONG keepComposingStartOffset =
+ std::max(oldComposition.StartOffset(), newStart);
+ LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd);
+ MOZ_ASSERT(
+ keepComposingStartOffset <= keepComposingEndOffset,
+ "Why keepComposingEndOffset is smaller than keepComposingStartOffset?");
+ LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset;
+ // Remove the overlapped part from the commit string.
+ nsAutoString commitString(oldComposition.DataRef());
+ commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength);
+ // Update the composition string.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->ReplaceTextWith(oldComposition.StartOffset(),
+ oldComposition.Length(), commitString);
+ MOZ_ASSERT(mComposition.isSome());
+ // Record a compositionupdate action for commit the part of composing string.
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ if (mComposition.isSome()) {
+ action->mData = mComposition->DataRef();
+ }
+ action->mRanges->Clear();
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>(
+ oldComposition.StartOffset() + commitString.Length());
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ action->mIncomplete = false;
+
+ // Record compositionend action.
+ RecordCompositionEndAction();
+
+ // Record compositionstart action only with the new start since this method
+ // hasn't restored composing string yet.
+ RecordCompositionStartAction(aCompositionView, newStart, 0, false);
+
+ // Restore the latest text content and selection.
+ contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring(
+ oldComposition.DataRef(),
+ keepComposingStartOffset - oldComposition.StartOffset(),
+ keepComposingLength));
+ selectionForTSF = Some(oldSelection);
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition() succeeded, "
+ "mComposition=%s, selectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+
+ return S_OK;
+}
+
+static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) {
+ switch (aTSFColor.type) {
+ case TF_CT_SYSCOLOR: {
+ DWORD sysColor = ::GetSysColor(aTSFColor.nIndex);
+ aResult =
+ NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor));
+ return true;
+ }
+ case TF_CT_COLORREF:
+ aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr),
+ GetBValue(aTSFColor.cr));
+ return true;
+ case TF_CT_NONE:
+ default:
+ return false;
+ }
+}
+
+static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle,
+ TextRangeStyle::LineStyle& aTextRangeLineStyle) {
+ switch (aTSFLineStyle) {
+ case TF_LS_NONE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::None;
+ return true;
+ case TF_LS_SOLID:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid;
+ return true;
+ case TF_LS_DOT:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted;
+ return true;
+ case TF_LS_DASH:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed;
+ return true;
+ case TF_LS_SQUIGGLE:
+ aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy;
+ return true;
+ default:
+ return false;
+ }
+}
+
+HRESULT
+TSFTextStore::RecordCompositionUpdateAction() {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to no composition view",
+ this));
+ return E_FAIL;
+ }
+
+ // Getting display attributes is *really* complicated!
+ // We first get the context and the property objects to query for
+ // attributes, but since a big range can have a variety of values for
+ // the attribute, we have to find out all the ranges that have distinct
+ // attribute values. Then we query for what the value represents through
+ // the display attribute manager and translate that to TextRange to be
+ // sent in eCompositionChange
+
+ RefPtr<ITfProperty> attrPropetry;
+ HRESULT hr =
+ mContext->GetProperty(GUID_PROP_ATTRIBUTE, getter_AddRefs(attrPropetry));
+ if (FAILED(hr) || !attrPropetry) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to mContext->GetProperty() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ RefPtr<ITfRange> composingRange;
+ hr = mComposition->GetView()->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "FAILED due to mComposition->GetView()->GetRange() failure",
+ this));
+ return hr;
+ }
+
+ RefPtr<IEnumTfRanges> enumRanges;
+ hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
+ getter_AddRefs(enumRanges), composingRange);
+ if (FAILED(hr) || !enumRanges) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to attrPropetry->EnumRanges() failure",
+ this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ // First, put the log of content and selection here.
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition->DataRef();
+ // The ranges might already have been initialized, however, if this is
+ // called again, that means we need to overwrite the ranges with current
+ // information.
+ action->mRanges->Clear();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (!action->mData.IsEmpty()) {
+ TextRange newRange;
+ // No matter if we have display attribute info or not,
+ // we always pass in at least one range to eCompositionChange
+ newRange.mStartOffset = 0;
+ newRange.mEndOffset = action->mData.Length();
+ newRange.mRangeType = TextRangeType::eRawClause;
+ action->mRanges->AppendElement(newRange);
+
+ RefPtr<ITfRange> range;
+ while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) {
+ if (NS_WARN_IF(!range)) {
+ break;
+ }
+
+ LONG rangeStart = 0, rangeLength = 0;
+ if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) {
+ continue;
+ }
+ // The range may include out of composition string. We should ignore
+ // outside of the composition string.
+ LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()),
+ mComposition->EndOffset());
+ LONG end = std::max(
+ std::min(rangeStart + rangeLength, mComposition->EndOffset()),
+ mComposition->StartOffset());
+ LONG length = end - start;
+ if (length < 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores invalid range (%ld-%ld)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+ if (!length) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores a range due to outside of the composition or empty "
+ "(%ld-%ld)",
+ this, rangeStart - mComposition->StartOffset(),
+ rangeStart - mComposition->StartOffset() + rangeLength));
+ continue;
+ }
+
+ TextRange newRange;
+ newRange.mStartOffset =
+ static_cast<uint32_t>(start - mComposition->StartOffset());
+ // The end of the last range in the array is
+ // always kept at the end of composition
+ newRange.mEndOffset = mComposition->Length();
+
+ TF_DISPLAYATTRIBUTE attr;
+ hr = GetDisplayAttribute(attrPropetry, range, &attr);
+ if (FAILED(hr)) {
+ newRange.mRangeType = TextRangeType::eRawClause;
+ } else {
+ newRange.mRangeType = GetGeckoSelectionValue(attr);
+ if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+ if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+ if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) {
+ newRange.mRangeStyle.mDefinedStyles |=
+ TextRangeStyle::DEFINED_LINESTYLE;
+ newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0;
+ }
+ }
+
+ TextRange& lastRange = action->mRanges->LastElement();
+ if (lastRange.mStartOffset == newRange.mStartOffset) {
+ // Replace range if last range is the same as this one
+ // So that ranges don't overlap and confuse the editor
+ lastRange = newRange;
+ } else {
+ lastRange.mEndOffset = newRange.mStartOffset;
+ action->mRanges->AppendElement(newRange);
+ }
+ }
+
+ // We need to hack for Korean Input System which is Korean standard TIP.
+ // It sets no change style to IME selection (the selection is always only
+ // one). So, the composition string looks like normal (or committed)
+ // string. At this time, current selection range is same as the
+ // composition string range. Other applications set a wide caret which
+ // covers the composition string, however, Gecko doesn't support the wide
+ // caret drawing now (Gecko doesn't support XOR drawing), unfortunately.
+ // For now, we should change the range style to undefined.
+ if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) {
+ TextRange& range = action->mRanges->ElementAt(0);
+ LONG start = selectionForTSF->MinOffset();
+ LONG end = selectionForTSF->MaxOffset();
+ if (static_cast<LONG>(range.mStartOffset) ==
+ start - mComposition->StartOffset() &&
+ static_cast<LONG>(range.mEndOffset) ==
+ end - mComposition->StartOffset() &&
+ range.mRangeStyle.IsNoChangeStyle()) {
+ range.mRangeStyle.Clear();
+ // The looks of selected type is better than others.
+ range.mRangeType = TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ // The caret position has to be collapsed.
+ uint32_t caretPosition = static_cast<uint32_t>(
+ selectionForTSF->HasRange()
+ ? selectionForTSF->MaxOffset() - mComposition->StartOffset()
+ : mComposition->StartOffset());
+
+ // If caret is in the target clause and it doesn't have specific style,
+ // the target clause will be painted as normal selection range. Since
+ // caret shouldn't be in selection range on Windows, we shouldn't append
+ // caret range in such case.
+ const TextRange* targetClause = action->mRanges->GetTargetClause();
+ if (!targetClause || targetClause->mRangeStyle.IsDefined() ||
+ caretPosition < targetClause->mStartOffset ||
+ caretPosition > targetClause->mEndOffset) {
+ TextRange caretRange;
+ caretRange.mStartOffset = caretRange.mEndOffset = caretPosition;
+ caretRange.mRangeType = TextRangeType::eCaret;
+ action->mRanges->AppendElement(caretRange);
+ }
+ }
+
+ action->mIncomplete = false;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "succeeded",
+ this));
+
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
+ bool aDispatchCompositionChangeEvent) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SetSelectionInternal(pSelection=%s, "
+ "aDispatchCompositionChangeEvent=%s), mComposition=%s",
+ this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr",
+ GetBoolName(aDispatchCompositionChangeEvent),
+ ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(IsReadWriteLocked());
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+
+ // If actually the range is not changing, we should do nothing.
+ // Perhaps, we can ignore the difference change because it must not be
+ // important for following edit.
+ if (selectionForTSF->EqualsExceptDirection(*pSelection)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
+ "did nothing because the selection range isn't changing",
+ this));
+ selectionForTSF->SetSelection(*pSelection);
+ return S_OK;
+ }
+
+ if (mComposition.isSome()) {
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RestartCompositionIfNecessary();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+ }
+ if (pSelection->acpStart < mComposition->StartOffset() ||
+ pSelection->acpEnd > mComposition->EndOffset()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "the selection being out of the composition string",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ // Emulate selection during compositions
+ selectionForTSF->SetSelection(*pSelection);
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+ }
+ return S_OK;
+ }
+
+ TS_SELECTION_ACP selectionInContent(*pSelection);
+
+ // If mContentForTSF caches old contents which is now different from
+ // actual contents, we need some complicated hack here...
+ // Note that this hack assumes that this is used for reconversion.
+ if (mContentForTSF.isSome() && mPendingTextChangeData.IsValid() &&
+ !mPendingTextChangeData.mCausedOnlyByComposition) {
+ uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart);
+ uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd);
+ if (mPendingTextChangeData.mStartOffset >= endOffset) {
+ // Setting selection before any changed ranges is fine.
+ } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) {
+ // Setting selection after removed range is fine with following
+ // adjustment.
+ selectionInContent.acpStart += mPendingTextChangeData.Difference();
+ selectionInContent.acpEnd += mPendingTextChangeData.Difference();
+ } else if (startOffset == endOffset) {
+ // Moving caret position may be fine in most cases even if the insertion
+ // point has already gone but in this case, composition will be inserted
+ // to unexpected position, though.
+ // It seems that moving caret into middle of the new text is odd.
+ // Perhaps, end of it is expected by users in most cases.
+ selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset;
+ selectionInContent.acpEnd = selectionInContent.acpStart;
+ } else {
+ // Otherwise, i.e., setting range has already gone, we cannot set
+ // selection properly.
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "there is unknown content change",
+ this));
+ return E_FAIL;
+ }
+ }
+
+ CompleteLastActionIfStillIncomplete();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eSetSelection;
+ action->mSelectionStart = selectionInContent.acpStart;
+ action->mSelectionLength =
+ selectionInContent.acpEnd - selectionInContent.acpStart;
+ action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START);
+
+ // Use TSF specified selection for updating mSelectionForTSF.
+ selectionForTSF->SetSelection(*pSelection);
+
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), "
+ "mComposition=%s",
+ this, ulCount,
+ pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr",
+ ToString(mComposition).c_str()));
+
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+ if (ulCount != 1) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "trying setting multiple selection",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = SetSelectionInternal(pSelection, true);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection() succeeded", this));
+ }
+ return hr;
+}
+
+STDMETHODIMP
+TSFTextStore::GetText(LONG acpStart, LONG acpEnd, WCHAR* pchPlain,
+ ULONG cchPlainReq, ULONG* pcchPlainOut,
+ TS_RUNINFO* prgRunInfo, ULONG ulRunInfoReq,
+ ULONG* pulRunInfoOut, LONG* pacpNext) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText(acpStart=%ld, acpEnd=%ld, pchPlain=0x%p, "
+ "cchPlainReq=%lu, pcchPlainOut=0x%p, prgRunInfo=0x%p, ulRunInfoReq=%lu, "
+ "pulRunInfoOut=0x%p, pacpNext=0x%p), mComposition=%s",
+ this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo,
+ ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str()));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pcchPlainOut || (!pchPlain && !prgRunInfo) ||
+ !cchPlainReq != !pchPlain || !ulRunInfoReq != !prgRunInfo) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (acpStart < 0 || acpEnd < -1 || (acpEnd != -1 && acpStart > acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ // Making sure to null-terminate string just to be on the safe side
+ *pcchPlainOut = 0;
+ if (pchPlain && cchPlainReq) *pchPlain = 0;
+ if (pulRunInfoOut) *pulRunInfoOut = 0;
+ if (pacpNext) *pacpNext = acpStart;
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = 0;
+ prgRunInfo->type = TS_RT_PLAIN;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpStart is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ if (acpEnd != -1 &&
+ contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "acpEnd is larger offset than the actual text length",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+ uint32_t length = (acpEnd == -1) ? contentForTSF->TextRef().Length() -
+ static_cast<uint32_t>(acpStart)
+ : static_cast<uint32_t>(acpEnd - acpStart);
+ if (cchPlainReq && cchPlainReq - 1 < length) {
+ length = cchPlainReq - 1;
+ }
+ if (length) {
+ if (pchPlain && cchPlainReq) {
+ const char16_t* startChar =
+ contentForTSF->TextRef().BeginReading() + acpStart;
+ memcpy(pchPlain, startChar, length * sizeof(*pchPlain));
+ pchPlain[length] = 0;
+ *pcchPlainOut = length;
+ }
+ if (prgRunInfo && ulRunInfoReq) {
+ prgRunInfo->uCount = length;
+ prgRunInfo->type = TS_RT_PLAIN;
+ if (pulRunInfoOut) *pulRunInfoOut = 1;
+ }
+ if (pacpNext) *pacpNext = acpStart + length;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetText() succeeded: pcchPlainOut=0x%p, "
+ "*prgRunInfo={ uCount=%lu, type=%s }, *pulRunInfoOut=%lu, "
+ "*pacpNext=%ld)",
+ this, pcchPlainOut, prgRunInfo ? prgRunInfo->uCount : 0,
+ prgRunInfo ? GetTextRunTypeName(prgRunInfo->type) : "N/A",
+ pulRunInfoOut ? *pulRunInfoOut : 0, pacpNext ? *pacpNext : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::SetText(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ const WCHAR* pchText, ULONG cch, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, acpEnd=%ld, "
+ "pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), mComposition=%s",
+ this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified",
+ acpStart, acpEnd, pchText,
+ pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch,
+ pChange, ToString(mComposition).c_str()));
+
+ // Per SDK documentation, and since we don't have better
+ // ways to do this, this method acts as a helper to
+ // call SetSelection followed by InsertTextAtSelection
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ TS_SELECTION_ACP selection;
+ selection.acpStart = acpStart;
+ selection.acpEnd = acpEnd;
+ selection.style.ase = TS_AE_END;
+ selection.style.fInterimChar = 0;
+ // Set selection to desired range
+ HRESULT hr = SetSelectionInternal(&selection);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "SetSelectionInternal() failure",
+ this));
+ return hr;
+ }
+ // Replace just selected text
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText() succeeded: pChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, pChange ? pChange->acpStart : 0,
+ pChange ? pChange->acpOldEnd : 0, pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetFormattedText(LONG acpStart, LONG acpEnd,
+ IDataObject** ppDataObject) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetFormattedText() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // no support for formatted text
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::GetEmbedded(LONG acpPos, REFGUID rguidService, REFIID riid,
+ IUnknown** ppunk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+TSFTextStore::QueryInsertEmbedded(const GUID* pguidService,
+ const FORMATETC* pFormatEtc,
+ BOOL* pfInsertable) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsertEmbedded() called "
+ "but not supported, *pfInsertable=FALSE (S_OK)",
+ this));
+
+ // embedded objects are not supported
+ *pfInsertable = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbedded(DWORD dwFlags, LONG acpStart, LONG acpEnd,
+ IDataObject* pDataObject, TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbedded() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+// static
+bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() {
+ // FYI: Google Japanese Input may be an IMM-IME. If it's installed on
+ // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP.
+ // However, if it's installed on Win7 and has not been updated yet
+ // after the OS is upgraded to Win8 or later, it's still an IMM-IME.
+ // Therefore, we also need to check with IMMHandler here.
+ if (!StaticPrefs::intl_ime_hack_set_input_scope_of_url_bar_to_default()) {
+ return false;
+ }
+
+ if (IMMHandler::IsGoogleJapaneseInputActive()) {
+ return true;
+ }
+
+ switch (TSFStaticSink::ActiveTIP()) {
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ case TextInputProcessorID::eGoogleJapaneseInput:
+ case TextInputProcessorID::eMicrosoftBopomofo:
+ case TextInputProcessorID::eMicrosoftChangJie:
+ case TextInputProcessorID::eMicrosoftPhonetic:
+ case TextInputProcessorID::eMicrosoftQuick:
+ case TextInputProcessorID::eMicrosoftNewChangJie:
+ case TextInputProcessorID::eMicrosoftNewPhonetic:
+ case TextInputProcessorID::eMicrosoftNewQuick:
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle:
+ case TextInputProcessorID::eMicrosoftOldHangul:
+ case TextInputProcessorID::eMicrosoftWubi:
+ case TextInputProcessorID::eMicrosoftIMEForKorean:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputMode) {
+ mInputScopes.Clear();
+
+ // IME may refer only first input scope, but we will append inputmode's
+ // input scopes too like Chrome since IME may refer it.
+ IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes);
+ IMEHandler::AppendInputScopeFromInputMode(aHTMLInputMode, mInputScopes);
+
+ if (mInPrivateBrowsing) {
+ mInputScopes.AppendElement(IS_PRIVATE);
+ }
+}
+
+int32_t TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID) {
+ if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
+ return eInputScope;
+ }
+ if (IsEqualGUID(aAttrID, sGUID_PROP_URL)) {
+ return eDocumentURL;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_VerticalWriting)) {
+ return eTextVerticalWriting;
+ }
+ if (IsEqualGUID(aAttrID, TSATTRID_Text_Orientation)) {
+ return eTextOrientation;
+ }
+ return eNotSupported;
+}
+
+TS_ATTRID
+TSFTextStore::GetAttrID(int32_t aIndex) {
+ switch (aIndex) {
+ case eInputScope:
+ return GUID_PROP_INPUTSCOPE;
+ case eDocumentURL:
+ return sGUID_PROP_URL;
+ case eTextVerticalWriting:
+ return TSATTRID_Text_VerticalWriting;
+ case eTextOrientation:
+ return TSATTRID_Text_Orientation;
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ return GUID_NULL;
+ }
+}
+
+HRESULT
+TSFTextStore::HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
+ const TS_ATTRID* aFilterAttrs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
+ "aFilterCount=%lu)",
+ this, GetFindFlagName(aFlags).get(), aFilterCount));
+
+ // This is a little weird! RequestSupportedAttrs gives us advanced notice
+ // of a support query via RetrieveRequestedAttrs for a specific attribute.
+ // RetrieveRequestedAttrs needs to return valid data for all attributes we
+ // support, but the text service will only want the input scope object
+ // returned in RetrieveRequestedAttrs if the dwFlags passed in here contains
+ // TS_ATTR_FIND_WANT_VALUE.
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ mRequestedAttrs[i] = false;
+ }
+ mRequestedAttrValues = !!(aFlags & TS_ATTR_FIND_WANT_VALUE);
+
+ for (uint32_t i = 0; i < aFilterCount; i++) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(), "
+ "requested attr=%s",
+ this, GetGUIDNameStrWithTable(aFilterAttrs[i]).get()));
+ int32_t index = GetRequestedAttrIndex(aFilterAttrs[i]);
+ if (index != eNotSupported) {
+ mRequestedAttrs[index] = true;
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::RequestSupportedAttrs(DWORD dwFlags, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestSupportedAttrs(dwFlags=%s, "
+ "cFilterAttrs=%lu)",
+ this, GetFindFlagName(dwFlags).get(), cFilterAttrs));
+
+ return HandleRequestAttrs(dwFlags, cFilterAttrs, paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsAtPosition(LONG acpPos, ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsAtPosition(acpPos=%ld, "
+ "cFilterAttrs=%lu, dwFlags=%s)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ return HandleRequestAttrs(dwFlags | TS_ATTR_FIND_WANT_VALUE, cFilterAttrs,
+ paFilterAttrs);
+}
+
+STDMETHODIMP
+TSFTextStore::RequestAttrsTransitioningAtPosition(LONG acpPos,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttr,
+ DWORD dwFlags) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RequestAttrsTransitioningAtPosition("
+ "acpPos=%ld, cFilterAttrs=%lu, dwFlags=%s) called but not supported "
+ "(S_OK)",
+ this, acpPos, cFilterAttrs, GetFindFlagName(dwFlags).get()));
+
+ // no per character attributes defined
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::FindNextAttrTransition(LONG acpStart, LONG acpHalt,
+ ULONG cFilterAttrs,
+ const TS_ATTRID* paFilterAttrs,
+ DWORD dwFlags, LONG* pacpNext,
+ BOOL* pfFound, LONG* plFoundOffset) {
+ if (!pacpNext || !pfFound || !plFoundOffset) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::FindNextAttrTransition() called "
+ "but not supported (S_OK)",
+ this));
+
+ // no per character attributes defined
+ *pacpNext = *plFoundOffset = acpHalt;
+ *pfFound = FALSE;
+ return S_OK;
+}
+
+// To test the document URL result, define this to out put it to the stdout
+// #define DEBUG_PRINT_DOCUMENT_URL
+
+STDMETHODIMP
+TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount, TS_ATTRVAL* paAttrVals,
+ ULONG* pcFetched) {
+ if (!pcFetched || !paAttrVals) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ ULONG expectedCount = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (mRequestedAttrs[i]) {
+ expectedCount++;
+ }
+ }
+ if (ulCount < expectedCount) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "not enough count ulCount=%lu, expectedCount=%lu",
+ this, ulCount, expectedCount));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "ulCount=%lu, mRequestedAttrValues=%s",
+ this, ulCount, GetBoolName(mRequestedAttrValues)));
+
+ auto GetExposingURL = [&]() -> BSTR {
+ const bool allowed =
+ StaticPrefs::intl_tsf_expose_url_allowed() &&
+ (!mInPrivateBrowsing ||
+ StaticPrefs::intl_tsf_expose_url_in_private_browsing_allowed());
+ if (!allowed || mDocumentURL.IsEmpty()) {
+ BSTR emptyString = ::SysAllocString(L"");
+ MOZ_ASSERT(
+ emptyString,
+ "We need to return valid BSTR pointer to notify TSF of supporting it "
+ "with a pointer to empty string");
+ return emptyString;
+ }
+ return ::SysAllocString(mDocumentURL.get());
+ };
+
+#ifdef DEBUG_PRINT_DOCUMENT_URL
+ {
+ BSTR exposingURL = GetExposingURL();
+ printf("TSFTextStore::RetrieveRequestedAttrs: DocumentURL=\"%s\"\n",
+ NS_ConvertUTF16toUTF8(static_cast<char16ptr_t>(_bstr_t(exposingURL)))
+ .get());
+ ::SysFreeString(exposingURL);
+ }
+#endif // #ifdef DEBUG_PRINT_DOCUMENT_URL
+
+ int32_t count = 0;
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ if (!mRequestedAttrs[i]) {
+ continue;
+ }
+ mRequestedAttrs[i] = false;
+
+ TS_ATTRID attrID = GetAttrID(i);
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() for %s", this,
+ GetGUIDNameStrWithTable(attrID).get()));
+
+ paAttrVals[count].idAttr = attrID;
+ paAttrVals[count].dwOverlapId = 0;
+
+ if (!mRequestedAttrValues) {
+ paAttrVals[count].varValue.vt = VT_EMPTY;
+ } else {
+ switch (i) {
+ case eInputScope: {
+ paAttrVals[count].varValue.vt = VT_UNKNOWN;
+ RefPtr<IUnknown> inputScope = new InputScopeImpl(mInputScopes);
+ paAttrVals[count].varValue.punkVal = inputScope.forget().take();
+ break;
+ }
+ case eDocumentURL: {
+ paAttrVals[count].varValue.vt = VT_BSTR;
+ paAttrVals[count].varValue.bstrVal = GetExposingURL();
+ break;
+ }
+ case eTextVerticalWriting: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_BOOL;
+ paAttrVals[count].varValue.boolVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->WritingModeRef().IsVertical()
+ ? VARIANT_TRUE
+ : VARIANT_FALSE;
+ break;
+ }
+ case eTextOrientation: {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ paAttrVals[count].varValue.vt = VT_I4;
+ paAttrVals[count].varValue.lVal =
+ selectionForTSF.isSome() &&
+ selectionForTSF->WritingModeRef().IsVertical()
+ ? 2700
+ : 0;
+ break;
+ }
+ default:
+ MOZ_CRASH("Invalid index? Or not implemented yet?");
+ break;
+ }
+ }
+ count++;
+ }
+
+ mRequestedAttrValues = false;
+
+ if (count) {
+ *pcFetched = count;
+ return S_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "for unknown TS_ATTRVAL, *pcFetched=0 (S_OK)",
+ this));
+
+ paAttrVals->dwOverlapId = 0;
+ paAttrVals->varValue.vt = VT_EMPTY;
+ *pcFetched = 0;
+ return S_OK;
+}
+
+#undef DEBUG_PRINT_DOCUMENT_URL
+
+STDMETHODIMP
+TSFTextStore::GetEndACP(LONG* pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ *pacp = static_cast<LONG>(contentForTSF->TextRef().Length());
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetActiveView(TsViewCookie* pvcView) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)", this, pvcView));
+
+ if (!pvcView) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetActiveView() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *pvcView = TEXTSTORE_DEFAULT_VIEW;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView() succeeded: *pvcView=%ld", this,
+ *pvcView));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetACPFromPoint(TsViewCookie vcView, const POINT* pt,
+ DWORD dwFlags, LONG* pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%ld, pt=%p (x=%ld, "
+ "y=%ld), dwFlags=%s, pacp=%p, mDeferNotifyingTSFUntilNextUpdate=%s, "
+ "mWaitingQueryLayout=%s",
+ this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
+ GetACPFromPointFlagName(dwFlags).get(), pacp,
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
+ GetBoolName(mWaitingQueryLayout)));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pt) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pt",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pacp",
+ this));
+ return E_INVALIDARG;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (mDestroyed ||
+ (mContentForTSF.isSome() && mContentForTSF->IsLayoutChanged())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() returned "
+ "TS_E_NOLAYOUT",
+ this));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ LayoutDeviceIntPoint ourPt(pt->x, pt->y);
+ // Convert to widget relative coordinates from screen's.
+ ourPt -= mWidget->WidgetToScreenOffset();
+
+ // NOTE: Don't check if the point is in the widget since the point can be
+ // outside of the widget if focused editor is in a XUL <panel>.
+
+ WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
+ mWidget);
+ mWidget->InitEvent(queryCharAtPointEvent, &ourPt);
+
+ // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+ // may cause focus change or something.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ DispatchEvent(queryCharAtPointEvent);
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "mWidget was destroyed during eQueryCharacterAtPoint",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetACPFromPoint(), queryCharAtPointEvent={ "
+ "mReply=%s }",
+ this, ToString(queryCharAtPointEvent.mReply).c_str()));
+
+ if (NS_WARN_IF(queryCharAtPointEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "eQueryCharacterAtPoint failure",
+ this));
+ return E_FAIL;
+ }
+
+ // If dwFlags isn't set and the point isn't in any character's bounding box,
+ // we should return TS_E_INVALIDPOINT.
+ if (!(dwFlags & GXFPF_NEAREST) && queryCharAtPointEvent.DidNotFindChar()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to the "
+ "point contained by no bounding box",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ // Although, we're not sure if mTentativeCaretOffset becomes NOT_FOUND,
+ // let's assume that there is no content in such case.
+ NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(),
+ "Tentative caret offset was not found");
+
+ uint32_t offset;
+
+ // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative
+ // caret offset (MSDN calls it "range position").
+ if (dwFlags & GXFPF_ROUND_NEAREST) {
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+ } else if (queryCharAtPointEvent.FoundChar()) {
+ // Otherwise, we should return character offset whose bounding box contains
+ // the point.
+ offset = queryCharAtPointEvent.mReply->StartOffset();
+ } else {
+ // If the point isn't in any character's bounding box but we need to return
+ // the nearest character from the point, we should *guess* the character
+ // offset since there is no inexpensive API to check it strictly.
+ // XXX If we retrieve 2 bounding boxes, one is before the offset and
+ // the other is after the offset, we could resolve the offset.
+ // However, dispatching 2 eQueryTextRect may be expensive.
+
+ // So, use tentative offset for now.
+ offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0);
+
+ // However, if it's after the last character, we need to decrement the
+ // offset.
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ if (contentForTSF->TextRef().Length() <= offset) {
+ // If the tentative caret is after the last character, let's return
+ // the last character's offset.
+ offset = contentForTSF->TextRef().Length() - 1;
+ }
+ }
+
+ if (NS_WARN_IF(offset > LONG_MAX)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to out of "
+ "range of the result",
+ this));
+ return TS_E_INVALIDPOINT;
+ }
+
+ *pacp = static_cast<LONG>(offset);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%ld", this,
+ *pacp));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetTextExt(TsViewCookie vcView, LONG acpStart, LONG acpEnd,
+ RECT* prc, BOOL* pfClipped) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
+ "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
+ "IsHandlingCompositionInParent()=%s, "
+ "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, "
+ "mSelectionForTSF=%s, mComposition=%s, "
+ "mDeferNotifyingTSFUntilNextUpdate=%s, mWaitingQueryLayout=%s, "
+ "IMEHandler::IsA11yHandlingNativeCaret()=%s",
+ this, vcView, acpStart, acpEnd, prc, pfClipped,
+ GetBoolName(IsHandlingCompositionInParent()),
+ GetBoolName(IsHandlingCompositionInContent()),
+ mozilla::ToString(mContentForTSF).c_str(),
+ ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(),
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate),
+ GetBoolName(mWaitingQueryLayout),
+ GetBoolName(IMEHandler::IsA11yHandlingNativeCaret())));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc || !pfClipped) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // According to MSDN, ITextStoreACP::GetTextExt() should return
+ // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range).
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx
+ // > TS_E_INVALIDARG: The specified start and end character positions are
+ // > equal.
+ // However, some TIPs (including Microsoft's Chinese TIPs!) call this with
+ // collapsed range and if we return TS_E_INVALIDARG, they stops showing their
+ // owning window or shows it but odd position. So, we should just return
+ // error only when acpStart and/or acpEnd are really odd.
+
+ if (acpStart < 0 || acpEnd < acpStart) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "invalid position",
+ this));
+ return TS_E_INVALIDPOS;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (IsHandlingCompositionInContent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition() &&
+ mContentForTSF->IsLayoutChanged() &&
+ mContentForTSF->MinModifiedOffset().value() >
+ static_cast<uint32_t>(LONG_MAX)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
+ "is too big for TSF (cannot treat modified offset as LONG), "
+ "mContentForTSF=%s",
+ this, ToString(mContentForTSF).c_str()));
+ return E_FAIL;
+ }
+
+ // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed
+ // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF
+ // returned E_FAIL to TIP). However, until we drop to support older Windows
+ // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning
+ // S_OK and available rectangle only for them.
+ if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) &&
+ mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%ld)",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%ld) because this has already been destroyed",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ // use eQueryTextRect to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, mWidget);
+ mWidget->InitEvent(queryTextRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = acpStart;
+ if (mComposition.isSome()) {
+ // If there is a composition, TSF must want character rects related to
+ // the composition. Therefore, we should use insertion point relative
+ // query because the composition might be at different position from
+ // the position where TSFTextStore believes it at.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mComposition->StartOffset();
+ } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() &&
+ mContentForTSF->HasOrHadComposition()) {
+ // If there was a composition and its commit event hasn't been dispatched
+ // yet, ContentCacheInParent is still open for relative offset query from
+ // the latest composition.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset();
+ } else if (!CanAccessActualContentDirectly() &&
+ mSelectionForTSF->HasRange()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mSelectionForTSF->StartOffset();
+ }
+ // ContentEventHandler and ContentCache return actual caret rect when
+ // the queried range is collapsed and selection is collapsed at the
+ // queried range. Then, its height (in horizontal layout, width in vertical
+ // layout) may be different from actual font height of the line. In such
+ // case, users see "dancing" of candidate or suggest window of TIP.
+ // For preventing it, we should query text rect with at least 1 length.
+ uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1);
+ queryTextRectEvent.InitForQueryTextRect(startOffset, length, options);
+
+ DispatchEvent(queryTextRectEvent);
+ if (NS_WARN_IF(queryTextRectEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "eQueryTextRect failure",
+ this));
+ return TS_E_INVALIDPOS; // but unexpected failure, maybe.
+ }
+
+ // IMEs don't like empty rects, fix here
+ if (queryTextRectEvent.mReply->mRect.Width() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetWidth(1);
+ }
+ if (queryTextRectEvent.mReply->mRect.Height() <= 0) {
+ queryTextRectEvent.mReply->mRect.SetHeight(1);
+ }
+
+ // convert to unclipped screen rect
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget
+ ? queryTextRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "no top level window",
+ this));
+ return E_FAIL;
+ }
+
+ queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset());
+
+ // get bounding screen rect to test for clipping
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // clip text rect to bounding rect
+ RECT textRect;
+ ::SetRect(&textRect, queryTextRectEvent.mReply->mRect.X(),
+ queryTextRectEvent.mReply->mRect.Y(),
+ queryTextRectEvent.mReply->mRect.XMost(),
+ queryTextRectEvent.mReply->mRect.YMost());
+ if (!::IntersectRect(prc, prc, &textRect))
+ // Text is not visible
+ ::SetRectEmpty(prc);
+
+ // not equal if text rect was clipped
+ *pfClipped = !::EqualRect(prc, &textRect);
+
+ // ATOK 2011 - 2016 refers native caret position and size on windows whose
+ // class name is one of Mozilla's windows for deciding candidate window
+ // position. Additionally, ATOK 2015 and earlier behaves really odd when
+ // we don't create native caret. Therefore, we need to create native caret
+ // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016).
+ // However, if a11y module is handling native caret, we shouldn't touch it.
+ // Note that ATOK must require the latest information of the caret. So,
+ // even if we'll create native caret later, we need to creat it here with
+ // current information.
+ if (!IMEHandler::IsA11yHandlingNativeCaret() &&
+ StaticPrefs::intl_tsf_hack_atok_create_native_caret() &&
+ TSFStaticSink::IsATOKReferringNativeCaretActive() &&
+ mComposition.isSome() &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpStart) &&
+ mComposition->IsOffsetInRangeOrEndOffset(acpEnd)) {
+ CreateNativeCaret();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }, *pfClipped=%s",
+ this, prc->left, prc->top, prc->right, prc->bottom,
+ GetBoolName(*pfClipped)));
+
+ return S_OK;
+}
+
+bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) {
+ // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL
+ // to its caller (typically, active TIP). Then, most TIPs abort current job
+ // or treat such application as non-GUI apps. E.g., some of them give up
+ // showing candidate window, some others show candidate window at top-left of
+ // the screen. For avoiding this issue, when there is composition (until
+ // composition is actually committed in remote content), we should not
+ // return TS_E_NOLAYOUT error for TIPs whose some features are broken by
+ // this issue.
+ // Note that ideally, this issue should be avoided by each TIP since this
+ // won't be fixed at least on non-latest Windows. Actually, Google Japanese
+ // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs
+ // should try to check result of GetRangeFromPoint() because TSF returns
+ // TS_E_NOLAYOUT correctly in this case. See:
+ // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257
+
+ if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() ||
+ !mContentForTSF->HasOrHadComposition() ||
+ !mContentForTSF->IsLayoutChangedAt(aACPEnd)) {
+ return false;
+ }
+
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->StartOffset() ==
+ mContentForTSF->LatestCompositionRange()->StartOffset());
+ MOZ_ASSERT(mComposition.isNothing() ||
+ mComposition->EndOffset() ==
+ mContentForTSF->LatestCompositionRange()->EndOffset());
+
+ // If TSF does not have the bug, we need to hack only with a few TIPs.
+ static const bool sAlllowToStopHackingIfFine =
+ IsWindows10BuildOrLater(17643) &&
+ StaticPrefs::
+ intl_tsf_hack_allow_to_stop_hacking_on_build_17643_or_later();
+
+ // We need to compute active TIP now. This may take a couple of milliseconds,
+ // however, it'll be cached, so, must be faster than check active TIP every
+ // GetTextExt() calls.
+ const Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ switch (TSFStaticSink::ActiveTIP()) {
+ // MS IME for Japanese doesn't support asynchronous handling at deciding
+ // its suggest list window position. The feature was implemented
+ // starting from Windows 8. And also we may meet same trouble in e10s
+ // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for
+ // Japanese.
+ case TextInputProcessorID::eMicrosoftIMEForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ // Note that according to bug 1609675, MS-IME for Japanese itself does
+ // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909).
+ if (StaticPrefs::
+ intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_first_char() &&
+ aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ break;
+ }
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ // (Let's return true if there is no selection which must be not expected
+ // by MS-IME nor TSF.)
+ if (StaticPrefs::
+ intl_tsf_hack_ms_japanese_ime_do_not_return_no_layout_error_at_caret() &&
+ aACPStart == aACPEnd && selectionForTSF.isSome() &&
+ (!selectionForTSF->HasRange() ||
+ (selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd))) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // The bug of Microsoft Office IME 2010 for Japanese is similar to
+ // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not
+ // released yet. So, we can hack it without prefs because there must be
+ // no developers who want to disable this hack for tests.
+ // XXX We have not tested with Microsoft Office IME 2010 since it's
+ // installable only with Win7 and Win8 (i.e., cannot install Win8.1
+ // and Win10), and requires upgrade to Win10.
+ case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese:
+ // Basically, MS-IME tries to retrieve whole composition string rect
+ // at deciding suggest window immediately after unlocking the document.
+ // However, in e10s mode, the content hasn't updated yet in most cases.
+ // Therefore, if the first character at the retrieving range rect is
+ // available, we should use it as the result.
+ if (aACPStart < aACPEnd) {
+ aACPEnd = aACPStart;
+ }
+ // Although, the condition is not clear, MS-IME sometimes retrieves the
+ // caret rect immediately after modifying the composition string but
+ // before unlocking the document. In such case, we should return the
+ // nearest character rect.
+ // (Let's return true if there is no selection which must be not expected
+ // by MS-IME nor TSF.)
+ else if (aACPStart == aACPEnd && selectionForTSF.isSome() &&
+ (!selectionForTSF->HasRange() ||
+ (selectionForTSF->Collapsed() &&
+ selectionForTSF->EndOffset() == aACPEnd))) {
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ } else {
+ return false;
+ }
+ break;
+ // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of
+ // suggest window. In such case, ATOK tries to query rect of whole or a
+ // part of composition string.
+ // FYI: ATOK changes their implementation around candidate window and
+ // suggest widget at ATOK 2016. Therefore, there are some differences
+ // ATOK 2015 (or older) and ATOK 2016 (or newer).
+ // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016
+ // and older may behave differently only on Gecko but this must be
+ // finished from ATOK 2017.
+ // FYI: For testing with legacy ATOK, we should hack it even if current ATOK
+ // refers native caret rect on windows whose window class is one of
+ // Mozilla window classes and we stop creating native caret for ATOK
+ // because creating native caret causes ATOK refers caret position
+ // when GetTextExt() returns TS_E_NOLAYOUT.
+ case TextInputProcessorID::eATOK2011:
+ case TextInputProcessorID::eATOK2012:
+ case TextInputProcessorID::eATOK2013:
+ case TextInputProcessorID::eATOK2014:
+ case TextInputProcessorID::eATOK2015:
+ // ATOK 2016 and later may temporarily show candidate window at odd
+ // position when you convert a word quickly (e.g., keep pressing
+ // space bar). So, on ATOK 2016 or later, we need to keep hacking the
+ // result of GetTextExt().
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ // If we'll create native caret where we paint our caret. Then, ATOK
+ // will refer native caret. So, we don't need to hack anything in
+ // this case.
+ if (StaticPrefs::intl_tsf_hack_atok_create_native_caret()) {
+ MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive());
+ return false;
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eATOK2016:
+ case TextInputProcessorID::eATOKUnknown:
+ if (!StaticPrefs::
+ intl_tsf_hack_atok_do_not_return_no_layout_error_of_composition_string()) {
+ return false;
+ }
+ // If the range is in the composition string, we should return rectangle
+ // in it as far as possible.
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position
+ // of candidate window. In such case, Japanist shows candidate window at
+ // top-left of the screen. So, we should return the nearest caret rect
+ // where we know. This is Japanist's bug. So, even after build 17643,
+ // we need this hack.
+ case TextInputProcessorID::eJapanist10:
+ if (!StaticPrefs::
+ intl_tsf_hack_japanist10_do_not_return_no_layout_error_of_composition_string()) {
+ return false;
+ }
+ if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPStart) ||
+ !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset(
+ aACPEnd)) {
+ return false;
+ }
+ break;
+ // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly.
+ // This must be caused by the bug of TSF since Free ChangJie works fine on
+ // build 17643 and later.
+ case TextInputProcessorID::eFreeChangJie:
+ if (sAlllowToStopHackingIfFine) {
+ return false;
+ }
+ if (!StaticPrefs::
+ intl_tsf_hack_free_chang_jie_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Traditional Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ case TextInputProcessorID::eMicrosoftQuick:
+ if (sAlllowToStopHackingIfFine) {
+ return false; // MS Quick works fine with Win10 build 17643.
+ }
+ [[fallthrough]];
+ case TextInputProcessorID::eMicrosoftChangJie:
+ if (!StaticPrefs::
+ intl_tsf_hack_ms_traditional_chinese_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ // Some Simplified Chinese TIPs of Microsoft don't show candidate window
+ // in e10s mode on Win8 or later.
+ // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack
+ // because they sometimes do not show candidate window when we return
+ // TS_E_NOLAYOUT for first query. Note that even when they show
+ // candidate window properly, we return TS_E_NOLAYOUT and following
+ // log looks same as when they don't show candidate window. Perhaps,
+ // there is stateful cause or race in them.
+ case TextInputProcessorID::eMicrosoftPinyin:
+ case TextInputProcessorID::eMicrosoftWubi:
+ if (!StaticPrefs::
+ intl_tsf_hack_ms_simplified_chinese_do_not_return_no_layout_error()) {
+ return false;
+ }
+ aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset();
+ aACPStart = std::min(aACPStart, aACPEnd);
+ break;
+ default:
+ return false;
+ }
+
+ // If we hack the queried range for active TIP, that means we should not
+ // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as
+ // far as possible, we should adjust the offset.
+ MOZ_ASSERT(mContentForTSF->IsLayoutChanged());
+ bool collapsed = aACPStart == aACPEnd;
+ // Note that even if all characters in the editor or the composition
+ // string was modified, 0 or start offset of the composition string is
+ // useful because it may return caret rect or old character's rect which
+ // the user still see. That must be useful information for TIP.
+ int32_t firstModifiedOffset =
+ static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value());
+ LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
+ if (mContentForTSF->IsLayoutChangedAt(aACPStart)) {
+ if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) {
+ // If mContentForTSF has last composition string and current
+ // composition string, we can assume that ContentCacheInParent has
+ // cached rects of composition string at least length of current
+ // composition string. Otherwise, we can assume that rect for
+ // first character of composition string is stored since it was
+ // selection start or caret position.
+ LONG maxCachedOffset =
+ mContentForTSF->LatestCompositionRange()->EndOffset();
+ if (mContentForTSF->LastComposition().isSome()) {
+ maxCachedOffset = std::min(
+ maxCachedOffset, mContentForTSF->LastComposition()->EndOffset());
+ }
+ aACPStart = std::min(aACPStart, maxCachedOffset);
+ }
+ // Otherwise, we don't know which character rects are cached. So, we
+ // need to use first unmodified character's rect in this case. Even
+ // if there is no character, the query event will return caret rect
+ // instead.
+ else {
+ aACPStart = lastUnmodifiedOffset;
+ }
+ MOZ_ASSERT(aACPStart <= aACPEnd);
+ }
+
+ // If TIP requests caret rect with collapsed range, we should keep
+ // collapsing the range.
+ if (collapsed) {
+ aACPEnd = aACPStart;
+ }
+ // Let's set aACPEnd to larger offset of last unmodified offset or
+ // aACPStart which may be the first character offset of the composition
+ // string. However, some TIPs may want to know the right edge of the
+ // range. Therefore, if aACPEnd is in composition string and active TIP
+ // doesn't retrieve caret rect (i.e., the range isn't collapsed), we
+ // should keep using the original aACPEnd. Otherwise, we should set
+ // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset.
+ else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) &&
+ !mContentForTSF->LatestCompositionRange()
+ ->IsOffsetInRangeOrEndOffset(aACPEnd)) {
+ aACPEnd = std::max(aACPStart, lastUnmodifiedOffset);
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range "
+ "for not returning TS_E_NOLAYOUT, new values are: "
+ "aACPStart=%ld, aACPEnd=%ld",
+ this, aACPStart, aACPEnd));
+
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetScreenExt(TsViewCookie vcView, RECT* prc) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)", this,
+ vcView, prc));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
+ "due to already destroyed",
+ this));
+ prc->left = prc->top = prc->right = prc->bottom = 0;
+ return S_OK;
+ }
+
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "GetScreenExtInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt() succeeded: "
+ "*prc={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, prc->left, prc->top, prc->right, prc->bottom));
+ return S_OK;
+}
+
+bool TSFTextStore::GetScreenExtInternal(RECT& aScreenExt) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal()", this));
+
+ MOZ_ASSERT(!mDestroyed);
+
+ // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
+ WidgetQueryContentEvent queryEditorRectEvent(true, eQueryEditorRect, mWidget);
+ mWidget->InitEvent(queryEditorRectEvent);
+ DispatchEvent(queryEditorRectEvent);
+ if (queryEditorRectEvent.Failed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "eQueryEditorRect failure",
+ this));
+ return false;
+ }
+
+ nsWindow* refWindow =
+ static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget
+ ? queryEditorRectEvent.mReply->mFocusedWidget
+ : static_cast<nsIWidget*>(mWidget.get()));
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "no top level window",
+ this));
+ return false;
+ }
+
+ LayoutDeviceIntRect boundRect = refWindow->GetClientBounds();
+ boundRect.MoveTo(0, 0);
+
+ // Clip frame rect to window rect
+ boundRect.IntersectRect(queryEditorRectEvent.mReply->mRect, boundRect);
+ if (!boundRect.IsEmpty()) {
+ boundRect.MoveBy(refWindow->WidgetToScreenOffset());
+ ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(),
+ boundRect.YMost());
+ } else {
+ ::SetRectEmpty(&aScreenExt);
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal() succeeded: "
+ "aScreenExt={ left=%ld, top=%ld, right=%ld, bottom=%ld }",
+ this, aScreenExt.left, aScreenExt.top, aScreenExt.right,
+ aScreenExt.bottom));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::GetWnd(TsViewCookie vcView, HWND* phwnd) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd(vcView=%ld, phwnd=0x%p), "
+ "mWidget=0x%p",
+ this, vcView, phwnd, mWidget.get()));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetWnd() FAILED due to "
+ "called with invalid view",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!phwnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetWnd() succeeded: *phwnd=0x%p", this,
+ static_cast<void*>(*phwnd)));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertTextAtSelection(DWORD dwFlags, const WCHAR* pchText,
+ ULONG cch, LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
+ "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
+ "pChange=0x%p), mComposition=%s",
+ this,
+ dwFlags == 0 ? "0"
+ : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY"
+ : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY"
+ : "Unknown",
+ pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "",
+ cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str()));
+
+ if (cch && !pchText) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pchText",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_QUERYONLY == dwFlags) {
+ if (!IsReadLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacpStart || !pacpEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Get selection first
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ // Simulate text insertion
+ if (selectionForTSF->HasRange()) {
+ *pacpStart = selectionForTSF->StartOffset();
+ *pacpEnd = selectionForTSF->EndOffset();
+ if (pChange) {
+ *pChange = TS_TEXTCHANGE{.acpStart = selectionForTSF->StartOffset(),
+ .acpOldEnd = selectionForTSF->EndOffset(),
+ .acpNewEnd = selectionForTSF->StartOffset() +
+ static_cast<LONG>(cch)};
+ }
+ } else {
+ // There is no error code to return "no selection" state from this method.
+ // This means that TSF/TIP should check `GetSelection` result first and
+ // stop using this. However, this could be called by TIP/TSF if they do
+ // not do so. Therefore, we should use start of editor instead, but
+ // notify the caller of nothing will be inserted with pChange->acpNewEnd.
+ *pacpStart = *pacpEnd = 0;
+ if (pChange) {
+ *pChange = TS_TEXTCHANGE{.acpStart = 0, .acpOldEnd = 0, .acpNewEnd = 0};
+ }
+ }
+ } else {
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read-write)",
+ this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pChange",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument",
+ this));
+ return E_INVALIDARG;
+ }
+
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "InsertTextAtSelectionInternal() failure",
+ this));
+ return E_FAIL;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags) {
+ *pacpStart = pChange->acpStart;
+ *pacpEnd = pChange->acpNewEnd;
+ }
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection() succeeded: "
+ "*pacpStart=%ld, *pacpEnd=%ld, "
+ "*pChange={ acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld })",
+ this, pacpStart ? *pacpStart : 0, pacpEnd ? *pacpEnd : 0,
+ pChange ? pChange->acpStart : 0, pChange ? pChange->acpOldEnd : 0,
+ pChange ? pChange->acpNewEnd : 0));
+ return S_OK;
+}
+
+bool TSFTextStore::InsertTextAtSelectionInternal(const nsAString& aInsertStr,
+ TS_TEXTCHANGE* aTextChange) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
+ "aInsertStr=\"%s\", aTextChange=0x%p), mComposition=%s",
+ this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
+ ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
+ "due to ContentForTSF() failure()",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ const auto numberOfCRLFs = [&]() -> uint32_t {
+ const auto* str = aInsertStr.BeginReading();
+ uint32_t num = 0;
+ for (uint32_t i = 0; i + 1 < aInsertStr.Length(); i++) {
+ if (str[i] == '\r' && str[i + 1] == '\n') {
+ num++;
+ i++;
+ }
+ }
+ return num;
+ }();
+ if (numberOfCRLFs) {
+ nsAutoString key;
+ if (TSFStaticSink::GetActiveTIPNameForTelemetry(key)) {
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS_INSERTED_CRLF, key,
+ true);
+ }
+ }
+
+ TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef();
+ if (mComposition.isNothing()) {
+ // Use a temporary composition to contain the text
+ PendingAction* compositionStart = mPendingActions.AppendElements(2);
+ PendingAction* compositionEnd = compositionStart + 1;
+
+ compositionStart->mType = PendingAction::Type::eCompositionStart;
+ compositionStart->mSelectionStart = oldSelection.acpStart;
+ compositionStart->mSelectionLength =
+ oldSelection.acpEnd - oldSelection.acpStart;
+ compositionStart->mAdjustSelection = false;
+
+ compositionEnd->mType = PendingAction::Type::eCompositionEnd;
+ compositionEnd->mData = aInsertStr;
+ compositionEnd->mSelectionStart = compositionStart->mSelectionStart;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "appending pending compositionstart and compositionend... "
+ "PendingCompositionStart={ mSelectionStart=%ld, "
+ "mSelectionLength=%ld }, PendingCompositionEnd={ mData=\"%s\" "
+ "(Length()=%zu), mSelectionStart=%ld }",
+ this, compositionStart->mSelectionStart,
+ compositionStart->mSelectionLength,
+ GetEscapedUTF8String(compositionEnd->mData).get(),
+ compositionEnd->mData.Length(), compositionEnd->mSelectionStart));
+ }
+
+ contentForTSF->ReplaceSelectedTextWith(aInsertStr);
+
+ if (aTextChange) {
+ aTextChange->acpStart = oldSelection.acpStart;
+ aTextChange->acpOldEnd = oldSelection.acpEnd;
+ aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset();
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "succeeded: mWidget=0x%p, mWidget->Destroyed()=%s, aTextChange={ "
+ "acpStart=%ld, acpOldEnd=%ld, acpNewEnd=%ld }",
+ this, mWidget.get(), GetBoolName(mWidget ? mWidget->Destroyed() : true),
+ aTextChange ? aTextChange->acpStart : 0,
+ aTextChange ? aTextChange->acpOldEnd : 0,
+ aTextChange ? aTextChange->acpNewEnd : 0));
+ return true;
+}
+
+STDMETHODIMP
+TSFTextStore::InsertEmbeddedAtSelection(DWORD dwFlags, IDataObject* pDataObject,
+ LONG* pacpStart, LONG* pacpEnd,
+ TS_TEXTCHANGE* pChange) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbeddedAtSelection() called "
+ "but not supported (E_NOTIMPL)",
+ this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, ITfRange* aRange,
+ bool aPreserveSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aRange, GetBoolName(aPreserveSelection),
+ ToString(mComposition).c_str()));
+
+ LONG start = 0, length = 0;
+ HRESULT hr = GetRangeExtent(aRange, &start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to GetRangeExtent() failure",
+ this));
+ return hr;
+ }
+
+ return RecordCompositionStartAction(aCompositionView, start, length,
+ aPreserveSelection);
+}
+
+HRESULT TSFTextStore::RecordCompositionStartAction(
+ ITfCompositionView* aCompositionView, LONG aStart, LONG aLength,
+ bool aPreserveSelection) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aCompositionView=0x%p, aStart=%ld, aLength=%ld, "
+ "aPreserveSelection=%s), "
+ "mComposition=%s",
+ this, aCompositionView, aStart, aLength,
+ GetBoolName(aPreserveSelection), ToString(mComposition).c_str()));
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ CompleteLastActionIfStillIncomplete();
+
+ // TIP may have inserted text at selection before calling
+ // OnStartComposition(). In this case, we've already created a pending
+ // compositionend. If new composition replaces all commit string of the
+ // pending compositionend, we should cancel the pending compositionend and
+ // keep the previous composition normally.
+ // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul
+ // may start composition with calling InsertTextAtSelection() and
+ // OnStartComposition() with this order (bug 1208043).
+ // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits
+ // last character and replace it with empty string with new composition
+ // when user removes last character of composition string with Backspace
+ // key (bug 1462257).
+ if (!aPreserveSelection &&
+ IsLastPendingActionCompositionEndAt(aStart, aLength)) {
+ const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+ contentForTSF->RestoreCommittedComposition(aCompositionView,
+ pendingCompositionEnd);
+ mPendingActions.RemoveLastElement();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() "
+ "succeeded: restoring the committed string as composing string, "
+ "mComposition=%s, mSelectionForTSF=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+ }
+
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionStart;
+ action->mSelectionStart = aStart;
+ action->mSelectionLength = aLength;
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to SelectionForTSF() failure",
+ this));
+ action->mAdjustSelection = true;
+ } else if (!selectionForTSF->HasRange()) {
+ // If there is no selection, let's collapse seletion to the insertion point.
+ action->mAdjustSelection = true;
+ } else if (selectionForTSF->MinOffset() != aStart ||
+ selectionForTSF->MaxOffset() != aStart + aLength) {
+ // If new composition range is different from current selection range,
+ // we need to set selection before dispatching compositionstart event.
+ action->mAdjustSelection = true;
+ } else {
+ // We shouldn't dispatch selection set event before dispatching
+ // compositionstart event because it may cause put caret different
+ // position in HTML editor since generated flat text content and offset in
+ // it are lossy data of HTML contents.
+ action->mAdjustSelection = false;
+ }
+
+ contentForTSF->StartComposition(aCompositionView, *action,
+ aPreserveSelection);
+ MOZ_ASSERT(mComposition.isSome());
+ action->mData = mComposition->DataRef();
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
+ "mComposition=%s, mSelectionForTSF=%s }",
+ this, ToString(mComposition).c_str(),
+ ToString(mSelectionForTSF).c_str()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RecordCompositionEndAction() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "mComposition=%s",
+ this, ToString(mComposition).c_str()));
+
+ MOZ_ASSERT(mComposition.isSome());
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "no composition",
+ this));
+ return false;
+ }
+
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return false;
+ }
+
+ // If we're handling incomplete composition update or already handled
+ // composition update, we can forget them since composition end will send
+ // the latest composition string and it overwrites the composition string
+ // even if we dispatch eCompositionChange event before that. So, let's
+ // forget all composition updates now.
+ RemoveLastCompositionUpdateActions();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::Type::eCompositionEnd;
+ action->mData = mComposition->DataRef();
+ action->mSelectionStart = mComposition->StartOffset();
+
+ Maybe<Content>& contentForTSF = ContentForTSF();
+ if (contentForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
+ "to ContentForTSF() failure",
+ this));
+ return E_FAIL;
+ }
+ contentForTSF->EndComposition(*action);
+
+ // If this composition was restart but the composition doesn't modify
+ // anything, we should remove the pending composition for preventing to
+ // dispatch redundant composition events.
+ for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) {
+ PendingAction& pendingAction = mPendingActions[i - 1];
+ if (pendingAction.mType == PendingAction::Type::eCompositionStart) {
+ if (pendingAction.mData != action->mData) {
+ break;
+ }
+ // When only setting selection is necessary, we should append it.
+ if (pendingAction.mAdjustSelection) {
+ LONG selectionStart = pendingAction.mSelectionStart;
+ LONG selectionLength = pendingAction.mSelectionLength;
+
+ PendingAction* setSelection = mPendingActions.AppendElement();
+ setSelection->mType = PendingAction::Type::eSetSelection;
+ setSelection->mSelectionStart = selectionStart;
+ setSelection->mSelectionLength = selectionLength;
+ setSelection->mSelectionReversed = false;
+ }
+ // Remove the redundant pending composition.
+ mPendingActions.RemoveElementsAt(i - 1, j);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "succeeded, but the composition was canceled due to redundant",
+ this));
+ return S_OK;
+ }
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnStartComposition(ITfCompositionView* pComposition, BOOL* pfOk) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
+ "pfOk=0x%p), mComposition=%s",
+ this, pComposition, pfOk, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ *pfOk = FALSE;
+
+ // Only one composition at a time
+ if (mComposition.isSome()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "there is another composition already (but returns S_OK)",
+ this));
+ return S_OK;
+ }
+
+ RefPtr<ITfRange> range;
+ HRESULT hr = pComposition->GetRange(getter_AddRefs(range));
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "pComposition->GetRange() failure",
+ this));
+ return hr;
+ }
+ hr = RecordCompositionStartAction(pComposition, range, false);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "RecordCompositionStartAction() failure",
+ this));
+ return hr;
+ }
+
+ *pfOk = TRUE;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
+ ITfRange* pRangeNew) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
+ "pRangeNew=0x%p), mComposition=%s",
+ this, pComposition, pRangeNew, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mDocumentMgr || !mContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "not ready for the composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ // pRangeNew is null when the update is not complete
+ if (!pRangeNew) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "destroyed during dispatching a keyboard event",
+ this));
+ return E_FAIL;
+ }
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mIncomplete = true;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
+ "not complete",
+ this));
+ return S_OK;
+ }
+
+ HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RestartCompositionIfNecessary() failure",
+ this));
+ return hr;
+ }
+
+ hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RecordCompositionUpdateAction() failure",
+ this));
+ return hr;
+ }
+
+ if (MOZ_LOG_TEST(gIMELog, LogLevel::Info)) {
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (selectionForTSF.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return S_OK; // Don't return error only when we're logging.
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
+ "mComposition=%s, SelectionForTSF()=%s",
+ this, ToString(mComposition).c_str(),
+ ToString(selectionForTSF).c_str()));
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
+ "mComposition=%s",
+ this, pComposition, ToString(mComposition).c_str()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (mComposition.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "no active composition",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (mComposition->GetView() != pComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "different composition view specified",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ HRESULT hr = RecordCompositionEndAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "RecordCompositionEndAction() failure",
+ this));
+ return hr;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseMouseSink(ITfRangeACP* range, ITfMouseSink* pSink,
+ DWORD* pdwCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
+ "pdwCookie=0x%p)",
+ this, range, pSink, pdwCookie));
+
+ if (!pdwCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pdwCookie is null",
+ this));
+ return E_INVALIDARG;
+ }
+ // Initialize the result with invalid cookie for safety.
+ *pdwCookie = MouseTracker::kInvalidCookie;
+
+ if (!range) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "range is null",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!pSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "pSink is null",
+ this));
+ return E_INVALIDARG;
+ }
+
+ // Looking for an unusing tracker.
+ MouseTracker* tracker = nullptr;
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ if (mMouseTrackers[i].IsUsing()) {
+ continue;
+ }
+ tracker = &mMouseTrackers[i];
+ }
+ // If there is no unusing tracker, create new one.
+ // XXX Should we make limitation of the number of installs?
+ if (!tracker) {
+ tracker = mMouseTrackers.AppendElement();
+ HRESULT hr = tracker->Init(this);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to "
+ "failure of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ }
+ HRESULT hr = tracker->AdviseSink(this, range, pSink);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
+ "of MouseTracker::Init()",
+ this));
+ return hr;
+ }
+ *pdwCookie = tracker->Cookie();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
+ "*pdwCookie=%ld",
+ this, *pdwCookie));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseMouseSink(DWORD dwCookie) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%ld)", this, dwCookie));
+ if (dwCookie == MouseTracker::kInvalidCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is invalid value",
+ this));
+ return E_INVALIDARG;
+ }
+ // The cookie value must be an index of mMouseTrackers.
+ // We can use this shortcut for now.
+ if (static_cast<size_t>(dwCookie) >= mMouseTrackers.Length()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the cookie is too large value",
+ this));
+ return E_INVALIDARG;
+ }
+ MouseTracker& tracker = mMouseTrackers[dwCookie];
+ if (!tracker.IsUsing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the found tracker uninstalled already",
+ this));
+ return E_INVALIDARG;
+ }
+ tracker.UnadviseSink();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
+ return S_OK;
+}
+
+// static
+nsresult TSFTextStore::OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
+ const InputContext& aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
+ "aFocusedWidget=0x%p, aContext=%s), "
+ "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
+ GetBoolName(aGotFocus), aFocusedWidget,
+ mozilla::ToString(aContext).c_str(), sThreadMgr.get(),
+ sEnabledTextStore.get()));
+
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ bool hasFocus = ThinksHavingFocus();
+ RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
+
+ // If currently oldTextStore still has focus, notifies TSF of losing focus.
+ if (hasFocus) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ DebugOnly<HRESULT> hr = threadMgr->AssociateFocus(
+ oldTextStore->mWidget->GetWindowHandle(), nullptr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed");
+ NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr,
+ "different documentMgr has been associated with the window");
+ }
+
+ // Even if there was a focused TextStore, we won't use it with new focused
+ // editor. So, release it now.
+ if (oldTextStore) {
+ oldTextStore->Destroy();
+ }
+
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(sEnabledTextStore)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED, due to "
+ "nested event handling has created another focused TextStore during "
+ "calling ITfThreadMgr::AssociateFocus()"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is a notification of blur, move focus to the dummy document
+ // manager.
+ if (!aGotFocus || !aContext.mIMEState.IsEditable()) {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr = sDisabledDocumentMgr;
+ HRESULT hr = threadMgr->SetFocus(disabledDocumentMgr);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::SetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ // If an editor is getting focus, create new TextStore and set focus.
+ if (NS_WARN_IF(!CreateAndSetFocus(aFocusedWidget, aContext))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::OnFocusChange() FAILED due to "
+ "ITfThreadMgr::CreateAndSetFocus() failure"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// static
+void TSFTextStore::EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore) {
+ aTextStore->Destroy();
+ if (sEnabledTextStore == aTextStore) {
+ sEnabledTextStore = nullptr;
+ }
+ aTextStore = nullptr;
+}
+
+// static
+bool TSFTextStore::CreateAndSetFocus(nsWindow* aFocusedWidget,
+ const InputContext& aContext) {
+ // TSF might do something which causes that we need to access static methods
+ // of TSFTextStore. At that time, sEnabledTextStore may be necessary.
+ // So, we should set sEnabledTextStore directly.
+ RefPtr<TSFTextStore> textStore = new TSFTextStore();
+ sEnabledTextStore = textStore;
+ if (NS_WARN_IF(!textStore->Init(aFocusedWidget, aContext))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "TSFTextStore::Init() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ RefPtr<ITfDocumentMgr> newDocMgr = textStore->mDocumentMgr;
+ if (NS_WARN_IF(!newDocMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "invalid TSFTextStore::mDocumentMgr"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (aContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ MarkContextAsKeyboardDisabled(textStore->mContext);
+ RefPtr<ITfContext> topContext;
+ newDocMgr->GetTop(getter_AddRefs(topContext));
+ if (topContext && topContext != textStore->mContext) {
+ MarkContextAsKeyboardDisabled(topContext);
+ }
+ }
+
+ HRESULT hr;
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ hr = threadMgr->SetFocus(newDocMgr);
+
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::SetFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfThreadMgr::SetFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ // Use AssociateFocus() for ensuring that any native focus event
+ // never steal focus from our documentMgr.
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ hr = threadMgr->AssociateFocus(aFocusedWidget->GetWindowHandle(), newDocMgr,
+ getter_AddRefs(prevFocusedDocumentMgr));
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::AssociateFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "sThreadMgr being destroyed during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITfTheadMgr::AssociateFocus()"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+
+ if (textStore->mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::CreateAndSetFocus(), calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE) for 0x%p...",
+ textStore.get()));
+ RefPtr<ITextStoreACPSink> sink = textStore->mSink;
+ sink->OnLayoutChange(TS_LC_CREATE, TEXTSTORE_DEFAULT_VIEW);
+ if (NS_WARN_IF(sEnabledTextStore != textStore)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "creating TextStore has lost focus during calling "
+ "ITextStoreACPSink::OnLayoutChange(TS_LC_CREATE)"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+IMENotificationRequests TSFTextStore::GetIMENotificationRequests() {
+ if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) {
+ // If there is no active text store, we don't need any notifications
+ // since there is no sink which needs notifications.
+ return IMENotificationRequests();
+ }
+
+ // Otherwise, requests all notifications since even if some of them may not
+ // be required by the sink of active TIP, active TIP may be changed and
+ // other TIPs may need all notifications.
+ // Note that Windows temporarily steal focus from active window if the main
+ // process which created the window becomes busy. In this case, we shouldn't
+ // commit composition since user may want to continue to compose the
+ // composition after becoming not busy. Therefore, we need notifications
+ // even during deactive.
+ // Be aware, we don't need to check actual focused text store. For example,
+ // MS-IME for Japanese handles focus messages by themselves and sets focused
+ // text store to nullptr when the process is being inactivated. However,
+ // we still need to reuse sEnabledTextStore if the process is activated and
+ // focused element isn't changed. Therefore, if sEnabledTextStore isn't
+ // nullptr, we need to keep notifying the sink even when it is not focused
+ // text store for the thread manager.
+ return IMENotificationRequests(
+ IMENotificationRequests::NOTIFY_TEXT_CHANGE |
+ IMENotificationRequests::NOTIFY_POSITION_CHANGE |
+ IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
+ IMENotificationRequests::NOTIFY_DURING_DEACTIVE);
+}
+
+nsresult TSFTextStore::OnTextChangeInternal(
+ const IMENotification& aIMENotification) {
+ const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
+ "mMessage=0x%08X, mTextChangeData=%s }), "
+ "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+ "mComposition=%s",
+ this, aIMENotification.mMessage,
+ mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ // Different from selection change, we don't modify anything with text
+ // change data. Therefore, if neither TSF not TIP wants text change
+ // notifications, we don't need to store the changes.
+ if (!mSink || !(mSinkMask & TS_AS_TEXT_CHANGE)) {
+ return NS_OK;
+ }
+
+ // Merge any text change data even if it's caused by composition.
+ mPendingTextChangeData.MergeWith(textChangeData);
+
+ MaybeFlushPendingNotifications();
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfTextChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingTextChangeData.IsValid());
+
+ // If the text changes are caused only by composition, we don't need to
+ // notify TSF of the text changes.
+ if (mPendingTextChangeData.mCausedOnlyByComposition) {
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ // First, forget cached selection.
+ mSelectionForTSF.reset();
+
+ // For making it safer, we should check if there is a valid sink to receive
+ // text change notification.
+ if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "mSink is not ready to call ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ if (NS_WARN_IF(!mPendingTextChangeData.IsInInt32Range())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange() FAILED due to "
+ "offset is too big for calling "
+ "ITextStoreACPSink::OnTextChange()...",
+ this));
+ mPendingTextChangeData.Clear();
+ return;
+ }
+
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = static_cast<LONG>(mPendingTextChangeData.mStartOffset);
+ textChange.acpOldEnd =
+ static_cast<LONG>(mPendingTextChangeData.mRemovedEndOffset);
+ textChange.acpNewEnd =
+ static_cast<LONG>(mPendingTextChangeData.mAddedEndOffset);
+ mPendingTextChangeData.Clear();
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfTextChange(), calling "
+ "ITextStoreACPSink::OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd, textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+}
+
+nsresult TSFTextStore::OnSelectionChangeInternal(
+ const IMENotification& aIMENotification) {
+ const SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnSelectionChangeInternal("
+ "aIMENotification={ mSelectionChangeData=%s }), mDestroyed=%s, "
+ "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
+ "mComposition=%s",
+ this, mozilla::ToString(selectionChangeData).c_str(),
+ GetBoolName(mDestroyed), mSink.get(),
+ GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mIsRecordingActionsWithoutLock),
+ ToString(mComposition).c_str()));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ // Assign the new selection change data to the pending selection change data
+ // because only the latest selection data is necessary.
+ // Note that this is necessary to update mSelectionForTSF. Therefore, even if
+ // neither TSF nor TIP wants selection change notifications, we need to
+ // store the selection information.
+ mPendingSelectionChangeData = Some(selectionChangeData);
+
+ // Flush remaining pending notifications here if it's possible.
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ // Note that if we have composition, we'll notified composition-updated
+ // later so that we don't need to create native caret in such case.
+ if (!IsHandlingCompositionInContent() &&
+ IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+void TSFTextStore::NotifyTSFOfSelectionChange() {
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(mPendingSelectionChangeData.isSome());
+
+ // If selection range isn't actually changed, we don't need to notify TSF
+ // of this selection change.
+ if (mSelectionForTSF.isNothing()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mIsInitializingSelectionForTSF,
+ "While mSelectionForTSF is being initialized, this "
+ "should not be called");
+ mSelectionForTSF.emplace(*mPendingSelectionChangeData);
+ } else if (!mSelectionForTSF->SetSelection(*mPendingSelectionChangeData)) {
+ mPendingSelectionChangeData.reset();
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
+ "selection isn't actually changed.",
+ this));
+ return;
+ }
+
+ mPendingSelectionChangeData.reset();
+
+ if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), calling "
+ "ITextStoreACPSink::OnSelectionChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnSelectionChange();
+}
+
+nsresult TSFTextStore::OnLayoutChangeInternal() {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(mSink, NS_ERROR_FAILURE);
+
+ mDeferNotifyingTSFUntilNextUpdate = false;
+
+ nsresult rv = NS_OK;
+
+ // We need to notify TSF of layout change even if the document is locked.
+ // So, don't use MaybeFlushPendingNotifications() for flushing pending
+ // layout change.
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "NotifyTSFOfLayoutChange()...",
+ this));
+ if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "MaybeFlushPendingNotifications()...",
+ this));
+ MaybeFlushPendingNotifications();
+
+ return rv;
+}
+
+bool TSFTextStore::NotifyTSFOfLayoutChange() {
+ MOZ_ASSERT(!mDestroyed);
+
+ // If we're waiting a query of layout information from TIP, it means that
+ // we've returned TS_E_NOLAYOUT error.
+ bool returnedNoLayoutError = mHasReturnedNoLayoutError || mWaitingQueryLayout;
+
+ // If we returned TS_E_NOLAYOUT, TIP should query the computed layout again.
+ mWaitingQueryLayout = returnedNoLayoutError;
+
+ // For avoiding to call this method again at unlocking the document during
+ // calls of OnLayoutChange(), reset mHasReturnedNoLayoutError.
+ mHasReturnedNoLayoutError = false;
+
+ // Now, layout has been computed. We should notify mContentForTSF for
+ // making GetTextExt() and GetACPFromPoint() not return TS_E_NOLAYOUT.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnLayoutChanged();
+ }
+
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ CreateNativeCaret();
+ } else {
+ // Now, the caret position is different from ours. Destroy the native caret
+ // if we've create it only for GetTextExt().
+ IMEHandler::MaybeDestroyNativeCaret();
+ }
+
+ // This method should return true if either way succeeds.
+ bool ret = true;
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITextStoreACPSink::OnLayoutChange()...",
+ this));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ HRESULT hr = sink->OnLayoutChange(TS_LC_CHANGE, TEXTSTORE_DEFAULT_VIEW);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITextStoreACPSink::OnLayoutChange()",
+ this));
+ ret = SUCCEEDED(hr);
+ }
+
+ // The layout change caused by composition string change should cause
+ // calling ITfContextOwnerServices::OnLayoutChange() too.
+ if (returnedNoLayoutError && mContext) {
+ RefPtr<ITfContextOwnerServices> service;
+ mContext->QueryInterface(IID_ITfContextOwnerServices,
+ getter_AddRefs(service));
+ if (service) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITfContextOwnerServices::OnLayoutChange()...",
+ this));
+ HRESULT hr = service->OnLayoutChange();
+ ret = ret && SUCCEEDED(hr);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITfContextOwnerServices::OnLayoutChange()",
+ this));
+ }
+ }
+
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the widget is destroyed during calling OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the TSFTextStore instance is destroyed during calling "
+ "OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ // If we returned TS_E_NOLAYOUT again, we need another call of
+ // OnLayoutChange() later. So, let's wait a query from TIP.
+ if (mHasReturnedNoLayoutError) {
+ mWaitingQueryLayout = true;
+ }
+
+ if (!mWaitingQueryLayout) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "succeeded notifying TIP of our layout change",
+ this));
+ return ret;
+ }
+
+ // If we believe that TIP needs to retry to retrieve our layout information
+ // later, we should call it with ::PostMessage() hack.
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "posing MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE for calling "
+ "OnLayoutChange() again...",
+ this));
+ ::PostMessage(mWidget->GetWindowHandle(), MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE,
+ reinterpret_cast<WPARAM>(this), 0);
+
+ return true;
+}
+
+void TSFTextStore::NotifyTSFOfLayoutChangeAgain() {
+ // Don't notify TSF of layout change after destroyed.
+ if (mDestroyed) {
+ mWaitingQueryLayout = false;
+ return;
+ }
+
+ // Before preforming this method, TIP has accessed our layout information by
+ // itself. In such case, we don't need to call OnLayoutChange() anymore.
+ if (!mWaitingQueryLayout) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "calling NotifyTSFOfLayoutChange()...",
+ this));
+ NotifyTSFOfLayoutChange();
+
+ // If TIP didn't retrieved our layout information during a call of
+ // NotifyTSFOfLayoutChange(), it means that the TIP already gave up to
+ // retry to retrieve layout information or doesn't necessary it anymore.
+ // But don't forget that the call may have caused returning TS_E_NOLAYOUT
+ // error again. In such case we still need to call OnLayoutChange() later.
+ if (!mHasReturnedNoLayoutError && mWaitingQueryLayout) {
+ mWaitingQueryLayout = false;
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
+ "retrieve the layout information",
+ this));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange()",
+ this));
+ }
+}
+
+nsresult TSFTextStore::OnUpdateCompositionInternal() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
+ "mDestroyed=%s, mDeferNotifyingTSFUntilNextUpdate=%s",
+ this, GetBoolName(mDestroyed),
+ GetBoolName(mDeferNotifyingTSFUntilNextUpdate)));
+
+ // There are nothing to do after destroyed.
+ if (mDestroyed) {
+ return NS_OK;
+ }
+
+ // Update cached data now because all pending events have been handled now.
+ if (mContentForTSF.isSome()) {
+ mContentForTSF->OnCompositionEventsHandled();
+ }
+
+ // If composition is completely finished both in TSF/TIP and the focused
+ // editor which may be in a remote process, we can clear the cache and don't
+ // have it until starting next composition.
+ if (mComposition.isNothing() && !IsHandlingCompositionInContent()) {
+ mDeferClearingContentForTSF = false;
+ }
+ mDeferNotifyingTSFUntilNextUpdate = false;
+ MaybeFlushPendingNotifications();
+
+ // If we're available, we should create native caret instead of IMEHandler
+ // because we may have some cache to do it.
+ if (IMEHandler::NeedsToCreateNativeCaret()) {
+ CreateNativeCaret();
+ }
+
+ return NS_OK;
+}
+
+nsresult TSFTextStore::OnMouseButtonEventInternal(
+ const IMENotification& aIMENotification) {
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // events.
+ return NS_OK;
+ }
+
+ if (mMouseTrackers.IsEmpty()) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnMouseButtonEventInternal("
+ "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos=%s, "
+ "mCharRect=%s, mButton=%s, mButtons=%s, mModifiers=%s })",
+ this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
+ aIMENotification.mMouseButtonEventData.mOffset,
+ ToString(aIMENotification.mMouseButtonEventData.mCursorPos).c_str(),
+ ToString(aIMENotification.mMouseButtonEventData.mCharRect).c_str(),
+ GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
+ GetMouseButtonsName(aIMENotification.mMouseButtonEventData.mButtons)
+ .get(),
+ GetModifiersName(aIMENotification.mMouseButtonEventData.mModifiers)
+ .get()));
+
+ uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
+ if (offset > static_cast<uint32_t>(LONG_MAX)) {
+ return NS_OK;
+ }
+ LayoutDeviceIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect;
+ LayoutDeviceIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos;
+ ULONG quadrant = 1;
+ if (charRect.Width() > 0) {
+ int32_t cursorXInChar = cursorPos.x - charRect.X();
+ quadrant = cursorXInChar * 4 / charRect.Width();
+ quadrant = (quadrant + 2) % 4;
+ }
+ ULONG edge = quadrant < 2 ? offset + 1 : offset;
+ DWORD buttonStatus = 0;
+ bool isMouseUp =
+ aIMENotification.mMouseButtonEventData.mEventMessage == eMouseUp;
+ if (!isMouseUp) {
+ switch (aIMENotification.mMouseButtonEventData.mButton) {
+ case MouseButton::ePrimary:
+ buttonStatus = MK_LBUTTON;
+ break;
+ case MouseButton::eMiddle:
+ buttonStatus = MK_MBUTTON;
+ break;
+ case MouseButton::eSecondary:
+ buttonStatus = MK_RBUTTON;
+ break;
+ }
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) {
+ buttonStatus |= MK_CONTROL;
+ }
+ if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) {
+ buttonStatus |= MK_SHIFT;
+ }
+ for (size_t i = 0; i < mMouseTrackers.Length(); i++) {
+ MouseTracker& tracker = mMouseTrackers[i];
+ if (!tracker.IsUsing() || tracker.Range().isNothing() ||
+ !tracker.Range()->IsOffsetInRange(offset)) {
+ continue;
+ }
+ if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(),
+ quadrant, buttonStatus)) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ }
+ return NS_OK;
+}
+
+void TSFTextStore::CreateNativeCaret() {
+ MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret());
+
+ IMEHandler::MaybeDestroyNativeCaret();
+
+ // Don't create native caret after destroyed.
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this,
+ ToString(mComposition).c_str()));
+
+ Maybe<Selection>& selectionForTSF = SelectionForTSF();
+ if (MOZ_UNLIKELY(selectionForTSF.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "SelectionForTSF() failure",
+ this));
+ return;
+ }
+ if (!selectionForTSF->HasRange() && mComposition.isNothing()) {
+ // If there is no selection range nor composition, then, we don't have a
+ // good position to show windows of TIP...
+ // XXX It seems that storing last caret rect and using it in this case might
+ // be better?
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p TSFTextStore::CreateNativeCaret() couludn't create native "
+ "caret due to no selection range",
+ this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget);
+ mWidget->InitEvent(queryCaretRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ // XXX If this is called without composition and the selection isn't
+ // collapsed, is it OK?
+ int64_t caretOffset = selectionForTSF->HasRange()
+ ? selectionForTSF->MaxOffset()
+ : mComposition->StartOffset();
+ if (mComposition.isSome()) {
+ // If there is a composition, use the relative query for deciding caret
+ // position because composition might be different place from that
+ // TSFTextStore assumes.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mComposition->StartOffset();
+ } else if (!CanAccessActualContentDirectly()) {
+ // If TSF/TIP cannot access actual content directly, there may be pending
+ // text and/or selection changes which have not been notified TSF yet.
+ // Therefore, we should use the relative query from start of selection where
+ // TSFTextStore assumes since TSF/TIP computes the offset from our cached
+ // selection.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= selectionForTSF->StartOffset();
+ }
+ queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options);
+
+ DispatchEvent(queryCaretRectEvent);
+ if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "eQueryCaretRect failure (offset=%lld)",
+ this, caretOffset));
+ return;
+ }
+
+ if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()),
+ queryCaretRectEvent.mReply->mRect)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "IMEHandler::CreateNativeCaret() failure",
+ this));
+ return;
+ }
+}
+
+void TSFTextStore::CommitCompositionInternal(bool aDiscard) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
+ "mSink=0x%p, mContext=0x%p, mComposition=%s",
+ this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
+ ToString(mComposition).c_str()));
+
+ // If the document is locked, TSF will fail to commit composition since
+ // TSF needs another document lock. So, let's put off the request.
+ // Note that TextComposition will commit composition in the focused editor
+ // with the latest composition string for web apps and waits asynchronous
+ // committing messages. Therefore, we can and need to perform this
+ // asynchronously.
+ if (IsReadLocked()) {
+ if (mDeferCommittingComposition || mDeferCancellingComposition) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "does nothing because already called and waiting unlock...",
+ this));
+ return;
+ }
+ if (aDiscard) {
+ mDeferCancellingComposition = true;
+ } else {
+ mDeferCommittingComposition = true;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "putting off to request to %s composition after unlocking the "
+ "document",
+ this, aDiscard ? "cancel" : "commit"));
+ return;
+ }
+
+ if (mComposition.isSome() && aDiscard) {
+ LONG endOffset = mComposition->EndOffset();
+ mComposition->SetData(EmptyString());
+ // Note that don't notify TSF of text change after this is destroyed.
+ if (mSink && !mDestroyed) {
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = mComposition->StartOffset();
+ textChange.acpOldEnd = endOffset;
+ textChange.acpNewEnd = mComposition->StartOffset();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), calling"
+ "mSink->OnTextChange(0, { acpStart=%ld, acpOldEnd=%ld, "
+ "acpNewEnd=%ld })...",
+ this, textChange.acpStart, textChange.acpOldEnd,
+ textChange.acpNewEnd));
+ RefPtr<ITextStoreACPSink> sink = mSink;
+ sink->OnTextChange(0, &textChange);
+ }
+ }
+ // Terminate two contexts, the base context (mContext) and the top
+ // if the top context is not the same as the base context
+ RefPtr<ITfContext> context = mContext;
+ do {
+ if (context) {
+ RefPtr<ITfContextOwnerCompositionServices> services;
+ context->QueryInterface(IID_ITfContextOwnerCompositionServices,
+ getter_AddRefs(services));
+ if (services) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "requesting TerminateComposition() for the context 0x%p...",
+ this, context.get()));
+ services->TerminateComposition(nullptr);
+ }
+ }
+ if (context != mContext) break;
+ if (mDocumentMgr) mDocumentMgr->GetTop(getter_AddRefs(context));
+ } while (context != mContext);
+}
+
+static bool GetCompartment(IUnknown* pUnk, const GUID& aID,
+ ITfCompartment** aCompartment) {
+ if (!pUnk) return false;
+
+ RefPtr<ITfCompartmentMgr> compMgr;
+ pUnk->QueryInterface(IID_ITfCompartmentMgr, getter_AddRefs(compMgr));
+ if (!compMgr) return false;
+
+ return SUCCEEDED(compMgr->GetCompartment(aID, aCompartment)) &&
+ (*aCompartment) != nullptr;
+}
+
+// static
+void TSFTextStore::SetIMEOpenState(bool aState) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::SetIMEOpenState(aState=%s)", GetBoolName(aState)));
+
+ if (!sThreadMgr) {
+ return;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to"
+ "no compartment available"));
+ return;
+ }
+
+ VARIANT variant;
+ variant.vt = VT_I4;
+ variant.lVal = aState;
+ HRESULT hr = comp->SetValue(sClientId, &variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to "
+ "ITfCompartment::SetValue() failure, hr=0x%08lX",
+ hr));
+ return;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState(), setting "
+ "0x%04lX to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
+ variant.lVal));
+}
+
+// static
+bool TSFTextStore::GetIMEOpenState() {
+ if (!sThreadMgr) {
+ return false;
+ }
+
+ RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose();
+ if (NS_WARN_IF(!comp)) {
+ return false;
+ }
+
+ VARIANT variant;
+ ::VariantInit(&variant);
+ HRESULT hr = comp->GetValue(&variant);
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "ITfCompartment::GetValue() failure, hr=0x%08lX",
+ hr));
+ return false;
+ }
+ // Until IME is open in this process, the result may be empty.
+ if (variant.vt == VT_EMPTY) {
+ return false;
+ }
+ if (NS_WARN_IF(variant.vt != VT_I4)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetIMEOpenState() FAILED due to "
+ "invalid result of ITfCompartment::GetValue()"));
+ ::VariantClear(&variant);
+ return false;
+ }
+
+ return variant.lVal != 0;
+}
+
+// static
+void TSFTextStore::SetInputContext(nsWindow* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::SetInputContext(aWidget=%p, "
+ "aContext=%s, aAction.mFocusChange=%s), "
+ "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s",
+ aWidget, mozilla::ToString(aContext).c_str(),
+ GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
+ sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr,
+ GetBoolName(ThinksHavingFocus())));
+
+ switch (aAction.mFocusChange) {
+ case InputContextAction::WIDGET_CREATED:
+ // If this is called when the widget is created, there is nothing to do.
+ return;
+ case InputContextAction::FOCUS_NOT_CHANGED:
+ case InputContextAction::MENU_LOST_PSEUDO_FOCUS:
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return;
+ }
+ // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore,
+ // we need to reset text store for new state right now.
+ break;
+ default:
+ NS_WARNING_ASSERTION(IsInTSFMode(),
+ "Why is this called when TSF is disabled?");
+ if (sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->mInPrivateBrowsing = aContext.mInPrivateBrowsing;
+ textStore->SetInputScope(aContext.mHTMLInputType,
+ aContext.mHTMLInputMode);
+ if (aContext.mURI) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(aContext.mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, textStore->mDocumentURL);
+ } else {
+ textStore->mDocumentURL.Truncate();
+ }
+ } else {
+ textStore->mDocumentURL.Truncate();
+ }
+ }
+ return;
+ }
+
+ // If focus isn't actually changed but the enabled state is changed,
+ // emulate the focus move.
+ if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
+ if (!IMEHandler::GetFocusedWindow()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::SetInputContent() gets called to enable IME, "
+ "but IMEHandler has not received focus notification"));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates focus for IME "
+ "state change"));
+ OnFocusChange(true, aWidget, aContext);
+ }
+ } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ (" TSFTextStore::SetInputContent() emulates blur for IME "
+ "state change"));
+ OnFocusChange(false, aWidget, aContext);
+ }
+}
+
+// static
+void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled(), setting "
+ "to disable context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::MarkContextAsEmpty(ITfContext* aContext) {
+ VARIANT variant_int4_value1;
+ variant_int4_value1.vt = VT_I4;
+ variant_int4_value1.lVal = 1;
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(aContext, GUID_COMPARTMENT_EMPTYCONTEXT,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsEmpty() failed"
+ "aContext=0x%p...",
+ aContext));
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsEmpty(), setting "
+ "to mark empty context 0x%p...",
+ aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void TSFTextStore::Initialize() {
+ MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Initialize() is called..."));
+
+ if (sThreadMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED due to already initialized"));
+ return;
+ }
+
+ const bool enableTsf = StaticPrefs::intl_tsf_enabled_AtStartup();
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), TSF is %s",
+ enableTsf ? "enabled" : "disabled"));
+ if (!enableTsf) {
+ return;
+ }
+
+ RefPtr<ITfThreadMgr> threadMgr;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfThreadMgr, getter_AddRefs(threadMgr));
+ if (FAILED(hr) || !threadMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "create the thread manager, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ hr = threadMgr->Activate(&sClientId);
+ if (FAILED(hr)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08lX", hr));
+ return;
+ }
+
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr;
+ hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
+ if (FAILED(hr) || !disabledDocumentMgr) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a document manager for disabled mode, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ RefPtr<ITfContext> disabledContext;
+ DWORD editCookie = 0;
+ hr = disabledDocumentMgr->CreateContext(
+ sClientId, 0, nullptr, getter_AddRefs(disabledContext), &editCookie);
+ if (FAILED(hr) || !disabledContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a context for disabled mode, hr=0x%08lX",
+ hr));
+ return;
+ }
+
+ MarkContextAsKeyboardDisabled(disabledContext);
+ MarkContextAsEmpty(disabledContext);
+
+ sThreadMgr = threadMgr;
+ sDisabledDocumentMgr = disabledDocumentMgr;
+ sDisabledContext = disabledContext;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
+ "sClientId=0x%08lX, sDisabledDocumentMgr=0x%p, sDisabledContext=%p",
+ sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(),
+ sDisabledContext.get()));
+}
+
+// static
+already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() {
+ RefPtr<ITfThreadMgr> threadMgr = sThreadMgr;
+ return threadMgr.forget();
+}
+
+// static
+already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() {
+ static bool sInitialized = false;
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+ if (sMessagePump) {
+ RefPtr<ITfMessagePump> messagePump = sMessagePump;
+ return messagePump.forget();
+ }
+ // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed,
+ // we shouldn't retry it at every message due to performance reason.
+ // Although this shouldn't occur actually.
+ if (sInitialized) {
+ return nullptr;
+ }
+ sInitialized = true;
+
+ RefPtr<ITfMessagePump> messagePump;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump,
+ getter_AddRefs(messagePump));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetMessagePump() FAILED to "
+ "QI message pump from the thread manager, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sMessagePump = messagePump;
+ return messagePump.forget();
+}
+
+// static
+already_AddRefed<ITfDisplayAttributeMgr>
+TSFTextStore::GetDisplayAttributeMgr() {
+ RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
+ if (sDisplayAttrMgr) {
+ displayAttributeMgr = sDisplayAttrMgr;
+ return displayAttributeMgr.forget();
+ }
+
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create "
+ "a display attribute manager instance, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sDisplayAttrMgr = displayAttributeMgr;
+ return displayAttributeMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() {
+ RefPtr<ITfCategoryMgr> categoryMgr;
+ if (sCategoryMgr) {
+ categoryMgr = sCategoryMgr;
+ return categoryMgr.forget();
+ }
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfCategoryMgr, getter_AddRefs(categoryMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCategoryMgr() FAILED to create "
+ "a category manager instance, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sCategoryMgr = categoryMgr;
+ return categoryMgr.forget();
+}
+
+// static
+already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() {
+ if (sCompartmentForOpenClose) {
+ RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose;
+ return compartment.forget();
+ }
+
+ if (!sThreadMgr) {
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartmentMgr> compartmentMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr,
+ getter_AddRefs(compartmentMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "sThreadMgr not having ITfCompartmentMgr, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+
+ RefPtr<ITfCompartment> compartment;
+ hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(compartment));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to"
+ "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+
+ sCompartmentForOpenClose = compartment;
+ return compartment.forget();
+}
+
+// static
+already_AddRefed<ITfInputProcessorProfiles>
+TSFTextStore::GetInputProcessorProfiles() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
+ if (sInputProcessorProfiles) {
+ inputProcessorProfiles = sInputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+ }
+ // XXX MSDN documents that ITfInputProcessorProfiles is available only on
+ // desktop apps. However, there is no known way to obtain
+ // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
+ // instance.
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input "
+ "processor profiles, hr=0x%08lX",
+ hr));
+ return nullptr;
+ }
+ sInputProcessorProfiles = inputProcessorProfiles;
+ return inputProcessorProfiles.forget();
+}
+
+// static
+void TSFTextStore::Terminate() {
+ MOZ_LOG(gIMELog, LogLevel::Info, ("TSFTextStore::Terminate()"));
+
+ TSFStaticSink::Shutdown();
+
+ sDisplayAttrMgr = nullptr;
+ sCategoryMgr = nullptr;
+ sEnabledTextStore = nullptr;
+ sDisabledDocumentMgr = nullptr;
+ sDisabledContext = nullptr;
+ sCompartmentForOpenClose = nullptr;
+ sInputProcessorProfiles = nullptr;
+ sClientId = 0;
+ if (sThreadMgr) {
+ sThreadMgr->Deactivate();
+ sThreadMgr = nullptr;
+ sMessagePump = nullptr;
+ sKeystrokeMgr = nullptr;
+ }
+}
+
+// static
+bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) {
+ if (!sThreadMgr) {
+ return false; // not in TSF mode
+ }
+ static bool sInitialized = false;
+ if (!sKeystrokeMgr) {
+ // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed,
+ // we shouldn't retry it at every keydown nor keyup due to performance
+ // reason. Although this shouldn't occur actually.
+ if (sInitialized) {
+ return false;
+ }
+ sInitialized = true;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr;
+ HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr,
+ getter_AddRefs(keystrokeMgr));
+ if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::ProcessRawKeyMessage() FAILED to "
+ "QI keystroke manager from the thread manager, hr=0x%08lX",
+ hr));
+ return false;
+ }
+ sKeystrokeMgr = keystrokeMgr.forget();
+ }
+
+ if (aMsg.message == WM_KEYDOWN) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ if (aMsg.message == WM_KEYUP) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ if (NS_WARN_IF(textStore != sEnabledTextStore)) {
+ // Let's handle the key message with new focused TSFTextStore.
+ textStore = sEnabledTextStore;
+ }
+ }
+ AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg);
+ AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched);
+ sHandlingKeyMsg = &aMsg;
+ sIsKeyboardEventDispatched = false;
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage(!!eaten);
+ }
+ return SUCCEEDED(hr) &&
+ (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched);
+ }
+ return false;
+}
+
+// static
+void TSFTextStore::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult) {
+ switch (aMessage) {
+ case WM_IME_SETCONTEXT:
+ // If a windowless plugin had focus and IME was handled on it, composition
+ // window was set the position. After that, even in TSF mode, WinXP keeps
+ // to use composition window at the position if the active IME is not
+ // aware TSF. For avoiding this issue, we need to hide the composition
+ // window here.
+ if (aWParam) {
+ aLParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ }
+ break;
+ case WM_ENTERIDLE:
+ // When an modal dialog such as a file picker is open, composition
+ // should be committed because IME might be used on it.
+ if (!IsComposingOn(aWindow)) {
+ break;
+ }
+ CommitComposition(false);
+ break;
+ case MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE: {
+ TSFTextStore* maybeTextStore = reinterpret_cast<TSFTextStore*>(aWParam);
+ if (maybeTextStore == sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(maybeTextStore);
+ textStore->NotifyTSFOfLayoutChangeAgain();
+ }
+ break;
+ }
+ }
+}
+
+// static
+bool TSFTextStore::IsIMM_IMEActive() {
+ return TSFStaticSink::IsIMM_IMEActive();
+}
+
+// static
+bool TSFTextStore::IsMSJapaneseIMEActive() {
+ return TSFStaticSink::IsMSJapaneseIMEActive();
+}
+
+// static
+bool TSFTextStore::IsGoogleJapaneseInputActive() {
+ return TSFStaticSink::IsGoogleJapaneseInputActive();
+}
+
+// static
+bool TSFTextStore::IsATOKActive() { return TSFStaticSink::IsATOKActive(); }
+
+/******************************************************************************
+ * TSFTextStore::Content
+ *****************************************************************************/
+
+const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return nsDependentSubstring();
+ }
+ return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()),
+ static_cast<uint32_t>(mSelection->Length()));
+}
+
+const nsDependentSubstring TSFTextStore::Content::GetSubstring(
+ uint32_t aStart, uint32_t aLength) const {
+ return nsDependentSubstring(mText, aStart, aLength);
+}
+
+void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) {
+ if (NS_WARN_IF(mSelection.isNothing())) {
+ return;
+ }
+ ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString);
+}
+
+inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1,
+ const nsAString& aStr2) {
+ MOZ_ASSERT(aStr1 != aStr2);
+ uint32_t i = 0;
+ uint32_t minLength = std::min(aStr1.Length(), aStr2.Length());
+ for (; i < minLength && aStr1[i] == aStr2[i]; i++) {
+ /* nothing to do */
+ }
+ return i;
+}
+
+void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength,
+ const nsAString& aReplaceString) {
+ MOZ_ASSERT(aStart >= 0);
+ MOZ_ASSERT(aLength >= 0);
+ const nsDependentSubstring replacedString = GetSubstring(
+ static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength));
+ if (aReplaceString != replacedString) {
+ uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX);
+ if (mComposition.isSome()) {
+ // Emulate text insertion during compositions, because during a
+ // composition, editor expects the whole composition string to
+ // be sent in eCompositionChange, not just the inserted part.
+ // The actual eCompositionChange will be sent in SetSelection
+ // or OnUpdateComposition.
+ MOZ_ASSERT(aStart >= mComposition->StartOffset());
+ MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset());
+ mComposition->ReplaceData(
+ static_cast<uint32_t>(aStart - mComposition->StartOffset()),
+ static_cast<uint32_t>(aLength), aReplaceString);
+ // TIP may set composition string twice or more times during a document
+ // lock. Therefore, we should compute the first difference offset with
+ // mLastComposition.
+ if (mLastComposition.isNothing()) {
+ firstDifferentOffset = mComposition->StartOffset();
+ } else if (mComposition->DataRef() != mLastComposition->DataRef()) {
+ firstDifferentOffset =
+ mComposition->StartOffset() +
+ FirstDifferentCharOffset(mComposition->DataRef(),
+ mLastComposition->DataRef());
+ // The previous change to the composition string is canceled.
+ if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() >=
+ static_cast<uint32_t>(mComposition->StartOffset()) &&
+ mMinModifiedOffset.value() < firstDifferentOffset) {
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ } else if (mMinModifiedOffset.isSome() &&
+ mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) &&
+ mComposition->IsOffsetInRange(
+ static_cast<long>(mMinModifiedOffset.value()))) {
+ // The previous change to the composition string is canceled.
+ firstDifferentOffset = mComposition->EndOffset();
+ mMinModifiedOffset = Some(firstDifferentOffset);
+ }
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%ld, "
+ "aLength=%ld, aReplaceString=\"%s\"), mComposition=%s, "
+ "mLastComposition=%s, mMinModifiedOffset=%s, "
+ "firstDifferentOffset=%u",
+ this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
+ ToString(mComposition).c_str(), ToString(mLastComposition).c_str(),
+ ToString(mMinModifiedOffset).c_str(), firstDifferentOffset));
+ } else {
+ firstDifferentOffset =
+ static_cast<uint32_t>(aStart) +
+ FirstDifferentCharOffset(aReplaceString, replacedString);
+ }
+ mMinModifiedOffset =
+ mMinModifiedOffset.isNothing()
+ ? Some(firstDifferentOffset)
+ : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset));
+ mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength),
+ aReplaceString);
+ }
+ // Selection should be collapsed at the end of the inserted string.
+ mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) +
+ aReplaceString.Length()));
+}
+
+void TSFTextStore::Content::StartComposition(
+ ITfCompositionView* aCompositionView, const PendingAction& aCompStart,
+ bool aPreserveSelection) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart);
+
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(
+ aCompositionView, aCompStart.mSelectionStart,
+ GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
+ static_cast<uint32_t>(aCompStart.mSelectionLength)));
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+ if (!aPreserveSelection) {
+ // XXX Do we need to set a new writing-mode here when setting a new
+ // selection? Currently, we just preserve the existing value.
+ WritingMode writingMode =
+ mSelection.isNothing() ? WritingMode() : mSelection->WritingModeRef();
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(),
+ mComposition->Length(), false,
+ writingMode));
+ }
+}
+
+void TSFTextStore::Content::RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aCanceledCompositionEnd) {
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(mComposition.isNothing());
+ MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+ PendingAction::Type::eCompositionEnd);
+ MOZ_ASSERT(
+ GetSubstring(
+ static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart),
+ static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+ aCanceledCompositionEnd.mData);
+
+ // Restore the committed string as composing string.
+ mComposition.reset(); // Avoid new crash in the beta and nightly channels.
+ mComposition.emplace(aCompositionView,
+ aCanceledCompositionEnd.mSelectionStart,
+ aCanceledCompositionEnd.mData);
+ mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets());
+}
+
+void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) {
+ MOZ_ASSERT(mComposition.isSome());
+ MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd);
+
+ if (mComposition.isNothing()) {
+ return; // Avoid new crash in the beta and nightly channels.
+ }
+
+ mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() +
+ aCompEnd.mData.Length()));
+ mComposition.reset();
+}
+
+/******************************************************************************
+ * TSFTextStore::MouseTracker
+ *****************************************************************************/
+
+TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {}
+
+HRESULT
+TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
+ "aTextStore->mMouseTrackers.Length()=%zu",
+ this, aTextStore, aTextStore->mMouseTrackers.Length()));
+
+ if (&aTextStore->mMouseTrackers.LastElement() != this) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "this is not the last element of mMouseTrackers",
+ this));
+ return E_FAIL;
+ }
+ if (aTextStore->mMouseTrackers.Length() > kInvalidCookie) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::Init() FAILED due to "
+ "no new cookie available",
+ this));
+ return E_FAIL;
+ }
+ MOZ_ASSERT(!aTextStore->mMouseTrackers.IsEmpty(),
+ "This instance must be in TSFTextStore::mMouseTrackers");
+ mCookie = static_cast<DWORD>(aTextStore->mMouseTrackers.Length() - 1);
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::MouseTracker::AdviseSink(TSFTextStore* aTextStore,
+ ITfRangeACP* aTextRange,
+ ITfMouseSink* aMouseSink) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
+ "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%ld, mSink=0x%p",
+ this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
+ MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
+
+ if (mSink) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to already being used",
+ this));
+ return E_FAIL;
+ }
+
+ MOZ_ASSERT(mRange.isNothing());
+
+ LONG start = 0, length = 0;
+ HRESULT hr = aTextRange->GetExtent(&start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of ITfRangeACP::GetExtent()",
+ this));
+ return hr;
+ }
+
+ if (start < 0 || length <= 0 || start + length > LONG_MAX) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to odd result of ITfRangeACP::GetExtent(), "
+ "start=%ld, length=%ld",
+ this, start, length));
+ return E_INVALIDARG;
+ }
+
+ nsAutoString textContent;
+ if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of TSFTextStore::GetCurrentText()",
+ this));
+ return E_FAIL;
+ }
+
+ if (textContent.Length() <= static_cast<uint32_t>(start) ||
+ textContent.Length() < static_cast<uint32_t>(start + length)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to out of range, start=%ld, length=%ld, "
+ "textContent.Length()=%zu",
+ this, start, length, textContent.Length()));
+ return E_INVALIDARG;
+ }
+
+ mRange.emplace(start, start + length);
+
+ mSink = aMouseSink;
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
+ "succeeded, mRange=%s, textContent.Length()=%zu",
+ this, ToString(mRange).c_str(), textContent.Length()));
+ return S_OK;
+}
+
+void TSFTextStore::MouseTracker::UnadviseSink() {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
+ "mCookie=%ld, mSink=0x%p, mRange=%s",
+ this, mCookie, mSink.get(), ToString(mRange).c_str()));
+ mSink = nullptr;
+ mRange.reset();
+}
+
+bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge,
+ ULONG aQuadrant,
+ DWORD aButtonStatus) {
+ MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()");
+
+ BOOL eaten = FALSE;
+ RefPtr<ITfMouseSink> sink = mSink;
+ HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten);
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%ld, "
+ "aQuadrant=%ld, aButtonStatus=0x%08lX), hr=0x%08lX, eaten=%s",
+ this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
+
+ return SUCCEEDED(hr) && eaten;
+}
+
+#ifdef DEBUG
+// static
+bool TSFTextStore::CurrentKeyboardLayoutHasIME() {
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles =
+ TSFTextStore::GetInputProcessorProfiles();
+ if (!inputProcessorProfiles) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
+ "there is no input processor profiles instance"));
+ return false;
+ }
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr = inputProcessorProfiles->QueryInterface(
+ IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr));
+ if (FAILED(hr) || !profileMgr) {
+ // On Windows Vista or later, ImmIsIME() API always returns true.
+ // If we failed to obtain the profile manager, we cannot know if current
+ // keyboard layout has IME.
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to query "
+ "ITfInputProcessorProfileMgr"));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ return false; // not found or not active
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
+ "active profile"));
+ return false;
+ }
+ return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
+}
+#endif // #ifdef DEBUG
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h
new file mode 100644
index 0000000000..17358a488d
--- /dev/null
+++ b/widget/windows/TSFTextStore.h
@@ -0,0 +1,1161 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 TSFTextStore_h_
+#define TSFTextStore_h_
+
+#include "nsCOMPtr.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "nsWindow.h"
+
+#include "WinUtils.h"
+#include "WritingModes.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/widget/IMEData.h"
+
+#include <msctf.h>
+#include <textstor.h>
+
+// GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID.
+// With initguid.h, we get its instance instead of extern declaration.
+#ifdef INPUTSCOPE_INIT_GUID
+# include <initguid.h>
+#endif
+#ifdef TEXTATTRS_INIT_GUID
+# include <tsattrs.h>
+#endif
+#include <inputscope.h>
+
+// TSF InputScope, for earlier SDK 8
+#define IS_SEARCH static_cast<InputScope>(50)
+
+struct ITfThreadMgr;
+struct ITfDocumentMgr;
+struct ITfDisplayAttributeMgr;
+struct ITfCategoryMgr;
+class nsWindow;
+
+inline std::ostream& operator<<(std::ostream& aStream,
+ const TS_SELECTIONSTYLE& aSelectionStyle) {
+ const char* ase = "Unknown";
+ switch (aSelectionStyle.ase) {
+ case TS_AE_START:
+ ase = "TS_AE_START";
+ break;
+ case TS_AE_END:
+ ase = "TS_AE_END";
+ break;
+ case TS_AE_NONE:
+ ase = "TS_AE_NONE";
+ break;
+ }
+ aStream << "{ ase=" << ase << ", fInterimChar="
+ << (aSelectionStyle.fInterimChar ? "TRUE" : "FALSE") << " }";
+ return aStream;
+}
+
+inline std::ostream& operator<<(std::ostream& aStream,
+ const TS_SELECTION_ACP& aACP) {
+ aStream << "{ acpStart=" << aACP.acpStart << ", acpEnd=" << aACP.acpEnd
+ << ", style=" << mozilla::ToString(aACP.style).c_str() << " }";
+ return aStream;
+}
+
+namespace mozilla {
+namespace widget {
+
+class TSFStaticSink;
+struct MSGResult;
+
+/*
+ * Text Services Framework text store
+ */
+
+class TSFTextStore final : public ITextStoreACP,
+ public ITfContextOwnerCompositionSink,
+ public ITfMouseTrackerACP {
+ friend class TSFStaticSink;
+
+ private:
+ typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
+ typedef IMENotification::SelectionChangeData SelectionChangeData;
+ typedef IMENotification::TextChangeDataBase TextChangeDataBase;
+ typedef IMENotification::TextChangeData TextChangeData;
+
+ public: /*IUnknown*/
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore)
+
+ public: /*ITextStoreACP*/
+ STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD);
+ STDMETHODIMP UnadviseSink(IUnknown*);
+ STDMETHODIMP RequestLock(DWORD, HRESULT*);
+ STDMETHODIMP GetStatus(TS_STATUS*);
+ STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*);
+ STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*);
+ STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*);
+ STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG,
+ ULONG*, LONG*);
+ STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*);
+ STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**);
+ STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**);
+ STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*);
+ STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*);
+ STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*);
+ STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD);
+ STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG,
+ const TS_ATTRID*, DWORD);
+ STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*,
+ DWORD, LONG*, BOOL*, LONG*);
+ STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*);
+ STDMETHODIMP GetEndACP(LONG*);
+ STDMETHODIMP GetActiveView(TsViewCookie*);
+ STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*);
+ STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*);
+ STDMETHODIMP GetScreenExt(TsViewCookie, RECT*);
+ STDMETHODIMP GetWnd(TsViewCookie, HWND*);
+ STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*,
+ TS_TEXTCHANGE*);
+ STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*,
+ TS_TEXTCHANGE*);
+
+ public: /*ITfContextOwnerCompositionSink*/
+ STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*);
+ STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*);
+ STDMETHODIMP OnEndComposition(ITfCompositionView*);
+
+ public: /*ITfMouseTrackerACP*/
+ STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*);
+ STDMETHODIMP UnadviseMouseSink(DWORD);
+
+ public:
+ static void Initialize(void);
+ static void Terminate(void);
+
+ static bool ProcessRawKeyMessage(const MSG& aMsg);
+ static void ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+
+ static void SetIMEOpenState(bool);
+ static bool GetIMEOpenState(void);
+
+ static void CommitComposition(bool aDiscard) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->CommitCompositionInternal(aDiscard);
+ }
+
+ static void SetInputContext(nsWindow* aWidget, const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ static nsresult OnFocusChange(bool aGotFocus, nsWindow* aFocusedWidget,
+ const InputContext& aContext);
+ static nsresult OnTextChange(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnTextChangeInternal(aIMENotification);
+ }
+
+ static nsresult OnSelectionChange(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnSelectionChangeInternal(aIMENotification);
+ }
+
+ static nsresult OnLayoutChange() {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnLayoutChangeInternal();
+ }
+
+ static nsresult OnUpdateComposition() {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnUpdateCompositionInternal();
+ }
+
+ static nsresult OnMouseButtonEvent(const IMENotification& aIMENotification) {
+ NS_ASSERTION(IsInTSFMode(), "Not in TSF mode, shouldn't be called");
+ if (!sEnabledTextStore) {
+ return NS_OK;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ return textStore->OnMouseButtonEventInternal(aIMENotification);
+ }
+
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ // Returns the address of the pointer so that the TSF automatic test can
+ // replace the system object with a custom implementation for testing.
+ // XXX TSF doesn't work now. Should we remove it?
+ static void* GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_TSF_THREAD_MGR:
+ Initialize(); // Apply any previous changes
+ return static_cast<void*>(&sThreadMgr);
+ case NS_NATIVE_TSF_CATEGORY_MGR:
+ return static_cast<void*>(&sCategoryMgr);
+ case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
+ return static_cast<void*>(&sDisplayAttrMgr);
+ default:
+ return nullptr;
+ }
+ }
+
+ static void* GetThreadManager() { return static_cast<void*>(sThreadMgr); }
+
+ static bool ThinksHavingFocus() {
+ return (sEnabledTextStore && sEnabledTextStore->mContext);
+ }
+
+ static bool IsInTSFMode() { return sThreadMgr != nullptr; }
+
+ static bool IsComposing() {
+ return (sEnabledTextStore && sEnabledTextStore->mComposition.isSome());
+ }
+
+ static bool IsComposingOn(nsWindow* aWidget) {
+ return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
+ }
+
+ static nsWindow* GetEnabledWindowBase() {
+ return sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr;
+ }
+
+ /**
+ * Returns true if active keyboard layout is a legacy IMM-IME.
+ */
+ static bool IsIMM_IMEActive();
+
+ /**
+ * Returns true if active TIP is MS-IME for Japanese.
+ */
+ static bool IsMSJapaneseIMEActive();
+
+ /**
+ * Returns true if active TIP is Google Japanese Input.
+ * Note that if Google Japanese Input is installed as an IMM-IME,
+ * this return false even if Google Japanese Input is active.
+ * So, you may need to check IMMHandler::IsGoogleJapaneseInputActive() too.
+ */
+ static bool IsGoogleJapaneseInputActive();
+
+ /**
+ * Returns true if active TIP is ATOK.
+ */
+ static bool IsATOKActive();
+
+ /**
+ * Returns true if active TIP or IME is a black listed one and we should
+ * set input scope of URL bar to IS_DEFAULT rather than IS_URL.
+ */
+ static bool ShouldSetInputScopeOfURLBarToDefault();
+
+ /**
+ * Returns true if TSF may crash if GetSelection() returns E_FAIL.
+ */
+ static bool DoNotReturnErrorFromGetSelection();
+
+#ifdef DEBUG
+ // Returns true when keyboard layout has IME (TIP).
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+ protected:
+ TSFTextStore();
+ ~TSFTextStore();
+
+ static bool CreateAndSetFocus(nsWindow* aFocusedWidget,
+ const InputContext& aContext);
+ static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore);
+ static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
+ static void MarkContextAsEmpty(ITfContext* aContext);
+
+ bool Init(nsWindow* aWidget, const InputContext& aContext);
+ void Destroy();
+ void ReleaseTSFObjects();
+
+ bool IsReadLock(DWORD aLock) const {
+ return (TS_LF_READ == (aLock & TS_LF_READ));
+ }
+ bool IsReadWriteLock(DWORD aLock) const {
+ return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE));
+ }
+ bool IsReadLocked() const { return IsReadLock(mLock); }
+ bool IsReadWriteLocked() const { return IsReadWriteLock(mLock); }
+
+ // This is called immediately after a call of OnLockGranted() of mSink.
+ // Note that mLock isn't cleared yet when this is called.
+ void DidLockGranted();
+
+ bool GetScreenExtInternal(RECT& aScreenExt);
+ // If aDispatchCompositionChangeEvent is true, this method will dispatch
+ // compositionchange event if this is called during IME composing.
+ // aDispatchCompositionChangeEvent should be true only when this is called
+ // from SetSelection. Because otherwise, the compositionchange event should
+ // not be sent from here.
+ HRESULT SetSelectionInternal(const TS_SELECTION_ACP*,
+ bool aDispatchCompositionChangeEvent = false);
+ bool InsertTextAtSelectionInternal(const nsAString& aInsertStr,
+ TS_TEXTCHANGE* aTextChange);
+ void CommitCompositionInternal(bool);
+ HRESULT GetDisplayAttribute(ITfProperty* aProperty, ITfRange* aRange,
+ TF_DISPLAYATTRIBUTE* aResult);
+ HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr);
+ class Composition;
+ HRESULT RestartComposition(Composition& aCurrentComposition,
+ ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange);
+
+ // Following methods record composing action(s) to mPendingActions.
+ // They will be flushed FlushPendingActions().
+ HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
+ ITfRange* aRange,
+ bool aPreserveSelection);
+ HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView,
+ LONG aStart, LONG aLength,
+ bool aPreserveSelection);
+ HRESULT RecordCompositionUpdateAction();
+ HRESULT RecordCompositionEndAction();
+
+ // DispatchEvent() dispatches the event and if it may not be handled
+ // synchronously, this makes the instance not notify TSF of pending
+ // notifications until next notification from content.
+ void DispatchEvent(WidgetGUIEvent& aEvent);
+ void OnLayoutInformationAvaliable();
+
+ // FlushPendingActions() performs pending actions recorded in mPendingActions
+ // and clear it.
+ void FlushPendingActions();
+ // MaybeFlushPendingNotifications() performs pending notifications to TSF.
+ void MaybeFlushPendingNotifications();
+
+ nsresult OnTextChangeInternal(const IMENotification& aIMENotification);
+ nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification);
+ nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification);
+ nsresult OnLayoutChangeInternal();
+ nsresult OnUpdateCompositionInternal();
+
+ // mPendingSelectionChangeData stores selection change data until notifying
+ // TSF of selection change. If two or more selection changes occur, this
+ // stores the latest selection change data because only it is necessary.
+ Maybe<SelectionChangeData> mPendingSelectionChangeData;
+
+ // mPendingTextChangeData stores one or more text change data until notifying
+ // TSF of text change. If two or more text changes occur, this merges
+ // every text change data.
+ TextChangeData mPendingTextChangeData;
+
+ void NotifyTSFOfTextChange();
+ void NotifyTSFOfSelectionChange();
+ bool NotifyTSFOfLayoutChange();
+ void NotifyTSFOfLayoutChangeAgain();
+
+ HRESULT HandleRequestAttrs(DWORD aFlags, ULONG aFilterCount,
+ const TS_ATTRID* aFilterAttrs);
+ void SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputMode);
+
+ // Creates native caret over our caret. This method only works on desktop
+ // application. Otherwise, this does nothing.
+ void CreateNativeCaret();
+ // Destroys native caret if there is.
+ void MaybeDestroyNativeCaret();
+
+ /**
+ * MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In
+ * strictly speaking, TSF is aware of asynchronous layout computation like us.
+ * However, Windows 10 version 1803 and older (including Windows 8.1 and
+ * older) Windows has a bug which is that the caller of GetTextExt() of TSF
+ * does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after
+ * fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT.
+ * For avoiding this issue, this method checks current Windows version and
+ * active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies
+ * aACPStart and aACPEnd to making sure that they are in range of unmodified
+ * characters.
+ *
+ * @param aACPStart Initial value should be acpStart of GetTextExt().
+ * If this method returns true, this may be modified
+ * to be in range of unmodified characters.
+ * @param aACPEnd Initial value should be acpEnd of GetTextExt().
+ * If this method returns true, this may be modified
+ * to be in range of unmodified characters.
+ * And also this may become same as aACPStart.
+ * @return true if the caller shouldn't return TS_E_NOLAYOUT.
+ * In this case, this method modifies aACPStart and/or
+ * aASCPEnd to compute rectangle of unmodified characters.
+ * false if the caller can return TS_E_NOLAYOUT or
+ * we cannot have proper unmodified characters.
+ */
+ bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd);
+
+ // Holds the pointer to our current win32 widget
+ RefPtr<nsWindow> mWidget;
+ // mDispatcher is a helper class to dispatch composition events.
+ RefPtr<TextEventDispatcher> mDispatcher;
+ // Document manager for the currently focused editor
+ RefPtr<ITfDocumentMgr> mDocumentMgr;
+ // Edit cookie associated with the current editing context
+ DWORD mEditCookie;
+ // Editing context at the bottom of mDocumentMgr's context stack
+ RefPtr<ITfContext> mContext;
+ // Currently installed notification sink
+ RefPtr<ITextStoreACPSink> mSink;
+ // TS_AS_* mask of what events to notify
+ DWORD mSinkMask;
+ // 0 if not locked, otherwise TS_LF_* indicating the current lock
+ DWORD mLock;
+ // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock
+ DWORD mLockQueued;
+
+ uint32_t mHandlingKeyMessage;
+ void OnStartToHandleKeyMessage() {
+ // If we're starting to handle another key message during handling a
+ // key message, let's assume that the handling key message is handled by
+ // TIP and it sends another key message for hacking something.
+ // Let's try to dispatch a keyboard event now.
+ // FYI: All callers of this method grab this instance with local variable.
+ // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+ // we're safe to access any members.
+ if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ }
+ ++mHandlingKeyMessage;
+ }
+ void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) {
+ // If sHandlingKeyMsg has been handled by TSF or TIP and we're still
+ // alive, but we haven't dispatch keyboard event for it, let's fire it now.
+ // FYI: All callers of this method grab this instance with local variable.
+ // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(),
+ // we're safe to access any members.
+ if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF &&
+ !sIsKeyboardEventDispatched) {
+ MaybeDispatchKeyboardEventAsProcessedByIME();
+ }
+ MOZ_ASSERT(mHandlingKeyMessage);
+ if (--mHandlingKeyMessage) {
+ return;
+ }
+ // If TSFTextStore instance is destroyed during handling key message(s),
+ // release all TSF objects when all nested key messages have been handled.
+ if (mDestroyed) {
+ ReleaseTSFObjects();
+ }
+ }
+
+ /**
+ * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown
+ * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching
+ * event as "processed by IME". Note that if the document is locked, this
+ * just adds a pending action into the queue and sets
+ * sIsKeyboardEventDispatched to true.
+ */
+ void MaybeDispatchKeyboardEventAsProcessedByIME();
+
+ /**
+ * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or
+ * eKeyUp event with NativeKey class and aMsg.
+ */
+ void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg);
+
+ // Composition class stores a copy of the active composition string. Only
+ // the data is updated during an InsertTextAtSelection call if we have a
+ // composition. The data acts as a buffer until OnUpdateComposition is
+ // called and the data is flushed to editor through eCompositionChange.
+ // This allows all changes to be updated in batches to avoid inconsistencies
+ // and artifacts.
+ class Composition final : public OffsetAndData<LONG> {
+ public:
+ explicit Composition(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString)
+ : OffsetAndData<LONG>(aCompositionStartOffset, aCompositionString),
+ mView(aCompositionView) {}
+
+ ITfCompositionView* GetView() const { return mView; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Composition& aComposition) {
+ aStream << "{ mView=0x" << aComposition.mView.get()
+ << ", OffsetAndData<LONG>="
+ << static_cast<const OffsetAndData<LONG>&>(aComposition) << " }";
+ return aStream;
+ }
+
+ private:
+ RefPtr<ITfCompositionView> const mView;
+ };
+ // While the document is locked, we cannot dispatch any events which cause
+ // DOM events since the DOM events' handlers may modify the locked document.
+ // However, even while the document is locked, TSF may queries us.
+ // For that, TSFTextStore modifies mComposition even while the document is
+ // locked. With mComposition, query methods can returns the text content
+ // information.
+ Maybe<Composition> mComposition;
+
+ /**
+ * IsHandlingCompositionInParent() returns true if eCompositionStart is
+ * dispatched, but eCompositionCommit(AsIs) is not dispatched. This means
+ * that if composition is handled in a content process, this status indicates
+ * whether ContentCacheInParent has composition or not. On the other hand,
+ * if it's handled in the chrome process, this is exactly same as
+ * IsHandlingCompositionInContent().
+ */
+ bool IsHandlingCompositionInParent() const {
+ return mDispatcher && mDispatcher->IsComposing();
+ }
+
+ /**
+ * IsHandlingCompositionInContent() returns true if there is a composition in
+ * the focused editor which may be in a content process.
+ */
+ bool IsHandlingCompositionInContent() const {
+ return mDispatcher && mDispatcher->IsHandlingComposition();
+ }
+
+ class Selection {
+ public:
+ static TS_SELECTION_ACP EmptyACP() {
+ return TS_SELECTION_ACP{
+ .acpStart = 0,
+ .acpEnd = 0,
+ .style = {.ase = TS_AE_NONE, .fInterimChar = FALSE}};
+ }
+
+ bool HasRange() const { return mACP.isSome(); }
+ const TS_SELECTION_ACP& ACPRef() const { return mACP.ref(); }
+
+ explicit Selection(const TS_SELECTION_ACP& aSelection) {
+ SetSelection(aSelection);
+ }
+
+ explicit Selection(uint32_t aOffsetToCollapse) {
+ Collapse(aOffsetToCollapse);
+ }
+
+ explicit Selection(const SelectionChangeDataBase& aSelectionChangeData) {
+ SetSelection(aSelectionChangeData);
+ }
+
+ explicit Selection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
+ SetSelection(aQuerySelectionEvent);
+ }
+
+ Selection(uint32_t aStart, uint32_t aLength, bool aReversed,
+ const WritingMode& aWritingMode) {
+ SetSelection(aStart, aLength, aReversed, aWritingMode);
+ }
+
+ void SetSelection(const TS_SELECTION_ACP& aSelection) {
+ mACP = Some(aSelection);
+ // Selection end must be active in our editor.
+ if (mACP->style.ase != TS_AE_START) {
+ mACP->style.ase = TS_AE_END;
+ }
+ // We're not support interim char selection for now.
+ // XXX Probably, this is necessary for supporting South Asian languages.
+ mACP->style.fInterimChar = FALSE;
+ }
+
+ bool SetSelection(const SelectionChangeDataBase& aSelectionChangeData) {
+ MOZ_ASSERT(aSelectionChangeData.IsInitialized());
+ if (!aSelectionChangeData.HasRange()) {
+ if (mACP.isNothing()) {
+ return false;
+ }
+ mACP.reset();
+ // Let's keep the WritingMode because users don't want to change the UI
+ // of TIP temporarily since no selection case is created only by web
+ // apps, but they or TIP would restore selection at last point later.
+ return true;
+ }
+ return SetSelection(aSelectionChangeData.mOffset,
+ aSelectionChangeData.Length(),
+ aSelectionChangeData.mReversed,
+ aSelectionChangeData.GetWritingMode());
+ }
+
+ bool SetSelection(const WidgetQueryContentEvent& aQuerySelectionEvent) {
+ MOZ_ASSERT(aQuerySelectionEvent.mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aQuerySelectionEvent.Succeeded());
+ if (aQuerySelectionEvent.DidNotFindSelection()) {
+ if (mACP.isNothing()) {
+ return false;
+ }
+ mACP.reset();
+ // Let's keep the WritingMode because users don't want to change the UI
+ // of TIP temporarily since no selection case is created only by web
+ // apps, but they or TIP would restore selection at last point later.
+ return true;
+ }
+ return SetSelection(aQuerySelectionEvent.mReply->StartOffset(),
+ aQuerySelectionEvent.mReply->DataLength(),
+ aQuerySelectionEvent.mReply->mReversed,
+ aQuerySelectionEvent.mReply->WritingModeRef());
+ }
+
+ bool SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed,
+ const WritingMode& aWritingMode) {
+ const bool changed = mACP.isNothing() ||
+ mACP->acpStart != static_cast<LONG>(aStart) ||
+ mACP->acpEnd != static_cast<LONG>(aStart + aLength);
+ mACP = Some(
+ TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aStart),
+ .acpEnd = static_cast<LONG>(aStart + aLength),
+ .style = {.ase = aReversed ? TS_AE_START : TS_AE_END,
+ .fInterimChar = FALSE}});
+ mWritingMode = aWritingMode;
+
+ return changed;
+ }
+
+ bool Collapsed() const {
+ return mACP.isNothing() || mACP->acpStart == mACP->acpEnd;
+ }
+
+ void Collapse(uint32_t aOffset) {
+ // XXX This does not update the selection's mWritingMode.
+ // If it is ever used to "collapse" to an entirely new location,
+ // we may need to fix that.
+ mACP = Some(
+ TS_SELECTION_ACP{.acpStart = static_cast<LONG>(aOffset),
+ .acpEnd = static_cast<LONG>(aOffset),
+ .style = {.ase = TS_AE_END, .fInterimChar = FALSE}});
+ }
+
+ LONG MinOffset() const {
+ MOZ_ASSERT(mACP.isSome());
+ LONG min = std::min(mACP->acpStart, mACP->acpEnd);
+ MOZ_ASSERT(min >= 0);
+ return min;
+ }
+
+ LONG MaxOffset() const {
+ MOZ_ASSERT(mACP.isSome());
+ LONG max = std::max(mACP->acpStart, mACP->acpEnd);
+ MOZ_ASSERT(max >= 0);
+ return max;
+ }
+
+ LONG StartOffset() const {
+ MOZ_ASSERT(mACP.isSome());
+ MOZ_ASSERT(mACP->acpStart >= 0);
+ return mACP->acpStart;
+ }
+
+ LONG EndOffset() const {
+ MOZ_ASSERT(mACP.isSome());
+ MOZ_ASSERT(mACP->acpEnd >= 0);
+ return mACP->acpEnd;
+ }
+
+ LONG Length() const {
+ MOZ_ASSERT_IF(mACP.isSome(), mACP->acpEnd >= mACP->acpStart);
+ return mACP.isSome() ? std::abs(mACP->acpEnd - mACP->acpStart) : 0;
+ }
+
+ bool IsReversed() const {
+ return mACP.isSome() && mACP->style.ase == TS_AE_START;
+ }
+
+ TsActiveSelEnd ActiveSelEnd() const {
+ return mACP.isSome() ? mACP->style.ase : TS_AE_NONE;
+ }
+
+ bool IsInterimChar() const {
+ return mACP.isSome() && mACP->style.fInterimChar != FALSE;
+ }
+
+ const WritingMode& WritingModeRef() const { return mWritingMode; }
+
+ bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const {
+ if (mACP.isNothing()) {
+ return false;
+ }
+ if (mACP->style.ase == aACP.style.ase) {
+ return mACP->acpStart == aACP.acpStart && mACP->acpEnd == aACP.acpEnd;
+ }
+ return mACP->acpStart == aACP.acpEnd && mACP->acpEnd == aACP.acpStart;
+ }
+
+ bool EqualsExceptDirection(
+ const SelectionChangeDataBase& aChangedSelection) const {
+ MOZ_ASSERT(aChangedSelection.IsInitialized());
+ if (mACP.isNothing()) {
+ return aChangedSelection.HasRange();
+ }
+ return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+ aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Selection& aSelection) {
+ aStream << "{ mACP=" << ToString(aSelection.mACP).c_str()
+ << ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str()
+ << ", Collapsed()="
+ << (aSelection.Collapsed() ? "true" : "false")
+ << ", Length=" << aSelection.Length() << " }";
+ return aStream;
+ }
+
+ private:
+ Maybe<TS_SELECTION_ACP> mACP; // If Nothing, there is no selection
+ WritingMode mWritingMode;
+ };
+ // Don't access mSelection directly. Instead, Use SelectionForTSFRef().
+ // This is modified immediately when TSF requests to set selection and not
+ // updated by selection change in content until mContentForTSF is cleared.
+ Maybe<Selection> mSelectionForTSF;
+
+ /**
+ * Get the selection expected by TSF. If mSelectionForTSF is already valid,
+ * this just return the reference to it. Otherwise, this initializes it
+ * with eQuerySelectedText. Please check if the result is valid before
+ * actually using it.
+ * Note that this is also called by ContentForTSF().
+ */
+ Maybe<Selection>& SelectionForTSF();
+
+ struct PendingAction final {
+ enum class Type : uint8_t {
+ eCompositionStart,
+ eCompositionUpdate,
+ eCompositionEnd,
+ eSetSelection,
+ eKeyboardEvent,
+ };
+ Type mType;
+ // For eCompositionStart, eCompositionEnd and eSetSelection
+ LONG mSelectionStart;
+ // For eCompositionStart and eSetSelection
+ LONG mSelectionLength;
+ // For eCompositionStart, eCompositionUpdate and eCompositionEnd
+ nsString mData;
+ // For eCompositionUpdate
+ RefPtr<TextRangeArray> mRanges;
+ // For eKeyboardEvent
+ MSG mKeyMsg;
+ // For eSetSelection
+ bool mSelectionReversed;
+ // For eCompositionUpdate
+ bool mIncomplete;
+ // For eCompositionStart
+ bool mAdjustSelection;
+ };
+ // Items of mPendingActions are appended when TSF tells us to need to dispatch
+ // DOM composition events. However, we cannot dispatch while the document is
+ // locked because it can cause modifying the locked document. So, the pending
+ // actions should be performed when document lock is unlocked.
+ nsTArray<PendingAction> mPendingActions;
+
+ PendingAction* LastOrNewPendingCompositionUpdate() {
+ if (!mPendingActions.IsEmpty()) {
+ PendingAction& lastAction = mPendingActions.LastElement();
+ if (lastAction.mType == PendingAction::Type::eCompositionUpdate) {
+ return &lastAction;
+ }
+ }
+ PendingAction* newAction = mPendingActions.AppendElement();
+ newAction->mType = PendingAction::Type::eCompositionUpdate;
+ newAction->mRanges = new TextRangeArray();
+ newAction->mIncomplete = true;
+ return newAction;
+ }
+
+ /**
+ * IsLastPendingActionCompositionEndAt() checks whether the previous pending
+ * action is committing composition whose range starts from aStart and its
+ * length is aLength. In other words, this checks whether new composition
+ * which will replace same range as previous pending commit can be merged
+ * with the previous composition.
+ *
+ * @param aStart The inserted offset you expected.
+ * @param aLength The inserted text length you expected.
+ * @return true if the last pending action is
+ * eCompositionEnd and it inserted the text
+ * between aStart and aStart + aLength.
+ */
+ bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& pendingLastAction = mPendingActions.LastElement();
+ return pendingLastAction.mType == PendingAction::Type::eCompositionEnd &&
+ pendingLastAction.mSelectionStart == aStart &&
+ pendingLastAction.mData.Length() == static_cast<ULONG>(aLength);
+ }
+
+ bool IsPendingCompositionUpdateIncomplete() const {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ return lastAction.mType == PendingAction::Type::eCompositionUpdate &&
+ lastAction.mIncomplete;
+ }
+
+ void CompleteLastActionIfStillIncomplete() {
+ if (!IsPendingCompositionUpdateIncomplete()) {
+ return;
+ }
+ RecordCompositionUpdateAction();
+ }
+
+ void RemoveLastCompositionUpdateActions() {
+ while (!mPendingActions.IsEmpty()) {
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ if (lastAction.mType != PendingAction::Type::eCompositionUpdate) {
+ break;
+ }
+ mPendingActions.RemoveLastElement();
+ }
+ }
+
+ // When On*Composition() is called without document lock, we need to flush
+ // the recorded actions at quitting the method.
+ // AutoPendingActionAndContentFlusher class is usedful for it.
+ class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final {
+ public:
+ explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore)
+ : mTextStore(aTextStore) {
+ MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock);
+ if (!mTextStore->IsReadWriteLocked()) {
+ mTextStore->mIsRecordingActionsWithoutLock = true;
+ }
+ }
+
+ ~AutoPendingActionAndContentFlusher() {
+ if (!mTextStore->mIsRecordingActionsWithoutLock) {
+ return;
+ }
+ mTextStore->FlushPendingActions();
+ mTextStore->mIsRecordingActionsWithoutLock = false;
+ }
+
+ private:
+ AutoPendingActionAndContentFlusher() {}
+
+ RefPtr<TSFTextStore> mTextStore;
+ };
+
+ class Content final {
+ public:
+ Content(TSFTextStore& aTSFTextStore, const nsAString& aText)
+ : mText(aText),
+ mLastComposition(aTSFTextStore.mComposition),
+ mComposition(aTSFTextStore.mComposition),
+ mSelection(aTSFTextStore.mSelectionForTSF) {}
+
+ void OnLayoutChanged() { mMinModifiedOffset.reset(); }
+
+ // OnCompositionEventsHandled() is called when all pending composition
+ // events are handled in the focused content which may be in a remote
+ // process.
+ void OnCompositionEventsHandled() { mLastComposition = mComposition; }
+
+ const nsDependentSubstring GetSelectedText() const;
+ const nsDependentSubstring GetSubstring(uint32_t aStart,
+ uint32_t aLength) const;
+ void ReplaceSelectedTextWith(const nsAString& aString);
+ void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
+
+ void StartComposition(ITfCompositionView* aCompositionView,
+ const PendingAction& aCompStart,
+ bool aPreserveSelection);
+ /**
+ * RestoreCommittedComposition() restores the committed string as
+ * composing string. If InsertTextAtSelection() or something is called
+ * before a call of OnStartComposition() or previous composition is
+ * committed and new composition is restarted to clean up the commited
+ * string, there is a pending compositionend. In this case, we need to
+ * cancel the pending compositionend and continue the composition.
+ *
+ * @param aCompositionView The composition view.
+ * @param aCanceledCompositionEnd The pending compositionend which is
+ * canceled for restarting the composition.
+ */
+ void RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aCanceledCompositionEnd);
+ void EndComposition(const PendingAction& aCompEnd);
+
+ const nsString& TextRef() const { return mText; }
+ const Maybe<OffsetAndData<LONG>>& LastComposition() const {
+ return mLastComposition;
+ }
+ const Maybe<uint32_t>& MinModifiedOffset() const {
+ return mMinModifiedOffset;
+ }
+ const Maybe<StartAndEndOffsets<LONG>>& LatestCompositionRange() const {
+ return mLatestCompositionRange;
+ }
+
+ // Returns true if layout of the character at the aOffset has not been
+ // calculated.
+ bool IsLayoutChangedAt(uint32_t aOffset) const {
+ return IsLayoutChanged() && (mMinModifiedOffset.value() <= aOffset);
+ }
+ // Returns true if layout of the content has been changed, i.e., the new
+ // layout has not been calculated.
+ bool IsLayoutChanged() const { return mMinModifiedOffset.isSome(); }
+ bool HasOrHadComposition() const {
+ return mLatestCompositionRange.isSome();
+ }
+
+ Maybe<TSFTextStore::Composition>& Composition() { return mComposition; }
+ Maybe<TSFTextStore::Selection>& Selection() { return mSelection; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Content& aContent) {
+ aStream << "{ mText="
+ << PrintStringDetail(aContent.mText,
+ PrintStringDetail::kMaxLengthForEditor)
+ .get()
+ << ", mLastComposition=" << aContent.mLastComposition
+ << ", mLatestCompositionRange="
+ << aContent.mLatestCompositionRange
+ << ", mMinModifiedOffset=" << aContent.mMinModifiedOffset << " }";
+ return aStream;
+ }
+
+ private:
+ nsString mText;
+
+ // mLastComposition may store the composition string and its start offset
+ // when the document is locked. This is necessary to compute
+ // mMinTextModifiedOffset.
+ Maybe<OffsetAndData<LONG>> mLastComposition;
+
+ Maybe<TSFTextStore::Composition>& mComposition;
+ Maybe<TSFTextStore::Selection>& mSelection;
+
+ // The latest composition's start and end offset.
+ Maybe<StartAndEndOffsets<LONG>> mLatestCompositionRange;
+
+ // The minimum offset of modified part of the text.
+ Maybe<uint32_t> mMinModifiedOffset;
+ };
+ // mContentForTSF is cache of content. The information is expected by TSF
+ // and TIP. Therefore, this is useful for answering the query from TSF or
+ // TIP.
+ // This is initialized by ContentForTSF() automatically (therefore, don't
+ // access this member directly except at calling Clear(), IsInitialized(),
+ // IsLayoutChangeAfter() or IsLayoutChanged()).
+ // This is cleared when:
+ // - When there is no composition, the document is unlocked.
+ // - When there is a composition, all dispatched events are handled by
+ // the focused editor which may be in a remote process.
+ // So, if two compositions are created very quickly, this cache may not be
+ // cleared between eCompositionCommit(AsIs) and eCompositionStart.
+ Maybe<Content> mContentForTSF;
+
+ Maybe<Content>& ContentForTSF();
+
+ class MOZ_STACK_CLASS AutoNotifyingTSFBatch final {
+ public:
+ explicit AutoNotifyingTSFBatch(TSFTextStore& aTextStore)
+ : mTextStore(aTextStore), mOldValue(aTextStore.mDeferNotifyingTSF) {
+ mTextStore.mDeferNotifyingTSF = true;
+ }
+ ~AutoNotifyingTSFBatch() {
+ mTextStore.mDeferNotifyingTSF = mOldValue;
+ mTextStore.MaybeFlushPendingNotifications();
+ }
+
+ private:
+ TSFTextStore& mTextStore;
+ bool mOldValue;
+ };
+
+ // CanAccessActualContentDirectly() returns true when TSF/TIP can access
+ // actual content directly. In other words, mContentForTSF and/or
+ // mSelectionForTSF doesn't cache content or they matches with actual
+ // contents due to no pending text/selection change notifications.
+ bool CanAccessActualContentDirectly() const;
+
+ // While mContentForTSF is valid, this returns the text stored by it.
+ // Otherwise, return the current text content retrieved by eQueryTextContent.
+ bool GetCurrentText(nsAString& aTextContent);
+
+ class MouseTracker final {
+ public:
+ static const DWORD kInvalidCookie = static_cast<DWORD>(-1);
+
+ MouseTracker();
+
+ HRESULT Init(TSFTextStore* aTextStore);
+ HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange,
+ ITfMouseSink* aMouseSink);
+ void UnadviseSink();
+
+ bool IsUsing() const { return mSink != nullptr; }
+ DWORD Cookie() const { return mCookie; }
+ bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
+ const Maybe<StartAndEndOffsets<LONG>> Range() const { return mRange; }
+
+ private:
+ RefPtr<ITfMouseSink> mSink;
+ Maybe<StartAndEndOffsets<LONG>> mRange;
+ DWORD mCookie;
+ };
+ // mMouseTrackers is an array to store each information of installed
+ // ITfMouseSink instance.
+ nsTArray<MouseTracker> mMouseTrackers;
+
+ // The input scopes for this context, defaults to IS_DEFAULT.
+ nsTArray<InputScope> mInputScopes;
+
+ // The URL cache of the focused document.
+ nsString mDocumentURL;
+
+ // Support retrieving attributes.
+ // TODO: We should support RightToLeft, perhaps.
+ enum {
+ // Used for result of GetRequestedAttrIndex()
+ eNotSupported = -1,
+
+ // Supported attributes
+ eInputScope = 0,
+ eDocumentURL,
+ eTextVerticalWriting,
+ eTextOrientation,
+
+ // Count of the supported attributes
+ NUM_OF_SUPPORTED_ATTRS
+ };
+ bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS] = {false};
+
+ int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
+ TS_ATTRID GetAttrID(int32_t aIndex);
+
+ bool mRequestedAttrValues = false;
+
+ // If edit actions are being recorded without document lock, this is true.
+ // Otherwise, false.
+ bool mIsRecordingActionsWithoutLock = false;
+ // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been
+ // calculated yet, these methods return TS_E_NOLAYOUT. At that time,
+ // mHasReturnedNoLayoutError is set to true.
+ bool mHasReturnedNoLayoutError = false;
+ // Before calling ITextStoreACPSink::OnLayoutChange() and
+ // ITfContextOwnerServices::OnLayoutChange(), mWaitingQueryLayout is set to
+ // true. This is set to false when GetTextExt() or GetACPFromPoint() is
+ // called.
+ bool mWaitingQueryLayout = false;
+ // During the document is locked, we shouldn't destroy the instance.
+ // If this is true, the instance will be destroyed after unlocked.
+ bool mPendingDestroy = false;
+ // If this is false, MaybeFlushPendingNotifications() will clear the
+ // mContentForTSF.
+ bool mDeferClearingContentForTSF = false;
+ // While the instance is initializing content/selection cache, another
+ // initialization shouldn't run recursively. Therefore, while the
+ // initialization is running, this is set to true. Use AutoNotifyingTSFBatch
+ // to set this.
+ bool mDeferNotifyingTSF = false;
+ // While the instance is dispatching events, the event may not be handled
+ // synchronously when remote content has focus. In the case, we cannot
+ // return the latest layout/content information to TSF/TIP until we get next
+ // update notification from ContentCacheInParent. For preventing TSF/TIP
+ // retrieves the latest content/layout information while it becomes available,
+ // we should put off notifying TSF of any updates.
+ bool mDeferNotifyingTSFUntilNextUpdate = false;
+ // While the document is locked, committing composition always fails since
+ // TSF needs another document lock for modifying the composition, selection
+ // and etc. So, committing composition should be performed after the
+ // document is unlocked.
+ bool mDeferCommittingComposition = false;
+ bool mDeferCancellingComposition = false;
+ // Immediately after a call of Destroy(), mDestroyed becomes true. If this
+ // is true, the instance shouldn't grant any requests from the TIP anymore.
+ bool mDestroyed = false;
+ // While the instance is being destroyed, this is set to true for avoiding
+ // recursive Destroy() calls.
+ bool mBeingDestroyed = false;
+ // Whether we're in the private browsing mode.
+ bool mInPrivateBrowsing = true;
+ // Debug flag to check whether we're initializing mContentForTSF and
+ // mSelectionForTSF.
+ bool mIsInitializingContentForTSF = false;
+ bool mIsInitializingSelectionForTSF = false;
+
+ // TSF thread manager object for the current application
+ static StaticRefPtr<ITfThreadMgr> sThreadMgr;
+ static already_AddRefed<ITfThreadMgr> GetThreadMgr();
+ // sMessagePump is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfMessagePump> sMessagePump;
+
+ public:
+ // Expose GetMessagePump() for WinUtils.
+ static already_AddRefed<ITfMessagePump> GetMessagePump();
+
+ private:
+ // sKeystrokeMgr is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
+ // TSF display attribute manager
+ static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
+ static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr();
+ // TSF category manager
+ static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
+ static already_AddRefed<ITfCategoryMgr> GetCategoryMgr();
+ // Compartment for (Get|Set)IMEOpenState()
+ static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose;
+ static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose();
+
+ // Current text store which is managing a keyboard enabled editor (i.e.,
+ // editable editor). Currently only ONE TSFTextStore instance is ever used,
+ // although Create is called when an editor is focused and Destroy called
+ // when the focused editor is blurred.
+ static StaticRefPtr<TSFTextStore> sEnabledTextStore;
+
+ // For IME (keyboard) disabled state:
+ static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr;
+ static StaticRefPtr<ITfContext> sDisabledContext;
+
+ static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles;
+ static already_AddRefed<ITfInputProcessorProfiles>
+ GetInputProcessorProfiles();
+
+ // Handling key message.
+ static const MSG* sHandlingKeyMsg;
+
+ // TSF client ID for the current application
+ static DWORD sClientId;
+
+ // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already
+ // been dispatched.
+ static bool sIsKeyboardEventDispatched;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef TSFTextStore_h_
diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp
new file mode 100644
index 0000000000..2b958f84c1
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,413 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "TaskbarPreview.h"
+#include <nsITaskbarPreviewController.h>
+#include <windows.h>
+
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsServiceManagerUtils.h>
+
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "nsAppShell.h"
+#include "TaskbarPreviewButton.h"
+#include "WinUtils.h"
+
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#ifndef DWM_SIT_DISPLAYFRAME
+# define DWM_SIT_DISPLAYFRAME 0x1
+#endif
+
+namespace mozilla {
+namespace widget {
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreview
+
+TaskbarPreview::TaskbarPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : mTaskbar(aTaskbar),
+ mController(aController),
+ mWnd(aHWND),
+ mVisible(false),
+ mDocShell(do_GetWeakReference(aShell)) {}
+
+TaskbarPreview::~TaskbarPreview() {
+ // Avoid dangling pointer
+ if (sActivePreview == this) sActivePreview = nullptr;
+
+ // Our subclass should have invoked DetachFromNSWindow already.
+ NS_ASSERTION(
+ !mWnd,
+ "TaskbarPreview::DetachFromNSWindow was not called before destruction");
+
+ // Make sure to release before potentially uninitializing COM
+ mTaskbar = nullptr;
+
+ ::CoUninitialize();
+}
+
+nsresult TaskbarPreview::Init() {
+ // TaskbarPreview may outlive the WinTaskbar that created it
+ if (FAILED(::CoInitialize(nullptr))) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return hook->AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetController(nsITaskbarPreviewController* aController) {
+ NS_ENSURE_ARG(aController);
+
+ mController = aController;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetController(nsITaskbarPreviewController** aController) {
+ NS_ADDREF(*aController = mController);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetTooltip(nsAString& aTooltip) {
+ aTooltip = mTooltip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetTooltip(const nsAString& aTooltip) {
+ mTooltip = aTooltip;
+ return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetVisible(bool visible) {
+ if (mVisible == visible) return NS_OK;
+ mVisible = visible;
+
+ // If the nsWindow has already been destroyed but the caller is still trying
+ // to use it then just pretend that everything succeeded. The caller doesn't
+ // actually have a way to detect this since it's the same case as when we
+ // CanMakeTaskbarCalls returns false.
+ if (!IsWindowAvailable()) return NS_OK;
+
+ return visible ? Enable() : Disable();
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetVisible(bool* visible) {
+ *visible = mVisible;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::SetActive(bool active) {
+ if (active)
+ sActivePreview = this;
+ else if (sActivePreview == this)
+ sActivePreview = nullptr;
+
+ return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::GetActive(bool* active) {
+ *active = sActivePreview == this;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreview::Invalidate() {
+ if (!mVisible) return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+nsresult TaskbarPreview::UpdateTaskbarProperties() {
+ nsresult rv = UpdateTooltip();
+
+ // If we are the active preview and our window is the active window, restore
+ // our active state - otherwise some other non-preview window is now active
+ // and should be displayed as so.
+ if (sActivePreview == this) {
+ if (mWnd == ::GetActiveWindow()) {
+ nsresult rvActive = ShowActive(true);
+ if (NS_FAILED(rvActive)) rv = rvActive;
+ } else {
+ sActivePreview = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Enable() {
+ nsresult rv = NS_OK;
+ if (CanMakeTaskbarCalls()) {
+ rv = UpdateTaskbarProperties();
+ } else if (IsWindowAvailable()) {
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook,
+ "IsWindowAvailable() should have eliminated the null case.");
+ hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case.");
+ (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ MainWindowHook, this);
+
+ return NS_OK;
+}
+
+bool TaskbarPreview::IsWindowAvailable() const {
+ if (mWnd) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win && !win->Destroyed()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void TaskbarPreview::DetachFromNSWindow() {
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this);
+ }
+ mWnd = nullptr;
+}
+
+LRESULT
+TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ switch (nMsg) {
+ case WM_DWMSENDICONICTHUMBNAIL: {
+ uint32_t width = HIWORD(lParam);
+ uint32_t height = LOWORD(lParam);
+ float aspectRatio = width / float(height);
+
+ nsresult rv;
+ float preferredAspectRatio;
+ rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio);
+ if (NS_FAILED(rv)) break;
+
+ uint32_t thumbnailWidth = width;
+ uint32_t thumbnailHeight = height;
+
+ if (aspectRatio > preferredAspectRatio) {
+ thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio);
+ } else {
+ thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio);
+ }
+
+ DrawBitmap(thumbnailWidth, thumbnailHeight, false);
+ } break;
+ case WM_DWMSENDICONICLIVEPREVIEWBITMAP: {
+ uint32_t width, height;
+ nsresult rv;
+ rv = mController->GetWidth(&width);
+ if (NS_FAILED(rv)) break;
+ rv = mController->GetHeight(&height);
+ if (NS_FAILED(rv)) break;
+
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale <= 0.0) {
+ scale = WinUtils::LogToPhysFactor(PreviewWindow());
+ }
+ DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height),
+ true);
+ } break;
+ }
+ return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam);
+}
+
+bool TaskbarPreview::CanMakeTaskbarCalls() {
+ // If the nsWindow has already been destroyed and we know it but our caller
+ // clearly doesn't so we can't make any calls.
+ if (!mWnd) return false;
+ // Certain functions like SetTabOrder seem to require a visible window. During
+ // window close, the window seems to be hidden before being destroyed.
+ if (!::IsWindowVisible(mWnd)) return false;
+ if (mVisible) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Could not get nsWindow from HWND");
+ return window ? window->HasTaskbarIconBeenCreated() : false;
+ }
+ return false;
+}
+
+WindowHook* TaskbarPreview::GetWindowHook() {
+ nsWindow* window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window ? &window->GetWindowHook() : nullptr;
+}
+
+void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled,
+ sizeof(enabled));
+
+ DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled,
+ sizeof(enabled));
+}
+
+nsresult TaskbarPreview::UpdateTooltip() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateTooltip called on invisible tab preview");
+
+ if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get())))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height,
+ bool isPreview) {
+ nsresult rv;
+ nsCOMPtr<nsITaskbarPreviewCallback> callback =
+ do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ ((TaskbarPreviewCallback*)callback.get())->SetPreview(this);
+
+ if (isPreview) {
+ ((TaskbarPreviewCallback*)callback.get())->SetIsPreview();
+ mController->RequestPreview(callback);
+ } else {
+ mController->RequestThumbnail(callback, width, height);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TaskbarPreviewCallback
+
+NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback)
+
+/* void done (in nsISupports aCanvas, in boolean aDrawBorder); */
+NS_IMETHODIMP
+TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) {
+ // We create and destroy TaskbarTabPreviews from front end code in response
+ // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a
+ // proxy HWND which it hands to Windows as a tab identifier. When a tab
+ // closes, TaskbarTabPreview Disable() method is called by front end, which
+ // destroys the proxy window and clears mProxyWindow which is the HWND
+ // returned from PreviewWindow(). So, since this is async, we should check to
+ // be sure the tab is still alive before doing all this gfx work and making
+ // dwm calls. To accomplish this we check the result of PreviewWindow().
+ if (!aCanvas || !mPreview || !mPreview->PreviewWindow() ||
+ !mPreview->IsWindowAvailable()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas));
+ auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content);
+ if (!canvas) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot();
+ if (!source) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface(
+ source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32);
+ if (target->CairoStatus() != CAIRO_STATUS_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ using DataSrcSurf = gfx::DataSourceSurface;
+ RefPtr<DataSrcSurf> srcSurface = source->GetDataSurface();
+ RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
+ if (!srcSurface || !imageSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (DataSrcSurf::ScopedMap const sourceMap(srcSurface, DataSrcSurf::READ);
+ sourceMap.IsMapped()) {
+ mozilla::gfx::CopySurfaceDataToPackedArray(
+ sourceMap.GetData(), imageSurface->Data(), srcSurface->GetSize(),
+ sourceMap.GetStride(), BytesPerPixel(srcSurface->GetFormat()));
+ } else if (source->GetSize().IsEmpty()) {
+ // A zero-size source-surface probably shouldn't happen, but is harmless
+ // here. Fall through.
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ HDC hDC = target->GetDC();
+ HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
+
+ DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
+ HRESULT hr;
+ if (!mIsThumbnail) {
+ POINT pptClient = {0, 0};
+ hr = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap,
+ &pptClient, flags);
+ } else {
+ hr = DwmSetIconicThumbnail(mPreview->PreviewWindow(), hBitmap, flags);
+ }
+ MOZ_ASSERT(SUCCEEDED(hr));
+ mozilla::Unused << hr;
+ return NS_OK;
+}
+
+/* static */
+bool TaskbarPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage() ||
+ nMsg == WM_DESTROY,
+ "Window hook proc called with wrong message");
+ NS_ASSERTION(aContext, "Null context in MainWindowHook");
+ if (!aContext) return false;
+ TaskbarPreview* preview = reinterpret_cast<TaskbarPreview*>(aContext);
+ if (nMsg == WM_DESTROY) {
+ // nsWindow is being destroyed
+ // We can't really do anything at this point including removing hooks
+ return false;
+ } else {
+ nsWindow* window = WinUtils::GetNSWindowPtr(preview->mWnd);
+ if (window) {
+ window->SetHasTaskbarIconBeenCreated();
+
+ if (preview->mVisible) preview->UpdateTaskbarProperties();
+ }
+ }
+ return false;
+}
+
+TaskbarPreview* TaskbarPreview::sActivePreview = nullptr;
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarPreview.h b/widget/windows/TaskbarPreview.h
new file mode 100644
index 0000000000..ca21e3eeb8
--- /dev/null
+++ b/widget/windows/TaskbarPreview.h
@@ -0,0 +1,132 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_TaskbarPreview_h__
+#define __mozilla_widget_TaskbarPreview_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include <nsITaskbarPreview.h>
+#include <nsITaskbarPreviewController.h>
+#include <nsString.h>
+#include <nsIWeakReferenceUtils.h>
+#include <nsIDocShell.h>
+#include "WindowHook.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarPreviewCallback;
+
+class TaskbarPreview : public nsITaskbarPreview {
+ public:
+ TaskbarPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init();
+
+ friend class TaskbarPreviewCallback;
+
+ NS_DECL_NSITASKBARPREVIEW
+
+ protected:
+ virtual ~TaskbarPreview();
+
+ // Called to update ITaskbarList4 dependent properties
+ virtual nsresult UpdateTaskbarProperties();
+
+ // Invoked when the preview is made visible
+ virtual nsresult Enable();
+ // Invoked when the preview is made invisible
+ virtual nsresult Disable();
+
+ // Detaches this preview from the nsWindow instance it's tied to
+ virtual void DetachFromNSWindow();
+
+ // Determines if the window is available and a destroy has not yet started
+ bool IsWindowAvailable() const;
+
+ // Marks this preview as being active
+ virtual nsresult ShowActive(bool active) = 0;
+ // Gets a reference to the window used to handle the preview messages
+ virtual HWND& PreviewWindow() = 0;
+
+ // Window procedure for the PreviewWindow (hooked for window previews)
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam);
+
+ // Returns whether or not the taskbar icon has been created for mWnd The
+ // ITaskbarList4 API requires that we wait until the icon has been created
+ // before we can call its methods.
+ bool CanMakeTaskbarCalls();
+
+ // Gets the WindowHook for the nsWindow
+ WindowHook* GetWindowHook();
+
+ // Enables/disables custom drawing for the given window
+ static void EnableCustomDrawing(HWND aHWND, bool aEnable);
+
+ // MSCOM Taskbar interface
+ RefPtr<ITaskbarList4> mTaskbar;
+ // Controller for this preview
+ nsCOMPtr<nsITaskbarPreviewController> mController;
+ // The HWND to the nsWindow that this object previews
+ HWND mWnd;
+ // Whether or not this preview is visible
+ bool mVisible;
+
+ private:
+ // Called when the tooltip should be updated
+ nsresult UpdateTooltip();
+
+ // Requests the controller to draw into a canvas of the given width and
+ // height. The resulting bitmap is sent to the DWM to display.
+ void DrawBitmap(uint32_t width, uint32_t height, bool isPreview);
+
+ // WindowHook procedure for hooking mWnd
+ static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ // Docshell corresponding to the <window> the nsWindow contains
+ nsWeakPtr mDocShell;
+ nsString mTooltip;
+
+ // The preview currently marked as active in the taskbar. nullptr if no
+ // preview is active (some other window is).
+ static TaskbarPreview* sActivePreview;
+};
+
+/*
+ * Callback object TaskbarPreview hands to preview controllers when we
+ * request async thumbnail or live preview images. Controllers invoke
+ * this interface once they have aquired the requested image.
+ */
+class TaskbarPreviewCallback : public nsITaskbarPreviewCallback {
+ public:
+ TaskbarPreviewCallback() : mIsThumbnail(true) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWCALLBACK
+
+ void SetPreview(TaskbarPreview* aPreview) { mPreview = aPreview; }
+
+ void SetIsPreview() { mIsThumbnail = false; }
+
+ protected:
+ virtual ~TaskbarPreviewCallback() {}
+
+ private:
+ RefPtr<TaskbarPreview> mPreview;
+ bool mIsThumbnail;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarPreview_h__ */
diff --git a/widget/windows/TaskbarPreviewButton.cpp b/widget/windows/TaskbarPreviewButton.cpp
new file mode 100644
index 0000000000..abbb1069a9
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.cpp
@@ -0,0 +1,137 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include <windows.h>
+#include <strsafe.h>
+
+#include "TaskbarWindowPreview.h"
+#include "TaskbarPreviewButton.h"
+#include "nsWindowGfx.h"
+#include <imgIContainer.h>
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(TaskbarPreviewButton, nsITaskbarPreviewButton,
+ nsISupportsWeakReference)
+
+TaskbarPreviewButton::TaskbarPreviewButton(TaskbarWindowPreview* preview,
+ uint32_t index)
+ : mPreview(preview), mIndex(index) {}
+
+TaskbarPreviewButton::~TaskbarPreviewButton() { SetVisible(false); }
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetTooltip(nsAString& aTooltip) {
+ aTooltip = mTooltip;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetTooltip(const nsAString& aTooltip) {
+ mTooltip = aTooltip;
+ size_t destLength = sizeof Button().szTip / (sizeof Button().szTip[0]);
+ wchar_t* tooltip = &(Button().szTip[0]);
+ StringCchCopyNW(tooltip, destLength, mTooltip.get(), mTooltip.Length());
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetDismissOnClick(bool* dismiss) {
+ *dismiss = (Button().dwFlags & THBF_DISMISSONCLICK) == THBF_DISMISSONCLICK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetDismissOnClick(bool dismiss) {
+ if (dismiss)
+ Button().dwFlags |= THBF_DISMISSONCLICK;
+ else
+ Button().dwFlags &= ~THBF_DISMISSONCLICK;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetHasBorder(bool* hasBorder) {
+ *hasBorder = (Button().dwFlags & THBF_NOBACKGROUND) != THBF_NOBACKGROUND;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetHasBorder(bool hasBorder) {
+ if (hasBorder)
+ Button().dwFlags &= ~THBF_NOBACKGROUND;
+ else
+ Button().dwFlags |= THBF_NOBACKGROUND;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetDisabled(bool* disabled) {
+ *disabled = (Button().dwFlags & THBF_DISABLED) == THBF_DISABLED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetDisabled(bool disabled) {
+ if (disabled)
+ Button().dwFlags |= THBF_DISABLED;
+ else
+ Button().dwFlags &= ~THBF_DISABLED;
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetImage(imgIContainer** img) {
+ if (mImage)
+ NS_ADDREF(*img = mImage);
+ else
+ *img = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetImage(imgIContainer* img) {
+ if (Button().hIcon) ::DestroyIcon(Button().hIcon);
+ if (img) {
+ nsresult rv;
+ rv = nsWindowGfx::CreateIcon(
+ img, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon),
+ &Button().hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ Button().hIcon = nullptr;
+ }
+ return Update();
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::GetVisible(bool* visible) {
+ *visible = (Button().dwFlags & THBF_HIDDEN) != THBF_HIDDEN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarPreviewButton::SetVisible(bool visible) {
+ if (visible)
+ Button().dwFlags &= ~THBF_HIDDEN;
+ else
+ Button().dwFlags |= THBF_HIDDEN;
+ return Update();
+}
+
+THUMBBUTTON& TaskbarPreviewButton::Button() {
+ return mPreview->mThumbButtons[mIndex];
+}
+
+nsresult TaskbarPreviewButton::Update() {
+ return mPreview->UpdateButton(mIndex);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarPreviewButton.h b/widget/windows/TaskbarPreviewButton.h
new file mode 100644
index 0000000000..e13b2d6771
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.h
@@ -0,0 +1,47 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_TaskbarPreviewButton_h__
+#define __mozilla_widget_TaskbarPreviewButton_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include <nsITaskbarPreviewButton.h>
+#include <nsString.h>
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarWindowPreview;
+class TaskbarPreviewButton : public nsITaskbarPreviewButton,
+ public nsSupportsWeakReference {
+ virtual ~TaskbarPreviewButton();
+
+ public:
+ TaskbarPreviewButton(TaskbarWindowPreview* preview, uint32_t index);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWBUTTON
+
+ private:
+ THUMBBUTTON& Button();
+ nsresult Update();
+
+ RefPtr<TaskbarWindowPreview> mPreview;
+ uint32_t mIndex;
+ nsString mTooltip;
+ nsCOMPtr<imgIContainer> mImage;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarPreviewButton_h__ */
diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp
new file mode 100644
index 0000000000..3421e68ffb
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.cpp
@@ -0,0 +1,344 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "TaskbarTabPreview.h"
+#include "nsWindowGfx.h"
+#include "nsUXThemeData.h"
+#include "WinUtils.h"
+#include <nsITaskbarPreviewController.h>
+
+#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd"
+
+namespace mozilla {
+namespace widget {
+
+NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview)
+
+const wchar_t* const kWindowClass = L"MozillaTaskbarPreviewClass";
+
+TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
+ mProxyWindow(nullptr),
+ mIcon(nullptr),
+ mRegistered(false) {}
+
+TaskbarTabPreview::~TaskbarTabPreview() {
+ if (mIcon) {
+ ::DestroyIcon(mIcon);
+ mIcon = nullptr;
+ }
+
+ // We need to ensure that proxy window disappears or else Bad Things happen.
+ if (mProxyWindow) Disable();
+
+ NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!");
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+nsresult TaskbarTabPreview::Init() {
+ nsresult rv = TaskbarPreview::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return hook->AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+}
+
+nsresult TaskbarTabPreview::ShowActive(bool active) {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "ShowActive called on invisible window or before taskbar calls "
+ "can be made for this window");
+ return FAILED(
+ mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0))
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+HWND& TaskbarTabPreview::PreviewWindow() { return mProxyWindow; }
+
+nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; }
+
+void TaskbarTabPreview::EnsureRegistration() {
+ NS_ASSERTION(mVisible && CanMakeTaskbarCalls(),
+ "EnsureRegistration called when it is not safe to do so");
+
+ (void)UpdateTaskbarProperties();
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetTitle(nsAString& aTitle) {
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return mVisible ? UpdateTitle() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::SetIcon(imgIContainer* icon) {
+ HICON hIcon = nullptr;
+ if (icon) {
+ nsresult rv;
+ rv = nsWindowGfx::CreateIcon(
+ icon, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mIcon) ::DestroyIcon(mIcon);
+ mIcon = hIcon;
+ mIconImage = icon;
+ return mVisible ? UpdateIcon() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::GetIcon(imgIContainer** icon) {
+ NS_IF_ADDREF(*icon = mIconImage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarTabPreview::Move(nsITaskbarTabPreview* aNext) {
+ if (aNext == this) return NS_ERROR_INVALID_ARG;
+ mNext = aNext;
+ return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateTaskbarProperties() {
+ if (mRegistered) return NS_OK;
+
+ if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd)))
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = UpdateNext();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = TaskbarPreview::UpdateTaskbarProperties();
+ mRegistered = true;
+ return rv;
+}
+
+LRESULT
+TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ RefPtr<TaskbarTabPreview> kungFuDeathGrip(this);
+ switch (nMsg) {
+ case WM_CREATE:
+ TaskbarPreview::EnableCustomDrawing(mProxyWindow, true);
+ return 0;
+ case WM_CLOSE:
+ mController->OnClose();
+ return 0;
+ case WM_ACTIVATE:
+ if (LOWORD(wParam) == WA_ACTIVE) {
+ // Activate the tab the user selected then restore the main window,
+ // keeping normal/max window state intact.
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(mWnd);
+ if (win) {
+ nsWindow* parent = win->GetTopLevelWindow(true);
+ if (parent) {
+ parent->Show(true);
+ }
+ }
+ }
+ }
+ return 0;
+ case WM_GETICON:
+ return (LRESULT)mIcon;
+ case WM_SYSCOMMAND:
+ // Send activation events to the top level window and select the proper
+ // tab through the controller.
+ if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) {
+ bool activateWindow;
+ nsresult rv = mController->OnActivate(&activateWindow);
+ if (NS_SUCCEEDED(rv) && activateWindow) {
+ // Note, restoring an iconic, maximized window here will only
+ // activate the maximized window. This is not a bug, it's default
+ // windows behavior.
+ ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ }
+ return 0;
+ }
+ // Forward everything else to the top level window. Do not forward
+ // close since that's intended for the tab. When the preview proxy
+ // closes, we'll close the tab above.
+ return wParam == SC_CLOSE
+ ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam)
+ : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam);
+ }
+ return TaskbarPreview::WndProc(nMsg, wParam, lParam);
+}
+
+/* static */
+LRESULT CALLBACK TaskbarTabPreview::GlobalWndProc(HWND hWnd, UINT nMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ TaskbarTabPreview* preview(nullptr);
+ if (nMsg == WM_CREATE) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+ preview = reinterpret_cast<TaskbarTabPreview*>(cs->lpCreateParams);
+ if (!::SetPropW(hWnd, TASKBARPREVIEW_HWNDID, preview))
+ NS_ERROR("Could not associate native window with tab preview");
+ preview->mProxyWindow = hWnd;
+ } else {
+ preview = reinterpret_cast<TaskbarTabPreview*>(
+ ::GetPropW(hWnd, TASKBARPREVIEW_HWNDID));
+ if (nMsg == WM_DESTROY) ::RemovePropW(hWnd, TASKBARPREVIEW_HWNDID);
+ }
+
+ if (preview) return preview->WndProc(nMsg, wParam, lParam);
+ return ::DefWindowProcW(hWnd, nMsg, wParam, lParam);
+}
+
+nsresult TaskbarTabPreview::Enable() {
+ WNDCLASSW wc;
+ HINSTANCE module = GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(module, kWindowClass, &wc)) {
+ wc.style = 0;
+ wc.lpfnWndProc = GlobalWndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = (HBRUSH) nullptr;
+ wc.lpszMenuName = (LPCWSTR) nullptr;
+ wc.lpszClassName = kWindowClass;
+ RegisterClassW(&wc);
+ }
+ ::CreateWindowW(kWindowClass, L"TaskbarPreviewWindow",
+ WS_CAPTION | WS_SYSMENU, 0, 0, 200, 60, nullptr, nullptr,
+ module, this);
+ // GlobalWndProc will set mProxyWindow so that WM_CREATE can have a valid HWND
+ if (!mProxyWindow) return NS_ERROR_INVALID_ARG;
+
+ UpdateProxyWindowStyle();
+
+ nsresult rv = TaskbarPreview::Enable();
+ nsresult rvUpdate;
+ rvUpdate = UpdateTitle();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ rvUpdate = UpdateIcon();
+ if (NS_FAILED(rvUpdate)) rv = rvUpdate;
+
+ return rv;
+}
+
+nsresult TaskbarTabPreview::Disable() {
+ // TaskbarPreview::Disable assumes that mWnd is valid but this method can be
+ // called when it is null iff the nsWindow has already been destroyed and we
+ // are still visible for some reason during object destruction.
+ if (mWnd) TaskbarPreview::Disable();
+
+ if (FAILED(mTaskbar->UnregisterTab(mProxyWindow))) return NS_ERROR_FAILURE;
+ mRegistered = false;
+
+ // TaskbarPreview::WndProc will set mProxyWindow to null
+ if (!DestroyWindow(mProxyWindow)) return NS_ERROR_FAILURE;
+ mProxyWindow = nullptr;
+ return NS_OK;
+}
+
+void TaskbarTabPreview::DetachFromNSWindow() {
+ (void)SetVisible(false);
+ if (WindowHook* hook = GetWindowHook()) {
+ hook->RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+ }
+ TaskbarPreview::DetachFromNSWindow();
+}
+
+/* static */
+bool TaskbarTabPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult) {
+ if (nMsg == WM_WINDOWPOSCHANGED) {
+ TaskbarTabPreview* preview = reinterpret_cast<TaskbarTabPreview*>(aContext);
+ WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam);
+ if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED))
+ preview->UpdateProxyWindowStyle();
+ } else {
+ MOZ_ASSERT_UNREACHABLE(
+ "Style changed hook fired on non-style changed "
+ "message");
+ }
+ return false;
+}
+
+void TaskbarTabPreview::UpdateProxyWindowStyle() {
+ if (!mProxyWindow) return;
+
+ DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE;
+ DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE);
+
+ DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE);
+ proxyStyle &= ~minMaxMask;
+ proxyStyle |= windowStyle & minMaxMask;
+ SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle);
+
+ DWORD exStyle =
+ (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0;
+ SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle);
+}
+
+nsresult TaskbarTabPreview::UpdateTitle() {
+ NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview");
+
+ if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateIcon() {
+ NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview");
+
+ ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon);
+
+ return NS_OK;
+}
+
+nsresult TaskbarTabPreview::UpdateNext() {
+ NS_ASSERTION(CanMakeTaskbarCalls() && mVisible,
+ "UpdateNext called on invisible tab preview");
+ HWND hNext = nullptr;
+ if (mNext) {
+ bool visible;
+ nsresult rv = mNext->GetVisible(&visible);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Can only move next to enabled previews
+ if (!visible) return NS_ERROR_FAILURE;
+
+ hNext = (HWND)mNext->GetHWND();
+
+ // hNext must be registered with the taskbar if the call is to succeed
+ mNext->EnsureRegistration();
+ }
+ if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext)))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarTabPreview.h b/widget/windows/TaskbarTabPreview.h
new file mode 100644
index 0000000000..9a15760557
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.h
@@ -0,0 +1,70 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_TaskbarTabPreview_h__
+#define __mozilla_widget_TaskbarTabPreview_h__
+
+#include "nsITaskbarTabPreview.h"
+#include "TaskbarPreview.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarTabPreview : public nsITaskbarTabPreview, public TaskbarPreview {
+ virtual ~TaskbarTabPreview();
+
+ public:
+ TaskbarTabPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init() override;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARTABPREVIEW
+ NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::)
+
+ private:
+ virtual nsresult ShowActive(bool active);
+ virtual HWND& PreviewWindow();
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam);
+ static LRESULT CALLBACK GlobalWndProc(HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam);
+
+ virtual nsresult UpdateTaskbarProperties();
+ virtual nsresult Enable();
+ virtual nsresult Disable();
+ virtual void DetachFromNSWindow();
+
+ // WindowHook procedure for hooking mWnd
+ static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ // Bug 520807 - we need to update the proxy window style based on the main
+ // window's style to workaround a bug with the way the DWM displays the
+ // previews.
+ void UpdateProxyWindowStyle();
+
+ nsresult UpdateTitle();
+ nsresult UpdateIcon();
+ nsresult UpdateNext();
+
+ // Handle to the toplevel proxy window
+ HWND mProxyWindow;
+ nsString mTitle;
+ nsCOMPtr<imgIContainer> mIconImage;
+ // Cached Windows icon of mIconImage
+ HICON mIcon;
+ // Preview that follows this preview in the taskbar (left-to-right order)
+ nsCOMPtr<nsITaskbarTabPreview> mNext;
+ // True if this preview has been registered with the taskbar
+ bool mRegistered;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarTabPreview_h__ */
diff --git a/widget/windows/TaskbarWindowPreview.cpp b/widget/windows/TaskbarWindowPreview.cpp
new file mode 100644
index 0000000000..212278be4a
--- /dev/null
+++ b/widget/windows/TaskbarWindowPreview.cpp
@@ -0,0 +1,326 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include <nsITaskbarPreviewController.h>
+#include "TaskbarWindowPreview.h"
+#include "WindowHook.h"
+#include "nsUXThemeData.h"
+#include "TaskbarPreviewButton.h"
+#include "nsWindow.h"
+#include "nsWindowGfx.h"
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+bool WindowHookProc(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ TaskbarWindowPreview* preview =
+ reinterpret_cast<TaskbarWindowPreview*>(aContext);
+ *aResult = preview->WndProc(nMsg, wParam, lParam);
+ return true;
+}
+} // namespace
+
+NS_IMPL_ISUPPORTS(TaskbarWindowPreview, nsITaskbarWindowPreview,
+ nsITaskbarProgress, nsITaskbarOverlayIconController,
+ nsISupportsWeakReference)
+
+/**
+ * These correspond directly to the states defined in nsITaskbarProgress.idl, so
+ * they should be kept in sync.
+ */
+static TBPFLAG sNativeStates[] = {TBPF_NOPROGRESS, TBPF_INDETERMINATE,
+ TBPF_NORMAL, TBPF_ERROR, TBPF_PAUSED};
+
+TaskbarWindowPreview::TaskbarWindowPreview(
+ ITaskbarList4* aTaskbar, nsITaskbarPreviewController* aController,
+ HWND aHWND, nsIDocShell* aShell)
+ : TaskbarPreview(aTaskbar, aController, aHWND, aShell),
+ mCustomDrawing(false),
+ mHaveButtons(false),
+ mState(TBPF_NOPROGRESS),
+ mCurrentValue(0),
+ mMaxValue(0),
+ mOverlayIcon(nullptr) {
+ // Window previews are visible by default
+ (void)SetVisible(true);
+
+ memset(mThumbButtons, 0, sizeof mThumbButtons);
+ for (int32_t i = 0; i < nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS; i++) {
+ mThumbButtons[i].dwMask = THB_FLAGS | THB_ICON | THB_TOOLTIP;
+ mThumbButtons[i].iId = i;
+ mThumbButtons[i].dwFlags = THBF_HIDDEN;
+ }
+}
+
+TaskbarWindowPreview::~TaskbarWindowPreview() {
+ if (mOverlayIcon) {
+ ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = nullptr;
+ }
+
+ // We need to clean up a hook associated with the "this" pointer.
+ SetVisible(false);
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+nsresult TaskbarWindowPreview::Init() {
+ nsresult rv = TaskbarPreview::Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (CanMakeTaskbarCalls()) {
+ return NS_OK;
+ }
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+}
+
+nsresult TaskbarWindowPreview::ShowActive(bool active) {
+ return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr))
+ ? NS_ERROR_FAILURE
+ : NS_OK;
+}
+
+HWND& TaskbarWindowPreview::PreviewWindow() { return mWnd; }
+
+nsresult TaskbarWindowPreview::GetButton(uint32_t index,
+ nsITaskbarPreviewButton** _retVal) {
+ if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsITaskbarPreviewButton> button(
+ do_QueryReferent(mWeakButtons[index]));
+
+ if (!button) {
+ // Lost reference
+ button = new TaskbarPreviewButton(this, index);
+ if (!button) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mWeakButtons[index] = do_GetWeakReference(button);
+ }
+
+ if (!mHaveButtons) {
+ mHaveButtons = true;
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ (void)hook->AddHook(WM_COMMAND, WindowHookProc, this);
+
+ if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS,
+ mThumbButtons))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ button.forget(_retVal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) {
+ if (aEnable == mCustomDrawing) return NS_OK;
+
+ WindowHook* hook = GetWindowHook();
+ if (!hook) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCustomDrawing = aEnable;
+ TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
+
+ if (aEnable) {
+ (void)hook->AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
+ (void)hook->AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
+ this);
+ } else {
+ (void)hook->RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc,
+ this);
+ (void)hook->RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::GetEnableCustomDrawing(bool* aEnable) {
+ *aEnable = mCustomDrawing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue,
+ uint64_t aMaxValue) {
+ NS_ENSURE_ARG_RANGE(aState, nsTaskbarProgressState(0),
+ nsTaskbarProgressState(ArrayLength(sNativeStates) - 1));
+
+ TBPFLAG nativeState = sNativeStates[aState];
+ if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+
+ if (aCurrentValue > aMaxValue) return NS_ERROR_ILLEGAL_VALUE;
+
+ mState = nativeState;
+ mCurrentValue = aCurrentValue;
+ mMaxValue = aMaxValue;
+
+ // Only update if we can
+ return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarWindowPreview::SetOverlayIcon(imgIContainer* aStatusIcon,
+ const nsAString& aStatusDescription) {
+ nsresult rv;
+ if (aStatusIcon) {
+ // The image shouldn't be animated
+ bool isAnimated;
+ rv = aStatusIcon->GetAnimated(&isAnimated);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG);
+ }
+
+ HICON hIcon = nullptr;
+ if (aStatusIcon) {
+ rv = nsWindowGfx::CreateIcon(
+ aStatusIcon, false, LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mOverlayIcon) ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = hIcon;
+ mIconDescription = aStatusDescription;
+
+ // Only update if we can
+ return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK;
+}
+
+nsresult TaskbarWindowPreview::UpdateTaskbarProperties() {
+ if (mHaveButtons) {
+ if (FAILED(mTaskbar->ThumbBarAddButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = UpdateTaskbarProgress();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = UpdateOverlayIcon();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return TaskbarPreview::UpdateTaskbarProperties();
+}
+
+nsresult TaskbarWindowPreview::UpdateTaskbarProgress() {
+ HRESULT hr = mTaskbar->SetProgressState(mWnd, mState);
+ if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS &&
+ mState != TBPF_INDETERMINATE)
+ hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue);
+
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult TaskbarWindowPreview::UpdateOverlayIcon() {
+ HRESULT hr =
+ mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, mIconDescription.get());
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+LRESULT
+TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) {
+ RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this);
+ switch (nMsg) {
+ case WM_COMMAND: {
+ uint32_t id = LOWORD(wParam);
+ uint32_t index = id;
+ nsCOMPtr<nsITaskbarPreviewButton> button;
+ nsresult rv = GetButton(index, getter_AddRefs(button));
+ if (NS_SUCCEEDED(rv)) mController->OnClick(button);
+ }
+ return 0;
+ }
+ return TaskbarPreview::WndProc(nMsg, wParam, lParam);
+}
+
+/* static */
+bool TaskbarWindowPreview::TaskbarWindowHook(void* aContext, HWND hWnd,
+ UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(),
+ "Window hook proc called with wrong message");
+ TaskbarWindowPreview* preview =
+ reinterpret_cast<TaskbarWindowPreview*>(aContext);
+ // Now we can make all the calls to mTaskbar
+ preview->UpdateTaskbarProperties();
+ return false;
+}
+
+nsresult TaskbarWindowPreview::Enable() {
+ nsresult rv = TaskbarPreview::Enable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FAILED(mTaskbar->AddTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult TaskbarWindowPreview::Disable() {
+ nsresult rv = TaskbarPreview::Disable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FAILED(mTaskbar->DeleteTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+void TaskbarWindowPreview::DetachFromNSWindow() {
+ // Remove the hooks we have for drawing
+ SetEnableCustomDrawing(false);
+
+ if (WindowHook* hook = GetWindowHook()) {
+ (void)hook->RemoveHook(WM_COMMAND, WindowHookProc, this);
+ (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+ }
+ TaskbarPreview::DetachFromNSWindow();
+}
+
+nsresult TaskbarWindowPreview::UpdateButtons() {
+ NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview");
+
+ if (FAILED(mTaskbar->ThumbBarUpdateButtons(
+ mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons)))
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+nsresult TaskbarWindowPreview::UpdateButton(uint32_t index) {
+ if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS)
+ return NS_ERROR_INVALID_ARG;
+ if (mVisible) {
+ if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index])))
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TaskbarWindowPreview.h b/widget/windows/TaskbarWindowPreview.h
new file mode 100644
index 0000000000..97c074197c
--- /dev/null
+++ b/widget/windows/TaskbarWindowPreview.h
@@ -0,0 +1,85 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_TaskbarWindowPreview_h__
+#define __mozilla_widget_TaskbarWindowPreview_h__
+
+#include "nsITaskbarWindowPreview.h"
+#include "nsITaskbarProgress.h"
+#include "nsITaskbarOverlayIconController.h"
+#include "TaskbarPreview.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+namespace widget {
+
+class TaskbarPreviewButton;
+class TaskbarWindowPreview : public TaskbarPreview,
+ public nsITaskbarWindowPreview,
+ public nsITaskbarProgress,
+ public nsITaskbarOverlayIconController,
+ public nsSupportsWeakReference {
+ virtual ~TaskbarWindowPreview();
+
+ public:
+ TaskbarWindowPreview(ITaskbarList4* aTaskbar,
+ nsITaskbarPreviewController* aController, HWND aHWND,
+ nsIDocShell* aShell);
+ virtual nsresult Init() override;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARWINDOWPREVIEW
+ NS_DECL_NSITASKBARPROGRESS
+ NS_DECL_NSITASKBAROVERLAYICONCONTROLLER
+ NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::)
+
+ virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) override;
+
+ private:
+ virtual nsresult ShowActive(bool active) override;
+ virtual HWND& PreviewWindow() override;
+
+ virtual nsresult UpdateTaskbarProperties() override;
+ virtual nsresult Enable() override;
+ virtual nsresult Disable() override;
+ virtual void DetachFromNSWindow() override;
+ nsresult UpdateButton(uint32_t index);
+ nsresult UpdateButtons();
+
+ // Is custom drawing enabled?
+ bool mCustomDrawing;
+ // Have we made any buttons?
+ bool mHaveButtons;
+ // Windows button format
+ THUMBBUTTON mThumbButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS];
+ // Pointers to our button class (cached instances)
+ nsWeakPtr mWeakButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS];
+
+ // Called to update ITaskbarList4 dependent properties
+ nsresult UpdateTaskbarProgress();
+ nsresult UpdateOverlayIcon();
+
+ // The taskbar progress
+ TBPFLAG mState;
+ ULONGLONG mCurrentValue;
+ ULONGLONG mMaxValue;
+
+ // Taskbar overlay icon
+ HICON mOverlayIcon;
+ nsString mIconDescription;
+
+ // WindowHook procedure for hooking mWnd for taskbar progress and icon stuff
+ static bool TaskbarWindowHook(void* aContext, HWND hWnd, UINT nMsg,
+ WPARAM wParam, LPARAM lParam, LRESULT* aResult);
+
+ friend class TaskbarPreviewButton;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_TaskbarWindowPreview_h__ */
diff --git a/widget/windows/ToastNotification.cpp b/widget/windows/ToastNotification.cpp
new file mode 100644
index 0000000000..afdffc19ac
--- /dev/null
+++ b/widget/windows/ToastNotification.cpp
@@ -0,0 +1,915 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "ToastNotification.h"
+
+#include <windows.h>
+#include <appmodel.h>
+#include <ktmw32.h>
+#include <windows.foundation.h>
+#include <wrl/client.h>
+
+#include "ErrorList.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/mscom/COMWrappers.h"
+#include "mozilla/mscom/Utils.h"
+#include "mozilla/widget/WinRegistry.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsAppRunner.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsWindowsHelpers.h"
+#include "nsXREDirProvider.h"
+#include "prenv.h"
+#include "ToastNotificationHandler.h"
+#include "ToastNotificationHeaderOnlyUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+using namespace toastnotification;
+
+using namespace ABI::Windows::Foundation;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+// Needed to disambiguate internal and Windows `ToastNotification` classes.
+using namespace ABI::Windows::UI::Notifications;
+using WinToastNotification = ABI::Windows::UI::Notifications::ToastNotification;
+using IVectorView_ToastNotification =
+ ABI::Windows::Foundation::Collections::IVectorView<WinToastNotification*>;
+using IVectorView_ScheduledToastNotification =
+ ABI::Windows::Foundation::Collections::IVectorView<
+ ScheduledToastNotification*>;
+
+LazyLogModule sWASLog("WindowsAlertsService");
+
+NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIWindowsAlertsService,
+ nsIAlertsDoNotDisturb, nsIObserver)
+
+ToastNotification::ToastNotification() = default;
+
+ToastNotification::~ToastNotification() = default;
+
+nsresult ToastNotification::Init() {
+ if (!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ // Windows Toast Notification requires AppId. But allow `xpcshell` to
+ // create the service to test other functionality.
+ if (!EnsureAumidRegistered()) {
+ MOZ_LOG(sWASLog, LogLevel::Warning, ("Failed to register AUMID!"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Info, ("Using dummy AUMID in xpcshell test"));
+ mAumid.emplace(u"XpcshellTestToastAumid"_ns);
+ }
+
+ MOZ_LOG(sWASLog, LogLevel::Info,
+ ("Using AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get()));
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ if (obsServ) {
+ Unused << NS_WARN_IF(
+ NS_FAILED(obsServ->AddObserver(this, "last-pb-context-exited", false)));
+ Unused << NS_WARN_IF(
+ NS_FAILED(obsServ->AddObserver(this, "quit-application", false)));
+ }
+
+ return NS_OK;
+}
+
+bool ToastNotification::EnsureAumidRegistered() {
+ // Check if this is an MSIX install, app identity is provided by the package
+ // so no registration is necessary.
+ if (AssignIfMsixAumid(mAumid)) {
+ MOZ_LOG(
+ sWASLog, LogLevel::Info,
+ ("Found MSIX AUMID: '%s'", NS_ConvertUTF16toUTF8(mAumid.ref()).get()));
+ return true;
+ }
+
+ nsAutoString installHash;
+ nsresult rv = gDirServiceProvider->GetInstallHash(installHash);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Check if toasts were registered during NSIS/MSI installation.
+ if (AssignIfNsisAumid(installHash, mAumid)) {
+ MOZ_LOG(sWASLog, LogLevel::Info,
+ ("Found AUMID from installer (with install hash '%s'): '%s'",
+ NS_ConvertUTF16toUTF8(installHash).get(),
+ NS_ConvertUTF16toUTF8(mAumid.ref()).get()));
+ return true;
+ }
+
+ // No AUMID registered, fall through to runtime registration for development
+ // and portable builds.
+ if (RegisterRuntimeAumid(installHash, mAumid)) {
+ MOZ_LOG(
+ sWASLog, LogLevel::Info,
+ ("Updated AUMID registration at runtime (for install hash '%s'): '%s'",
+ NS_ConvertUTF16toUTF8(installHash).get(),
+ NS_ConvertUTF16toUTF8(mAumid.ref()).get()));
+ return true;
+ }
+
+ MOZ_LOG(sWASLog, LogLevel::Warning,
+ ("Failed to register AUMID at runtime! (for install hash '%s')",
+ NS_ConvertUTF16toUTF8(installHash).get()));
+ return false;
+}
+
+bool ToastNotification::AssignIfMsixAumid(Maybe<nsAutoString>& aAumid) {
+ UINT32 len = 0;
+ // ERROR_INSUFFICIENT_BUFFER signals that we're in an MSIX package, and
+ // therefore should use the package's AUMID.
+ if (GetCurrentApplicationUserModelId(&len, nullptr) !=
+ ERROR_INSUFFICIENT_BUFFER) {
+ MOZ_LOG(sWASLog, LogLevel::Debug, ("Not an MSIX package"));
+ return false;
+ }
+ mozilla::Buffer<wchar_t> buffer(len);
+ LONG success = GetCurrentApplicationUserModelId(&len, buffer.Elements());
+ NS_ENSURE_TRUE(success == ERROR_SUCCESS, false);
+
+ aAumid.emplace(buffer.Elements());
+ return true;
+}
+
+bool ToastNotification::AssignIfNsisAumid(nsAutoString& aInstallHash,
+ Maybe<nsAutoString>& aAumid) {
+ nsAutoString nsisAumidName =
+ u""_ns MOZ_TOAST_APP_NAME u"Toast-"_ns + aInstallHash;
+ nsAutoString nsisAumidPath = u"AppUserModelId\\"_ns + nsisAumidName;
+ if (!WinRegistry::HasKey(HKEY_CLASSES_ROOT, nsisAumidPath)) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("No CustomActivator value from installer in key 'HKCR\\%s'",
+ NS_ConvertUTF16toUTF8(nsisAumidPath).get()));
+ return false;
+ }
+
+ aAumid.emplace(nsisAumidName);
+ return true;
+}
+
+bool ToastNotification::RegisterRuntimeAumid(nsAutoString& aInstallHash,
+ Maybe<nsAutoString>& aAumid) {
+ // Portable AUMID slightly differs from installed AUMID so we can
+ // differentiate installed to HKCU vs portable installs if necessary.
+ nsAutoString portableAumid =
+ u""_ns MOZ_TOAST_APP_NAME u"PortableToast-"_ns + aInstallHash;
+
+ nsCOMPtr<nsIFile> appdir;
+ nsresult rv = gDirServiceProvider->GetGREDir()->Clone(getter_AddRefs(appdir));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIFile> icon;
+ rv = appdir->Clone(getter_AddRefs(icon));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = icon->Append(u"browser"_ns);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = icon->Append(u"VisualElements"_ns);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = icon->Append(u"VisualElements_70.png"_ns);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString iconPath;
+ rv = icon->GetPath(iconPath);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIFile> comDll;
+ rv = appdir->Clone(getter_AddRefs(comDll));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = comDll->Append(u"notificationserver.dll"_ns);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString dllPath;
+ rv = comDll->GetPath(dllPath);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoHandle txn;
+ // Manipulate the registry using a transaction so that any failures are
+ // rolled back.
+ wchar_t transactionName[] = L"" MOZ_TOAST_APP_NAME L" toast registration";
+ txn.own(::CreateTransaction(nullptr, nullptr, TRANSACTION_DO_NOT_PROMOTE, 0,
+ 0, 0, transactionName));
+ NS_ENSURE_TRUE(txn.get() != INVALID_HANDLE_VALUE, false);
+
+ LSTATUS status;
+
+ auto RegisterKey = [&](const nsAString& path, nsAutoRegKey& key) {
+ HKEY rawKey;
+ status = ::RegCreateKeyTransactedW(
+ HKEY_CURRENT_USER, PromiseFlatString(path).get(), 0, nullptr,
+ REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, nullptr, txn,
+ nullptr);
+ NS_ENSURE_TRUE(status == ERROR_SUCCESS, false);
+
+ key.own(rawKey);
+ return true;
+ };
+ auto RegisterValue = [&](nsAutoRegKey& key, const nsAString& name,
+ unsigned long type, const nsAString& data) {
+ status = ::RegSetValueExW(
+ key, PromiseFlatString(name).get(), 0, type,
+ static_cast<const BYTE*>(PromiseFlatString(data).get()),
+ (data.Length() + 1) * sizeof(wchar_t));
+
+ return status == ERROR_SUCCESS;
+ };
+
+ // clang-format off
+ /* Writes the following keys and values to the registry.
+ * HKEY_CURRENT_USER\Software\Classes\AppID\{GUID} DllSurrogate : REG_SZ = ""
+ * \AppUserModelId\{MOZ_TOAST_APP_NAME}PortableToast-{install hash} CustomActivator : REG_SZ = {GUID}
+ * DisplayName : REG_EXPAND_SZ = {display name}
+ * IconUri : REG_EXPAND_SZ = {icon path}
+ * \CLSID\{GUID} AppID : REG_SZ = {GUID}
+ * \InprocServer32 (Default) : REG_SZ = {notificationserver.dll path}
+ */
+ // clang-format on
+
+ constexpr nsLiteralString classes = u"Software\\Classes\\"_ns;
+
+ nsAutoString aumid = classes + u"AppUserModelId\\"_ns + portableAumid;
+ nsAutoRegKey aumidKey;
+ NS_ENSURE_TRUE(RegisterKey(aumid, aumidKey), false);
+
+ nsAutoString guidStr;
+ {
+ DWORD bufferSizeBytes = NSID_LENGTH * sizeof(wchar_t);
+ Buffer<wchar_t> guidBuffer(bufferSizeBytes);
+ status = ::RegGetValueW(HKEY_CURRENT_USER, aumid.get(), L"CustomActivator",
+ RRF_RT_REG_SZ, 0, guidBuffer.Elements(),
+ &bufferSizeBytes);
+
+ CLSID unused;
+ if (status == ERROR_SUCCESS &&
+ SUCCEEDED(CLSIDFromString(guidBuffer.Elements(), &unused))) {
+ guidStr = guidBuffer.Elements();
+ } else {
+ nsIDToCString uuidString(nsID::GenerateUUID());
+ size_t len = strlen(uuidString.get());
+ MOZ_ASSERT(len == NSID_LENGTH - 1);
+ CopyASCIItoUTF16(nsDependentCSubstring(uuidString.get(), len), guidStr);
+ }
+
+ if (status == ERROR_SUCCESS) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Existing CustomActivator guid found: '%s'",
+ NS_ConvertUTF16toUTF8(guidStr).get()));
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("New CustomActivator guid generated: '%s'",
+ NS_ConvertUTF16toUTF8(guidStr).get()));
+ }
+ }
+ NS_ENSURE_TRUE(
+ RegisterValue(aumidKey, u"CustomActivator"_ns, REG_SZ, guidStr), false);
+ nsAutoString brandName;
+ WidgetUtils::GetBrandShortName(brandName);
+ NS_ENSURE_TRUE(
+ RegisterValue(aumidKey, u"DisplayName"_ns, REG_EXPAND_SZ, brandName),
+ false);
+ NS_ENSURE_TRUE(
+ RegisterValue(aumidKey, u"IconUri"_ns, REG_EXPAND_SZ, iconPath), false);
+
+ nsAutoString appid = classes + u"AppID\\"_ns + guidStr;
+ nsAutoRegKey appidKey;
+ NS_ENSURE_TRUE(RegisterKey(appid, appidKey), false);
+ NS_ENSURE_TRUE(RegisterValue(appidKey, u"DllSurrogate"_ns, REG_SZ, u""_ns),
+ false);
+
+ nsAutoString clsid = classes + u"CLSID\\"_ns + guidStr;
+ nsAutoRegKey clsidKey;
+ NS_ENSURE_TRUE(RegisterKey(clsid, clsidKey), false);
+ NS_ENSURE_TRUE(RegisterValue(clsidKey, u"AppID"_ns, REG_SZ, guidStr), false);
+
+ nsAutoString inproc = clsid + u"\\InprocServer32"_ns;
+ nsAutoRegKey inprocKey;
+ NS_ENSURE_TRUE(RegisterKey(inproc, inprocKey), false);
+ // Set the component's path to this DLL
+ NS_ENSURE_TRUE(RegisterValue(inprocKey, u""_ns, REG_SZ, dllPath), false);
+
+ NS_ENSURE_TRUE(::CommitTransaction(txn), false);
+
+ MOZ_LOG(
+ sWASLog, LogLevel::Debug,
+ ("Updated registration for CustomActivator value in key 'HKCU\\%s': '%s'",
+ NS_ConvertUTF16toUTF8(aumid).get(),
+ NS_ConvertUTF16toUTF8(guidStr).get()));
+ aAumid.emplace(portableAumid);
+ return true;
+}
+
+nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) {
+ return NS_DispatchBackgroundTask(runnable);
+}
+
+NS_IMETHODIMP
+ToastNotification::GetSuppressForScreenSharing(bool* aRetVal) {
+ *aRetVal = mSuppressForScreenSharing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::SetSuppressForScreenSharing(bool aSuppress) {
+ mSuppressForScreenSharing = aSuppress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsDependentCString topic(aTopic);
+
+ for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ToastNotificationHandler> handler = iter.UserData();
+
+ auto removeNotification = [&]() {
+ // The handlers' destructors will do the right thing (de-register with
+ // Windows).
+ iter.Remove();
+
+ // Break the cycle between the handler and the MSCOM notification so the
+ // handler's destructor will be called.
+ handler->UnregisterHandler();
+ };
+
+ if (topic == "last-pb-context-exited"_ns) {
+ if (handler->IsPrivate()) {
+ handler->HideAlert();
+ removeNotification();
+ }
+ } else if (topic == "quit-application"_ns) {
+ removeNotification();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ if (NS_WARN_IF(!alert)) {
+ return NS_ERROR_FAILURE;
+ }
+ // vibrate is unused for now
+ nsTArray<uint32_t> vibrate;
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
+ aAlertTextClickable, aAlertCookie, aBidi, aLang,
+ aData, aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction, false, vibrate);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+ToastNotification::SetManualDoNotDisturb(bool aDoNotDisturb) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ToastNotification::GetManualDoNotDisturb(bool* aRet) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ToastNotification::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ NS_ENSURE_ARG(aAlert);
+
+ if (mSuppressForScreenSharing) {
+ return NS_OK;
+ }
+
+ nsAutoString cookie;
+ MOZ_TRY(aAlert->GetCookie(cookie));
+
+ nsAutoString name;
+ MOZ_TRY(aAlert->GetName(name));
+
+ nsAutoString title;
+ MOZ_TRY(aAlert->GetTitle(title));
+ if (!EnsureUTF16Validity(title)) {
+ MOZ_LOG(sWASLog, LogLevel::Warning,
+ ("Notification title was invalid UTF16, unpaired surrogates have "
+ "been replaced."));
+ }
+
+ nsAutoString text;
+ MOZ_TRY(aAlert->GetText(text));
+ if (!EnsureUTF16Validity(text)) {
+ MOZ_LOG(sWASLog, LogLevel::Warning,
+ ("Notification text was invalid UTF16, unpaired surrogates have "
+ "been replaced."));
+ }
+
+ bool textClickable;
+ MOZ_TRY(aAlert->GetTextClickable(&textClickable));
+
+ bool isSilent;
+ MOZ_TRY(aAlert->GetSilent(&isSilent));
+
+ nsAutoString hostPort;
+ MOZ_TRY(aAlert->GetSource(hostPort));
+
+ nsAutoString opaqueRelaunchData;
+ MOZ_TRY(aAlert->GetOpaqueRelaunchData(opaqueRelaunchData));
+
+ bool requireInteraction;
+ MOZ_TRY(aAlert->GetRequireInteraction(&requireInteraction));
+
+ bool inPrivateBrowsing;
+ MOZ_TRY(aAlert->GetInPrivateBrowsing(&inPrivateBrowsing));
+
+ nsTArray<RefPtr<nsIAlertAction>> actions;
+ MOZ_TRY(aAlert->GetActions(actions));
+
+ nsCOMPtr<nsIPrincipal> principal;
+ MOZ_TRY(aAlert->GetPrincipal(getter_AddRefs(principal)));
+ bool isSystemPrincipal = principal && principal->IsSystemPrincipal();
+
+ bool handleActions = false;
+ auto imagePlacement = ImagePlacement::eInline;
+ if (isSystemPrincipal) {
+ nsCOMPtr<nsIWindowsAlertNotification> winAlert(do_QueryInterface(aAlert));
+ if (winAlert) {
+ MOZ_TRY(winAlert->GetHandleActions(&handleActions));
+
+ nsIWindowsAlertNotification::ImagePlacement placement;
+ MOZ_TRY(winAlert->GetImagePlacement(&placement));
+ switch (placement) {
+ case nsIWindowsAlertNotification::eHero:
+ imagePlacement = ImagePlacement::eHero;
+ break;
+ case nsIWindowsAlertNotification::eIcon:
+ imagePlacement = ImagePlacement::eIcon;
+ break;
+ case nsIWindowsAlertNotification::eInline:
+ imagePlacement = ImagePlacement::eInline;
+ break;
+ default:
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("Invalid image placement enum value: %hhu", placement));
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ }
+
+ RefPtr<ToastNotificationHandler> oldHandler = mActiveHandlers.Get(name);
+
+ NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED);
+ RefPtr<ToastNotificationHandler> handler = new ToastNotificationHandler(
+ this, mAumid.ref(), aAlertListener, name, cookie, title, text, hostPort,
+ textClickable, requireInteraction, actions, isSystemPrincipal,
+ opaqueRelaunchData, inPrivateBrowsing, isSilent, handleActions,
+ imagePlacement);
+ mActiveHandlers.InsertOrUpdate(name, RefPtr{handler});
+
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Adding handler '%s': [%p] (now %d handlers)",
+ NS_ConvertUTF16toUTF8(name).get(), handler.get(),
+ mActiveHandlers.Count()));
+
+ nsresult rv = handler->InitAlertAsync(aAlert);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Failed to init alert, removing '%s'",
+ NS_ConvertUTF16toUTF8(name).get()));
+ mActiveHandlers.Remove(name);
+ handler->UnregisterHandler();
+ return rv;
+ }
+
+ // If there was a previous handler with the same name then unregister it.
+ if (oldHandler) {
+ oldHandler->UnregisterHandler();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::GetXmlStringForWindowsAlert(nsIAlertNotification* aAlert,
+ const nsAString& aWindowsTag,
+ nsAString& aString) {
+ NS_ENSURE_ARG(aAlert);
+
+ nsAutoString cookie;
+ MOZ_TRY(aAlert->GetCookie(cookie));
+
+ nsAutoString name;
+ MOZ_TRY(aAlert->GetName(name));
+
+ nsAutoString title;
+ MOZ_TRY(aAlert->GetTitle(title));
+
+ nsAutoString text;
+ MOZ_TRY(aAlert->GetText(text));
+
+ bool textClickable;
+ MOZ_TRY(aAlert->GetTextClickable(&textClickable));
+
+ bool isSilent;
+ MOZ_TRY(aAlert->GetSilent(&isSilent));
+
+ nsAutoString hostPort;
+ MOZ_TRY(aAlert->GetSource(hostPort));
+
+ nsAutoString opaqueRelaunchData;
+ MOZ_TRY(aAlert->GetOpaqueRelaunchData(opaqueRelaunchData));
+
+ bool requireInteraction;
+ MOZ_TRY(aAlert->GetRequireInteraction(&requireInteraction));
+
+ bool inPrivateBrowsing;
+ MOZ_TRY(aAlert->GetInPrivateBrowsing(&inPrivateBrowsing));
+
+ nsTArray<RefPtr<nsIAlertAction>> actions;
+ MOZ_TRY(aAlert->GetActions(actions));
+
+ nsCOMPtr<nsIPrincipal> principal;
+ MOZ_TRY(aAlert->GetPrincipal(getter_AddRefs(principal)));
+ bool isSystemPrincipal = principal && principal->IsSystemPrincipal();
+
+ NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED);
+ RefPtr<ToastNotificationHandler> handler = new ToastNotificationHandler(
+ this, mAumid.ref(), nullptr /* aAlertListener */, name, cookie, title,
+ text, hostPort, textClickable, requireInteraction, actions,
+ isSystemPrincipal, opaqueRelaunchData, inPrivateBrowsing, isSilent);
+
+ // Usually, this will be empty during testing, making test output
+ // deterministic.
+ MOZ_TRY(handler->SetWindowsTag(aWindowsTag));
+
+ nsAutoString imageURL;
+ MOZ_TRY(aAlert->GetImageURL(imageURL));
+
+ return handler->CreateToastXmlString(imageURL, aString);
+}
+
+// Verifies that the tag recieved associates to a notification created during
+// this application's session, or handles fallback behavior.
+RefPtr<ToastHandledPromise> ToastNotification::VerifyTagPresentOrFallback(
+ const nsAString& aWindowsTag) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Iterating %d handlers", mActiveHandlers.Count()));
+
+ for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ToastNotificationHandler> handler = iter.UserData();
+ nsAutoString tag;
+ nsresult rv = handler->GetWindowsTag(tag);
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Comparing external windowsTag '%s' to handled windowsTag '%s'",
+ NS_ConvertUTF16toUTF8(aWindowsTag).get(),
+ NS_ConvertUTF16toUTF8(tag).get()));
+ if (aWindowsTag.Equals(tag)) {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("External windowsTag '%s' is handled by handler [%p]",
+ NS_ConvertUTF16toUTF8(aWindowsTag).get(), handler.get()));
+ return ToastHandledPromise::CreateAndResolve(true, __func__);
+ }
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("Failed to get windowsTag for handler [%p]", handler.get()));
+ }
+ }
+
+ // Fallback handling is required.
+
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("External windowsTag '%s' is not handled",
+ NS_ConvertUTF16toUTF8(aWindowsTag).get()));
+
+ RefPtr<ToastHandledPromise::Private> fallbackPromise =
+ new ToastHandledPromise::Private(__func__);
+
+ // TODO: Bug 1806005 - At time of writing this function is called in a call
+ // stack containing `WndProc` callback on an STA thread. As a result attempts
+ // to create a `ToastNotificationManager` instance results an an
+ // `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` error. We can simplify the the XPCOM
+ // interface and synchronize the COM interactions if notification fallback
+ // handling were no longer handled in a `WndProc` context.
+ NS_DispatchBackgroundTask(NS_NewRunnableFunction(
+ "VerifyTagPresentOrFallback fallback background task",
+ [fallbackPromise]() { fallbackPromise->Resolve(false, __func__); }));
+
+ return fallbackPromise;
+}
+
+// Send our window's PID to the notification server so that it can grant us
+// `SetForegroundWindow` permissions. PID 0 is sent to signal no window PID.
+// Absense of PID which may occur when we are yet unable to retrieve the
+// window during startup, which is not a problem in practice as new windows
+// receive focus by default.
+void ToastNotification::SignalComNotificationHandled(
+ const nsAString& aWindowsTag) {
+ DWORD pid = 0;
+
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (winMediator) {
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+ winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (navWin) {
+ nsCOMPtr<nsIWidget> widget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin));
+ if (widget) {
+ HWND hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ GetWindowThreadProcessId(hwnd, &pid);
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get widget"));
+ }
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get navWin"));
+ }
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Debug, ("Failed to get WinMediator"));
+ }
+
+ // Run pipe communication off the main thread to prevent UI jank from
+ // blocking. Nothing relies on the COM server's response or that it has
+ // responded at time of commit.
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "SignalComNotificationHandled background task",
+ [pid, aWindowsTag = nsString{aWindowsTag}]() mutable {
+ std::wstring pipeName = GetNotificationPipeName(aWindowsTag.get());
+
+ nsAutoHandle pipe;
+ pipe.own(CreateFileW(pipeName.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, nullptr, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, nullptr));
+ if (pipe.get() == INVALID_HANDLE_VALUE) {
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("Unable to open notification server pipe."));
+ return;
+ }
+
+ DWORD pipeFlags = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(pipe.get(), &pipeFlags, nullptr,
+ nullptr)) {
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("Error setting pipe handle state, error %lu",
+ GetLastError()));
+ return;
+ }
+
+ // Pass our window's PID to the COM server receive
+ // `SetForegroundWindow` permissions, and wait for a message
+ // acknowledging the permission has been granted.
+ ToastNotificationPidMessage in{};
+ in.pid = pid;
+ ToastNotificationPermissionMessage out{};
+ auto transact = [&](OVERLAPPED& overlapped) {
+ return TransactNamedPipe(pipe.get(), &in, sizeof(in), &out,
+ sizeof(out), nullptr, &overlapped);
+ };
+ bool result =
+ SyncDoOverlappedIOWithTimeout(pipe, sizeof(out), transact);
+
+ if (result && out.setForegroundPermissionGranted && pid != 0) {
+ MOZ_LOG(
+ sWASLog, LogLevel::Info,
+ ("SetForegroundWindow permission granted to our window."));
+ } else {
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("SetForegroundWindow permission not granted to our "
+ "window."));
+ }
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+NS_IMETHODIMP
+ToastNotification::HandleWindowsTag(const nsAString& aWindowsTag,
+ JSContext* aCx, dom::Promise** aPromise) {
+ NS_ENSURE_TRUE(mAumid.isSome(), NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
+
+ ErrorResult rv;
+ RefPtr<dom::Promise> promise =
+ dom::Promise::Create(xpc::CurrentNativeGlobal(aCx), rv);
+ ENSURE_SUCCESS(rv, rv.StealNSResult());
+
+ this->VerifyTagPresentOrFallback(aWindowsTag)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aWindowsTag = nsString(aWindowsTag),
+ promise](const bool aTagWasHandled) {
+ // We no longer need to query toast information from OS and can
+ // allow the COM server to proceed (toast information is lost once
+ // the COM server's `Activate` callback returns).
+ SignalComNotificationHandled(aWindowsTag);
+
+ dom::AutoJSAPI js;
+ if (NS_WARN_IF(!js.Init(promise->GetGlobalObject()))) {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Resolve the DOM Promise with a JS object. Set properties if
+ // fallback handling is necessary.
+
+ JSContext* cx = js.cx();
+ JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
+
+ JS::Rooted<JS::Value> attVal(cx, JS::BooleanValue(aTagWasHandled));
+ Unused << NS_WARN_IF(
+ !JS_SetProperty(cx, obj, "tagWasHandled", attVal));
+
+ promise->MaybeResolve(obj);
+ },
+ [aWindowsTag = nsString(aWindowsTag), promise]() {
+ // We no longer need to query toast information from OS and can
+ // allow the COM server to proceed (toast information is lost once
+ // the COM server's `Activate` callback returns).
+ SignalComNotificationHandled(aWindowsTag);
+
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ });
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotification::CloseAlert(const nsAString& aAlertName,
+ bool aContextClosed) {
+ RefPtr<ToastNotificationHandler> handler;
+ if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+ return NS_OK;
+ }
+
+ if (!aContextClosed || handler->IsPrivate()) {
+ // Hide the alert when not implicitly closed by tab/window closing or when
+ // notification originated from a private tab.
+ handler->HideAlert();
+ }
+
+ mActiveHandlers.Remove(aAlertName);
+ handler->UnregisterHandler();
+
+ return NS_OK;
+}
+
+bool ToastNotification::IsActiveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler) {
+ RefPtr<ToastNotificationHandler> handler;
+ if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) {
+ return false;
+ }
+ return handler == aHandler;
+}
+
+void ToastNotification::RemoveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler) {
+ // The alert may have been replaced; only remove it from the active
+ // handler's map if it's the same.
+ if (IsActiveHandler(aAlertName, aHandler)) {
+ // Terrible things happen if the destructor of a handler is called inside
+ // the hashtable .Remove() method. Wait until we have returned from there.
+ RefPtr<ToastNotificationHandler> kungFuDeathGrip(aHandler);
+ mActiveHandlers.Remove(aAlertName);
+ aHandler->UnregisterHandler();
+ }
+}
+
+NS_IMETHODIMP
+ToastNotification::RemoveAllNotificationsForInstall() {
+ HRESULT hr = S_OK;
+
+ ComPtr<IToastNotificationManagerStatics> manager;
+ hr = GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_UI_Notifications_ToastNotificationManager)
+ .Get(),
+ &manager);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
+
+ HString aumid;
+ MOZ_ASSERT(mAumid.isSome());
+ hr = aumid.Set(mAumid.ref().get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
+
+ // Hide toasts in action center.
+ [&]() {
+ ComPtr<IToastNotificationManagerStatics2> manager2;
+ hr = manager.As(&manager2);
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+
+ ComPtr<IToastNotificationHistory> history;
+ hr = manager2->get_History(&history);
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+
+ hr = history->ClearWithId(aumid.Get());
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+ }();
+
+ // Hide scheduled toasts.
+ [&]() {
+ ComPtr<IToastNotifier> notifier;
+ hr = manager->CreateToastNotifierWithId(aumid.Get(), &notifier);
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+
+ ComPtr<IVectorView_ScheduledToastNotification> scheduledToasts;
+ hr = notifier->GetScheduledToastNotifications(&scheduledToasts);
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+
+ unsigned int schedSize;
+ hr = scheduledToasts->get_Size(&schedSize);
+ NS_ENSURE_TRUE_VOID(SUCCEEDED(hr));
+
+ for (unsigned int i = 0; i < schedSize; i++) {
+ ComPtr<IScheduledToastNotification> schedToast;
+ hr = scheduledToasts->GetAt(i, &schedToast);
+ if (NS_WARN_IF(FAILED(hr))) {
+ continue;
+ }
+
+ hr = notifier->RemoveFromSchedule(schedToast.Get());
+ Unused << NS_WARN_IF(FAILED(hr));
+ }
+ }();
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(WindowsAlertNotification, AlertNotification,
+ nsIWindowsAlertNotification)
+
+NS_IMETHODIMP
+WindowsAlertNotification::GetHandleActions(bool* aHandleActions) {
+ *aHandleActions = mHandleActions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsAlertNotification::SetHandleActions(bool aHandleActions) {
+ mHandleActions = aHandleActions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP WindowsAlertNotification::GetImagePlacement(
+ nsIWindowsAlertNotification::ImagePlacement* aImagePlacement) {
+ *aImagePlacement = mImagePlacement;
+ return NS_OK;
+}
+
+NS_IMETHODIMP WindowsAlertNotification::SetImagePlacement(
+ nsIWindowsAlertNotification::ImagePlacement aImagePlacement) {
+ switch (aImagePlacement) {
+ case eHero:
+ case eIcon:
+ case eInline:
+ mImagePlacement = aImagePlacement;
+ break;
+ default:
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("Invalid image placement enum value: %hhu", aImagePlacement));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ToastNotification.h b/widget/windows/ToastNotification.h
new file mode 100644
index 0000000000..9beb08cc27
--- /dev/null
+++ b/widget/windows/ToastNotification.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 ToastNotification_h__
+#define ToastNotification_h__
+
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "nsIAlertsService.h"
+#include "nsIObserver.h"
+#include "nsIThread.h"
+#include "nsIWindowsAlertsService.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/AlertNotification.h"
+
+namespace mozilla {
+namespace widget {
+
+using ToastHandledPromise = MozPromise<bool, bool, true>;
+
+class ToastNotificationHandler;
+
+class WindowsAlertNotification final : public AlertNotification,
+ public nsIWindowsAlertNotification {
+ public:
+ NS_DECL_NSIWINDOWSALERTNOTIFICATION
+ NS_FORWARD_NSIALERTNOTIFICATION(AlertNotification::)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ WindowsAlertNotification() = default;
+
+ protected:
+ virtual ~WindowsAlertNotification() = default;
+ bool mHandleActions = false;
+ nsIWindowsAlertNotification::ImagePlacement mImagePlacement = eInline;
+};
+
+class ToastNotification final : public nsIWindowsAlertsService,
+ public nsIAlertsDoNotDisturb,
+ public nsIObserver {
+ public:
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIWINDOWSALERTSSERVICE
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_NSIOBSERVER
+ NS_DECL_ISUPPORTS
+
+ ToastNotification();
+
+ nsresult Init();
+
+ bool IsActiveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler);
+ void RemoveHandler(const nsAString& aAlertName,
+ ToastNotificationHandler* aHandler);
+
+ nsresult BackgroundDispatch(nsIRunnable* runnable);
+
+ protected:
+ virtual ~ToastNotification();
+ bool EnsureAumidRegistered();
+
+ static bool AssignIfMsixAumid(Maybe<nsAutoString>& aAumid);
+ static bool AssignIfNsisAumid(nsAutoString& aInstallHash,
+ Maybe<nsAutoString>& aAumid);
+ static bool RegisterRuntimeAumid(nsAutoString& aInstallHash,
+ Maybe<nsAutoString>& aAumid);
+
+ RefPtr<ToastHandledPromise> VerifyTagPresentOrFallback(
+ const nsAString& aWindowsTag);
+ static void SignalComNotificationHandled(const nsAString& aWindowsTag);
+
+ nsRefPtrHashtable<nsStringHashKey, ToastNotificationHandler> mActiveHandlers;
+ Maybe<nsAutoString> mAumid;
+ bool mSuppressForScreenSharing = false;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/ToastNotificationHandler.cpp b/widget/windows/ToastNotificationHandler.cpp
new file mode 100644
index 0000000000..a493342719
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.cpp
@@ -0,0 +1,1167 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "ToastNotificationHandler.h"
+
+#include <windows.foundation.h>
+
+#include "gfxUtils.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "json/json.h"
+#include "mozilla/gfx/2D.h"
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+#include "mozilla/HashFunctions.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+#include "mozilla/Result.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/intl/Localization.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsAppRunner.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIDUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIToolkitProfile.h"
+#include "nsIToolkitProfileService.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
+#include "nsXREDirProvider.h"
+#include "ToastNotificationHeaderOnlyUtils.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+
+#include "ToastNotification.h"
+
+namespace mozilla {
+namespace widget {
+
+extern LazyLogModule sWASLog;
+
+using namespace ABI::Windows::Data::Xml::Dom;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::UI::Notifications;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace toastnotification;
+
+// Needed to disambiguate internal and Windows `ToastNotification` classes.
+using WinToastNotification = ABI::Windows::UI::Notifications::ToastNotification;
+using ToastActivationHandler =
+ ITypedEventHandler<WinToastNotification*, IInspectable*>;
+using ToastDismissedHandler =
+ ITypedEventHandler<WinToastNotification*, ToastDismissedEventArgs*>;
+using ToastFailedHandler =
+ ITypedEventHandler<WinToastNotification*, ToastFailedEventArgs*>;
+using IVectorView_ToastNotification =
+ Collections::IVectorView<WinToastNotification*>;
+
+NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener)
+
+static bool SetNodeValueString(const nsString& aString, IXmlNode* node,
+ IXmlDocument* xml) {
+ ComPtr<IXmlText> inputText;
+ HRESULT hr;
+ hr = xml->CreateTextNode(HStringReference(aString.get()).Get(), &inputText);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ ComPtr<IXmlNode> inputTextNode;
+ hr = inputText.As(&inputTextNode);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ ComPtr<IXmlNode> appendedChild;
+ hr = node->AppendChild(inputTextNode.Get(), &appendedChild);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ return true;
+}
+
+static bool SetAttribute(ComPtr<IXmlElement>& element,
+ const HStringReference& name, const nsAString& value) {
+ HString valueStr;
+ valueStr.Set(PromiseFlatString(value).get());
+
+ HRESULT hr = element->SetAttribute(name.Get(), valueStr.Get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ return true;
+}
+
+static bool AddActionNode(ComPtr<IXmlDocument>& toastXml,
+ ComPtr<IXmlNode>& actionsNode,
+ const nsAString& actionTitle,
+ const nsAString& launchArg,
+ const nsAString& actionArgs,
+ const nsAString& actionPlacement = u""_ns,
+ const nsAString& activationType = u""_ns) {
+ ComPtr<IXmlElement> action;
+ HRESULT hr =
+ toastXml->CreateElement(HStringReference(L"action").Get(), &action);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ bool success =
+ SetAttribute(action, HStringReference(L"content"), actionTitle);
+ NS_ENSURE_TRUE(success, false);
+
+ // Action arguments overwrite the toast's launch arguments, so we need to
+ // prepend the launch arguments necessary for the Notification Server to
+ // reconstruct the toast's origin.
+ //
+ // Web Notification actions are arbitrary strings; to prevent breaking launch
+ // argument parsing the action argument must be last. All delimiters after
+ // `action` are part of the action arugment.
+ nsAutoString args = launchArg + u"\n"_ns +
+ nsDependentString(kLaunchArgAction) + u"\n"_ns +
+ actionArgs;
+ success = SetAttribute(action, HStringReference(L"arguments"), args);
+ NS_ENSURE_TRUE(success, false);
+
+ if (!actionPlacement.IsEmpty()) {
+ success =
+ SetAttribute(action, HStringReference(L"placement"), actionPlacement);
+ NS_ENSURE_TRUE(success, false);
+ }
+
+ if (!activationType.IsEmpty()) {
+ success = SetAttribute(action, HStringReference(L"activationType"),
+ activationType);
+ NS_ENSURE_TRUE(success, false);
+
+ // No special argument handling: when `activationType="system"`, `arguments`
+ // should be a Windows-specific keyword, namely "dismiss" or "snooze", which
+ // are supposed to make a system handled dismiss/snooze buttons.
+ // https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml#snoozedismiss
+ //
+ // Note that while using it prevents calling our notification COM server,
+ // it somehow still calls OnActivate instead of OnDismiss. Thus, we still
+ // need to handle such callbacks manually by checking `arguments`.
+ success = SetAttribute(action, HStringReference(L"arguments"), actionArgs);
+ NS_ENSURE_TRUE(success, false);
+ }
+
+ // Add <action> to <actions>
+ ComPtr<IXmlNode> actionNode;
+ hr = action.As(&actionNode);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ ComPtr<IXmlNode> appendedChild;
+ hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ return true;
+}
+
+nsresult ToastNotificationHandler::GetWindowsTag(nsAString& aWindowsTag) {
+ aWindowsTag.Assign(mWindowsTag);
+ return NS_OK;
+}
+
+nsresult ToastNotificationHandler::SetWindowsTag(const nsAString& aWindowsTag) {
+ mWindowsTag.Assign(aWindowsTag);
+ return NS_OK;
+}
+
+// clang - format off
+/* Populate the launch argument so the COM server can reconstruct the toast
+ * origin.
+ *
+ * program
+ * {MOZ_APP_NAME}
+ * profile
+ * {path to profile}
+ */
+// clang-format on
+Result<nsString, nsresult> ToastNotificationHandler::GetLaunchArgument() {
+ nsString launchArg;
+
+ // When the preference is false, the COM notification server will be invoked,
+ // discover that there is no `program`, and exit (successfully), after which
+ // Windows will invoke the in-product Windows 8-style callbacks. When true,
+ // the COM notification server will launch Firefox with sufficient arguments
+ // for Firefox to handle the notification.
+ if (!Preferences::GetBool(
+ "alerts.useSystemBackend.windows.notificationserver.enabled",
+ false)) {
+ // Include dummy key/value so that newline appended arguments aren't off by
+ // one line.
+ launchArg += u"invalid key\ninvalid value"_ns;
+ return launchArg;
+ }
+
+ // `program` argument.
+ launchArg += nsDependentString(kLaunchArgProgram) + u"\n"_ns MOZ_APP_NAME;
+
+ // `profile` argument.
+ nsCOMPtr<nsIFile> profDir;
+ bool wantCurrentProfile = true;
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // Notifications popped from a background task want to invoke Firefox with a
+ // different profile -- the default browsing profile. We'd prefer to not
+ // specify a profile, so that the Firefox invoked by the notification server
+ // chooses its default profile, but this might pop the profile chooser in
+ // some configurations.
+ wantCurrentProfile = false;
+
+ nsCOMPtr<nsIToolkitProfileService> profileSvc =
+ do_GetService(NS_PROFILESERVICE_CONTRACTID);
+ if (profileSvc) {
+ nsCOMPtr<nsIToolkitProfile> defaultProfile;
+ nsresult rv =
+ profileSvc->GetDefaultProfile(getter_AddRefs(defaultProfile));
+ if (NS_SUCCEEDED(rv) && defaultProfile) {
+ // Not all installations have a default profile. But if one is set,
+ // then it should have a profile directory.
+ MOZ_TRY(defaultProfile->GetRootDir(getter_AddRefs(profDir)));
+ }
+ }
+ }
+#endif
+ if (wantCurrentProfile) {
+ MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir)));
+ }
+
+ if (profDir) {
+ nsAutoString profilePath;
+ MOZ_TRY(profDir->GetPath(profilePath));
+ launchArg += u"\n"_ns + nsDependentString(kLaunchArgProfile) + u"\n"_ns +
+ profilePath;
+ }
+
+ // `windowsTag` argument.
+ launchArg +=
+ u"\n"_ns + nsDependentString(kLaunchArgTag) + u"\n"_ns + mWindowsTag;
+
+ // `logging` argument.
+ if (Preferences::GetBool(
+ "alerts.useSystemBackend.windows.notificationserver.verbose",
+ false)) {
+ // Signal notification to log verbose messages.
+ launchArg +=
+ u"\n"_ns + nsDependentString(kLaunchArgLogging) + u"\nverbose"_ns;
+ }
+
+ return launchArg;
+}
+
+static ComPtr<IToastNotificationManagerStatics>
+GetToastNotificationManagerStatics() {
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics;
+ HRESULT hr = GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_UI_Notifications_ToastNotificationManager)
+ .Get(),
+ &toastNotificationManagerStatics);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ return toastNotificationManagerStatics;
+}
+
+ToastNotificationHandler::~ToastNotificationHandler() {
+ if (mImageRequest) {
+ mImageRequest->Cancel(NS_BINDING_ABORTED);
+ mImageRequest = nullptr;
+ }
+
+ if (mHasImage && mImageFile) {
+ DebugOnly<nsresult> rv = mImageFile->Remove(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Cannot remove temporary image file");
+ }
+
+ UnregisterHandler();
+}
+
+void ToastNotificationHandler::UnregisterHandler() {
+ if (mNotification) {
+ mNotification->remove_Dismissed(mDismissedToken);
+ mNotification->remove_Activated(mActivatedToken);
+ mNotification->remove_Failed(mFailedToken);
+ }
+
+ mNotification = nullptr;
+ mNotifier = nullptr;
+
+ SendFinished();
+}
+
+nsresult ToastNotificationHandler::InitAlertAsync(
+ nsIAlertNotification* aAlert) {
+ MOZ_TRY(InitWindowsTag());
+
+#ifdef MOZ_BACKGROUNDTASKS
+ nsAutoString imageUrl;
+ if (BackgroundTasks::IsBackgroundTaskMode() &&
+ NS_SUCCEEDED(aAlert->GetImageURL(imageUrl)) && !imageUrl.IsEmpty()) {
+ // Bug 1870750: Image decoding relies on gfx and runs on a thread pool,
+ // which expects to have been initialized early and on the main thread.
+ // Since background tasks run headless this never occurs. In this case we
+ // force gfx initialization.
+ Unused << NS_WARN_IF(!gfxPlatform::GetPlatform());
+ }
+#endif
+
+ return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+ getter_AddRefs(mImageRequest));
+}
+
+// Uniquely identify this toast to Windows. Existing names and cookies are not
+// suitable: we want something generated and unique. This is needed to check if
+// toast is still present in the Windows Action Center when we receive a dismiss
+// timeout.
+//
+// Local testing reveals that the space of tags is not global but instead is per
+// AUMID. Since an installation uses a unique AUMID incorporating the install
+// directory hash, it should not witness another installation's tag.
+nsresult ToastNotificationHandler::InitWindowsTag() {
+ mWindowsTag.Truncate();
+
+ nsAutoString tag;
+
+ // Multiple profiles might overwrite each other's toast messages when a
+ // common name is used for a given host port. We prevent this by including
+ // the profile directory as part of the toast hash.
+ nsCOMPtr<nsIFile> profDir;
+ MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir)));
+ MOZ_TRY(profDir->GetPath(tag));
+
+ if (!mHostPort.IsEmpty()) {
+ // Notification originated from a web notification.
+ // `mName` will be in the form `{mHostPort}#tag:{tag}` if the notification
+ // was created with a tag and `{mHostPort}#notag:{uuid}` otherwise.
+ tag += mName;
+ } else {
+ // Notification originated from the browser chrome.
+ if (!mName.IsEmpty()) {
+ tag += u"chrome#tag:"_ns;
+ // Browser chrome notifications don't follow any convention for naming.
+ tag += mName;
+ } else {
+ // No associated name, append a UUID to prevent reuse of the same tag.
+ nsIDToCString uuidString(nsID::GenerateUUID());
+ size_t len = strlen(uuidString.get());
+ MOZ_ASSERT(len == NSID_LENGTH - 1);
+ nsAutoString uuid;
+ CopyASCIItoUTF16(nsDependentCSubstring(uuidString.get(), len), uuid);
+
+ tag += u"chrome#notag:"_ns;
+ tag += uuid;
+ }
+ }
+
+ // Windows notification tags are limited to 16 characters, or 64 characters
+ // after the Creators Update; therefore we hash the tag to fit the minimum
+ // range.
+ HashNumber hash = HashString(tag);
+ mWindowsTag.AppendPrintf("%010u", hash);
+
+ return NS_OK;
+}
+
+nsString ToastNotificationHandler::ActionArgsJSONString(
+ const nsString& aAction, const nsString& aOpaqueRelaunchData = u""_ns) {
+ nsAutoCString actionArgsData;
+
+ JSONStringRefWriteFunc js(actionArgsData);
+ JSONWriter w(js, JSONWriter::SingleLineStyle);
+ w.Start();
+
+ w.StringProperty("action", NS_ConvertUTF16toUTF8(aAction));
+
+ if (mIsSystemPrincipal) {
+ // Privileged/chrome alerts (not activated by Windows) can have custom
+ // relaunch data.
+ if (!aOpaqueRelaunchData.IsEmpty()) {
+ w.StringProperty("opaqueRelaunchData",
+ NS_ConvertUTF16toUTF8(aOpaqueRelaunchData));
+ }
+
+ // Privileged alerts include any provided name for metrics.
+ if (!mName.IsEmpty()) {
+ w.StringProperty("privilegedName", NS_ConvertUTF16toUTF8(mName));
+ }
+ } else {
+ if (!mHostPort.IsEmpty()) {
+ w.StringProperty("launchUrl", NS_ConvertUTF16toUTF8(mHostPort));
+ }
+ }
+
+ w.End();
+
+ return NS_ConvertUTF8toUTF16(actionArgsData);
+}
+
+ComPtr<IXmlDocument> ToastNotificationHandler::CreateToastXmlDocument() {
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+ GetToastNotificationManagerStatics();
+ NS_ENSURE_TRUE(toastNotificationManagerStatics, nullptr);
+
+ ToastTemplateType toastTemplate;
+ if (mHostPort.IsEmpty()) {
+ toastTemplate =
+ mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText03
+ : ToastTemplateType::ToastTemplateType_ToastText03;
+ } else {
+ toastTemplate =
+ mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText04
+ : ToastTemplateType::ToastTemplateType_ToastText04;
+ }
+
+ ComPtr<IXmlDocument> toastXml;
+ toastNotificationManagerStatics->GetTemplateContent(toastTemplate, &toastXml);
+
+ if (!toastXml) {
+ return nullptr;
+ }
+
+ nsresult ns;
+ HRESULT hr;
+ bool success;
+
+ if (mHasImage) {
+ ComPtr<IXmlNodeList> toastImageElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"image").Get(),
+ &toastImageElements);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> imageNode;
+ hr = toastImageElements->Item(0, &imageNode);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlElement> image;
+ hr = imageNode.As(&image);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ success = SetAttribute(image, HStringReference(L"src"), mImageUri);
+ NS_ENSURE_TRUE(success, nullptr);
+
+ switch (mImagePlacement) {
+ case ImagePlacement::eHero:
+ success =
+ SetAttribute(image, HStringReference(L"placement"), u"hero"_ns);
+ NS_ENSURE_TRUE(success, nullptr);
+ break;
+ case ImagePlacement::eIcon:
+ success = SetAttribute(image, HStringReference(L"placement"),
+ u"appLogoOverride"_ns);
+ NS_ENSURE_TRUE(success, nullptr);
+ break;
+ case ImagePlacement::eInline:
+ // No attribute placement attribute for inline images.
+ break;
+ }
+ }
+
+ ComPtr<IXmlNodeList> toastTextElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"text").Get(),
+ &toastTextElements);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> titleTextNodeRoot;
+ hr = toastTextElements->Item(0, &titleTextNodeRoot);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> msgTextNodeRoot;
+ hr = toastTextElements->Item(1, &msgTextNodeRoot);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ success = SetNodeValueString(mTitle, titleTextNodeRoot.Get(), toastXml.Get());
+ NS_ENSURE_TRUE(success, nullptr);
+
+ success = SetNodeValueString(mMsg, msgTextNodeRoot.Get(), toastXml.Get());
+ NS_ENSURE_TRUE(success, nullptr);
+
+ ComPtr<IXmlNodeList> toastElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"toast").Get(),
+ &toastElements);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> toastNodeRoot;
+ hr = toastElements->Item(0, &toastNodeRoot);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlElement> toastElement;
+ hr = toastNodeRoot.As(&toastElement);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ if (mRequireInteraction) {
+ success = SetAttribute(toastElement, HStringReference(L"scenario"),
+ u"reminder"_ns);
+ NS_ENSURE_TRUE(success, nullptr);
+ }
+
+ auto maybeLaunchArg = GetLaunchArgument();
+ NS_ENSURE_TRUE(maybeLaunchArg.isOk(), nullptr);
+ nsString launchArg = maybeLaunchArg.unwrap();
+
+ nsString launchArgWithoutAction = launchArg;
+
+ if (!mIsSystemPrincipal) {
+ // Unprivileged/content alerts can't have custom relaunch data.
+ NS_WARNING_ASSERTION(mOpaqueRelaunchData.IsEmpty(),
+ "unprivileged/content alert "
+ "should have trivial `mOpaqueRelaunchData`");
+ }
+
+ launchArg += u"\n"_ns + nsDependentString(kLaunchArgAction) + u"\n"_ns +
+ ActionArgsJSONString(u""_ns, mOpaqueRelaunchData);
+
+ success = SetAttribute(toastElement, HStringReference(L"launch"), launchArg);
+ NS_ENSURE_TRUE(success, nullptr);
+
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("launchArg: '%s'", NS_ConvertUTF16toUTF8(launchArg).get()));
+
+ // Use newer toast layout for system (chrome-privileged) toasts. This gains us
+ // UI elements such as new image placement options (default image placement is
+ // larger and inline) and buttons.
+ if (mIsSystemPrincipal) {
+ ComPtr<IXmlNodeList> bindingElements;
+ hr = toastXml->GetElementsByTagName(HStringReference(L"binding").Get(),
+ &bindingElements);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> bindingNodeRoot;
+ hr = bindingElements->Item(0, &bindingNodeRoot);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlElement> bindingElement;
+ hr = bindingNodeRoot.As(&bindingElement);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ success = SetAttribute(bindingElement, HStringReference(L"template"),
+ u"ToastGeneric"_ns);
+ NS_ENSURE_TRUE(success, nullptr);
+ }
+
+ ComPtr<IXmlElement> actions;
+ hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IXmlNode> actionsNode;
+ hr = actions.As(&actionsNode);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ NS_ENSURE_TRUE(sbs, nullptr);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_TRUE(bundle, nullptr);
+
+ if (!mHostPort.IsEmpty()) {
+ AutoTArray<nsString, 1> formatStrings = {mHostPort};
+
+ ComPtr<IXmlNode> urlTextNodeRoot;
+ hr = toastTextElements->Item(2, &urlTextNodeRoot);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ nsAutoString urlReference;
+ bundle->FormatStringFromName("source.label", formatStrings, urlReference);
+
+ success =
+ SetNodeValueString(urlReference, urlTextNodeRoot.Get(), toastXml.Get());
+ NS_ENSURE_TRUE(success, nullptr);
+
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ ComPtr<IXmlElement> placementText;
+ hr = urlTextNodeRoot.As(&placementText);
+ if (SUCCEEDED(hr)) {
+ // placement is supported on Windows 10 Anniversary Update or later
+ SetAttribute(placementText, HStringReference(L"placement"),
+ u"attribution"_ns);
+ }
+ }
+
+ nsAutoString disableButtonTitle;
+ ns = bundle->FormatStringFromName("webActions.disableForOrigin.label",
+ formatStrings, disableButtonTitle);
+ NS_ENSURE_SUCCESS(ns, nullptr);
+
+ AddActionNode(toastXml, actionsNode, disableButtonTitle,
+ // TODO: launch into `about:preferences`?
+ launchArgWithoutAction, ActionArgsJSONString(u"snooze"_ns),
+ u"contextmenu"_ns);
+ }
+
+ bool wantSettings = true;
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // Notifications popped from a background task want to invoke Firefox with a
+ // different profile -- the default browsing profile. Don't link to Firefox
+ // settings in some different profile: the relevant Firefox settings won't
+ // take effect.
+ wantSettings = false;
+ }
+#endif
+ if (MOZ_LIKELY(wantSettings)) {
+ nsAutoString settingsButtonTitle;
+ bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
+ success = AddActionNode(
+ toastXml, actionsNode, settingsButtonTitle, launchArgWithoutAction,
+ // TODO: launch into `about:preferences`?
+ ActionArgsJSONString(u"settings"_ns), u"contextmenu"_ns);
+ NS_ENSURE_TRUE(success, nullptr);
+ }
+
+ for (const auto& action : mActions) {
+ // Bug 1778596: include per-action icon from image URL.
+ nsString title;
+ ns = action->GetTitle(title);
+ NS_ENSURE_SUCCESS(ns, nullptr);
+
+ nsString actionString;
+ ns = action->GetAction(actionString);
+ NS_ENSURE_SUCCESS(ns, nullptr);
+
+ nsString opaqueRelaunchData;
+ ns = action->GetOpaqueRelaunchData(opaqueRelaunchData);
+ NS_ENSURE_SUCCESS(ns, nullptr);
+
+ MOZ_LOG(sWASLog, LogLevel::Debug,
+ ("launchArgWithoutAction for '%s': '%s'",
+ NS_ConvertUTF16toUTF8(actionString).get(),
+ NS_ConvertUTF16toUTF8(launchArgWithoutAction).get()));
+
+ // Privileged/chrome alerts can have actions that are activated by Windows.
+ // Recognize these actions and enable these activations.
+ bool activationType(false);
+ ns = action->GetWindowsSystemActivationType(&activationType);
+ NS_ENSURE_SUCCESS(ns, nullptr);
+
+ nsString activationTypeString(
+ (mIsSystemPrincipal && activationType) ? u"system"_ns : u""_ns);
+
+ nsString actionArgs;
+ if (mIsSystemPrincipal && activationType) {
+ // Privileged/chrome alerts that are activated by Windows can't have
+ // custom relaunch data.
+ actionArgs = actionString;
+
+ NS_WARNING_ASSERTION(opaqueRelaunchData.IsEmpty(),
+ "action with `windowsSystemActivationType=true` "
+ "should have trivial `opaqueRelaunchData`");
+ } else {
+ actionArgs = ActionArgsJSONString(actionString, opaqueRelaunchData);
+ }
+
+ success = AddActionNode(toastXml, actionsNode, title,
+ /* launchArg */ launchArgWithoutAction,
+ /* actionArgs */ actionArgs,
+ /* actionPlacement */ u""_ns,
+ /* activationType */ activationTypeString);
+ NS_ENSURE_TRUE(success, nullptr);
+ }
+
+ // Windows ignores scenario=reminder added by mRequiredInteraction if
+ // there's no non-contextmenu action.
+ if (mRequireInteraction && !mActions.Length()) {
+ // `activationType="system" arguments="dismiss" content=""` provides
+ // localized text from Windows, but we support more locales than Windows
+ // does, so let's have our own.
+ nsTArray<nsCString> resIds = {
+ "toolkit/global/alert.ftl"_ns,
+ };
+ RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true);
+ IgnoredErrorResult rv;
+ nsAutoCString closeTitle;
+ l10n->FormatValueSync("notification-default-dismiss"_ns, {}, closeTitle,
+ rv);
+ NS_ENSURE_TRUE(!rv.Failed(), nullptr);
+
+ NS_ENSURE_TRUE(
+ AddActionNode(toastXml, actionsNode, NS_ConvertUTF8toUTF16(closeTitle),
+ u""_ns, u"dismiss"_ns, u""_ns, u"system"_ns),
+ nullptr);
+ }
+
+ ComPtr<IXmlNode> appendedChild;
+ hr = toastNodeRoot->AppendChild(actionsNode.Get(), &appendedChild);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ if (mIsSilent) {
+ ComPtr<IXmlNode> audioNode;
+ // Create <audio silent="true"/> for silent notifications.
+ ComPtr<IXmlElement> audio;
+ hr = toastXml->CreateElement(HStringReference(L"audio").Get(), &audio);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ SetAttribute(audio, HStringReference(L"silent"), u"true"_ns);
+
+ hr = audio.As(&audioNode);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ hr = toastNodeRoot->AppendChild(audioNode.Get(), &appendedChild);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ }
+
+ return toastXml;
+}
+
+nsresult ToastNotificationHandler::CreateToastXmlString(
+ const nsAString& aImageURL, nsAString& aString) {
+ HRESULT hr;
+
+ if (!aImageURL.IsEmpty()) {
+ // For testing: don't fetch and write image to disk, just include the URL.
+ mHasImage = true;
+ mImageUri.Assign(aImageURL);
+ }
+
+ ComPtr<IXmlDocument> toastXml = CreateToastXmlDocument();
+ if (!toastXml) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ComPtr<IXmlNodeSerializer> ser;
+ hr = toastXml.As(&ser);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
+
+ HString data;
+ hr = ser->GetXml(data.GetAddressOf());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
+
+ uint32_t len = 0;
+ const wchar_t* rawData = data.GetRawBuffer(&len);
+ NS_ENSURE_TRUE(rawData, NS_ERROR_FAILURE);
+ aString.Assign(rawData, len);
+
+ return NS_OK;
+}
+
+bool ToastNotificationHandler::ShowAlert() {
+ if (!mBackend->IsActiveHandler(mName, this)) {
+ return false;
+ }
+
+ ComPtr<IXmlDocument> toastXml = CreateToastXmlDocument();
+
+ if (!toastXml) {
+ return false;
+ }
+
+ return CreateWindowsNotificationFromXml(toastXml);
+}
+
+bool ToastNotificationHandler::IsPrivate() { return mInPrivateBrowsing; }
+
+void ToastNotificationHandler::HideAlert() {
+ if (mNotifier && mNotification) {
+ mNotifier->Hide(mNotification.Get());
+ }
+}
+
+bool ToastNotificationHandler::CreateWindowsNotificationFromXml(
+ ComPtr<IXmlDocument>& aXml) {
+ ComPtr<IToastNotificationFactory> factory;
+ HRESULT hr;
+
+ hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification)
+ .Get(),
+ &factory);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = factory->CreateToastNotification(aXml.Get(), &mNotification);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ RefPtr<ToastNotificationHandler> self = this;
+
+ hr = mNotification->add_Activated(
+ Callback<ToastActivationHandler>([self](IToastNotification* aNotification,
+ IInspectable* aInspectable) {
+ return self->OnActivate(ComPtr<IToastNotification>(aNotification),
+ ComPtr<IInspectable>(aInspectable));
+ }).Get(),
+ &mActivatedToken);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = mNotification->add_Dismissed(
+ Callback<ToastDismissedHandler>([self](IToastNotification* aNotification,
+ IToastDismissedEventArgs* aArgs) {
+ return self->OnDismiss(ComPtr<IToastNotification>(aNotification),
+ ComPtr<IToastDismissedEventArgs>(aArgs));
+ }).Get(),
+ &mDismissedToken);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = mNotification->add_Failed(
+ Callback<ToastFailedHandler>([self](IToastNotification* aNotification,
+ IToastFailedEventArgs* aArgs) {
+ return self->OnFail(ComPtr<IToastNotification>(aNotification),
+ ComPtr<IToastFailedEventArgs>(aArgs));
+ }).Get(),
+ &mFailedToken);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ ComPtr<IToastNotification2> notification2;
+ hr = mNotification.As(&notification2);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ HString hTag;
+ hr = hTag.Set(mWindowsTag.get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = notification2->put_Tag(hTag.Get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics =
+ GetToastNotificationManagerStatics();
+ NS_ENSURE_TRUE(toastNotificationManagerStatics, false);
+
+ HString aumid;
+ hr = aumid.Set(mAumid.get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ hr = toastNotificationManagerStatics->CreateToastNotifierWithId(aumid.Get(),
+ &mNotifier);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ hr = mNotifier->Show(mNotification.Get());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+ if (mAlertListener) {
+ mAlertListener->Observe(nullptr, "alertshow", mCookie.get());
+ }
+
+ return true;
+}
+
+void ToastNotificationHandler::SendFinished() {
+ if (!mSentFinished && mAlertListener) {
+ mAlertListener->Observe(nullptr, "alertfinished", mCookie.get());
+ }
+
+ mSentFinished = true;
+}
+
+HRESULT
+ToastNotificationHandler::OnActivate(
+ const ComPtr<IToastNotification>& notification,
+ const ComPtr<IInspectable>& inspectable) {
+ MOZ_LOG(sWASLog, LogLevel::Info, ("OnActivate"));
+
+ if (mAlertListener) {
+ // Extract the `action` value from the argument string.
+ nsAutoString argumentsString;
+ nsAutoString actionString;
+ if (inspectable) {
+ ComPtr<IToastActivatedEventArgs> eventArgs;
+ HRESULT hr = inspectable.As(&eventArgs);
+ if (SUCCEEDED(hr)) {
+ HString arguments;
+ hr = eventArgs->get_Arguments(arguments.GetAddressOf());
+ if (SUCCEEDED(hr)) {
+ uint32_t len = 0;
+ const char16_t* buffer = (char16_t*)arguments.GetRawBuffer(&len);
+ if (buffer) {
+ MOZ_LOG(sWASLog, LogLevel::Info,
+ ("OnActivate: arguments: %s",
+ NS_ConvertUTF16toUTF8(buffer).get()));
+ argumentsString.Assign(buffer);
+
+ // Toast arguments are a newline separated key/value combination of
+ // launch arguments and an optional action argument provided as an
+ // argument to the toast's constructor. After the `action` key is
+ // found, the remainder of toast argument (including newlines) is
+ // the `action` value.
+ Tokenizer16 parse(buffer);
+ nsDependentSubstring token;
+
+ while (parse.ReadUntil(Tokenizer16::Token::NewLine(), token)) {
+ if (token == nsDependentString(kLaunchArgAction)) {
+ Unused << parse.ReadUntil(Tokenizer16::Token::EndOfFile(),
+ actionString);
+ } else {
+ // Next line is a value in a key/value pair, skip.
+ parse.SkipUntil(Tokenizer16::Token::NewLine());
+ }
+ // Skip newline.
+ Tokenizer16::Token unused;
+ Unused << parse.Next(unused);
+ }
+ }
+ }
+ }
+ }
+
+ if (argumentsString.EqualsLiteral("dismiss")) {
+ // XXX: Somehow Windows still fires OnActivate instead of OnDismiss for
+ // supposedly system managed dismiss button (with activationType=system
+ // and arguments=dismiss). We have to manually treat such callback as a
+ // dismiss action. For this case `arguments` only includes a keyword so we
+ // don't need to compare with a parsed result.
+ SendFinished();
+ } else if (actionString.EqualsLiteral("settings")) {
+ mAlertListener->Observe(nullptr, "alertsettingscallback", mCookie.get());
+ } else if (actionString.EqualsLiteral("snooze")) {
+ mAlertListener->Observe(nullptr, "alertdisablecallback", mCookie.get());
+ } else if (mClickable) {
+ // When clicking toast, focus moves to another process, but we want to set
+ // focus on Firefox process.
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (winMediator) {
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+ winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (navWin) {
+ nsCOMPtr<nsIWidget> widget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin));
+ if (widget) {
+ SetForegroundWindow(
+ static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)));
+ }
+ }
+ }
+
+ if (mHandleActions) {
+ Json::Value jsonData;
+ Json::Reader jsonReader;
+
+ if (jsonReader.parse(NS_ConvertUTF16toUTF8(actionString).get(),
+ jsonData, false)) {
+ char actionKey[] = "action";
+ if (jsonData.isMember(actionKey) && jsonData[actionKey].isString()) {
+ mAlertListener->Observe(
+ nullptr, "alertactioncallback",
+ NS_ConvertUTF8toUTF16(jsonData[actionKey].asCString()).get());
+ }
+ }
+ }
+
+ mAlertListener->Observe(nullptr, "alertclickcallback", mCookie.get());
+ }
+ }
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+// Returns `nullptr` if no such toast exists.
+/* static */ ComPtr<IToastNotification>
+ToastNotificationHandler::FindNotificationByTag(const nsAString& aWindowsTag,
+ const nsAString& aAumid) {
+ HRESULT hr = S_OK;
+
+ HString current_id;
+ current_id.Set(PromiseFlatString(aWindowsTag).get());
+
+ ComPtr<IToastNotificationManagerStatics> manager =
+ GetToastNotificationManagerStatics();
+ NS_ENSURE_TRUE(manager, nullptr);
+
+ ComPtr<IToastNotificationManagerStatics2> manager2;
+ hr = manager.As(&manager2);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IToastNotificationHistory> history;
+ hr = manager2->get_History(&history);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ ComPtr<IToastNotificationHistory2> history2;
+ hr = history.As(&history2);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ ComPtr<IVectorView_ToastNotification> toasts;
+ hr = history2->GetHistoryWithId(
+ HStringReference(PromiseFlatString(aAumid).get()).Get(), &toasts);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ unsigned int hist_size;
+ hr = toasts->get_Size(&hist_size);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+ for (unsigned int i = 0; i < hist_size; i++) {
+ ComPtr<IToastNotification> hist_toast;
+ hr = toasts->GetAt(i, &hist_toast);
+ if (NS_WARN_IF(FAILED(hr))) {
+ continue;
+ }
+
+ ComPtr<IToastNotification2> hist_toast2;
+ hr = hist_toast.As(&hist_toast2);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ HString history_id;
+ hr = hist_toast2->get_Tag(history_id.GetAddressOf());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);
+
+ // We can not directly compare IToastNotification objects; their IUnknown
+ // pointers should be equivalent but under inspection were not. Therefore we
+ // use the notification's tag instead.
+ if (current_id == history_id) {
+ return hist_toast;
+ }
+ }
+
+ return nullptr;
+}
+
+// A single toast message can receive multiple dismiss events, at most one for
+// the popup and at most one for the action center. We can't simply count
+// dismiss events as the user may have disabled either popups or action center
+// notifications, therefore we have to check if the toast remains in the history
+// (action center) to determine if the toast is fully dismissed.
+HRESULT
+ToastNotificationHandler::OnDismiss(
+ const ComPtr<IToastNotification>& notification,
+ const ComPtr<IToastDismissedEventArgs>& aArgs) {
+ ComPtr<IToastNotification2> notification2;
+ HRESULT hr = notification.As(&notification2);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
+
+ HString tagHString;
+ hr = notification2->get_Tag(tagHString.GetAddressOf());
+ NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL);
+
+ unsigned int len;
+ const wchar_t* tagPtr = tagHString.GetRawBuffer(&len);
+ nsAutoString tag(tagPtr, len);
+
+ if (FindNotificationByTag(tag, mAumid)) {
+ return S_OK;
+ }
+
+ SendFinished();
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+HRESULT
+ToastNotificationHandler::OnFail(const ComPtr<IToastNotification>& notification,
+ const ComPtr<IToastFailedEventArgs>& aArgs) {
+ HRESULT err;
+ aArgs->get_ErrorCode(&err);
+ MOZ_LOG(sWASLog, LogLevel::Error,
+ ("Error creating notification, error: %ld", err));
+
+ if (mHandleActions) {
+ mAlertListener->Observe(nullptr, "alerterror", mCookie.get());
+ }
+
+ SendFinished();
+ mBackend->RemoveHandler(mName, this);
+ return S_OK;
+}
+
+nsresult ToastNotificationHandler::TryShowAlert() {
+ if (NS_WARN_IF(!ShowAlert())) {
+ mBackend->RemoveHandler(mName, this);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageMissing(nsISupports*) {
+ return TryShowAlert();
+}
+
+NS_IMETHODIMP
+ToastNotificationHandler::OnImageReady(nsISupports*, imgIRequest* aRequest) {
+ nsresult rv = AsyncSaveImage(aRequest);
+ if (NS_FAILED(rv)) {
+ return TryShowAlert();
+ }
+ return rv;
+}
+
+nsresult ToastNotificationHandler::AsyncSaveImage(imgIRequest* aRequest) {
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mImageFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mImageFile->Append(u"notificationimages"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mImageFile->Create(nsIFile::DIRECTORY_TYPE, 0500);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ return rv;
+ }
+
+ nsID uuid;
+ rv = nsID::GenerateUUIDInPlace(uuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NSID_TrimBracketsASCII uuidStr(uuid);
+ uuidStr.AppendLiteral(".png");
+ mImageFile->AppendNative(uuidStr);
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ rv = aRequest->GetImage(getter_AddRefs(imgContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMainThreadPtrHandle<ToastNotificationHandler> self(
+ new nsMainThreadPtrHolder<ToastNotificationHandler>(
+ "ToastNotificationHandler", this));
+
+ nsCOMPtr<nsIFile> imageFile(mImageFile);
+ RefPtr<mozilla::gfx::SourceSurface> surface = imgContainer->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "ToastNotificationHandler::AsyncWriteImage",
+ [self, imageFile, surface]() -> void {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (surface) {
+ FILE* file = nullptr;
+ rv = imageFile->OpenANSIFileDesc("wb", &file);
+ if (NS_SUCCEEDED(rv)) {
+ rv = gfxUtils::EncodeSourceSurface(surface, ImageType::PNG, u""_ns,
+ gfxUtils::eBinaryEncode, file);
+ fclose(file);
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction(
+ "ToastNotificationHandler::AsyncWriteImageCb",
+ [self, rv]() -> void {
+ auto handler = const_cast<ToastNotificationHandler*>(self.get());
+ handler->OnWriteImageFinished(rv);
+ });
+
+ NS_DispatchToMainThread(cbRunnable);
+ });
+
+ return mBackend->BackgroundDispatch(r);
+}
+
+void ToastNotificationHandler::OnWriteImageFinished(nsresult rv) {
+ if (NS_SUCCEEDED(rv)) {
+ OnWriteImageSuccess();
+ }
+ TryShowAlert();
+}
+
+nsresult ToastNotificationHandler::OnWriteImageSuccess() {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewFileURI(getter_AddRefs(fileURI), mImageFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uriStr;
+ rv = fileURI->GetSpec(uriStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AppendUTF8toUTF16(uriStr, mImageUri);
+
+ mHasImage = true;
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/ToastNotificationHandler.h b/widget/windows/ToastNotificationHandler.h
new file mode 100644
index 0000000000..b3be34709c
--- /dev/null
+++ b/widget/windows/ToastNotificationHandler.h
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 ToastNotificationHandler_h__
+#define ToastNotificationHandler_h__
+
+#include <windows.ui.notifications.h>
+#include <windows.data.xml.dom.h>
+#include <wrl.h>
+#include "nsCOMPtr.h"
+#include "nsICancelable.h"
+#include "nsIFile.h"
+#include "nsIWindowsAlertsService.h"
+#include "nsString.h"
+#include "mozilla/Result.h"
+
+namespace mozilla {
+namespace widget {
+
+enum class ImagePlacement {
+ eInline,
+ eHero,
+ eIcon,
+};
+
+class ToastNotification;
+
+class ToastNotificationHandler final
+ : public nsIAlertNotificationImageListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ ToastNotificationHandler(
+ ToastNotification* backend, const nsAString& aumid,
+ nsIObserver* aAlertListener, const nsAString& aName,
+ const nsAString& aCookie, const nsAString& aTitle, const nsAString& aMsg,
+ const nsAString& aHostPort, bool aClickable, bool aRequireInteraction,
+ const nsTArray<RefPtr<nsIAlertAction>>& aActions, bool aIsSystemPrincipal,
+ const nsAString& aOpaqueRelaunchData, bool aInPrivateBrowsing,
+ bool aIsSilent, bool aHandlesActions = false,
+ ImagePlacement aImagePlacement = ImagePlacement::eInline)
+ : mBackend(backend),
+ mAumid(aumid),
+ mHasImage(false),
+ mAlertListener(aAlertListener),
+ mName(aName),
+ mCookie(aCookie),
+ mTitle(aTitle),
+ mMsg(aMsg),
+ mHostPort(aHostPort),
+ mClickable(aClickable),
+ mRequireInteraction(aRequireInteraction),
+ mInPrivateBrowsing(aInPrivateBrowsing),
+ mActions(aActions.Clone()),
+ mIsSystemPrincipal(aIsSystemPrincipal),
+ mOpaqueRelaunchData(aOpaqueRelaunchData),
+ mIsSilent(aIsSilent),
+ mSentFinished(!aAlertListener),
+ mHandleActions(aHandlesActions),
+ mImagePlacement(aImagePlacement) {}
+
+ nsresult InitAlertAsync(nsIAlertNotification* aAlert);
+
+ void OnWriteImageFinished(nsresult rv);
+
+ void HideAlert();
+ bool IsPrivate();
+
+ void UnregisterHandler();
+
+ nsString ActionArgsJSONString(
+ const nsString& aAction,
+ const nsString& aOpaqueRelaunchData /* = u""_ns */);
+ nsresult CreateToastXmlString(const nsAString& aImageURL, nsAString& aString);
+
+ nsresult GetWindowsTag(nsAString& aWindowsTag);
+ nsresult SetWindowsTag(const nsAString& aWindowsTag);
+
+ // Exposed for consumption by `ToastNotification.cpp`.
+ static nsresult FindNotificationDataForWindowsTag(
+ const nsAString& aWindowsTag, const nsAString& aAumid, bool& aFoundTag,
+ nsAString& aNotificationData);
+
+ protected:
+ virtual ~ToastNotificationHandler();
+
+ using IXmlDocument = ABI::Windows::Data::Xml::Dom::IXmlDocument;
+ using IToastNotifier = ABI::Windows::UI::Notifications::IToastNotifier;
+ using IToastNotification =
+ ABI::Windows::UI::Notifications::IToastNotification;
+ using IToastDismissedEventArgs =
+ ABI::Windows::UI::Notifications::IToastDismissedEventArgs;
+ using IToastFailedEventArgs =
+ ABI::Windows::UI::Notifications::IToastFailedEventArgs;
+ using ToastTemplateType = ABI::Windows::UI::Notifications::ToastTemplateType;
+ template <typename T>
+ using ComPtr = Microsoft::WRL::ComPtr<T>;
+
+ Result<nsString, nsresult> GetLaunchArgument();
+
+ ComPtr<IToastNotification> mNotification;
+ ComPtr<IToastNotifier> mNotifier;
+
+ RefPtr<ToastNotification> mBackend;
+
+ nsString mAumid;
+ nsString mWindowsTag;
+
+ nsCOMPtr<nsICancelable> mImageRequest;
+ nsCOMPtr<nsIFile> mImageFile;
+ nsString mImageUri;
+ bool mHasImage;
+
+ EventRegistrationToken mActivatedToken;
+ EventRegistrationToken mDismissedToken;
+ EventRegistrationToken mFailedToken;
+
+ nsCOMPtr<nsIObserver> mAlertListener;
+ nsString mName;
+ nsString mCookie;
+ nsString mTitle;
+ nsString mMsg;
+ nsString mHostPort;
+ bool mClickable;
+ bool mRequireInteraction;
+ bool mInPrivateBrowsing;
+ nsTArray<RefPtr<nsIAlertAction>> mActions;
+ bool mIsSystemPrincipal;
+ nsString mOpaqueRelaunchData;
+ bool mIsSilent;
+ bool mSentFinished;
+ bool mHandleActions;
+ ImagePlacement mImagePlacement;
+
+ nsresult TryShowAlert();
+ bool ShowAlert();
+ nsresult AsyncSaveImage(imgIRequest* aRequest);
+ nsresult OnWriteImageSuccess();
+ void SendFinished();
+
+ nsresult InitWindowsTag();
+ bool CreateWindowsNotificationFromXml(ComPtr<IXmlDocument>& aToastXml);
+ ComPtr<IXmlDocument> CreateToastXmlDocument();
+
+ HRESULT OnActivate(const ComPtr<IToastNotification>& notification,
+ const ComPtr<IInspectable>& inspectable);
+ HRESULT OnDismiss(const ComPtr<IToastNotification>& notification,
+ const ComPtr<IToastDismissedEventArgs>& aArgs);
+ HRESULT OnFail(const ComPtr<IToastNotification>& notification,
+ const ComPtr<IToastFailedEventArgs>& aArgs);
+
+ static ComPtr<IToastNotification> FindNotificationByTag(
+ const nsAString& aWindowsTag, const nsAString& aAumid);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/windows/ToastNotificationHeaderOnlyUtils.h b/widget/windows/ToastNotificationHeaderOnlyUtils.h
new file mode 100644
index 0000000000..dd777c0c32
--- /dev/null
+++ b/widget/windows/ToastNotificationHeaderOnlyUtils.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ToastNotificationHeaderOnlyUtils_h
+#define mozilla_ToastNotificationHeaderOnlyUtils_h
+
+/**
+ * This header is intended for self-contained, header-only, utility code to
+ * share between Windows toast notification code in firefox.exe and
+ * notificationserver.dll.
+ */
+
+// Use XPCOM logging if we're in a XUL context, otherwise use Windows Event
+// logging.
+// NOTE: The `printf` `format` equivalent argument to `NOTIFY_LOG` is converted
+// to a wide string when outside of a XUL context. String format specifiers need
+// to specify they're a wide string with `%ls` or narrow string with `%hs`.
+#include "mozilla/Logging.h"
+#ifdef IMPL_LIBXUL
+namespace mozilla::widget {
+extern LazyLogModule sWASLog;
+} // namespace mozilla::widget
+# define NOTIFY_LOG(_level, _args) \
+ MOZ_LOG(mozilla::widget::sWASLog, _level, _args)
+#else
+# include "mozilla/WindowsEventLog.h"
+
+bool gVerbose = false;
+
+# define NOTIFY_LOG(_level, _args) \
+ if (gVerbose || _level == mozilla::LogLevel::Error) { \
+ POST_EXPAND_NOTIFY_LOG(MOZ_LOG_EXPAND_ARGS _args); \
+ }
+# define POST_EXPAND_NOTIFY_LOG(...) \
+ MOZ_WIN_EVENT_LOG_ERROR_MESSAGE( \
+ L"" MOZ_APP_DISPLAYNAME " Notification Server", L"" __VA_ARGS__)
+#endif
+
+#include <functional>
+#include <string>
+
+#include "nsWindowsHelpers.h"
+
+namespace mozilla::widget::toastnotification {
+
+const wchar_t kLaunchArgProgram[] = L"program";
+const wchar_t kLaunchArgProfile[] = L"profile";
+const wchar_t kLaunchArgTag[] = L"windowsTag";
+const wchar_t kLaunchArgLogging[] = L"logging";
+const wchar_t kLaunchArgAction[] = L"action";
+
+const DWORD kNotificationServerTimeoutMs = (10 * 1000);
+
+struct ToastNotificationPidMessage {
+ DWORD pid = 0;
+};
+
+struct ToastNotificationPermissionMessage {
+ DWORD setForegroundPermissionGranted = 0;
+};
+
+inline std::wstring GetNotificationPipeName(const wchar_t* aTag) {
+ // Prefix required by pipe API.
+ std::wstring pipeName(LR"(\\.\pipe\)");
+
+ pipeName += L"" MOZ_APP_NAME;
+ pipeName += aTag;
+
+ return pipeName;
+}
+
+inline bool WaitEventWithTimeout(const HANDLE& event) {
+ DWORD result = WaitForSingleObject(event, kNotificationServerTimeoutMs);
+
+ switch (result) {
+ case WAIT_OBJECT_0:
+ NOTIFY_LOG(LogLevel::Info, ("Pipe wait signaled"));
+ return true;
+ case WAIT_TIMEOUT:
+ NOTIFY_LOG(LogLevel::Warning, ("Pipe wait timed out"));
+ return false;
+ case WAIT_FAILED:
+ NOTIFY_LOG(LogLevel::Error,
+ ("Pipe wait failed, error %lu", GetLastError()));
+ return false;
+ case WAIT_ABANDONED:
+ NOTIFY_LOG(LogLevel::Error, ("Pipe wait abandoned"));
+ return false;
+ default:
+ NOTIFY_LOG(LogLevel::Error, ("Pipe wait unknown error"));
+ return false;
+ }
+}
+
+/* Handles running overlapped transactions for a Windows pipe. This function
+ * manages lifetimes of Event and OVERLAPPED objects to ensure they are not used
+ * while an overlapped operation is pending. */
+inline bool SyncDoOverlappedIOWithTimeout(
+ const nsAutoHandle& pipe, const size_t bytesExpected,
+ const std::function<BOOL(OVERLAPPED&)>& transactPipe) {
+ nsAutoHandle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
+ if (!event) {
+ NOTIFY_LOG(
+ LogLevel::Error,
+ ("Error creating pipe transaction event, error %lu", GetLastError()));
+ return false;
+ }
+
+ OVERLAPPED overlapped{};
+ overlapped.hEvent = event.get();
+ BOOL result = transactPipe(overlapped);
+
+ if (!result && GetLastError() != ERROR_IO_PENDING) {
+ NOTIFY_LOG(LogLevel::Error,
+ ("Error reading from pipe, error %lu", GetLastError()));
+ return false;
+ }
+
+ if (!WaitEventWithTimeout(overlapped.hEvent)) {
+ NOTIFY_LOG(LogLevel::Warning, ("Pipe transaction timed out, canceling "
+ "(transaction may still succeed)."));
+
+ CancelIo(pipe.get());
+
+ // Transaction may still succeed before cancellation is handled; fall
+ // through to normal handling.
+ }
+
+ DWORD bytesTransferred = 0;
+ // Pipe transfer has either been signaled or cancelled by this point, so it
+ // should be safe to wait on.
+ BOOL overlappedResult =
+ GetOverlappedResult(pipe.get(), &overlapped, &bytesTransferred, TRUE);
+
+ if (!overlappedResult) {
+ NOTIFY_LOG(
+ LogLevel::Error,
+ ("Error retrieving pipe overlapped result, error %lu", GetLastError()));
+ return false;
+ } else if (bytesTransferred != bytesExpected) {
+ NOTIFY_LOG(LogLevel::Error,
+ ("%lu bytes read from pipe, but %zu bytes expected",
+ bytesTransferred, bytesExpected));
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla::widget::toastnotification
+
+#endif // mozilla_ToastNotificationHeaderOnlyUtils_h
diff --git a/widget/windows/UrlmonHeaderOnlyUtils.h b/widget/windows/UrlmonHeaderOnlyUtils.h
new file mode 100644
index 0000000000..dd9209f78f
--- /dev/null
+++ b/widget/windows/UrlmonHeaderOnlyUtils.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UrlmonHeaderOnlyUtils_h
+#define mozilla_UrlmonHeaderOnlyUtils_h
+
+#include "mozilla/ShellHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+/**
+ * We used to validate a uri with SHParseDisplayName to mitigate the Windows
+ * bug (Bug 394974). However, Bug 1573051 revealed an issue that a fragment,
+ * a string following a hash mark (#), is dropped when we extract a string
+ * from PIDL. This is the intended behavior of Windows.
+ *
+ * To deal with the fragment issue as well as keeping our mitigation, we
+ * decided to use CreateUri to validate a uri string, but we also keep using
+ * SHParseDisplayName as a pre-check. This is because there are several
+ * cases where CreateUri succeeds while SHParseDisplayName fails such as
+ * a non-existent file: uri.
+ *
+ * To minimize the impact of introducing CreateUri into the validation logic,
+ * we try to mimic the logic of windows_storage!IUriToPidl (ieframe!IUriToPidl
+ * in Win7) which is executed behind SHParseDisplayName.
+ * What IUriToPidl does is:
+ * 1) If a given uri has a fragment, removes a fragment.
+ * 2) Takes an absolute uri if it's available in the given uri, otherwise
+ * takes a raw uri.
+ *
+ * As we need to get a full uri including a fragment, this function does 2).
+ */
+inline LauncherResult<_bstr_t> UrlmonValidateUri(const wchar_t* aUri) {
+ LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri);
+ if (pidlResult.isErr()) {
+ return pidlResult.propagateErr();
+ }
+
+ // The value of |flags| is the same value as used in ieframe!_EnsureIUri in
+ // Win7, which is called behind SHParseDisplayName. In Win10, on the other
+ // hand, an flag 0x03000000 is also passed to CreateUri, but we don't
+ // specify it because it's undocumented and unknown.
+ constexpr DWORD flags =
+ Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+ Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+ Uri_CREATE_IE_SETTINGS;
+ RefPtr<IUri> uri;
+ HRESULT hr;
+ SAFECALL_URLMON_FUNC(CreateUri, aUri, flags, 0, getter_AddRefs(uri));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ _bstr_t bstrUri;
+
+ hr = uri->GetAbsoluteUri(bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ if (hr == S_FALSE) {
+ hr = uri->GetRawUri(bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+ }
+
+ return bstrUri;
+}
+
+} // namespace mozilla
+
+#endif // mozilla_UrlmonHeaderOnlyUtils_h
diff --git a/widget/windows/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..15bb4d720c
--- /dev/null
+++ b/widget/windows/WidgetTraceEvent.cpp
@@ -0,0 +1,121 @@
+/* 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/. */
+
+/*
+ * Windows widget support for event loop instrumentation.
+ * See toolkit/xre/EventTracer.cpp for more details.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShellCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIAppShellService.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsISupportsImpl.h"
+#include "nsIWidget.h"
+#include "nsIAppWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsWindowDefs.h"
+
+namespace {
+
+// Used for signaling the background thread from the main thread.
+HANDLE sEventHandle = nullptr;
+
+// We need a runnable in order to find the hidden window on the main
+// thread.
+class HWNDGetter : public mozilla::Runnable {
+ public:
+ HWNDGetter() : Runnable("HWNDGetter"), hidden_window_hwnd(nullptr) {}
+
+ HWND hidden_window_hwnd;
+
+ NS_IMETHOD Run() override {
+ // Jump through some hoops to locate the hidden window.
+ nsCOMPtr<nsIAppShellService> appShell(
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ nsCOMPtr<nsIAppWindow> hiddenWindow;
+
+ nsresult rv = appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = hiddenWindow->GetDocShell(getter_AddRefs(docShell));
+ if (NS_FAILED(rv) || !docShell) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell));
+
+ if (!baseWindow) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+
+ if (!widget) return NS_ERROR_FAILURE;
+
+ hidden_window_hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+
+ return NS_OK;
+ }
+};
+
+HWND GetHiddenWindowHWND() {
+ // Need to dispatch this to the main thread because plenty of
+ // the things it wants to access are main-thread-only.
+ RefPtr<HWNDGetter> getter = new HWNDGetter();
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "GetHiddenWindowHWND"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(getter));
+ return getter->hidden_window_hwnd;
+}
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing() {
+ sEventHandle = CreateEventW(nullptr, FALSE, FALSE, nullptr);
+ return sEventHandle != nullptr;
+}
+
+void CleanUpWidgetTracing() {
+ CloseHandle(sEventHandle);
+ sEventHandle = nullptr;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread() {
+ if (sEventHandle != nullptr) SetEvent(sEventHandle);
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent() {
+ MOZ_ASSERT(sEventHandle, "Tracing not initialized!");
+
+ // First, try to find the hidden window.
+ static HWND hidden_window = nullptr;
+ if (hidden_window == nullptr) {
+ hidden_window = GetHiddenWindowHWND();
+ }
+
+ if (hidden_window == nullptr) return false;
+
+ // Post the tracer message into the hidden window's message queue,
+ // and then block until it's processed.
+ PostMessage(hidden_window, MOZ_WM_TRACE, 0, 0);
+ WaitForSingleObject(sEventHandle, INFINITE);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.cpp b/widget/windows/WinCompositorWidget.cpp
new file mode 100644
index 0000000000..15957a1c3f
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "WinCompositorWidget.h"
+
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+#include "WinCompositorWindowThread.h"
+#include "VRShMem.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+WinCompositorWidget::WinCompositorWidget(
+ const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : CompositorWidget(aOptions),
+ mSetParentCompleted(false),
+ mWidgetKey(aInitData.widgetKey()),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mCompositorWnds(nullptr, nullptr) {
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+}
+
+WinCompositorWidget::~WinCompositorWidget() { DestroyCompositorWindow(); }
+
+uintptr_t WinCompositorWidget::GetWidgetKey() { return mWidgetKey; }
+
+void WinCompositorWidget::EnsureCompositorWindow() {
+ if (mCompositorWnds.mCompositorWnd || mCompositorWnds.mInitialParentWnd) {
+ return;
+ }
+
+ mCompositorWnds = WinCompositorWindowThread::CreateCompositorWindow();
+ UpdateCompositorWnd(mCompositorWnds.mCompositorWnd, mWnd);
+
+ MOZ_ASSERT(mCompositorWnds.mCompositorWnd);
+ MOZ_ASSERT(mCompositorWnds.mInitialParentWnd);
+}
+
+void WinCompositorWidget::DestroyCompositorWindow() {
+ if (!mCompositorWnds.mCompositorWnd && !mCompositorWnds.mInitialParentWnd) {
+ return;
+ }
+ WinCompositorWindowThread::DestroyCompositorWindow(mCompositorWnds);
+ mCompositorWnds = WinCompositorWnds(nullptr, nullptr);
+}
+
+void WinCompositorWidget::UpdateCompositorWndSizeIfNecessary() {
+ if (!mCompositorWnds.mCompositorWnd) {
+ return;
+ }
+
+ LayoutDeviceIntSize size = GetClientSize();
+ if (mLastCompositorWndSize == size) {
+ return;
+ }
+
+ // This code is racing with the compositor, which needs to reparent the
+ // compositor surface to the actual window (mWnd). To avoid racing mutations,
+ // we refuse to proceed until ::SetParent() is called in the parent process.
+ // After the ::SetParent() call, composition is scheduled in
+ // CompositorWidgetParent::UpdateCompositorWnd().
+ if (!mSetParentCompleted) {
+ // ::SetParent() is not completed yet.
+ return;
+ }
+
+ MOZ_ASSERT(mWnd == ::GetParent(mCompositorWnds.mCompositorWnd));
+
+ // Force a resize and redraw (but not a move, activate, etc.).
+ if (!::SetWindowPos(
+ mCompositorWnds.mCompositorWnd, nullptr, 0, 0, size.width,
+ size.height,
+ SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER | SWP_NOZORDER)) {
+ return;
+ }
+
+ mLastCompositorWndSize = size;
+}
+
+// Creates a new instance of FxROutputHandler so that this compositor widget
+// can send its output to Firefox Reality for Desktop.
+void WinCompositorWidget::RequestFxrOutput() {
+ MOZ_ASSERT(mFxrHandler == nullptr);
+
+ mFxrHandler.reset(new FxROutputHandler());
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h
new file mode 100644
index 0000000000..fef967380c
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 widget_windows_WinCompositorWidget_h
+#define widget_windows_WinCompositorWidget_h
+
+#include "CompositorWidget.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gfx/CriticalSection.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/widget/WinCompositorWindowThread.h"
+#include "FxROutputHandler.h"
+#include "nsIWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
+ public:
+ // Callbacks for nsWindow.
+ virtual void EnterPresentLock() = 0;
+ virtual void LeavePresentLock() = 0;
+ virtual void OnDestroyWindow() = 0;
+ virtual bool OnWindowResize(const LayoutDeviceIntSize& aSize) = 0;
+ virtual void OnWindowModeChange(nsSizeMode aSizeMode) = 0;
+
+ // Transparency handling.
+ virtual void UpdateTransparency(TransparencyMode aMode) = 0;
+ virtual void ClearTransparentWindow() = 0;
+
+ // Deliver visibility info
+ virtual void NotifyVisibilityUpdated(nsSizeMode aSizeMode,
+ bool aIsFullyOccluded) = 0;
+
+ // CompositorWidgetDelegate Overrides
+
+ PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override {
+ return this;
+ }
+};
+
+class WinCompositorWidgetInitData;
+
+// This is the Windows-specific implementation of CompositorWidget. For
+// the most part it only requires an HWND, however it maintains extra state
+// for transparent windows, as well as for synchronizing WM_SETTEXT messages
+// with the compositor.
+class WinCompositorWidget : public CompositorWidget {
+ public:
+ WinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~WinCompositorWidget() override;
+
+ // CompositorWidget Overrides
+
+ uintptr_t GetWidgetKey() override;
+ WinCompositorWidget* AsWindows() override { return this; }
+
+ HWND GetHwnd() const {
+ return mCompositorWnds.mCompositorWnd ? mCompositorWnds.mCompositorWnd
+ : mWnd;
+ }
+
+ HWND GetCompositorHwnd() const { return mCompositorWnds.mCompositorWnd; }
+
+ void EnsureCompositorWindow();
+ void DestroyCompositorWindow();
+ void UpdateCompositorWndSizeIfNecessary();
+
+ void RequestFxrOutput();
+ bool HasFxrOutputHandler() const { return mFxrHandler != nullptr; }
+ FxROutputHandler* GetFxrOutputHandler() const { return mFxrHandler.get(); }
+
+ virtual nsSizeMode GetWindowSizeMode() const = 0;
+ virtual bool GetWindowIsFullyOccluded() const = 0;
+
+ virtual void UpdateCompositorWnd(const HWND aCompositorWnd,
+ const HWND aParentWnd) = 0;
+ virtual void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) = 0;
+
+ protected:
+ bool mSetParentCompleted;
+
+ private:
+ uintptr_t mWidgetKey;
+ HWND mWnd;
+
+ WinCompositorWnds mCompositorWnds;
+ LayoutDeviceIntSize mLastCompositorWndSize;
+
+ UniquePtr<FxROutputHandler> mFxrHandler;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinCompositorWidget_h
diff --git a/widget/windows/WinCompositorWindowThread.cpp b/widget/windows/WinCompositorWindowThread.cpp
new file mode 100644
index 0000000000..3b06c098e6
--- /dev/null
+++ b/widget/windows/WinCompositorWindowThread.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "base/platform_thread.h"
+#include "WinCompositorWindowThread.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/StaticPtr.h"
+#include "transport/runnable_utils.h"
+#include "mozilla/StaticPrefs_apz.h"
+
+namespace mozilla {
+namespace widget {
+
+static StaticRefPtr<WinCompositorWindowThread> sWinCompositorWindowThread;
+
+/// A window procedure that logs when an input event is received to the gfx
+/// error log
+///
+/// This is done because this window is supposed to be WM_DISABLED, but
+/// malfunctioning software may still end up targetting this window. If that
+/// happens, it's almost-certainly a bug and should be brought to the attention
+/// of the developers that are debugging the issue.
+static LRESULT CALLBACK InputEventRejectingWindowProc(HWND window, UINT msg,
+ WPARAM wparam,
+ LPARAM lparam) {
+ switch (msg) {
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONUP:
+ case WM_MBUTTONDOWN:
+ case WM_MBUTTONUP:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_MOUSEMOVE:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYDOWN:
+ case WM_SYSKEYUP:
+ gfxCriticalNoteOnce
+ << "The compositor window received an input event even though it's "
+ "disabled. There is likely malfunctioning "
+ "software on the user's machine.";
+
+ break;
+ default:
+ break;
+ }
+ return ::DefWindowProcW(window, msg, wparam, lparam);
+}
+
+WinCompositorWindowThread::WinCompositorWindowThread(base::Thread* aThread)
+ : mThread(aThread), mMonitor("WinCompositorWindowThread") {}
+
+/* static */
+WinCompositorWindowThread* WinCompositorWindowThread::Get() {
+ if (!sWinCompositorWindowThread ||
+ sWinCompositorWindowThread->mHasAttemptedShutdown) {
+ return nullptr;
+ }
+ return sWinCompositorWindowThread;
+}
+
+/* static */
+void WinCompositorWindowThread::Start() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ base::Thread::Options options;
+ // HWND requests ui thread.
+ options.message_loop_type = MessageLoop::TYPE_UI;
+
+ if (sWinCompositorWindowThread) {
+ // Try to reuse the thread, which involves stopping and restarting it.
+ sWinCompositorWindowThread->mThread->Stop();
+ if (sWinCompositorWindowThread->mThread->StartWithOptions(options)) {
+ // Success!
+ sWinCompositorWindowThread->mHasAttemptedShutdown = false;
+ return;
+ }
+ // Restart failed, so null out our sWinCompositorWindowThread and
+ // try again with a new thread. This will cause the old singleton
+ // instance to be deallocated, which will destroy its mThread as well.
+ sWinCompositorWindowThread = nullptr;
+ }
+
+ base::Thread* thread = new base::Thread("WinCompositor");
+ if (!thread->StartWithOptions(options)) {
+ delete thread;
+ return;
+ }
+
+ sWinCompositorWindowThread = new WinCompositorWindowThread(thread);
+}
+
+/* static */
+void WinCompositorWindowThread::ShutDown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sWinCompositorWindowThread);
+
+ sWinCompositorWindowThread->mHasAttemptedShutdown = true;
+
+ // Our thread could hang while we're waiting for it to stop.
+ // Since we're shutting down, that's not a critical problem.
+ // We set a reasonable amount of time to wait for shutdown,
+ // and if it succeeds within that time, we correctly stop
+ // our thread by nulling out the refptr, which will cause it
+ // to be deallocated and join the thread. If it times out,
+ // we do nothing, which means that the thread will not be
+ // joined and sWinCompositorWindowThread memory will leak.
+ CVStatus status;
+ {
+ // It's important to hold the lock before posting the
+ // runnable. This ensures that the runnable can't begin
+ // until we've started our Wait, which prevents us from
+ // Waiting on a monitor that has already been notified.
+ MonitorAutoLock lock(sWinCompositorWindowThread->mMonitor);
+
+ static const TimeDuration TIMEOUT = TimeDuration::FromSeconds(2.0);
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod("WinCompositorWindowThread::ShutDownTask",
+ sWinCompositorWindowThread.get(),
+ &WinCompositorWindowThread::ShutDownTask);
+ Loop()->PostTask(runnable.forget());
+
+ // Monitor uses SleepConditionVariableSRW, which can have
+ // spurious wakeups which are reported as timeouts, so we
+ // check timestamps to ensure that we've waited as long we
+ // intended to. If we wake early, we don't bother calculating
+ // a precise amount for the next wait; we just wait the same
+ // amount of time. This means timeout might happen after as
+ // much as 2x the TIMEOUT time.
+ TimeStamp timeStart = TimeStamp::NowLoRes();
+ do {
+ status = sWinCompositorWindowThread->mMonitor.Wait(TIMEOUT);
+ } while ((status == CVStatus::Timeout) &&
+ ((TimeStamp::NowLoRes() - timeStart) < TIMEOUT));
+ }
+
+ if (status == CVStatus::NoTimeout) {
+ sWinCompositorWindowThread = nullptr;
+ }
+}
+
+void WinCompositorWindowThread::ShutDownTask() {
+ MonitorAutoLock lock(mMonitor);
+
+ MOZ_ASSERT(IsInCompositorWindowThread());
+ mMonitor.NotifyAll();
+}
+
+/* static */
+MessageLoop* WinCompositorWindowThread::Loop() {
+ return sWinCompositorWindowThread
+ ? sWinCompositorWindowThread->mThread->message_loop()
+ : nullptr;
+}
+
+/* static */
+bool WinCompositorWindowThread::IsInCompositorWindowThread() {
+ return sWinCompositorWindowThread &&
+ sWinCompositorWindowThread->mThread->thread_id() ==
+ PlatformThread::CurrentId();
+}
+
+const wchar_t kClassNameCompositorInitalParent[] =
+ L"MozillaCompositorInitialParentClass";
+const wchar_t kClassNameCompositor[] = L"MozillaCompositorWindowClass";
+
+ATOM g_compositor_inital_parent_window_class;
+ATOM g_compositor_window_class;
+
+// This runs on the window owner thread.
+void InitializeInitialParentWindowClass() {
+ if (g_compositor_inital_parent_window_class) {
+ return;
+ }
+
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = ::DefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameCompositorInitalParent;
+ g_compositor_inital_parent_window_class = ::RegisterClassW(&wc);
+}
+
+// This runs on the window owner thread.
+void InitializeWindowClass() {
+ if (g_compositor_window_class) {
+ return;
+ }
+
+ WNDCLASSW wc;
+ wc.style = CS_OWNDC;
+ wc.lpfnWndProc = InputEventRejectingWindowProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameCompositor;
+ g_compositor_window_class = ::RegisterClassW(&wc);
+}
+
+/* static */
+WinCompositorWnds WinCompositorWindowThread::CreateCompositorWindow() {
+ MOZ_ASSERT(Loop());
+
+ if (!Loop()) {
+ return WinCompositorWnds(nullptr, nullptr);
+ }
+
+ layers::SynchronousTask task("Create compositor window");
+
+ HWND initialParentWnd = nullptr;
+ HWND compositorWnd = nullptr;
+
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "WinCompositorWindowThread::CreateCompositorWindow::Runnable", [&]() {
+ layers::AutoCompleteTask complete(&task);
+
+ InitializeInitialParentWindowClass();
+ InitializeWindowClass();
+
+ // Create initial parent window.
+ // We could not directly create a compositor window with a main window
+ // as parent window, so instead create it with a temporary placeholder
+ // parent. Its parent is set as main window in UI process.
+ initialParentWnd =
+ ::CreateWindowEx(WS_EX_TOOLWINDOW, kClassNameCompositorInitalParent,
+ nullptr, WS_POPUP | WS_DISABLED, 0, 0, 1, 1,
+ nullptr, 0, GetModuleHandle(nullptr), 0);
+ if (!initialParentWnd) {
+ gfxCriticalNoteOnce << "Inital parent window failed "
+ << ::GetLastError();
+ return;
+ }
+
+ DWORD extendedStyle = WS_EX_NOPARENTNOTIFY | WS_EX_NOREDIRECTIONBITMAP;
+
+ if (!StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
+ extendedStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+ }
+
+ compositorWnd = ::CreateWindowEx(
+ extendedStyle, kClassNameCompositor, nullptr,
+ WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, 1, 1,
+ initialParentWnd, 0, GetModuleHandle(nullptr), 0);
+ if (!compositorWnd) {
+ gfxCriticalNoteOnce << "Compositor window failed "
+ << ::GetLastError();
+ }
+ });
+
+ Loop()->PostTask(runnable.forget());
+
+ task.Wait();
+
+ return WinCompositorWnds(compositorWnd, initialParentWnd);
+}
+
+/* static */
+void WinCompositorWindowThread::DestroyCompositorWindow(
+ WinCompositorWnds aWnds) {
+ MOZ_ASSERT(aWnds.mCompositorWnd);
+ MOZ_ASSERT(aWnds.mInitialParentWnd);
+ MOZ_ASSERT(Loop());
+
+ if (!Loop()) {
+ return;
+ }
+
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "WinCompositorWidget::CreateNativeWindow::Runnable", [aWnds]() {
+ ::DestroyWindow(aWnds.mCompositorWnd);
+ ::DestroyWindow(aWnds.mInitialParentWnd);
+ });
+
+ Loop()->PostTask(runnable.forget());
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWindowThread.h b/widget/windows/WinCompositorWindowThread.h
new file mode 100644
index 0000000000..372e3d91c2
--- /dev/null
+++ b/widget/windows/WinCompositorWindowThread.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_WinCompositorWindowThread_h
+#define widget_windows_WinCompositorWindowThread_h
+
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "mozilla/Monitor.h"
+
+namespace mozilla {
+namespace widget {
+
+struct WinCompositorWnds {
+ HWND mCompositorWnd;
+ HWND mInitialParentWnd;
+ WinCompositorWnds(HWND aCompositorWnd, HWND aInitialParentWnd)
+ : mCompositorWnd(aCompositorWnd), mInitialParentWnd(aInitialParentWnd) {}
+};
+
+class WinCompositorWindowThread final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(
+ WinCompositorWindowThread)
+
+ public:
+ /// Can be called from any thread.
+ static WinCompositorWindowThread* Get();
+
+ /// Can only be called from the main thread.
+ static void Start();
+
+ /// Can only be called from the main thread.
+ static void ShutDown();
+
+ /// Can be called from any thread.
+ static MessageLoop* Loop();
+
+ /// Can be called from any thread.
+ static bool IsInCompositorWindowThread();
+
+ /// Can be called from any thread.
+ static WinCompositorWnds CreateCompositorWindow();
+
+ /// Can be called from any thread.
+ static void DestroyCompositorWindow(WinCompositorWnds aWnds);
+
+ private:
+ explicit WinCompositorWindowThread(base::Thread* aThread);
+ ~WinCompositorWindowThread() {}
+
+ void ShutDownTask();
+
+ UniquePtr<base::Thread> const mThread;
+ Monitor mMonitor;
+
+ // Has ShutDown been called on us? We might have survived if our thread join
+ // timed out.
+ bool mHasAttemptedShutdown = false;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinCompositorWindowThread_h
diff --git a/widget/windows/WinEventObserver.cpp b/widget/windows/WinEventObserver.cpp
new file mode 100644
index 0000000000..7abac8a59a
--- /dev/null
+++ b/widget/windows/WinEventObserver.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include <windows.h>
+#include <winuser.h>
+#include <wtsapi32.h>
+
+#include "WinEventObserver.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
+#include "nsHashtablesFwd.h"
+#include "nsdefs.h"
+
+namespace mozilla::widget {
+
+LazyLogModule gWinEventObserverLog("WinEventObserver");
+#define LOG(...) MOZ_LOG(gWinEventObserverLog, LogLevel::Info, (__VA_ARGS__))
+
+// static
+StaticRefPtr<WinEventHub> WinEventHub::sInstance;
+
+// static
+bool WinEventHub::Ensure() {
+ if (sInstance) {
+ return true;
+ }
+
+ LOG("WinEventHub::Ensure()");
+
+ RefPtr<WinEventHub> instance = new WinEventHub();
+ if (!instance->Initialize()) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return false;
+ }
+ sInstance = instance;
+ ClearOnShutdown(&sInstance);
+ return true;
+}
+
+WinEventHub::WinEventHub() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG("WinEventHub::WinEventHub()");
+}
+
+WinEventHub::~WinEventHub() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mObservers.IsEmpty());
+ LOG("WinEventHub::~WinEventHub()");
+
+ if (mHWnd) {
+ ::DestroyWindow(mHWnd);
+ mHWnd = nullptr;
+ }
+}
+
+bool WinEventHub::Initialize() {
+ WNDCLASSW wc;
+ HMODULE hSelf = ::GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(hSelf, L"MozillaWinEventHubClass", &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hSelf;
+ wc.lpfnWndProc = WinEventProc;
+ wc.lpszClassName = L"MozillaWinEventHubClass";
+ RegisterClassW(&wc);
+ }
+
+ mHWnd = ::CreateWindowW(L"MozillaWinEventHubClass", L"WinEventHub", 0, 0, 0,
+ 0, 0, nullptr, nullptr, hSelf, nullptr);
+ if (!mHWnd) {
+ return false;
+ }
+
+ return true;
+}
+
+// static
+LRESULT CALLBACK WinEventHub::WinEventProc(HWND aHwnd, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam) {
+ if (sInstance) {
+ sInstance->ProcessWinEventProc(aHwnd, aMsg, aWParam, aLParam);
+ }
+ return ::DefWindowProc(aHwnd, aMsg, aWParam, aLParam);
+}
+
+void WinEventHub::ProcessWinEventProc(HWND aHwnd, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam) {
+ for (const auto& observer : mObservers) {
+ observer->OnWinEventProc(aHwnd, aMsg, aWParam, aLParam);
+ }
+}
+
+void WinEventHub::AddObserver(WinEventObserver* aObserver) {
+ LOG("WinEventHub::AddObserver() aObserver %p", aObserver);
+
+ mObservers.Insert(aObserver);
+}
+
+void WinEventHub::RemoveObserver(WinEventObserver* aObserver) {
+ LOG("WinEventHub::RemoveObserver() aObserver %p", aObserver);
+
+ mObservers.Remove(aObserver);
+}
+
+WinEventObserver::~WinEventObserver() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mDestroyed);
+}
+
+void WinEventObserver::Destroy() {
+ LOG("WinEventObserver::Destroy() this %p", this);
+
+ WinEventHub::Get()->RemoveObserver(this);
+ mDestroyed = true;
+}
+
+// static
+already_AddRefed<DisplayStatusObserver> DisplayStatusObserver::Create(
+ DisplayStatusListener* aListener) {
+ if (!WinEventHub::Ensure()) {
+ return nullptr;
+ }
+ RefPtr<DisplayStatusObserver> observer = new DisplayStatusObserver(aListener);
+ WinEventHub::Get()->AddObserver(observer);
+ return observer.forget();
+}
+
+DisplayStatusObserver::DisplayStatusObserver(DisplayStatusListener* aListener)
+ : mListener(aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG("DisplayStatusObserver::DisplayStatusObserver() this %p", this);
+
+ mDisplayStatusHandle = ::RegisterPowerSettingNotification(
+ WinEventHub::Get()->GetWnd(), &GUID_SESSION_DISPLAY_STATUS,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+DisplayStatusObserver::~DisplayStatusObserver() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG("DisplayStatusObserver::~DisplayStatusObserver() this %p", this);
+
+ if (mDisplayStatusHandle) {
+ ::UnregisterPowerSettingNotification(mDisplayStatusHandle);
+ mDisplayStatusHandle = nullptr;
+ }
+}
+
+void DisplayStatusObserver::OnWinEventProc(HWND aHwnd, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam) {
+ if (aMsg == WM_POWERBROADCAST && aWParam == PBT_POWERSETTINGCHANGE) {
+ POWERBROADCAST_SETTING* setting = (POWERBROADCAST_SETTING*)aLParam;
+ if (setting &&
+ ::IsEqualGUID(setting->PowerSetting, GUID_SESSION_DISPLAY_STATUS) &&
+ setting->DataLength == sizeof(DWORD)) {
+ bool displayOn = PowerMonitorOff !=
+ static_cast<MONITOR_DISPLAY_STATE>(setting->Data[0]);
+
+ LOG("DisplayStatusObserver::OnWinEventProc() displayOn %d this %p",
+ displayOn, this);
+ mListener->OnDisplayStateChanged(displayOn);
+ }
+ }
+}
+
+// static
+already_AddRefed<SessionChangeObserver> SessionChangeObserver::Create(
+ SessionChangeListener* aListener) {
+ if (!WinEventHub::Ensure()) {
+ return nullptr;
+ }
+ RefPtr<SessionChangeObserver> observer = new SessionChangeObserver(aListener);
+ WinEventHub::Get()->AddObserver(observer);
+ return observer.forget();
+}
+
+SessionChangeObserver::SessionChangeObserver(SessionChangeListener* aListener)
+ : mListener(aListener) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG("SessionChangeObserver::SessionChangeObserver() this %p", this);
+
+ auto hwnd = WinEventHub::Get()->GetWnd();
+ DebugOnly<BOOL> wtsRegistered =
+ ::WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION);
+ NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
+}
+SessionChangeObserver::~SessionChangeObserver() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG("SessionChangeObserver::~SessionChangeObserver() this %p", this);
+
+ auto hwnd = WinEventHub::Get()->GetWnd();
+ // Unregister notifications from terminal services
+ ::WTSUnRegisterSessionNotification(hwnd);
+}
+
+void SessionChangeObserver::OnWinEventProc(HWND aHwnd, UINT aMsg,
+ WPARAM aWParam, LPARAM aLParam) {
+ if (aMsg == WM_WTSSESSION_CHANGE &&
+ (aWParam == WTS_SESSION_LOCK || aWParam == WTS_SESSION_UNLOCK)) {
+ Maybe<bool> isCurrentSession;
+ DWORD currentSessionId = 0;
+ if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId)) {
+ isCurrentSession = Nothing();
+ } else {
+ LOG("SessionChangeObserver::OnWinEventProc() aWParam %zu aLParam "
+ "%" PRIdLPTR
+ " "
+ "currentSessionId %lu this %p",
+ aWParam, aLParam, currentSessionId, this);
+
+ isCurrentSession = Some(static_cast<DWORD>(aLParam) == currentSessionId);
+ }
+ mListener->OnSessionChange(aWParam, isCurrentSession);
+ }
+}
+
+#undef LOG
+
+} // namespace mozilla::widget
diff --git a/widget/windows/WinEventObserver.h b/widget/windows/WinEventObserver.h
new file mode 100644
index 0000000000..6a267871dd
--- /dev/null
+++ b/widget/windows/WinEventObserver.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_WinEventObserver_h
+#define widget_windows_WinEventObserver_h
+
+#include <windows.h>
+
+#include "mozilla/Maybe.h"
+#include "nsISupportsImpl.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+
+namespace widget {
+
+class WinEventObserver {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinEventObserver)
+ public:
+ virtual void OnWinEventProc(HWND aHwnd, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam) {}
+ virtual void Destroy();
+
+ protected:
+ virtual ~WinEventObserver();
+
+ bool mDestroyed = false;
+};
+
+// Uses singleton window to observe events like display status and session
+// change.
+class WinEventHub final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinEventHub)
+
+ public:
+ // Returns true if the singleton exists (or was created). Will return
+ // false if the singleton couldn't be created, in which case a call
+ // to Get() will return a nullptr. It is safe to call this function
+ // repeatedly.
+ static bool Ensure();
+ static RefPtr<WinEventHub> Get() { return sInstance; }
+
+ void AddObserver(WinEventObserver* aObserver);
+ void RemoveObserver(WinEventObserver* aObserver);
+
+ HWND GetWnd() { return mHWnd; }
+
+ private:
+ WinEventHub();
+ ~WinEventHub();
+
+ static LRESULT CALLBACK WinEventProc(HWND aHwnd, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam);
+
+ bool Initialize();
+ void ProcessWinEventProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+ HWND mHWnd = nullptr;
+ nsTHashSet<nsRefPtrHashKey<WinEventObserver>> mObservers;
+
+ static StaticRefPtr<WinEventHub> sInstance;
+};
+
+class DisplayStatusListener {
+ public:
+ virtual void OnDisplayStateChanged(bool aDisplayOn) = 0;
+};
+
+// Observe Display on/off event
+class DisplayStatusObserver final : public WinEventObserver {
+ public:
+ static already_AddRefed<DisplayStatusObserver> Create(
+ DisplayStatusListener* aListener);
+
+ private:
+ explicit DisplayStatusObserver(DisplayStatusListener* aListener);
+ virtual ~DisplayStatusObserver();
+ void OnWinEventProc(HWND aHwnd, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam) override;
+
+ DisplayStatusListener* mListener;
+
+ HPOWERNOTIFY mDisplayStatusHandle = nullptr;
+};
+
+class SessionChangeListener {
+ public:
+ virtual void OnSessionChange(WPARAM aStatusCode,
+ Maybe<bool> aIsCurrentSession) = 0;
+};
+
+// Observe session lock/unlock event
+class SessionChangeObserver : public WinEventObserver {
+ public:
+ static already_AddRefed<SessionChangeObserver> Create(
+ SessionChangeListener* aListener);
+
+ private:
+ explicit SessionChangeObserver(SessionChangeListener* aListener);
+ virtual ~SessionChangeObserver();
+
+ void Initialize();
+ void OnWinEventProc(HWND aHwnd, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam) override;
+
+ SessionChangeListener* mListener;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinEventObserver_h
diff --git a/widget/windows/WinHeaderOnlyUtils.h b/widget/windows/WinHeaderOnlyUtils.h
new file mode 100644
index 0000000000..15861cc73b
--- /dev/null
+++ b/widget/windows/WinHeaderOnlyUtils.h
@@ -0,0 +1,820 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WinHeaderOnlyUtils_h
+#define mozilla_WinHeaderOnlyUtils_h
+
+#include <windows.h>
+#include <winerror.h>
+#include <winnt.h>
+#include <winternl.h>
+#include <objbase.h>
+#include <shlwapi.h>
+#undef ParseURL
+#include <stdlib.h>
+#include <tuple>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/UniquePtr.h"
+#include "nsWindowsHelpers.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsIFile.h"
+# include "nsString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+/**
+ * This header is intended for self-contained, header-only, utility code for
+ * Win32. It may be used outside of xul.dll, in places such as firefox.exe or
+ * mozglue.dll. If your code creates dependencies on Mozilla libraries, you
+ * should put it elsewhere.
+ */
+
+#if !defined(STATUS_SUCCESS)
+# define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif // !defined(STATUS_SUCCESS)
+
+// Our data indicates a few users of Win7 x86 hit failure to load urlmon.dll
+// for unknown reasons. Since we don't always require urlmon.dll on Win7,
+// we delay-load it, which causes a crash if loading urlmon.dll fails. This
+// macro is to safely load and call urlmon's API graciously without crash.
+#if defined(_X86_)
+# define SAFECALL_URLMON_FUNC(FuncName, ...) \
+ do { \
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr< \
+ decltype(&::FuncName)> \
+ func(L"urlmon.dll", #FuncName); \
+ hr = \
+ func ? func(__VA_ARGS__) : HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND); \
+ } while (0)
+#else
+# define SAFECALL_URLMON_FUNC(FuncName, ...) hr = ::FuncName(__VA_ARGS__)
+#endif
+
+namespace mozilla {
+
+class WindowsError final {
+ private:
+ // HRESULT and NTSTATUS are both typedefs of LONG, so we cannot use
+ // overloading to properly differentiate between the two. Instead we'll use
+ // static functions to convert the various error types to HRESULTs before
+ // instantiating.
+ explicit constexpr WindowsError(HRESULT aHResult) : mHResult(aHResult) {}
+
+ public:
+ using UniqueString = UniquePtr<WCHAR[], LocalFreeDeleter>;
+
+ static constexpr WindowsError FromNtStatus(NTSTATUS aNtStatus) {
+ if (aNtStatus == STATUS_SUCCESS) {
+ // Special case: we don't want to set FACILITY_NT_BIT
+ // (HRESULT_FROM_NT does not handle this case, unlike HRESULT_FROM_WIN32)
+ return WindowsError(S_OK);
+ }
+
+ return WindowsError(HRESULT_FROM_NT(aNtStatus));
+ }
+
+ static constexpr WindowsError FromHResult(HRESULT aHResult) {
+ return WindowsError(aHResult);
+ }
+
+ static constexpr WindowsError FromWin32Error(DWORD aWin32Err) {
+ return WindowsError(HRESULT_FROM_WIN32(aWin32Err));
+ }
+
+ static WindowsError FromLastError() {
+ return FromWin32Error(::GetLastError());
+ }
+
+ static WindowsError CreateSuccess() { return WindowsError(S_OK); }
+
+ static WindowsError CreateGeneric() {
+ return FromWin32Error(ERROR_UNIDENTIFIED_ERROR);
+ }
+
+ bool IsSuccess() const { return SUCCEEDED(mHResult); }
+
+ bool IsFailure() const { return FAILED(mHResult); }
+
+ bool IsAvailableAsWin32Error() const {
+ return IsAvailableAsNtStatus() ||
+ HRESULT_FACILITY(mHResult) == FACILITY_WIN32;
+ }
+
+ bool IsAvailableAsNtStatus() const {
+ return mHResult == S_OK || (mHResult & FACILITY_NT_BIT);
+ }
+
+ bool IsAvailableAsHResult() const { return true; }
+
+ UniqueString AsString() const {
+ LPWSTR rawMsgBuf = nullptr;
+ constexpr DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS;
+ DWORD result =
+ ::FormatMessageW(flags, nullptr, mHResult, 0,
+ reinterpret_cast<LPWSTR>(&rawMsgBuf), 0, nullptr);
+ if (!result) {
+ return nullptr;
+ }
+
+ return UniqueString(rawMsgBuf);
+ }
+
+ HRESULT AsHResult() const { return mHResult; }
+
+ // Not all HRESULTs are convertible to Win32 Errors, so we use Maybe
+ Maybe<DWORD> AsWin32Error() const {
+ if (mHResult == S_OK) {
+ return Some(static_cast<DWORD>(ERROR_SUCCESS));
+ }
+
+ if (HRESULT_FACILITY(mHResult) == FACILITY_WIN32) {
+ // This is the inverse of HRESULT_FROM_WIN32
+ return Some(static_cast<DWORD>(HRESULT_CODE(mHResult)));
+ }
+
+ // The NTSTATUS facility is a special case and thus does not utilize the
+ // HRESULT_FACILITY and HRESULT_CODE macros.
+ if (mHResult & FACILITY_NT_BIT) {
+ return Some(NtStatusToWin32Error(
+ static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT)));
+ }
+
+ return Nothing();
+ }
+
+ // Not all HRESULTs are convertible to NTSTATUS, so we use Maybe
+ Maybe<NTSTATUS> AsNtStatus() const {
+ if (mHResult == S_OK) {
+ return Some(STATUS_SUCCESS);
+ }
+
+ // The NTSTATUS facility is a special case and thus does not utilize the
+ // HRESULT_FACILITY and HRESULT_CODE macros.
+ if (mHResult & FACILITY_NT_BIT) {
+ return Some(static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT));
+ }
+
+ return Nothing();
+ }
+
+ constexpr bool operator==(const WindowsError& aOther) const {
+ return mHResult == aOther.mHResult;
+ }
+
+ constexpr bool operator!=(const WindowsError& aOther) const {
+ return mHResult != aOther.mHResult;
+ }
+
+ static DWORD NtStatusToWin32Error(NTSTATUS aNtStatus) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&RtlNtStatusToDosError)>
+ pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
+
+ MOZ_ASSERT(!!pRtlNtStatusToDosError);
+ if (!pRtlNtStatusToDosError) {
+ return ERROR_UNIDENTIFIED_ERROR;
+ }
+
+ return pRtlNtStatusToDosError(aNtStatus);
+ }
+
+ private:
+ // We store the error code as an HRESULT because they can encode both Win32
+ // error codes and NTSTATUS codes.
+ HRESULT mHResult;
+};
+
+namespace detail {
+template <>
+struct UnusedZero<WindowsError> {
+ using StorageType = WindowsError;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = WindowsError::FromHResult(S_OK);
+
+ static constexpr void AssertValid(StorageType aValue) {}
+ static constexpr const WindowsError& Inspect(const StorageType& aValue) {
+ return aValue;
+ }
+ static constexpr WindowsError Unwrap(StorageType aValue) { return aValue; }
+ static constexpr StorageType Store(WindowsError aValue) { return aValue; }
+};
+} // namespace detail
+
+enum DetourResultCode : uint32_t {
+ RESULT_OK = 0,
+ INTERCEPTOR_MOD_NULL,
+ INTERCEPTOR_MOD_INACCESSIBLE,
+ INTERCEPTOR_PROC_NULL,
+ INTERCEPTOR_PROC_INACCESSIBLE,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR,
+ DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR,
+ DETOUR_PATCHER_DO_RESERVE_ERROR,
+ DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR,
+ DETOUR_PATCHER_INVALID_TRAMPOLINE,
+ DETOUR_PATCHER_WRITE_POINTER_ERROR,
+ DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR,
+ FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
+ MMPOLICY_RESERVE_INVALIDARG,
+ MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE,
+ MMPOLICY_RESERVE_CREATEFILEMAPPING,
+ MMPOLICY_RESERVE_MAPVIEWOFFILE,
+ MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR,
+ MMPOLICY_RESERVE_FINDREGION_INVALIDLEN,
+ MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE,
+ MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION,
+ MMPOLICY_RESERVE_FINAL_RESERVE_ERROR,
+};
+
+#if defined(NIGHTLY_BUILD)
+struct DetourError {
+ // We have a 16-bytes buffer, but only minimum bytes to detour per
+ // architecture are copied. See CreateTrampoline in PatcherDetour.h.
+ DetourResultCode mErrorCode;
+ uint8_t mOrigBytes[16];
+ explicit DetourError(DetourResultCode aError)
+ : mErrorCode(aError), mOrigBytes{} {}
+ DetourError(DetourResultCode aError, DWORD aWin32Error)
+ : mErrorCode(aError), mOrigBytes{} {
+ static_assert(sizeof(mOrigBytes) >= sizeof(aWin32Error),
+ "Can't fit a DWORD in mOrigBytes");
+ *reinterpret_cast<DWORD*>(mOrigBytes) = aWin32Error;
+ }
+ operator WindowsError() const {
+ return WindowsError::FromHResult(mErrorCode);
+ }
+};
+#endif // defined(NIGHTLY_BUILD)
+
+template <typename T>
+using WindowsErrorResult = Result<T, WindowsError>;
+
+struct LauncherError {
+ LauncherError(const char* aFile, int aLine, WindowsError aWin32Error)
+ : mFile(aFile), mLine(aLine), mError(aWin32Error) {}
+
+#if defined(NIGHTLY_BUILD)
+ LauncherError(const char* aFile, int aLine,
+ const Maybe<DetourError>& aDetourError)
+ : mFile(aFile),
+ mLine(aLine),
+ mError(aDetourError.isSome() ? aDetourError.value()
+ : WindowsError::CreateGeneric()),
+ mDetourError(aDetourError) {}
+#endif // defined(NIGHTLY_BUILD)
+
+ const char* mFile;
+ int mLine;
+ WindowsError mError;
+#if defined(NIGHTLY_BUILD)
+ Maybe<DetourError> mDetourError;
+#endif // defined(NIGHTLY_BUILD)
+
+ bool operator==(const LauncherError& aOther) const {
+ return mError == aOther.mError;
+ }
+
+ bool operator!=(const LauncherError& aOther) const {
+ return mError != aOther.mError;
+ }
+
+ bool operator==(const WindowsError& aOther) const { return mError == aOther; }
+
+ bool operator!=(const WindowsError& aOther) const { return mError != aOther; }
+};
+
+#if defined(MOZ_USE_LAUNCHER_ERROR)
+
+template <typename T>
+using LauncherResult = Result<T, LauncherError>;
+
+template <typename T>
+using LauncherResultWithLineInfo = LauncherResult<T>;
+
+using WindowsErrorType = LauncherError;
+
+#else
+
+template <typename T>
+using LauncherResult = WindowsErrorResult<T>;
+
+template <typename T>
+using LauncherResultWithLineInfo = Result<T, LauncherError>;
+
+using WindowsErrorType = WindowsError;
+
+#endif // defined(MOZ_USE_LAUNCHER_ERROR)
+
+using LauncherVoidResult = LauncherResult<Ok>;
+
+using LauncherVoidResultWithLineInfo = LauncherResultWithLineInfo<Ok>;
+
+#if defined(MOZ_USE_LAUNCHER_ERROR)
+
+# define LAUNCHER_ERROR_GENERIC() \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::CreateGeneric()))
+
+# if defined(NIGHTLY_BUILD)
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) \
+ ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+# else
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC()
+# endif // defined(NIGHTLY_BUILD)
+
+# define LAUNCHER_ERROR_FROM_WIN32(err) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromWin32Error(err)))
+
+# define LAUNCHER_ERROR_FROM_LAST() \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromLastError()))
+
+# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromNtStatus(ntstatus)))
+
+# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+ ::mozilla::Err(::mozilla::LauncherError( \
+ __FILE__, __LINE__, ::mozilla::WindowsError::FromHResult(hresult)))
+
+// This macro wraps the supplied WindowsError with a LauncherError
+# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
+ ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+
+#else
+
+# define LAUNCHER_ERROR_GENERIC() \
+ ::mozilla::Err(::mozilla::WindowsError::CreateGeneric())
+
+# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC()
+
+# define LAUNCHER_ERROR_FROM_WIN32(err) \
+ ::mozilla::Err(::mozilla::WindowsError::FromWin32Error(err))
+
+# define LAUNCHER_ERROR_FROM_LAST() \
+ ::mozilla::Err(::mozilla::WindowsError::FromLastError())
+
+# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+ ::mozilla::Err(::mozilla::WindowsError::FromNtStatus(ntstatus))
+
+# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+ ::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult))
+
+# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) ::mozilla::Err(err)
+
+#endif // defined(MOZ_USE_LAUNCHER_ERROR)
+
+// How long to wait for a created process to become available for input,
+// to prevent that process's windows being forced to the background.
+// This is used across update, restart, and the launcher.
+const DWORD kWaitForInputIdleTimeoutMS = 10 * 1000;
+
+/**
+ * Wait for a child GUI process to become "idle." Idle means that the process
+ * has created its message queue and has begun waiting for user input.
+ *
+ * Note that this must only be used when the child process is going to display
+ * GUI! Otherwise you're going to be waiting for a very long time ;-)
+ *
+ * @return true if we successfully waited for input idle;
+ * false if we timed out or failed to wait.
+ */
+inline bool WaitForInputIdle(HANDLE aProcess,
+ DWORD aTimeoutMs = kWaitForInputIdleTimeoutMS) {
+ const DWORD kSleepTimeMs = 10;
+ const DWORD waitStart = aTimeoutMs == INFINITE ? 0 : ::GetTickCount();
+ DWORD elapsed = 0;
+
+ while (true) {
+ if (aTimeoutMs != INFINITE) {
+ elapsed = ::GetTickCount() - waitStart;
+ }
+
+ if (elapsed >= aTimeoutMs) {
+ return false;
+ }
+
+ // ::WaitForInputIdle() doesn't always set the last-error code on failure
+ ::SetLastError(ERROR_SUCCESS);
+
+ DWORD waitResult = ::WaitForInputIdle(aProcess, aTimeoutMs - elapsed);
+ if (!waitResult) {
+ return true;
+ }
+
+ if (waitResult == WAIT_FAILED &&
+ ::GetLastError() == ERROR_NOT_GUI_PROCESS) {
+ ::Sleep(kSleepTimeMs);
+ continue;
+ }
+
+ return false;
+ }
+}
+
+enum class PathType {
+ eNtPath,
+ eDosPath,
+};
+
+class FileUniqueId final {
+ public:
+ explicit FileUniqueId(const wchar_t* aPath, PathType aPathType)
+ : mId(FILE_ID_INFO()) {
+ if (!aPath) {
+ mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ return;
+ }
+
+ nsAutoHandle file;
+
+ switch (aPathType) {
+ default:
+ mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ MOZ_ASSERT_UNREACHABLE("Unhandled PathType");
+ return;
+
+ case PathType::eNtPath: {
+ UNICODE_STRING unicodeString;
+ ::RtlInitUnicodeString(&unicodeString, aPath);
+ OBJECT_ATTRIBUTES objectAttributes;
+ InitializeObjectAttributes(&objectAttributes, &unicodeString,
+ OBJ_CASE_INSENSITIVE, nullptr, nullptr);
+ IO_STATUS_BLOCK ioStatus = {};
+ HANDLE ntHandle;
+ NTSTATUS status = ::NtOpenFile(
+ &ntHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &objectAttributes,
+ &ioStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT);
+ // We don't need to check |ntHandle| for INVALID_HANDLE_VALUE here,
+ // as that value is set by the Win32 layer.
+ if (!NT_SUCCESS(status)) {
+ mId = LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ return;
+ }
+
+ file.own(ntHandle);
+ break;
+ }
+
+ case PathType::eDosPath: {
+ file.own(::CreateFileW(
+ aPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+ if (file == INVALID_HANDLE_VALUE) {
+ mId = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+
+ break;
+ }
+ }
+
+ GetId(file);
+ }
+
+ explicit FileUniqueId(const nsAutoHandle& aFile) : mId(FILE_ID_INFO()) {
+ GetId(aFile);
+ }
+
+ ~FileUniqueId() = default;
+
+ bool IsError() const { return mId.isErr(); }
+
+ const WindowsErrorType& GetError() const { return mId.inspectErr(); }
+
+ FileUniqueId(FileUniqueId&& aOther) = default;
+ FileUniqueId& operator=(FileUniqueId&& aOther) = delete;
+
+ bool operator==(const FileUniqueId& aOther) const {
+ return mId.isOk() && aOther.mId.isOk() &&
+ !memcmp(&mId.inspect(), &aOther.mId.inspect(), sizeof(FILE_ID_INFO));
+ }
+
+ bool operator!=(const FileUniqueId& aOther) const {
+ return !((*this) == aOther);
+ }
+
+ private:
+ void GetId(const nsAutoHandle& aFile) {
+ FILE_ID_INFO fileIdInfo = {};
+ if (::GetFileInformationByHandleEx(aFile.get(), FileIdInfo, &fileIdInfo,
+ sizeof(fileIdInfo))) {
+ mId = fileIdInfo;
+ return;
+ }
+ // Only NTFS and ReFS support FileIdInfo. So we have to fallback if
+ // GetFileInformationByHandleEx failed.
+
+ BY_HANDLE_FILE_INFORMATION info = {};
+ if (!::GetFileInformationByHandle(aFile.get(), &info)) {
+ mId = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+
+ fileIdInfo.VolumeSerialNumber = info.dwVolumeSerialNumber;
+ memcpy(&fileIdInfo.FileId.Identifier[0], &info.nFileIndexLow,
+ sizeof(DWORD));
+ memcpy(&fileIdInfo.FileId.Identifier[sizeof(DWORD)], &info.nFileIndexHigh,
+ sizeof(DWORD));
+ mId = fileIdInfo;
+ }
+
+ private:
+ LauncherResult<FILE_ID_INFO> mId;
+};
+
+class MOZ_RAII AutoVirtualProtect final {
+ public:
+ AutoVirtualProtect(void* aAddress, size_t aLength, DWORD aProtFlags,
+ HANDLE aTargetProcess = ::GetCurrentProcess())
+ : mAddress(aAddress),
+ mLength(aLength),
+ mTargetProcess(aTargetProcess),
+ mPrevProt(0),
+ mError(WindowsError::CreateSuccess()) {
+ if (!::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags,
+ &mPrevProt)) {
+ mError = WindowsError::FromLastError();
+ }
+ }
+
+ ~AutoVirtualProtect() {
+ if (mError.IsFailure()) {
+ return;
+ }
+
+ ::VirtualProtectEx(mTargetProcess, mAddress, mLength, mPrevProt,
+ &mPrevProt);
+ }
+
+ explicit operator bool() const { return mError.IsSuccess(); }
+
+ WindowsError GetError() const { return mError; }
+
+ DWORD PrevProt() const { return mPrevProt; }
+
+ AutoVirtualProtect(const AutoVirtualProtect&) = delete;
+ AutoVirtualProtect(AutoVirtualProtect&&) = delete;
+ AutoVirtualProtect& operator=(const AutoVirtualProtect&) = delete;
+ AutoVirtualProtect& operator=(AutoVirtualProtect&&) = delete;
+
+ private:
+ void* mAddress;
+ size_t mLength;
+ HANDLE mTargetProcess;
+ DWORD mPrevProt;
+ WindowsError mError;
+};
+
+inline UniquePtr<wchar_t[]> GetFullModulePath(HMODULE aModule) {
+ DWORD bufLen = MAX_PATH;
+ mozilla::UniquePtr<wchar_t[]> buf;
+ DWORD retLen;
+
+ while (true) {
+ buf = mozilla::MakeUnique<wchar_t[]>(bufLen);
+ retLen = ::GetModuleFileNameW(aModule, buf.get(), bufLen);
+ if (!retLen) {
+ return nullptr;
+ }
+
+ if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufLen *= 2;
+ continue;
+ }
+
+ break;
+ }
+
+ // Upon success, retLen *excludes* the null character
+ ++retLen;
+
+ // Since we're likely to have a bunch of unused space in buf, let's
+ // reallocate a string to the actual size of the file name.
+ auto result = mozilla::MakeUnique<wchar_t[]>(retLen);
+ if (wcscpy_s(result.get(), retLen, buf.get())) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+inline UniquePtr<wchar_t[]> GetFullBinaryPath() {
+ return GetFullModulePath(nullptr);
+}
+
+// Generates the install directory without a trailing path separator.
+inline bool GetInstallDirectory(UniquePtr<wchar_t[]>& installPath) {
+ installPath = GetFullBinaryPath();
+ // It's not safe to use PathRemoveFileSpecW with strings longer than MAX_PATH
+ // (including null terminator).
+ if (wcslen(installPath.get()) >= MAX_PATH) {
+ return false;
+ }
+ ::PathRemoveFileSpecW(installPath.get());
+ return true;
+}
+
+class ModuleVersion final {
+ public:
+ constexpr ModuleVersion() : mVersion(0ULL) {}
+
+ explicit ModuleVersion(const VS_FIXEDFILEINFO& aFixedInfo)
+ : mVersion((static_cast<uint64_t>(aFixedInfo.dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(aFixedInfo.dwFileVersionLS)) {}
+
+ explicit ModuleVersion(const uint64_t aVersion) : mVersion(aVersion) {}
+
+ ModuleVersion(const ModuleVersion& aOther) : mVersion(aOther.mVersion) {}
+
+ uint64_t AsInteger() const { return mVersion; }
+
+ operator uint64_t() const { return AsInteger(); }
+
+ std::tuple<uint16_t, uint16_t, uint16_t, uint16_t> AsTuple() const {
+ uint16_t major = static_cast<uint16_t>((mVersion >> 48) & 0xFFFFU);
+ uint16_t minor = static_cast<uint16_t>((mVersion >> 32) & 0xFFFFU);
+ uint16_t patch = static_cast<uint16_t>((mVersion >> 16) & 0xFFFFU);
+ uint16_t build = static_cast<uint16_t>(mVersion & 0xFFFFU);
+
+ return {major, minor, patch, build};
+ }
+
+ explicit operator bool() const { return !!mVersion; }
+
+ bool operator<(const ModuleVersion& aOther) const {
+ return mVersion < aOther.mVersion;
+ }
+
+ bool operator<(const uint64_t& aOther) const { return mVersion < aOther; }
+
+ ModuleVersion& operator=(const uint64_t aIntVersion) {
+ mVersion = aIntVersion;
+ return *this;
+ }
+
+ private:
+ uint64_t mVersion;
+};
+
+inline LauncherResult<ModuleVersion> GetModuleVersion(
+ const wchar_t* aModuleFullPath) {
+ DWORD verInfoLen = ::GetFileVersionInfoSizeW(aModuleFullPath, nullptr);
+ if (!verInfoLen) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ auto verInfoBuf = MakeUnique<BYTE[]>(verInfoLen);
+ if (!::GetFileVersionInfoW(aModuleFullPath, 0, verInfoLen,
+ verInfoBuf.get())) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ UINT fixedInfoLen;
+ VS_FIXEDFILEINFO* fixedInfo = nullptr;
+ if (!::VerQueryValueW(verInfoBuf.get(), L"\\",
+ reinterpret_cast<LPVOID*>(&fixedInfo), &fixedInfoLen)) {
+ // VerQueryValue may fail if the resource does not exist. This is not an
+ // error; we'll return 0 in this case.
+ return ModuleVersion(0ULL);
+ }
+
+ return ModuleVersion(*fixedInfo);
+}
+
+inline LauncherResult<ModuleVersion> GetModuleVersion(HMODULE aModule) {
+ UniquePtr<wchar_t[]> fullPath(GetFullModulePath(aModule));
+ if (!fullPath) {
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ return GetModuleVersion(fullPath.get());
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+inline LauncherResult<ModuleVersion> GetModuleVersion(nsIFile* aFile) {
+ if (!aFile) {
+ return LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG);
+ }
+
+ nsAutoString fullPath;
+ nsresult rv = aFile->GetPath(fullPath);
+ if (NS_FAILED(rv)) {
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ return GetModuleVersion(fullPath.get());
+}
+#endif // defined(MOZILLA_INTERNAL_API)
+
+struct CoTaskMemFreeDeleter {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+inline LauncherResult<TOKEN_ELEVATION_TYPE> GetElevationType(
+ const nsAutoHandle& aToken) {
+ DWORD retLen;
+ TOKEN_ELEVATION_TYPE elevationType;
+ if (!::GetTokenInformation(aToken.get(), TokenElevationType, &elevationType,
+ sizeof(elevationType), &retLen)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return elevationType;
+}
+
+inline bool HasPackageIdentity() {
+ HMODULE kernel32Dll = ::GetModuleHandleW(L"kernel32");
+ if (!kernel32Dll) {
+ return false;
+ }
+
+ typedef LONG(WINAPI * GetCurrentPackageIdProc)(UINT32*, BYTE*);
+ GetCurrentPackageIdProc pGetCurrentPackageId =
+ (GetCurrentPackageIdProc)::GetProcAddress(kernel32Dll,
+ "GetCurrentPackageId");
+
+ // If there was any package identity to retrieve, we get
+ // ERROR_INSUFFICIENT_BUFFER. If there had been no package identity it
+ // would instead return APPMODEL_ERROR_NO_PACKAGE.
+ UINT32 packageNameSize = 0;
+ return pGetCurrentPackageId &&
+ (pGetCurrentPackageId(&packageNameSize, nullptr) ==
+ ERROR_INSUFFICIENT_BUFFER);
+}
+
+inline UniquePtr<wchar_t[]> GetPackageFamilyName() {
+ HMODULE kernel32Dll = ::GetModuleHandleW(L"kernel32");
+ if (!kernel32Dll) {
+ return nullptr;
+ }
+
+ typedef LONG(WINAPI * GetCurrentPackageFamilyNameProc)(UINT32*, PWSTR);
+ GetCurrentPackageFamilyNameProc pGetCurrentPackageFamilyName =
+ (GetCurrentPackageFamilyNameProc)::GetProcAddress(
+ kernel32Dll, "GetCurrentPackageFamilyName");
+ if (!pGetCurrentPackageFamilyName) {
+ return nullptr;
+ }
+
+ UINT32 packageNameSize = 0;
+ if (pGetCurrentPackageFamilyName(&packageNameSize, nullptr) !=
+ ERROR_INSUFFICIENT_BUFFER) {
+ return nullptr;
+ }
+
+ UniquePtr<wchar_t[]> packageIdentity = MakeUnique<wchar_t[]>(packageNameSize);
+ if (pGetCurrentPackageFamilyName(&packageNameSize, packageIdentity.get()) !=
+ ERROR_SUCCESS) {
+ return nullptr;
+ }
+
+ return packageIdentity;
+}
+
+// This implementation is equivalent to PathGetDriveNumber[AW].
+// We define our own version because using PathGetDriveNumber
+// delay-loads shlwapi.dll, which may fail when the process is
+// sandboxed.
+template <typename T>
+int MozPathGetDriveNumber(const T* aPath) {
+ const auto ToDriveNumber = [](const T* aPath) -> int {
+ if (*aPath == '\0' || *(aPath + 1) != ':') {
+ return -1;
+ }
+
+ T c = *aPath;
+ return (c >= 'A' && c <= 'Z') ? c - 'A'
+ : (c >= 'a' && c <= 'z') ? c - 'a'
+ : -1;
+ };
+
+ if (!aPath) {
+ return -1;
+ }
+
+ if (*aPath == '\\' && *(aPath + 1) == '\\' && *(aPath + 2) == '?' &&
+ *(aPath + 3) == '\\') {
+ return ToDriveNumber(aPath + 4);
+ }
+
+ return ToDriveNumber(aPath);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_WinHeaderOnlyUtils_h
diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp
new file mode 100644
index 0000000000..961c61fae0
--- /dev/null
+++ b/widget/windows/WinIMEHandler.cpp
@@ -0,0 +1,1081 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "WinIMEHandler.h"
+
+#include "IMMHandler.h"
+#include "KeyboardLayout.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowDefs.h"
+#include "WinTextEventDispatcherListener.h"
+
+#include "TSFTextStore.h"
+
+#include "OSKInputPaneManager.h"
+#include "OSKTabTipManager.h"
+#include "OSKVRManager.h"
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "nsIWindowsRegKey.h"
+#include "WindowsUIUtils.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif // #ifdef ACCESSIBILITY
+
+#include "shellapi.h"
+#include "shlobj.h"
+#include "powrprof.h"
+#include "setupapi.h"
+#include "cfgmgr32.h"
+
+#include "FxRWindowManager.h"
+#include "moz_external_vr.h"
+
+const char* kOskEnabled = "ui.osk.enabled";
+const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
+const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
+
+namespace mozilla {
+namespace widget {
+
+/******************************************************************************
+ * IMEHandler
+ ******************************************************************************/
+
+nsWindow* IMEHandler::sFocusedWindow = nullptr;
+InputContextAction::Cause IMEHandler::sLastContextActionCause =
+ InputContextAction::CAUSE_UNKNOWN;
+bool IMEHandler::sMaybeEditable = false;
+bool IMEHandler::sForceDisableCurrentIMM_IME = false;
+bool IMEHandler::sNativeCaretIsCreated = false;
+bool IMEHandler::sHasNativeCaretBeenRequested = false;
+
+bool IMEHandler::sIsInTSFMode = false;
+bool IMEHandler::sIsIMMEnabled = true;
+decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
+
+static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
+static bool sDeterminedPowerPlatformRole = false;
+
+// static
+void IMEHandler::Initialize() {
+ TSFTextStore::Initialize();
+ sIsInTSFMode = TSFTextStore::IsInTSFMode();
+ sIsIMMEnabled =
+ !sIsInTSFMode || StaticPrefs::intl_tsf_support_imm_AtStartup();
+ if (!sIsInTSFMode) {
+ // When full TSFTextStore is not available, try to use SetInputScopes API
+ // to enable at least InputScope. Use GET_MODULE_HANDLE_EX_FLAG_PIN to
+ // ensure that msctf.dll will not be unloaded.
+ HMODULE module = nullptr;
+ if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, L"msctf.dll",
+ &module)) {
+ sSetInputScopes = reinterpret_cast<decltype(SetInputScopes)*>(
+ GetProcAddress(module, "SetInputScopes"));
+ }
+ }
+
+ IMMHandler::Initialize();
+
+ sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList();
+}
+
+// static
+void IMEHandler::Terminate() {
+ if (sIsInTSFMode) {
+ TSFTextStore::Terminate();
+ sIsInTSFMode = false;
+ }
+
+ IMMHandler::Terminate();
+ WinTextEventDispatcherListener::Shutdown();
+}
+
+// static
+void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) {
+ if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::GetThreadManager();
+ }
+ IMEContext context(aWindow);
+ if (context.IsValid()) {
+ return context.get();
+ }
+ // If IMC isn't associated with the window, IME is disabled on the window
+ // now. In such case, we should return default IMC instead.
+ const IMEContext& defaultIMC = aWindow->DefaultIMC();
+ if (defaultIMC.IsValid()) {
+ return defaultIMC.get();
+ }
+ // If there is no default IMC, we should return the pointer to the window
+ // since if we return nullptr, IMEStateManager cannot manage composition
+ // with TextComposition instance. This is possible if no IME is installed,
+ // but composition may occur with dead key sequence.
+ return aWindow;
+ }
+
+ void* result = TSFTextStore::GetNativeData(aDataType);
+ if (!result || !(*(static_cast<void**>(result)))) {
+ return nullptr;
+ }
+ // XXX During the TSF module test, sIsInTSFMode must be true. After that,
+ // the value should be restored but currently, there is no way for that.
+ // When the TSF test is enabled again, we need to fix this. Perhaps,
+ // sending a message can fix this.
+ sIsInTSFMode = true;
+ return result;
+}
+
+// static
+bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) {
+ if (StaticPrefs::ui_key_layout_load_when_first_needed()) {
+ // Getting instance creates the singleton instance and that will
+ // automatically load active keyboard layout data. We should do that
+ // before TSF or TranslateMessage handles a key message.
+ Unused << KeyboardLayout::GetInstance();
+ }
+ if (IsTSFAvailable()) {
+ return TSFTextStore::ProcessRawKeyMessage(aMsg);
+ }
+ return false; // noting to do in IMM mode.
+}
+
+// static
+bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult) {
+ // If we're putting native caret over our caret, Windows dispatches
+ // EVENT_OBJECT_LOCATIONCHANGE event on other applications which hook
+ // the event with ::SetWinEventHook() and handles WM_GETOBJECT for
+ // OBJID_CARET (this is request of caret from such applications) instead
+ // of us. If a11y module is active, it observes every our caret change
+ // and put native caret over it automatically. However, if other
+ // applications require only caret information, activating a11y module is
+ // overwork and such applications may requires carets only in editors.
+ // Therefore, if it'd be possible, IMEHandler should put native caret over
+ // our caret, but there is a problem. Some versions of ATOK (Japanese TIP)
+ // refer native caret and if there is, the behavior is worse than the
+ // behavior without native caret. Therefore, we shouldn't put native caret
+ // as far as possible.
+ if (!sHasNativeCaretBeenRequested && aMessage == WM_GETOBJECT &&
+ static_cast<LONG>(aLParam) == OBJID_CARET) {
+ // So, when we receive first WM_GETOBJECT for OBJID_CARET, let's start to
+ // create native caret for such applications.
+ sHasNativeCaretBeenRequested = true;
+ // If an editable element has focus, we can put native caret now.
+ // XXX Should we avoid doing this if there is composition?
+ MaybeCreateNativeCaret(aWindow);
+ }
+
+ if (IsTSFAvailable()) {
+ TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
+ if (aResult.mConsumed) {
+ return true;
+ }
+ // If we don't support IMM in TSF mode, we don't use IMMHandler.
+ if (!sIsIMMEnabled) {
+ return false;
+ }
+ // IME isn't implemented with IMM, IMMHandler shouldn't handle any
+ // messages.
+ if (!IsIMMActive()) {
+ return false;
+ }
+ }
+
+ bool keepGoing =
+ IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult);
+
+ // If user changes active IME to an IME which is listed in our block list,
+ // we should disassociate IMC from the window for preventing the IME to work
+ // and crash.
+ if (aMessage == WM_INPUTLANGCHANGE) {
+ bool disableIME = IMMHandler::IsActiveIMEInBlockList();
+ if (disableIME != sForceDisableCurrentIMM_IME) {
+ bool enable =
+ !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef());
+ AssociateIMEContext(aWindow, enable);
+ sForceDisableCurrentIMM_IME = disableIME;
+ }
+ }
+
+ return keepGoing;
+}
+
+// static
+bool IMEHandler::IsA11yHandlingNativeCaret() {
+#ifndef ACCESSIBILITY
+ return false;
+#else // #ifndef ACCESSIBILITY
+ // Let's assume that when there is the service, it handles native caret.
+ return GetAccService() != nullptr;
+#endif // #ifndef ACCESSIBILITY #else
+}
+
+// static
+bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); }
+
+// static
+bool IMEHandler::IsComposing() {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
+ }
+
+ return IMMHandler::IsComposing();
+}
+
+// static
+bool IMEHandler::IsComposingOn(nsWindow* aWindow) {
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposingOn(aWindow) ||
+ IMMHandler::IsComposingOn(aWindow);
+ }
+
+ return IMMHandler::IsComposingOn(aWindow);
+}
+
+// static
+nsresult IMEHandler::NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification) {
+ if (IsTSFAvailable()) {
+ switch (aIMENotification.mMessage) {
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification);
+ // If IMM IME is active, we need to notify IMMHandler of updating
+ // composition change. It will adjust candidate window position or
+ // composition window position.
+ bool isIMMActive = IsIMMActive();
+ if (isIMMActive) {
+ IMMHandler::OnUpdateComposition(aWindow);
+ }
+ IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive);
+ return rv;
+ }
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ // If IMM IME is active, we need to notify IMMHandler of updating
+ // composition change. It will adjust candidate window position or
+ // composition window position.
+ if (IsIMMActive()) {
+ IMMHandler::OnUpdateComposition(aWindow);
+ } else {
+ TSFTextStore::OnUpdateComposition();
+ }
+ return NS_OK;
+ case NOTIFY_IME_OF_TEXT_CHANGE:
+ return TSFTextStore::OnTextChange(aIMENotification);
+ case NOTIFY_IME_OF_FOCUS: {
+ sFocusedWindow = aWindow;
+ IMMHandler::OnFocusChange(true, aWindow);
+ nsresult rv = TSFTextStore::OnFocusChange(true, aWindow,
+ aWindow->GetInputContext());
+ MaybeCreateNativeCaret(aWindow);
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
+ aWindow->GetInputContext());
+ return rv;
+ }
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+ return TSFTextStore::OnFocusChange(false, aWindow,
+ aWindow->GetInputContext());
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ // If IMM IME is active, we should send a mouse button event via IMM.
+ if (IsIMMActive()) {
+ return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
+ }
+ return TSFTextStore::OnMouseButtonEvent(aIMENotification);
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ if (TSFTextStore::IsComposingOn(aWindow)) {
+ TSFTextStore::CommitComposition(false);
+ } else if (IsIMMActive()) {
+ IMMHandler::CommitComposition(aWindow);
+ }
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ if (TSFTextStore::IsComposingOn(aWindow)) {
+ TSFTextStore::CommitComposition(true);
+ } else if (IsIMMActive()) {
+ IMMHandler::CancelComposition(aWindow);
+ }
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ return TSFTextStore::OnLayoutChange();
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+
+ switch (aIMENotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ IMMHandler::CommitComposition(aWindow);
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ IMMHandler::CancelComposition(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ IMMHandler::OnUpdateComposition(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ IMMHandler::OnSelectionChange(aWindow, aIMENotification, true);
+ // IMMHandler::OnSelectionChange() cannot work without its singleton
+ // instance. Therefore, IMEHandler needs to create native caret instead
+ // if it's necessary.
+ MaybeCreateNativeCaret(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
+ return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification);
+ case NOTIFY_IME_OF_FOCUS:
+ sFocusedWindow = aWindow;
+ IMMHandler::OnFocusChange(true, aWindow);
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow,
+ aWindow->GetInputContext());
+ MaybeCreateNativeCaret(aWindow);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+ // If a plugin gets focus while TSF has focus, we need to notify TSF of
+ // the blur.
+ if (TSFTextStore::ThinksHavingFocus()) {
+ return TSFTextStore::OnFocusChange(false, aWindow,
+ aWindow->GetInputContext());
+ }
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+// static
+IMENotificationRequests IMEHandler::GetIMENotificationRequests() {
+ if (IsTSFAvailable()) {
+ if (!sIsIMMEnabled) {
+ return TSFTextStore::GetIMENotificationRequests();
+ }
+ // Even if TSF is available, the active IME may be an IMM-IME.
+ // Unfortunately, changing the result of GetIMENotificationRequests() while
+ // an editor has focus isn't supported by IMEContentObserver nor
+ // ContentCacheInParent. Therefore, we need to request whole notifications
+ // which are necessary either IMMHandler or TSFTextStore.
+ return IMMHandler::GetIMENotificationRequests() |
+ TSFTextStore::GetIMENotificationRequests();
+ }
+
+ return IMMHandler::GetIMENotificationRequests();
+}
+
+// static
+TextEventDispatcherListener*
+IMEHandler::GetNativeTextEventDispatcherListener() {
+ return WinTextEventDispatcherListener::GetInstance();
+}
+
+// static
+bool IMEHandler::GetOpenState(nsWindow* aWindow) {
+ if (IsTSFAvailable() && !IsIMMActive()) {
+ return TSFTextStore::GetIMEOpenState();
+ }
+
+ IMEContext context(aWindow);
+ return context.GetOpenState();
+}
+
+// static
+void IMEHandler::OnDestroyWindow(nsWindow* aWindow) {
+ // When focus is in remote process, but the window is being destroyed, we
+ // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach
+ // here because BrowserParent already lost the reference to the nsWindow when
+ // it receives from the remote process.
+ if (sFocusedWindow == aWindow) {
+ MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(),
+ "input context of focused widget should've been set by a remote "
+ "process "
+ "if IME focus isn't cleared before destroying the widget");
+ NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
+ }
+
+ // We need to do nothing here for TSF. Just restore the default context
+ // if it's been disassociated.
+ if (!sIsInTSFMode) {
+ // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use
+ // SetInputScopes API. Use an empty string to do this.
+ SetInputScopeForIMM32(aWindow, u""_ns, u""_ns, false);
+ }
+ AssociateIMEContext(aWindow, true);
+}
+
+// static
+bool IMEHandler::NeedsToAssociateIMC() { return !sForceDisableCurrentIMM_IME; }
+
+// static
+void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
+ const InputContextAction& aAction) {
+ sLastContextActionCause = aAction.mCause;
+ // FYI: If there is no composition, this call will do nothing.
+ NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION));
+
+ if (aInputContext.mHTMLInputMode.EqualsLiteral("none")) {
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow, Sync::Yes);
+ } else if (aAction.UserMightRequestOpenVKB()) {
+ IMEHandler::MaybeShowOnScreenKeyboard(aWindow, aInputContext);
+ }
+
+ bool enable = WinUtils::IsIMEEnabled(aInputContext);
+ bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen !=
+ IMEState::DONT_CHANGE_OPEN_STATE);
+ bool open =
+ (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN);
+
+ // Note that even while a plugin has focus, we need to notify TSF of that.
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(aWindow, aInputContext, aAction);
+ if (IsTSFAvailable()) {
+ if (sIsIMMEnabled) {
+ // Associate IMC with aWindow only when it's necessary.
+ AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC());
+ }
+ if (adjustOpenState) {
+ TSFTextStore::SetIMEOpenState(open);
+ }
+ return;
+ }
+ } else {
+ // Set at least InputScope even when TextStore is not available.
+ SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
+ aInputContext.mHTMLInputMode,
+ aInputContext.mInPrivateBrowsing);
+ }
+
+ AssociateIMEContext(aWindow, enable);
+
+ IMEContext context(aWindow);
+ if (adjustOpenState) {
+ context.SetOpenState(open);
+ }
+}
+
+// static
+void IMEHandler::AssociateIMEContext(nsWindow* aWindowBase, bool aEnable) {
+ IMEContext context(aWindowBase);
+ if (aEnable) {
+ context.AssociateDefaultContext();
+ return;
+ }
+ // Don't disassociate the context after the window is destroyed.
+ if (aWindowBase->Destroyed()) {
+ return;
+ }
+ context.Disassociate();
+}
+
+// static
+void IMEHandler::InitInputContext(nsWindow* aWindow,
+ InputContext& aInputContext) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->GetWindowHandle(),
+ "IMEHandler::SetInputContext() requires non-nullptr HWND");
+
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ // Some TIPs like QQ Input (Simplified Chinese) may need normal window
+ // (i.e., windows except message window) when initializing themselves.
+ // Therefore, we need to initialize TSF/IMM modules after first normal
+ // window is created. InitInputContext() should be called immediately
+ // after creating each normal window, so, here is a good place to
+ // initialize these modules.
+ Initialize();
+ }
+
+ // For a11y, the default enabled state should be 'enabled'.
+ aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled;
+
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(
+ aWindow, aInputContext,
+ InputContextAction(InputContextAction::CAUSE_UNKNOWN,
+ InputContextAction::WIDGET_CREATED));
+ // IME context isn't necessary in pure TSF mode.
+ if (!sIsIMMEnabled) {
+ AssociateIMEContext(aWindow, false);
+ }
+ return;
+ }
+
+#ifdef DEBUG
+ // NOTE: IMC may be null if IMM module isn't installed.
+ IMEContext context(aWindow);
+ MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME());
+#endif // #ifdef DEBUG
+}
+
+#ifdef DEBUG
+// static
+bool IMEHandler::CurrentKeyboardLayoutHasIME() {
+ if (sIsInTSFMode) {
+ return TSFTextStore::CurrentKeyboardLayoutHasIME();
+ }
+
+ return IMMHandler::IsIMEAvailable();
+}
+#endif // #ifdef DEBUG
+
+// static
+void IMEHandler::OnKeyboardLayoutChanged() {
+ // Be aware, this method won't be called until TSFStaticSink starts to
+ // observe active TIP change. If you need to be notified of this, you
+ // need to create TSFStaticSink::Observe() or something and call it
+ // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly.
+
+ if (!sIsIMMEnabled || !IsTSFAvailable()) {
+ return;
+ }
+}
+
+// static
+void IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputMode,
+ bool aInPrivateBrowsing) {
+ if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
+ return;
+ }
+ AutoTArray<InputScope, 3> scopes;
+
+ // IME may refer only first input scope, but we will append inputmode's
+ // input scopes since IME may refer it like Chrome.
+ AppendInputScopeFromType(aHTMLInputType, scopes);
+ AppendInputScopeFromInputMode(aHTMLInputMode, scopes);
+
+ if (aInPrivateBrowsing) {
+ scopes.AppendElement(IS_PRIVATE);
+ }
+
+ if (scopes.IsEmpty()) {
+ // At least, 1 item is necessary.
+ scopes.AppendElement(IS_DEFAULT);
+ }
+
+ sSetInputScopes(aWindow->GetWindowHandle(), scopes.Elements(),
+ scopes.Length(), nullptr, 0, nullptr, nullptr);
+}
+
+// static
+void IMEHandler::AppendInputScopeFromInputMode(const nsAString& aHTMLInputMode,
+ nsTArray<InputScope>& aScopes) {
+ if (aHTMLInputMode.EqualsLiteral("mozAwesomebar")) {
+ // Even if Awesomebar has focus, user may not input URL directly.
+ // However, on-screen keyboard for URL should be shown because it has
+ // some useful additional keys like ".com" and they are not hindrances
+ // even when inputting non-URL text, e.g., words to search something in
+ // the web. On the other hand, a lot of Microsoft's IMEs and Google
+ // Japanese Input make their open state "closed" automatically if we
+ // notify them of URL as the input scope. However, this is very annoying
+ // for the users when they try to input some words to search the web or
+ // bookmark/history items. Therefore, if they are active, we need to
+ // notify them of the default input scope for avoiding this issue.
+ // FYI: We cannot check active TIP without TSF. Therefore, if it's
+ // not in TSF mode, this will check only if active IMM-IME is Google
+ // Japanese Input. Google Japanese Input is a TIP of TSF basically.
+ // However, if the OS is Win7 or it's installed on Win7 but has not
+ // been updated yet even after the OS is upgraded to Win8 or later,
+ // it's installed as IMM-IME.
+ if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) {
+ return;
+ }
+ // Don't append IS_SEARCH here for showing on-screen keyboard for URL.
+ if (!aScopes.Contains(IS_URL)) {
+ aScopes.AppendElement(IS_URL);
+ }
+ return;
+ }
+
+ // https://html.spec.whatwg.org/dev/interaction.html#attr-inputmode
+ if (aHTMLInputMode.EqualsLiteral("url")) {
+ if (!aScopes.Contains(IS_SEARCH)) {
+ aScopes.AppendElement(IS_URL);
+ }
+ return;
+ }
+ if (aHTMLInputMode.EqualsLiteral("email")) {
+ if (!aScopes.Contains(IS_EMAIL_SMTPEMAILADDRESS)) {
+ aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ }
+ return;
+ }
+ if (aHTMLInputMode.EqualsLiteral("tel")) {
+ if (!aScopes.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER)) {
+ aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ }
+ if (!aScopes.Contains(IS_TELEPHONE_LOCALNUMBER)) {
+ aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ }
+ return;
+ }
+ if (aHTMLInputMode.EqualsLiteral("numeric")) {
+ if (!aScopes.Contains(IS_DIGITS)) {
+ aScopes.AppendElement(IS_DIGITS);
+ }
+ return;
+ }
+ if (aHTMLInputMode.EqualsLiteral("decimal")) {
+ if (!aScopes.Contains(IS_NUMBER)) {
+ aScopes.AppendElement(IS_NUMBER);
+ }
+ return;
+ }
+ if (aHTMLInputMode.EqualsLiteral("search")) {
+ if (NeedsSearchInputScope() && !aScopes.Contains(IS_SEARCH)) {
+ aScopes.AppendElement(IS_SEARCH);
+ }
+ return;
+ }
+}
+
+// static
+void IMEHandler::AppendInputScopeFromType(const nsAString& aHTMLInputType,
+ nsTArray<InputScope>& aScopes) {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.EqualsLiteral("url")) {
+ aScopes.AppendElement(IS_URL);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("search")) {
+ if (NeedsSearchInputScope()) {
+ aScopes.AppendElement(IS_SEARCH);
+ }
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("email")) {
+ aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("password")) {
+ aScopes.AppendElement(IS_PASSWORD);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ aScopes.AppendElement(IS_DATE_FULLDATE);
+ aScopes.AppendElement(IS_TIME_FULLTIME);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ aScopes.AppendElement(IS_DATE_FULLDATE);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("time")) {
+ aScopes.AppendElement(IS_TIME_FULLTIME);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("tel")) {
+ aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ return;
+ }
+ if (aHTMLInputType.EqualsLiteral("number")) {
+ aScopes.AppendElement(IS_NUMBER);
+ return;
+ }
+}
+
+// static
+bool IMEHandler::NeedsSearchInputScope() {
+ return !StaticPrefs::intl_tsf_hack_atok_search_input_scope_disabled() ||
+ !TSFTextStore::IsATOKActive();
+}
+
+// static
+bool IMEHandler::IsOnScreenKeyboardSupported() {
+#ifdef NIGHTLY_BUILD
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
+ return true;
+ }
+#endif // NIGHTLY_BUILD
+ if (!Preferences::GetBool(kOskEnabled, true) ||
+ !IMEHandler::NeedOnScreenKeyboard()) {
+ return false;
+ }
+
+ // On Windows 11, we ignore tablet mode (see bug 1722208)
+ if (!IsWin11OrLater()) {
+ // On Windows 10 we require tablet mode, unless the user has set the
+ // relevant setting to enable the on-screen keyboard in desktop mode.
+ if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// static
+void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow* aWindow,
+ const InputContext& aInputContext) {
+ if (aInputContext.mHTMLInputMode.EqualsLiteral("none")) {
+ return;
+ }
+
+ if (!IsOnScreenKeyboardSupported()) {
+ return;
+ }
+
+ IMEHandler::ShowOnScreenKeyboard(aWindow);
+}
+
+// static
+void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow, Sync aSync) {
+#ifdef NIGHTLY_BUILD
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) {
+ OSKVRManager::DismissOnScreenKeyboard();
+ }
+#endif // NIGHTLY_BUILD
+ if (aSync == Sync::Yes) {
+ DismissOnScreenKeyboard(aWindow);
+ return;
+ }
+
+ RefPtr<nsWindow> window(aWindow);
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard",
+ [window]() {
+ if (window->Destroyed()) {
+ return;
+ }
+ if (!sFocusedWindow) {
+ DismissOnScreenKeyboard(window);
+ }
+ }),
+ EventQueuePriority::Idle);
+}
+
+// static
+bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
+ const std::wstring& aNeedle) {
+ std::wstring lowerCaseHaystack(aHaystack);
+ std::wstring lowerCaseNeedle(aNeedle);
+ std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(),
+ lowerCaseHaystack.begin(), ::tolower);
+ std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(),
+ lowerCaseNeedle.begin(), ::tolower);
+ return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) ==
+ lowerCaseHaystack.c_str();
+}
+
+// Returns false if a physical keyboard is detected on Windows 8 and up,
+// or there is some other reason why an onscreen keyboard is not necessary.
+// Returns true if no keyboard is found and this device looks like it needs
+// an on-screen keyboard for text input.
+// static
+bool IMEHandler::NeedOnScreenKeyboard() {
+ if (!Preferences::GetBool(kOskDetectPhysicalKeyboard, true)) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Detection disabled.");
+ return true;
+ }
+
+ // If the last focus cause was not user-initiated (ie a result of code
+ // setting focus to an element) then don't auto-show a keyboard. This
+ // avoids cases where the keyboard would pop up "just" because e.g. a
+ // web page chooses to focus a search field on the page, even when that
+ // really isn't what the user is trying to do at that moment.
+ if (!InputContextAction::IsHandlingUserInput(sLastContextActionCause)) {
+ return false;
+ }
+
+ // This function should be only invoked for machines with touch screens.
+ if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) !=
+ NID_INTEGRATED_TOUCH) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found.");
+ return false;
+ }
+
+ // If the device is docked, the user is treating the device as a PC.
+ if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: System docked.");
+ return false;
+ }
+
+ // To determine whether a keyboard is present on the device, we do the
+ // following:-
+ // 1. If the platform role is that of a mobile or slate device, check the
+ // system metric SM_CONVERTIBLESLATEMODE to see if it is being used
+ // in slate mode. If it is, also check that the last input was a touch.
+ // If all of this is true, then we should show the on-screen keyboard.
+
+ // 2. If step 1 didn't determine we should show the keyboard, we check if
+ // this device has keyboards attached to it.
+
+ // Check if the device is being used as a laptop or a tablet. This can be
+ // checked by first checking the role of the device and then the
+ // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being
+ // used as a tablet then we want the OSK to show up.
+ if (!sDeterminedPowerPlatformRole) {
+ sDeterminedPowerPlatformRole = true;
+ sPowerPlatformRole = WinUtils::GetPowerPlatformRole();
+ }
+
+ // If this a mobile or slate (tablet) device, check if it is in slate mode.
+ // If the last input was touch, ignore whether or not a keyboard is present.
+ if ((sPowerPlatformRole == PlatformRoleMobile ||
+ sPowerPlatformRole == PlatformRoleSlate) &&
+ ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 &&
+ sLastContextActionCause == InputContextAction::CAUSE_TOUCH) {
+ Preferences::SetString(
+ kOskDebugReason,
+ L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event.");
+ return true;
+ }
+
+ return !IMEHandler::IsKeyboardPresentOnSlate();
+}
+
+// Uses the Setup APIs to enumerate the attached keyboards and returns true
+// if the keyboard count is 1 or more. While this will work in most cases
+// it won't work if there are devices which expose keyboard interfaces which
+// are attached to the machine.
+// Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc.
+// static
+bool IMEHandler::IsKeyboardPresentOnSlate() {
+ const GUID KEYBOARD_CLASS_GUID = {
+ 0x4D36E96B,
+ 0xE325,
+ 0x11CE,
+ {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}};
+
+ // Query for all the keyboard devices.
+ HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr,
+ nullptr, DIGCF_PRESENT);
+ if (device_info == INVALID_HANDLE_VALUE) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info.");
+ return false;
+ }
+
+ // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If
+ // the count is more than 1 we assume that a keyboard is present. This is
+ // under the assumption that there will always be one keyboard device.
+ for (DWORD i = 0;; ++i) {
+ SP_DEVINFO_DATA device_info_data = {0};
+ device_info_data.cbSize = sizeof(device_info_data);
+ if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
+ break;
+ }
+
+ // Get the device ID.
+ wchar_t device_id[MAX_DEVICE_ID_LEN];
+ CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id,
+ MAX_DEVICE_ID_LEN, 0);
+ if (status == CR_SUCCESS) {
+ static const std::wstring BT_HID_DEVICE = L"HID\\{00001124";
+ static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812";
+ // To reduce the scope of the hack we only look for ACPI and HID\\VID
+ // prefixes in the keyboard device ids.
+ if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ L"HID\\VID") ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ BT_HID_DEVICE) ||
+ IMEHandler::WStringStartsWithCaseInsensitive(device_id,
+ BT_HOGP_DEVICE)) {
+ // The heuristic we are using is to check the count of keyboards and
+ // return true if the API's report one or more keyboards. Please note
+ // that this will break for non keyboard devices which expose a
+ // keyboard PDO.
+ Preferences::SetString(kOskDebugReason,
+ L"IKPOS: Keyboard presence confirmed.");
+ return true;
+ }
+ }
+ }
+ Preferences::SetString(kOskDebugReason,
+ L"IKPOS: Lack of keyboard confirmed.");
+ return false;
+}
+
+// static
+bool IMEHandler::IsInTabletMode() {
+ bool isInTabletMode = WindowsUIUtils::GetInTabletMode();
+ if (isInTabletMode) {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
+ } else {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
+ }
+ return isInTabletMode;
+}
+
+static bool ReadEnableDesktopModeAutoInvoke(uint32_t aRoot,
+ nsIWindowsRegKey* aRegKey,
+ uint32_t& aValue) {
+ nsresult rv;
+ rv = aRegKey->Open(aRoot, u"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed opening regkey.");
+ return false;
+ }
+ // EnableDesktopModeAutoInvoke is an opt-in option from the Windows
+ // Settings to "Automatically show the touch keyboard in windowed apps
+ // when there's no keyboard attached to your device." If the user has
+ // opted-in to this behavior, the tablet-mode requirement is skipped.
+ rv = aRegKey->ReadIntValue(u"EnableDesktopModeAutoInvoke"_ns, &aValue);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed reading value of regkey.");
+ return false;
+ }
+ return true;
+}
+
+// static
+bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey(
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: "
+ L"nsIWindowsRegKey not available");
+ return false;
+ }
+
+ uint32_t value;
+ if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ regKey, value) &&
+ !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ regKey, value)) {
+ return false;
+ }
+ if (!!value) {
+ Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true.");
+ } else {
+ Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false.");
+ }
+ return !!value;
+}
+
+// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc.
+// static
+void IMEHandler::ShowOnScreenKeyboard(nsWindow* aWindow) {
+#ifdef NIGHTLY_BUILD
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
+ OSKVRManager::ShowOnScreenKeyboard();
+ return;
+ }
+#endif // NIGHTLY_BUILD
+
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ OSKInputPaneManager::ShowOnScreenKeyboard(aWindow->GetWindowHandle());
+ return;
+ }
+
+ OSKTabTipManager::ShowOnScreenKeyboard();
+}
+
+// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
+// static
+void IMEHandler::DismissOnScreenKeyboard(nsWindow* aWindow) {
+ // Dismiss the virtual keyboard if it's open
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ OSKInputPaneManager::DismissOnScreenKeyboard(aWindow->GetWindowHandle());
+ return;
+ }
+
+ OSKTabTipManager::DismissOnScreenKeyboard();
+}
+
+bool IMEHandler::MaybeCreateNativeCaret(nsWindow* aWindow) {
+ MOZ_ASSERT(aWindow);
+
+ if (IsA11yHandlingNativeCaret()) {
+ return false;
+ }
+
+ if (!sHasNativeCaretBeenRequested) {
+ // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new
+ // application which requires our caret information. For kicking its
+ // window event proc, we should fire a window event here.
+ // (If there is such application, sHasNativeCaretBeenRequested will be set
+ // to true later.)
+ // FYI: If we create native caret and move its position, native caret
+ // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and
+ // OBJID_CLIENT.
+ ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, aWindow->GetWindowHandle(),
+ OBJID_CARET, OBJID_CLIENT);
+ return false;
+ }
+
+ MaybeDestroyNativeCaret();
+
+ // If focused content is not text editable, we don't support caret
+ // caret information without a11y module.
+ if (!aWindow->GetInputContext().mIMEState.IsEditable()) {
+ return false;
+ }
+
+ WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow);
+ aWindow->InitEvent(queryCaretRectEvent);
+
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ queryCaretRectEvent.InitForQueryCaretRect(0, options);
+
+ aWindow->DispatchWindowEvent(queryCaretRectEvent);
+ if (NS_WARN_IF(queryCaretRectEvent.Failed())) {
+ return false;
+ }
+
+ return CreateNativeCaret(aWindow, queryCaretRectEvent.mReply->mRect);
+}
+
+bool IMEHandler::CreateNativeCaret(nsWindow* aWindow,
+ const LayoutDeviceIntRect& aCaretRect) {
+ MOZ_ASSERT(aWindow);
+
+ MOZ_ASSERT(!IsA11yHandlingNativeCaret());
+
+ sNativeCaretIsCreated =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr, aCaretRect.Width(),
+ aCaretRect.Height());
+ if (!sNativeCaretIsCreated) {
+ return false;
+ }
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ if (NS_WARN_IF(!toplevelWindow)) {
+ MaybeDestroyNativeCaret();
+ return false;
+ }
+
+ LayoutDeviceIntPoint caretPosition(aCaretRect.TopLeft());
+ if (toplevelWindow != aWindow) {
+ caretPosition += toplevelWindow->WidgetToScreenOffset();
+ caretPosition -= aWindow->WidgetToScreenOffset();
+ }
+
+ ::SetCaretPos(caretPosition.x, caretPosition.y);
+ return true;
+}
+
+void IMEHandler::MaybeDestroyNativeCaret() {
+ if (!sNativeCaretIsCreated) {
+ return;
+ }
+ ::DestroyCaret();
+ sNativeCaretIsCreated = false;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h
new file mode 100644
index 0000000000..410e1ebd22
--- /dev/null
+++ b/widget/windows/WinIMEHandler.h
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WinIMEHandler_h_
+#define WinIMEHandler_h_
+
+#include "nscore.h"
+#include "nsWindow.h"
+#include "npapi.h"
+#include <windows.h>
+#include <inputscope.h>
+
+#define NS_WM_IMEFIRST WM_IME_SETCONTEXT
+#define NS_WM_IMELAST WM_IME_KEYUP
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+/**
+ * IMEHandler class is a mediator class. On Windows, there are two IME API
+ * sets: One is IMM which is legacy API set. The other is TSF which is modern
+ * API set. By using this class, non-IME handler classes don't need to worry
+ * that we're in which mode.
+ */
+class IMEHandler final {
+ private:
+ /**
+ * Initialize() initializes both TSF modules and IMM modules. Some TIPs
+ * may require a normal window (i.e., not message window) belonging to
+ * this process. Therefore, this is called immediately after first normal
+ * window is created.
+ */
+ static void Initialize();
+
+ public:
+ static void Terminate();
+
+ /**
+ * Returns TSF related native data or native IME context.
+ */
+ static void* GetNativeData(nsWindow* aWindow, uint32_t aDataType);
+
+ /**
+ * ProcessRawKeyMessage() message is called before calling TranslateMessage()
+ * and DispatchMessage(). If this returns true, the message is consumed.
+ * Then, caller must not perform TranslateMessage() nor DispatchMessage().
+ */
+ static bool ProcessRawKeyMessage(const MSG& aMsg);
+
+ /**
+ * When the message is not needed to handle anymore by the caller, this
+ * returns true. Otherwise, false.
+ */
+ static bool ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+
+ /**
+ * IsA11yHandlingNativeCaret() returns true if a11y is handling
+ * native caret. In such case, IME modules shouldn't touch native caret.
+ **/
+ static bool IsA11yHandlingNativeCaret();
+
+ /**
+ * NeedsToCreateNativeCaret() returns true if IME handler needs to create
+ * native caret for other applications which requests OBJID_CARET with
+ * WM_GETOBJECT and a11y module isn't active (if a11y module is active,
+ * it always creates native caret, i.e., even if no editor has focus).
+ */
+ static bool NeedsToCreateNativeCaret() {
+ return sHasNativeCaretBeenRequested && !IsA11yHandlingNativeCaret();
+ }
+
+ /**
+ * CreateNativeCaret() create native caret if this has been created it.
+ *
+ * @param aWindow The window which owns the caret.
+ * @param aCaretRect The caret rect relative to aWindow.
+ */
+ static bool CreateNativeCaret(nsWindow* aWindow,
+ const LayoutDeviceIntRect& aCaretRect);
+
+ /**
+ * MaybeDestroyNativeCaret() destroies native caret if it has been created
+ * by IMEHandler.
+ */
+ static void MaybeDestroyNativeCaret();
+
+ /**
+ * HasNativeCaret() returns true if there is native caret and it was created
+ * by IMEHandler.
+ */
+ static bool HasNativeCaret() { return sNativeCaretIsCreated; }
+
+ /**
+ * When there is a composition, returns true. Otherwise, false.
+ */
+ static bool IsComposing();
+
+ /**
+ * When there is a composition and it's in the window, returns true.
+ * Otherwise, false.
+ */
+ static bool IsComposingOn(nsWindow* aWindow);
+
+ /**
+ * Notifies IME of the notification (a request or an event).
+ */
+ static nsresult NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification);
+
+ /**
+ * Returns notification requests of IME.
+ */
+ static IMENotificationRequests GetIMENotificationRequests();
+
+ /**
+ * Returns native text event dispatcher listener.
+ */
+ static TextEventDispatcherListener* GetNativeTextEventDispatcherListener();
+
+ /**
+ * Returns IME open state on the window.
+ */
+ static bool GetOpenState(nsWindow* aWindow);
+
+ /**
+ * Called when the window is destroying.
+ */
+ static void OnDestroyWindow(nsWindow* aWindow);
+
+ /**
+ * Called when nsIWidget::SetInputContext() is called before the window's
+ * InputContext is modified actually.
+ */
+ static void SetInputContext(nsWindow* aWindow, InputContext& aInputContext,
+ const InputContextAction& aAction);
+
+ /**
+ * Associate or disassociate IME context to/from the aWindowBase.
+ */
+ static void AssociateIMEContext(nsWindow* aWindowBase, bool aEnable);
+
+ /**
+ * Called when the window is created.
+ */
+ static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext);
+
+ /**
+ * This is called by TSFStaticSink when active IME is changed.
+ */
+ static void OnKeyboardLayoutChanged();
+
+#ifdef DEBUG
+ /**
+ * Returns true when current keyboard layout has IME. Otherwise, false.
+ */
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+ /**
+ * Append InputScope values from inputmode string.
+ */
+ static void AppendInputScopeFromInputMode(const nsAString& aHTMLInputMode,
+ nsTArray<InputScope>& aScopes);
+
+ /**
+ * Append InputScope values from type attreibute string of input element
+ */
+ static void AppendInputScopeFromType(const nsAString& aInputType,
+ nsTArray<InputScope>& aScopes);
+
+ /**
+ * Return focused window if this receives focus notification and has not
+ * received blur notification yet.
+ */
+ static nsWindow* GetFocusedWindow() { return sFocusedWindow; }
+
+ private:
+ static nsWindow* sFocusedWindow;
+ static InputContextAction::Cause sLastContextActionCause;
+
+ static bool sMaybeEditable;
+ static bool sForceDisableCurrentIMM_IME;
+ static bool sNativeCaretIsCreated;
+ static bool sHasNativeCaretBeenRequested;
+
+ /**
+ * MaybeCreateNativeCaret() may create native caret over our caret if
+ * focused content is text editable and we need to create native caret
+ * for other applications.
+ *
+ * @param aWindow The window which owns the native caret.
+ */
+ static bool MaybeCreateNativeCaret(nsWindow* aWindow);
+
+ static decltype(SetInputScopes)* sSetInputScopes;
+ static void SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputMode,
+ bool aInPrivateBrowsing);
+ static bool sIsInTSFMode;
+ // If sIMMEnabled is false, any IME messages are not handled in TSF mode.
+ // Additionally, IME context is always disassociated from focused window.
+ static bool sIsIMMEnabled;
+
+ static bool IsTSFAvailable() { return sIsInTSFMode; }
+ static bool IsIMMActive();
+
+ static bool IsOnScreenKeyboardSupported();
+
+ static void MaybeShowOnScreenKeyboard(nsWindow* aWindow,
+ const InputContext& aInputContext);
+ enum class Sync { Yes, No };
+ static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow,
+ Sync aSync = Sync::No);
+ static bool WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
+ const std::wstring& aNeedle);
+ static bool NeedOnScreenKeyboard();
+ static bool IsKeyboardPresentOnSlate();
+ static bool IsInTabletMode();
+ static bool AutoInvokeOnScreenKeyboardInDesktopMode();
+ static bool NeedsToAssociateIMC();
+ static bool NeedsSearchInputScope();
+
+ /**
+ * Show the Windows on-screen keyboard. Only allowed for
+ * chrome documents and Windows 8 and higher.
+ */
+ static void ShowOnScreenKeyboard(nsWindow* aWindow);
+
+ /**
+ * Dismiss the Windows on-screen keyboard. Only allowed for
+ * Windows 8 and higher.
+ */
+ static void DismissOnScreenKeyboard(nsWindow* aWindow);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef WinIMEHandler_h_
diff --git a/widget/windows/WinMessages.h b/widget/windows/WinMessages.h
new file mode 100644
index 0000000000..8c90460e8e
--- /dev/null
+++ b/widget/windows/WinMessages.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WinMessages_h_
+#define mozilla_widget_WinMessages_h_
+
+/*****************************************************************************
+ * MOZ_WM_* messages
+ ****************************************************************************/
+
+// A magic APP message that can be sent to quit, sort of like a
+// QUERYENDSESSION/ENDSESSION, but without the query.
+#define MOZ_WM_APP_QUIT (WM_APP + 0x0300)
+// Used as a "tracer" event to probe event loop latency.
+#define MOZ_WM_TRACE (WM_APP + 0x0301)
+// accessibility priming
+#define MOZ_WM_STARTA11Y (WM_APP + 0x0302)
+// Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and
+// WM_HSCROLL
+#define MOZ_WM_MOUSEVWHEEL (WM_APP + 0x0310)
+#define MOZ_WM_MOUSEHWHEEL (WM_APP + 0x0311)
+#define MOZ_WM_VSCROLL (WM_APP + 0x0312)
+#define MOZ_WM_HSCROLL (WM_APP + 0x0313)
+#define MOZ_WM_MOUSEWHEEL_FIRST MOZ_WM_MOUSEVWHEEL
+#define MOZ_WM_MOUSEWHEEL_LAST MOZ_WM_HSCROLL
+// If a popup window is being activated, we try to reactivate the previous
+// window with this message.
+#define MOZ_WM_REACTIVATE (WM_APP + 0x0314)
+// If TSFTextStore needs to notify TSF/TIP of layout change later, this
+// message is posted.
+#define MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE (WM_APP + 0x0315)
+// Internal message used in correcting backwards clock skew
+#define MOZ_WM_SKEWFIX (WM_APP + 0x0316)
+// Internal message used for rolling up popups for dmanip events
+#define MOZ_WM_DMANIP (WM_APP + 0x0317)
+
+// Internal message used to work around race condition in explorer.exe's
+// fullscreen window-state update handler in Windows 10+. (See bug 1835851.)
+#define MOZ_WM_FULLSCREEN_STATE_UPDATE (WM_APP + 0x0318)
+
+// XXX Should rename them to MOZ_WM_* and use safer values!
+// Messages for fullscreen transition window
+#define WM_FULLSCREEN_TRANSITION_BEFORE (WM_USER + 0)
+#define WM_FULLSCREEN_TRANSITION_AFTER (WM_USER + 1)
+
+#ifndef APPCOMMAND_BROWSER_BACKWARD
+# define APPCOMMAND_BROWSER_BACKWARD 1
+# define APPCOMMAND_BROWSER_FORWARD 2
+# define APPCOMMAND_BROWSER_REFRESH 3
+# define APPCOMMAND_BROWSER_STOP 4
+# define APPCOMMAND_BROWSER_SEARCH 5
+# define APPCOMMAND_BROWSER_FAVORITES 6
+# define APPCOMMAND_BROWSER_HOME 7
+
+# define APPCOMMAND_MEDIA_NEXTTRACK 11
+# define APPCOMMAND_MEDIA_PREVIOUSTRACK 12
+# define APPCOMMAND_MEDIA_STOP 13
+# define APPCOMMAND_MEDIA_PLAY_PAUSE 14
+
+/*
+ * Additional commands currently not in use.
+ *
+ *#define APPCOMMAND_VOLUME_MUTE 8
+ *#define APPCOMMAND_VOLUME_DOWN 9
+ *#define APPCOMMAND_VOLUME_UP 10
+ *#define APPCOMMAND_LAUNCH_MAIL 15
+ *#define APPCOMMAND_LAUNCH_MEDIA_SELECT 16
+ *#define APPCOMMAND_LAUNCH_APP1 17
+ *#define APPCOMMAND_LAUNCH_APP2 18
+ *#define APPCOMMAND_BASS_DOWN 19
+ *#define APPCOMMAND_BASS_BOOST 20
+ *#define APPCOMMAND_BASS_UP 21
+ *#define APPCOMMAND_TREBLE_DOWN 22
+ *#define APPCOMMAND_TREBLE_UP 23
+ *#define FAPPCOMMAND_MOUSE 0x8000
+ *#define FAPPCOMMAND_KEY 0
+ *#define FAPPCOMMAND_OEM 0x1000
+ */
+
+# define GET_APPCOMMAND_LPARAM(lParam) \
+ ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
+
+/*
+ *#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) &
+ *FAPPCOMMAND_MASK)) #define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM
+ *#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam))
+ *#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam)
+ */
+#endif // #ifndef APPCOMMAND_BROWSER_BACKWARD
+
+#endif // #ifndef mozilla_widget_WinMessages_h_
diff --git a/widget/windows/WinModifierKeyState.h b/widget/windows/WinModifierKeyState.h
new file mode 100644
index 0000000000..0788c25939
--- /dev/null
+++ b/widget/windows/WinModifierKeyState.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WinModifierKeyState_h_
+#define mozilla_widget_WinModifierKeyState_h_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/EventForwards.h"
+#include "nsStringFwd.h"
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+class MOZ_STACK_CLASS ModifierKeyState final {
+ public:
+ ModifierKeyState();
+ explicit ModifierKeyState(Modifiers aModifiers);
+
+ void Update();
+
+ void Unset(Modifiers aRemovingModifiers);
+ void Set(Modifiers aAddingModifiers);
+
+ void InitInputEvent(WidgetInputEvent& aInputEvent) const;
+
+ // Do not create IsAltGr() because it's unclear whether:
+ // - AltGr key is actually pressed.
+ // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+ // has AltGr key.
+ // - Both Ctrl and Alt keys are pressed when a keyboard layout which
+ // does not have AltGr key.
+ bool IsShift() const;
+ bool IsControl() const;
+ bool IsAlt() const;
+ bool IsWin() const;
+
+ bool MaybeMatchShortcutKey() const;
+
+ bool IsCapsLocked() const;
+ bool IsNumLocked() const;
+ bool IsScrollLocked() const;
+
+ MOZ_ALWAYS_INLINE Modifiers GetModifiers() const { return mModifiers; }
+
+ private:
+ Modifiers mModifiers;
+
+ void InitMouseEvent(WidgetInputEvent& aMouseEvent) const;
+};
+
+const nsCString ToString(const ModifierKeyState& aModifierKeyState);
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_WinModifierKeyState_h_
diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp
new file mode 100644
index 0000000000..e10f7b29dc
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.cpp
@@ -0,0 +1,1634 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "mozilla/Logging.h"
+
+#include "WinMouseScrollHandler.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "KeyboardLayout.h"
+#include "WinUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMWindowUtils.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "mozilla/StaticPrefs_mousewheel.h"
+#include "mozilla/widget/WinRegistry.h"
+
+#include <psapi.h>
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gMouseScrollLog("MouseScrollHandlerWidgets");
+
+static const char* GetBoolName(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
+
+MouseScrollHandler* MouseScrollHandler::sInstance = nullptr;
+
+bool MouseScrollHandler::Device::sFakeScrollableWindowNeeded = false;
+
+bool MouseScrollHandler::Device::SynTP::sInitialized = false;
+int32_t MouseScrollHandler::Device::SynTP::sMajorVersion = 0;
+int32_t MouseScrollHandler::Device::SynTP::sMinorVersion = -1;
+
+bool MouseScrollHandler::Device::Elantech::sUseSwipeHack = false;
+bool MouseScrollHandler::Device::Elantech::sUsePinchHack = false;
+DWORD MouseScrollHandler::Device::Elantech::sZoomUntil = 0;
+
+bool MouseScrollHandler::Device::Apoint::sInitialized = false;
+int32_t MouseScrollHandler::Device::Apoint::sMajorVersion = 0;
+int32_t MouseScrollHandler::Device::Apoint::sMinorVersion = -1;
+
+bool MouseScrollHandler::Device::SetPoint::sMightBeUsing = false;
+
+// The duration until timeout of events transaction. The value is 1.5 sec,
+// it's just a magic number, it was suggested by Logitech's engineer, see
+// bug 605648 comment 90.
+#define DEFAULT_TIMEOUT_DURATION 1500
+
+/******************************************************************************
+ *
+ * MouseScrollHandler
+ *
+ ******************************************************************************/
+
+/* static */
+POINTS
+MouseScrollHandler::GetCurrentMessagePos() {
+ if (SynthesizingEvent::IsSynthesizing()) {
+ return sInstance->mSynthesizingEvent->GetCursorPoint();
+ }
+ DWORD pos = ::GetMessagePos();
+ return MAKEPOINTS(pos);
+}
+
+// Get rid of the GetMessagePos() API.
+#define GetMessagePos()
+
+/* static */
+void MouseScrollHandler::Initialize() { Device::Init(); }
+
+/* static */
+void MouseScrollHandler::Shutdown() {
+ delete sInstance;
+ sInstance = nullptr;
+}
+
+/* static */
+MouseScrollHandler* MouseScrollHandler::GetInstance() {
+ if (!sInstance) {
+ sInstance = new MouseScrollHandler();
+ }
+ return sInstance;
+}
+
+MouseScrollHandler::MouseScrollHandler()
+ : mIsWaitingInternalMessage(false), mSynthesizingEvent(nullptr) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll: Creating an instance, this=%p, sInstance=%p", this,
+ sInstance));
+}
+
+MouseScrollHandler::~MouseScrollHandler() {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll: Destroying an instance, this=%p, sInstance=%p", this,
+ sInstance));
+
+ delete mSynthesizingEvent;
+}
+
+/* static */
+void MouseScrollHandler::MaybeLogKeyState() {
+ if (!MOZ_LOG_TEST(gMouseScrollLog, LogLevel::Debug)) {
+ return;
+ }
+ BYTE keyboardState[256];
+ if (::GetKeyboardState(keyboardState)) {
+ for (size_t i = 0; i < ArrayLength(keyboardState); i++) {
+ if (keyboardState[i]) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Debug,
+ (" Current key state: keyboardState[0x%02zX]=0x%02X (%s)", i,
+ keyboardState[i],
+ ((keyboardState[i] & 0x81) == 0x81) ? "Pressed and Toggled"
+ : (keyboardState[i] & 0x80) ? "Pressed"
+ : (keyboardState[i] & 0x01) ? "Toggled"
+ : "Unknown"));
+ }
+ }
+ } else {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Debug,
+ ("MouseScroll::MaybeLogKeyState(): Failed to print current keyboard "
+ "state"));
+ }
+}
+
+/* static */
+bool MouseScrollHandler::NeedsMessage(UINT aMsg) {
+ switch (aMsg) {
+ case WM_SETTINGCHANGE:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ case WM_HSCROLL:
+ case WM_VSCROLL:
+ case MOZ_WM_MOUSEVWHEEL:
+ case MOZ_WM_MOUSEHWHEEL:
+ case MOZ_WM_HSCROLL:
+ case MOZ_WM_VSCROLL:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ return true;
+ }
+ return false;
+}
+
+/* static */
+bool MouseScrollHandler::ProcessMessage(nsWindow* aWidget, UINT msg,
+ WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ Device::Elantech::UpdateZoomUntil();
+
+ switch (msg) {
+ case WM_SETTINGCHANGE:
+ if (!sInstance) {
+ return false;
+ }
+ if (wParam == SPI_SETWHEELSCROLLLINES ||
+ wParam == SPI_SETWHEELSCROLLCHARS) {
+ sInstance->mSystemSettings.MarkDirty();
+ }
+ return false;
+
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ GetInstance()->ProcessNativeMouseWheelMessage(aWidget, msg, wParam,
+ lParam);
+ sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
+ // We don't need to call next wndproc for WM_MOUSEWHEEL and
+ // WM_MOUSEHWHEEL. We should consume them always. If the messages
+ // would be handled by our window again, it caused making infinite
+ // message loop.
+ aResult.mConsumed = true;
+ aResult.mResult = (msg != WM_MOUSEHWHEEL);
+ return true;
+
+ case WM_HSCROLL:
+ case WM_VSCROLL:
+ aResult.mConsumed = GetInstance()->ProcessNativeScrollMessage(
+ aWidget, msg, wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyNativeMessageHandlingFinished();
+ aResult.mResult = 0;
+ return true;
+
+ case MOZ_WM_MOUSEVWHEEL:
+ case MOZ_WM_MOUSEHWHEEL:
+ GetInstance()->HandleMouseWheelMessage(aWidget, msg, wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
+ // Doesn't need to call next wndproc for internal wheel message.
+ aResult.mConsumed = true;
+ return true;
+
+ case MOZ_WM_HSCROLL:
+ case MOZ_WM_VSCROLL:
+ GetInstance()->HandleScrollMessageAsMouseWheelMessage(aWidget, msg,
+ wParam, lParam);
+ sInstance->mSynthesizingEvent->NotifyInternalMessageHandlingFinished();
+ // Doesn't need to call next wndproc for internal scroll message.
+ aResult.mConsumed = true;
+ return true;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessMessage(): aWidget=%p, "
+ "msg=%s(0x%04X), wParam=0x%02zX, ::GetMessageTime()=%ld",
+ aWidget,
+ msg == WM_KEYDOWN ? "WM_KEYDOWN"
+ : msg == WM_KEYUP ? "WM_KEYUP"
+ : "Unknown",
+ msg, wParam, ::GetMessageTime()));
+ MaybeLogKeyState();
+ if (Device::Elantech::HandleKeyMessage(aWidget, msg, wParam, lParam)) {
+ aResult.mResult = 0;
+ aResult.mConsumed = true;
+ return true;
+ }
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+/* static */
+nsresult MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
+ nsWindow* aWidget, const LayoutDeviceIntPoint& aPoint,
+ uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags) {
+ bool useFocusedWindow = !(
+ aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_PREFER_WIDGET_AT_POINT);
+
+ POINT pt;
+ pt.x = aPoint.x;
+ pt.y = aPoint.y;
+
+ HWND target = useFocusedWindow ? ::WindowFromPoint(pt) : ::GetFocus();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+
+ WPARAM wParam = 0;
+ LPARAM lParam = 0;
+ switch (aNativeMessage) {
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL: {
+ lParam = MAKELPARAM(pt.x, pt.y);
+ WORD mod = 0;
+ if (aModifierFlags & (nsIWidget::CTRL_L | nsIWidget::CTRL_R)) {
+ mod |= MK_CONTROL;
+ }
+ if (aModifierFlags & (nsIWidget::SHIFT_L | nsIWidget::SHIFT_R)) {
+ mod |= MK_SHIFT;
+ }
+ wParam = MAKEWPARAM(mod, aDelta);
+ break;
+ }
+ case WM_VSCROLL:
+ case WM_HSCROLL:
+ lParam = (aAdditionalFlags &
+ nsIDOMWindowUtils::MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL)
+ ? reinterpret_cast<LPARAM>(target)
+ : 0;
+ wParam = aDelta;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Ensure to make the instance.
+ GetInstance();
+
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+
+ AutoTArray<KeyPair, 10> keySequence;
+ WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags,
+ aNativeMessage);
+
+ for (uint32_t i = 0; i < keySequence.Length(); ++i) {
+ uint8_t key = keySequence[i].mGeneral;
+ uint8_t keySpecific = keySequence[i].mSpecific;
+ kbdState[key] = 0x81; // key is down and toggled on if appropriate
+ if (keySpecific) {
+ kbdState[keySpecific] = 0x81;
+ }
+ }
+
+ if (!sInstance->mSynthesizingEvent) {
+ sInstance->mSynthesizingEvent = new SynthesizingEvent();
+ }
+
+ POINTS pts;
+ pts.x = static_cast<SHORT>(pt.x);
+ pts.y = static_cast<SHORT>(pt.y);
+ return sInstance->mSynthesizingEvent->Synthesize(pts, target, aNativeMessage,
+ wParam, lParam, kbdState);
+}
+
+/* static */
+void MouseScrollHandler::InitEvent(nsWindow* aWidget, WidgetGUIEvent& aEvent,
+ LPARAM* aPoint) {
+ NS_ENSURE_TRUE_VOID(aWidget);
+
+ // If a point is provided, use it; otherwise, get current message point or
+ // synthetic point
+ POINTS pointOnScreen;
+ if (aPoint != nullptr) {
+ pointOnScreen = MAKEPOINTS(*aPoint);
+ } else {
+ pointOnScreen = GetCurrentMessagePos();
+ }
+
+ // InitEvent expects the point to be in window coordinates, so translate the
+ // point from screen coordinates.
+ POINT pointOnWindow;
+ POINTSTOPOINT(pointOnWindow, pointOnScreen);
+ ::ScreenToClient(aWidget->GetWindowHandle(), &pointOnWindow);
+
+ LayoutDeviceIntPoint point;
+ point.x = pointOnWindow.x;
+ point.y = pointOnWindow.y;
+
+ aWidget->InitEvent(aEvent, &point);
+}
+
+/* static */
+ModifierKeyState MouseScrollHandler::GetModifierKeyState(UINT aMessage) {
+ ModifierKeyState result;
+ // Assume the Control key is down if the Elantech touchpad has sent the
+ // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
+ // MouseScrollHandler::Device::Elantech::HandleKeyMessage().)
+ if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) &&
+ !result.IsControl() && Device::Elantech::IsZooming()) {
+ // XXX Do we need to unset MODIFIER_SHIFT, MODIFIER_ALT, MODIFIER_META too?
+ // If one of them are true, the default action becomes not zooming.
+ result.Unset(MODIFIER_ALTGRAPH);
+ result.Set(MODIFIER_CONTROL);
+ }
+ return result;
+}
+
+POINT
+MouseScrollHandler::ComputeMessagePos(UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam) {
+ POINT point;
+ if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, aWParam,
+ aLParam)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()"));
+ ::GetCursorPos(&point);
+ } else {
+ POINTS pts = GetCurrentMessagePos();
+ point.x = pts.x;
+ point.y = pts.y;
+ }
+ return point;
+}
+
+void MouseScrollHandler::ProcessNativeMouseWheelMessage(nsWindow* aWidget,
+ UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (SynthesizingEvent::IsSynthesizing()) {
+ mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
+ aLParam);
+ }
+
+ POINT point = ComputeMessagePos(aMessage, aWParam, aLParam);
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: aWidget=%p, "
+ "aMessage=%s, wParam=0x%08zX, lParam=0x%08" PRIXLPTR
+ ", point: { x=%ld, y=%ld }",
+ aWidget,
+ aMessage == WM_MOUSEWHEEL ? "WM_MOUSEWHEEL"
+ : aMessage == WM_MOUSEHWHEEL ? "WM_MOUSEHWHEEL"
+ : aMessage == WM_VSCROLL ? "WM_VSCROLL"
+ : "WM_HSCROLL",
+ aWParam, aLParam, point.x, point.y));
+ MaybeLogKeyState();
+
+ HWND underCursorWnd = ::WindowFromPoint(point);
+ if (!underCursorWnd) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "No window is not found under the cursor"));
+ return;
+ }
+
+ if (Device::Elantech::IsPinchHackNeeded() &&
+ Device::Elantech::IsHelperWindow(underCursorWnd)) {
+ // The Elantech driver places a window right underneath the cursor
+ // when sending a WM_MOUSEWHEEL event to us as part of a pinch-to-zoom
+ // gesture. We detect that here, and search for our window that would
+ // be beneath the cursor if that window wasn't there.
+ underCursorWnd = WinUtils::FindOurWindowAtPoint(point);
+ if (!underCursorWnd) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window is not found under the Elantech helper window"));
+ return;
+ }
+ }
+
+ // Handle most cases first. If the window under mouse cursor is our window
+ // except plugin window (MozillaWindowClass), we should handle the message
+ // on the window.
+ if (WinUtils::IsOurProcessWindow(underCursorWnd)) {
+ nsWindow* destWindow = WinUtils::GetNSWindowPtr(underCursorWnd);
+ if (!destWindow) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Found window under the cursor isn't managed by nsWindow..."));
+ HWND wnd = ::GetParent(underCursorWnd);
+ for (; wnd; wnd = ::GetParent(wnd)) {
+ destWindow = WinUtils::GetNSWindowPtr(wnd);
+ if (destWindow) {
+ break;
+ }
+ }
+ if (!wnd) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Our window which is "
+ "managed by nsWindow is not found under the cursor"));
+ return;
+ }
+ }
+
+ MOZ_ASSERT(destWindow, "destWindow must not be NULL");
+
+ // Some odd touchpad utils sets focus to window under the mouse cursor.
+ // this emulates the odd behavior for debug.
+ if (mUserPrefs.ShouldEmulateToMakeWindowUnderCursorForeground() &&
+ (aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL) &&
+ ::GetForegroundWindow() != destWindow->GetWindowHandle()) {
+ ::SetForegroundWindow(destWindow->GetWindowHandle());
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Posting internal message to an nsWindow (%p)...",
+ destWindow));
+ mIsWaitingInternalMessage = true;
+ UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
+ ::PostMessage(destWindow->GetWindowHandle(), internalMessage, aWParam,
+ aLParam);
+ return;
+ }
+
+ // If the window under cursor is not in our process, it means:
+ // 1. The window may be a plugin window (GeckoPluginWindow or its descendant).
+ // 2. The window may be another application's window.
+ HWND pluginWnd = WinUtils::FindOurProcessWindow(underCursorWnd);
+ if (!pluginWnd) {
+ // If there is no plugin window in ancestors of the window under cursor,
+ // the window is for another applications (case 2).
+ // We don't need to handle this message.
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window is not found under the cursor"));
+ return;
+ }
+
+ // If the window is a part of plugin, we should post the message to it.
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Redirecting the message to a window which is a plugin child window"));
+ ::PostMessage(underCursorWnd, aMessage, aWParam, aLParam);
+}
+
+bool MouseScrollHandler::ProcessNativeScrollMessage(nsWindow* aWidget,
+ UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (aLParam || mUserPrefs.IsScrollMessageHandledAsWheelMessage()) {
+ // Scroll message generated by Thinkpad Trackpoint Driver or similar
+ // Treat as a mousewheel message and scroll appropriately
+ ProcessNativeMouseWheelMessage(aWidget, aMessage, aWParam, aLParam);
+ // Always consume the scroll message if we try to emulate mouse wheel
+ // action.
+ return true;
+ }
+
+ if (SynthesizingEvent::IsSynthesizing()) {
+ mSynthesizingEvent->NativeMessageReceived(aWidget, aMessage, aWParam,
+ aLParam);
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeScrollMessage: aWidget=%p, "
+ "aMessage=%s, wParam=0x%08zX, lParam=0x%08" PRIXLPTR,
+ aWidget, aMessage == WM_VSCROLL ? "WM_VSCROLL" : "WM_HSCROLL",
+ aWParam, aLParam));
+
+ // Scroll message generated by external application
+ WidgetContentCommandEvent commandEvent(true, eContentCommandScroll, aWidget);
+ commandEvent.mScroll.mIsHorizontal = (aMessage == WM_HSCROLL);
+
+ switch (LOWORD(aWParam)) {
+ case SB_LINEUP: // SB_LINELEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Line;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_LINEDOWN: // SB_LINERIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Line;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ case SB_PAGEUP: // SB_PAGELEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Page;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_PAGEDOWN: // SB_PAGERIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Page;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ case SB_TOP: // SB_LEFT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Whole;
+ commandEvent.mScroll.mAmount = -1;
+ break;
+ case SB_BOTTOM: // SB_RIGHT
+ commandEvent.mScroll.mUnit =
+ WidgetContentCommandEvent::eCmdScrollUnit_Whole;
+ commandEvent.mScroll.mAmount = 1;
+ break;
+ default:
+ return false;
+ }
+ // XXX If this is a plugin window, we should dispatch the event from
+ // parent window.
+ aWidget->DispatchContentCommandEvent(&commandEvent);
+ return true;
+}
+
+void MouseScrollHandler::HandleMouseWheelMessage(nsWindow* aWidget,
+ UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam) {
+ MOZ_ASSERT((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == MOZ_WM_MOUSEHWHEEL),
+ "HandleMouseWheelMessage must be called with "
+ "MOZ_WM_MOUSEVWHEEL or MOZ_WM_MOUSEHWHEEL");
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: aWidget=%p, "
+ "aMessage=MOZ_WM_MOUSE%sWHEEL, aWParam=0x%08zX, aLParam=0x%08" PRIXLPTR,
+ aWidget, aMessage == MOZ_WM_MOUSEVWHEEL ? "V" : "H", aWParam, aLParam));
+
+ mIsWaitingInternalMessage = false;
+
+ // If it's not allowed to cache system settings, we need to reset the cache
+ // before handling the mouse wheel message.
+ mSystemSettings.TrustedScrollSettingsDriver();
+
+ EventInfo eventInfo(aWidget, WinUtils::GetNativeMessage(aMessage), aWParam,
+ aLParam);
+ if (!eventInfo.CanDispatchWheelEvent()) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: Cannot dispatch the events"));
+ mLastEventInfo.ResetTransaction();
+ return;
+ }
+
+ // Discard the remaining delta if current wheel message and last one are
+ // received by different window or to scroll different direction or
+ // different unit scroll. Furthermore, if the last event was too old.
+ if (!mLastEventInfo.CanContinueTransaction(eventInfo)) {
+ mLastEventInfo.ResetTransaction();
+ }
+
+ mLastEventInfo.RecordEvent(eventInfo);
+
+ ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
+
+ // Grab the widget, it might be destroyed by a DOM event handler.
+ RefPtr<nsWindow> kungFuDethGrip(aWidget);
+
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ if (mLastEventInfo.InitWheelEvent(aWidget, wheelEvent, modKeyState,
+ aLParam)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: dispatching "
+ "eWheel event"));
+ aWidget->DispatchWheelEvent(&wheelEvent);
+ if (aWidget->Destroyed()) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: The window was destroyed "
+ "by eWheel event"));
+ mLastEventInfo.ResetTransaction();
+ return;
+ }
+ } else {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleMouseWheelMessage: eWheel event is not "
+ "dispatched"));
+ }
+}
+
+void MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage(
+ nsWindow* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ MOZ_ASSERT((aMessage == MOZ_WM_VSCROLL || aMessage == MOZ_WM_HSCROLL),
+ "HandleScrollMessageAsMouseWheelMessage must be called with "
+ "MOZ_WM_VSCROLL or MOZ_WM_HSCROLL");
+
+ mIsWaitingInternalMessage = false;
+
+ ModifierKeyState modKeyState = GetModifierKeyState(aMessage);
+
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ double& delta =
+ (aMessage == MOZ_WM_VSCROLL) ? wheelEvent.mDeltaY : wheelEvent.mDeltaX;
+ int32_t& lineOrPageDelta = (aMessage == MOZ_WM_VSCROLL)
+ ? wheelEvent.mLineOrPageDeltaY
+ : wheelEvent.mLineOrPageDeltaX;
+
+ delta = 1.0;
+ lineOrPageDelta = 1;
+
+ switch (LOWORD(aWParam)) {
+ case SB_PAGEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ [[fallthrough]];
+ case SB_PAGEDOWN:
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PAGE;
+ break;
+
+ case SB_LINEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ [[fallthrough]];
+ case SB_LINEDOWN:
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
+ break;
+
+ default:
+ return;
+ }
+ modKeyState.InitInputEvent(wheelEvent);
+
+ // Current mouse position may not be same as when the original message
+ // is received. However, this data is not available with the original
+ // message, which is why nullptr is passed in. We need to know the actual
+ // mouse cursor position when the original message was received.
+ InitEvent(aWidget, wheelEvent, nullptr);
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, "
+ "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08zX, aLParam=0x%08" PRIXLPTR ", "
+ "wheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
+ "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
+ "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s }",
+ aWidget, (aMessage == MOZ_WM_VSCROLL) ? "V" : "H", aWParam, aLParam,
+ wheelEvent.mRefPoint.x.value, wheelEvent.mRefPoint.y.value,
+ wheelEvent.mDeltaX, wheelEvent.mDeltaY, wheelEvent.mLineOrPageDeltaX,
+ wheelEvent.mLineOrPageDeltaY, GetBoolName(wheelEvent.IsShift()),
+ GetBoolName(wheelEvent.IsControl()), GetBoolName(wheelEvent.IsAlt()),
+ GetBoolName(wheelEvent.IsMeta())));
+
+ aWidget->DispatchWheelEvent(&wheelEvent);
+}
+
+/******************************************************************************
+ *
+ * EventInfo
+ *
+ ******************************************************************************/
+
+MouseScrollHandler::EventInfo::EventInfo(nsWindow* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam) {
+ MOZ_ASSERT(
+ aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL,
+ "EventInfo must be initialized with WM_MOUSEWHEEL or WM_MOUSEHWHEEL");
+
+ MouseScrollHandler::GetInstance()->mSystemSettings.Init();
+
+ mIsVertical = (aMessage == WM_MOUSEWHEEL);
+ mIsPage =
+ MouseScrollHandler::sInstance->mSystemSettings.IsPageScroll(mIsVertical);
+ mDelta = (short)HIWORD(aWParam);
+ mWnd = aWidget->GetWindowHandle();
+ mTimeStamp = TimeStamp::Now();
+}
+
+bool MouseScrollHandler::EventInfo::CanDispatchWheelEvent() const {
+ if (!GetScrollAmount()) {
+ // XXX I think that we should dispatch mouse wheel events even if the
+ // operation will not scroll because the wheel operation really happened
+ // and web application may want to handle the event for non-scroll action.
+ return false;
+ }
+
+ return (mDelta != 0);
+}
+
+int32_t MouseScrollHandler::EventInfo::GetScrollAmount() const {
+ if (mIsPage) {
+ return 1;
+ }
+ return MouseScrollHandler::sInstance->mSystemSettings.GetScrollAmount(
+ mIsVertical);
+}
+
+/******************************************************************************
+ *
+ * LastEventInfo
+ *
+ ******************************************************************************/
+
+bool MouseScrollHandler::LastEventInfo::CanContinueTransaction(
+ const EventInfo& aNewEvent) {
+ int32_t timeout = MouseScrollHandler::sInstance->mUserPrefs
+ .GetMouseScrollTransactionTimeout();
+ return !mWnd ||
+ (mWnd == aNewEvent.GetWindowHandle() &&
+ IsPositive() == aNewEvent.IsPositive() &&
+ mIsVertical == aNewEvent.IsVertical() &&
+ mIsPage == aNewEvent.IsPage() &&
+ (timeout < 0 || TimeStamp::Now() - mTimeStamp <=
+ TimeDuration::FromMilliseconds(timeout)));
+}
+
+void MouseScrollHandler::LastEventInfo::ResetTransaction() {
+ if (!mWnd) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::LastEventInfo::ResetTransaction()"));
+
+ mWnd = nullptr;
+ mAccumulatedDelta = 0;
+}
+
+void MouseScrollHandler::LastEventInfo::RecordEvent(const EventInfo& aEvent) {
+ mWnd = aEvent.GetWindowHandle();
+ mDelta = aEvent.GetNativeDelta();
+ mIsVertical = aEvent.IsVertical();
+ mIsPage = aEvent.IsPage();
+ mTimeStamp = TimeStamp::Now();
+}
+
+/* static */
+int32_t MouseScrollHandler::LastEventInfo::RoundDelta(double aDelta) {
+ return (aDelta >= 0) ? (int32_t)floor(aDelta) : (int32_t)ceil(aDelta);
+}
+
+bool MouseScrollHandler::LastEventInfo::InitWheelEvent(
+ nsWindow* aWidget, WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState, LPARAM aLParam) {
+ MOZ_ASSERT(aWheelEvent.mMessage == eWheel);
+
+ if (StaticPrefs::mousewheel_ignore_cursor_position_in_lparam()) {
+ InitEvent(aWidget, aWheelEvent, nullptr);
+ } else {
+ InitEvent(aWidget, aWheelEvent, &aLParam);
+ }
+
+ aModKeyState.InitInputEvent(aWheelEvent);
+
+ // Our positive delta value means to bottom or right.
+ // But positive native delta value means to top or right.
+ // Use orienter for computing our delta value with native delta value.
+ int32_t orienter = mIsVertical ? -1 : 1;
+
+ aWheelEvent.mDeltaMode = mIsPage ? dom::WheelEvent_Binding::DOM_DELTA_PAGE
+ : dom::WheelEvent_Binding::DOM_DELTA_LINE;
+
+ double ticks = double(mDelta) * orienter / double(WHEEL_DELTA);
+ if (mIsVertical) {
+ aWheelEvent.mWheelTicksY = ticks;
+ } else {
+ aWheelEvent.mWheelTicksX = ticks;
+ }
+
+ double& delta = mIsVertical ? aWheelEvent.mDeltaY : aWheelEvent.mDeltaX;
+ int32_t& lineOrPageDelta = mIsVertical ? aWheelEvent.mLineOrPageDeltaY
+ : aWheelEvent.mLineOrPageDeltaX;
+
+ double nativeDeltaPerUnit =
+ mIsPage ? double(WHEEL_DELTA) : double(WHEEL_DELTA) / GetScrollAmount();
+
+ delta = double(mDelta) * orienter / nativeDeltaPerUnit;
+ mAccumulatedDelta += mDelta;
+ lineOrPageDelta =
+ mAccumulatedDelta * orienter / RoundDelta(nativeDeltaPerUnit);
+ mAccumulatedDelta -=
+ lineOrPageDelta * orienter * RoundDelta(nativeDeltaPerUnit);
+
+ if (aWheelEvent.mDeltaMode != dom::WheelEvent_Binding::DOM_DELTA_LINE) {
+ // If the scroll delta mode isn't per line scroll, we shouldn't allow to
+ // override the system scroll speed setting.
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
+ }
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::LastEventInfo::InitWheelEvent: aWidget=%p, "
+ "aWheelEvent { mRefPoint: { x: %d, y: %d }, mDeltaX: %f, mDeltaY: %f, "
+ "mLineOrPageDeltaX: %d, mLineOrPageDeltaY: %d, "
+ "isShift: %s, isControl: %s, isAlt: %s, isMeta: %s, "
+ "mAllowToOverrideSystemScrollSpeed: %s }, "
+ "mAccumulatedDelta: %d",
+ aWidget, aWheelEvent.mRefPoint.x.value, aWheelEvent.mRefPoint.y.value,
+ aWheelEvent.mDeltaX, aWheelEvent.mDeltaY, aWheelEvent.mLineOrPageDeltaX,
+ aWheelEvent.mLineOrPageDeltaY, GetBoolName(aWheelEvent.IsShift()),
+ GetBoolName(aWheelEvent.IsControl()), GetBoolName(aWheelEvent.IsAlt()),
+ GetBoolName(aWheelEvent.IsMeta()),
+ GetBoolName(aWheelEvent.mAllowToOverrideSystemScrollSpeed),
+ mAccumulatedDelta));
+
+ return (delta != 0);
+}
+
+/******************************************************************************
+ *
+ * SystemSettings
+ *
+ ******************************************************************************/
+
+void MouseScrollHandler::SystemSettings::Init() {
+ if (mInitialized) {
+ return;
+ }
+
+ InitScrollLines();
+ InitScrollChars();
+
+ mInitialized = true;
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::Init(): initialized, "
+ "mScrollLines=%d, mScrollChars=%d",
+ mScrollLines, mScrollChars));
+}
+
+bool MouseScrollHandler::SystemSettings::InitScrollLines() {
+ int32_t oldValue = mInitialized ? mScrollLines : 0;
+ mIsReliableScrollLines = false;
+ mScrollLines = MouseScrollHandler::sInstance->mUserPrefs
+ .GetOverriddenVerticalScrollAmout();
+ if (mScrollLines >= 0) {
+ // overridden by the pref.
+ mIsReliableScrollLines = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): mScrollLines is "
+ "overridden by the pref: %d",
+ mScrollLines));
+ } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &mScrollLines,
+ 0)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): "
+ "::SystemParametersInfo("
+ "SPI_GETWHEELSCROLLLINES) failed"));
+ mScrollLines = DefaultScrollLines();
+ }
+
+ if (mScrollLines > WHEEL_DELTA) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollLines(): the result of "
+ "::SystemParametersInfo(SPI_GETWHEELSCROLLLINES) is too large: %d",
+ mScrollLines));
+ // sScrollLines usually equals 3 or 0 (for no scrolling)
+ // However, if sScrollLines > WHEEL_DELTA, we assume that
+ // the mouse driver wants a page scroll. The docs state that
+ // sScrollLines should explicitly equal WHEEL_PAGESCROLL, but
+ // since some mouse drivers use an arbitrary large number instead,
+ // we have to handle that as well.
+ mScrollLines = WHEEL_PAGESCROLL;
+ }
+
+ return oldValue != mScrollLines;
+}
+
+bool MouseScrollHandler::SystemSettings::InitScrollChars() {
+ int32_t oldValue = mInitialized ? mScrollChars : 0;
+ mIsReliableScrollChars = false;
+ mScrollChars = MouseScrollHandler::sInstance->mUserPrefs
+ .GetOverriddenHorizontalScrollAmout();
+ if (mScrollChars >= 0) {
+ // overridden by the pref.
+ mIsReliableScrollChars = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): mScrollChars is "
+ "overridden by the pref: %d",
+ mScrollChars));
+ } else if (!::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &mScrollChars,
+ 0)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): "
+ "::SystemParametersInfo("
+ "SPI_GETWHEELSCROLLCHARS) failed, this is unexpected on Vista or "
+ "later"));
+ // XXX Should we use DefaultScrollChars()?
+ mScrollChars = 1;
+ }
+
+ if (mScrollChars > WHEEL_DELTA) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::SystemSettings::InitScrollChars(): the result of "
+ "::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d",
+ mScrollChars));
+ // See the comments for the case mScrollLines > WHEEL_DELTA.
+ mScrollChars = WHEEL_PAGESCROLL;
+ }
+
+ return oldValue != mScrollChars;
+}
+
+void MouseScrollHandler::SystemSettings::MarkDirty() {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SystemSettings::MarkDirty(): "
+ "Marking SystemSettings dirty"));
+ mInitialized = false;
+ // When system settings are changed, we should reset current transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+void MouseScrollHandler::SystemSettings::RefreshCache() {
+ bool isChanged = InitScrollLines();
+ isChanged = InitScrollChars() || isChanged;
+ if (!isChanged) {
+ return;
+ }
+ // If the scroll amount is changed, we should reset current transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+void MouseScrollHandler::SystemSettings::TrustedScrollSettingsDriver() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // if the cache is initialized with prefs, we don't need to refresh it.
+ if (mIsReliableScrollLines && mIsReliableScrollChars) {
+ return;
+ }
+
+ MouseScrollHandler::UserPrefs& userPrefs =
+ MouseScrollHandler::sInstance->mUserPrefs;
+
+ // If system settings cache is disabled, we should always refresh them.
+ if (!userPrefs.IsSystemSettingCacheEnabled()) {
+ RefreshCache();
+ return;
+ }
+
+ // If pref is set to as "always trust the cache", we shouldn't refresh them
+ // in any environments.
+ if (userPrefs.IsSystemSettingCacheForciblyEnabled()) {
+ return;
+ }
+
+ // If SynTP of Synaptics or Apoint of Alps is installed, it may hook
+ // ::SystemParametersInfo() and returns different value from system settings.
+ if (Device::SynTP::IsDriverInstalled() ||
+ Device::Apoint::IsDriverInstalled()) {
+ RefreshCache();
+ return;
+ }
+
+ // XXX We're not sure about other touchpad drivers...
+}
+
+/******************************************************************************
+ *
+ * UserPrefs
+ *
+ ******************************************************************************/
+
+MouseScrollHandler::UserPrefs::UserPrefs() : mInitialized(false) {
+ // We need to reset mouse wheel transaction when all of mousewheel related
+ // prefs are changed.
+ DebugOnly<nsresult> rv =
+ Preferences::RegisterPrefixCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to register callback for mousewheel.");
+}
+
+MouseScrollHandler::UserPrefs::~UserPrefs() {
+ DebugOnly<nsresult> rv =
+ Preferences::UnregisterPrefixCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to unregister callback for mousewheel.");
+}
+
+void MouseScrollHandler::UserPrefs::Init() {
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+
+ mScrollMessageHandledAsWheelMessage =
+ Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false);
+ mEnableSystemSettingCache =
+ Preferences::GetBool("mousewheel.system_settings_cache.enabled", true);
+ mForceEnableSystemSettingCache = Preferences::GetBool(
+ "mousewheel.system_settings_cache.force_enabled", false);
+ mEmulateToMakeWindowUnderCursorForeground = Preferences::GetBool(
+ "mousewheel.debug.make_window_under_cursor_foreground", false);
+ mOverriddenVerticalScrollAmount =
+ Preferences::GetInt("mousewheel.windows.vertical_amount_override", -1);
+ mOverriddenHorizontalScrollAmount =
+ Preferences::GetInt("mousewheel.windows.horizontal_amount_override", -1);
+ mMouseScrollTransactionTimeout = Preferences::GetInt(
+ "mousewheel.windows.transaction.timeout", DEFAULT_TIMEOUT_DURATION);
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::UserPrefs::Init(): initialized, "
+ "mScrollMessageHandledAsWheelMessage=%s, "
+ "mEnableSystemSettingCache=%s, "
+ "mForceEnableSystemSettingCache=%s, "
+ "mEmulateToMakeWindowUnderCursorForeground=%s, "
+ "mOverriddenVerticalScrollAmount=%d, "
+ "mOverriddenHorizontalScrollAmount=%d, "
+ "mMouseScrollTransactionTimeout=%d",
+ GetBoolName(mScrollMessageHandledAsWheelMessage),
+ GetBoolName(mEnableSystemSettingCache),
+ GetBoolName(mForceEnableSystemSettingCache),
+ GetBoolName(mEmulateToMakeWindowUnderCursorForeground),
+ mOverriddenVerticalScrollAmount, mOverriddenHorizontalScrollAmount,
+ mMouseScrollTransactionTimeout));
+}
+
+void MouseScrollHandler::UserPrefs::MarkDirty() {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty"));
+ mInitialized = false;
+ // Some prefs might override system settings, so, we should mark them dirty.
+ MouseScrollHandler::sInstance->mSystemSettings.MarkDirty();
+ // When user prefs for mousewheel are changed, we should reset current
+ // transaction.
+ MOZ_ASSERT(sInstance,
+ "Must not be called at initializing MouseScrollHandler");
+ MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction();
+}
+
+/******************************************************************************
+ *
+ * Device
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName,
+ bool aValueIfAutomatic) {
+ if (!aPrefName) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is "
+ "NULL"));
+ return aValueIfAutomatic;
+ }
+
+ int32_t lHackValue = 0;
+ if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() "
+ "failed,"
+ " aPrefName=\"%s\", aValueIfAutomatic=%s",
+ aPrefName, GetBoolName(aValueIfAutomatic)));
+ return aValueIfAutomatic;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::GetWorkaroundPref(): Succeeded, "
+ "aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d",
+ aPrefName, GetBoolName(aValueIfAutomatic), lHackValue));
+
+ switch (lHackValue) {
+ case 0: // disabled
+ return false;
+ case 1: // enabled
+ return true;
+ default: // -1: autodetect
+ return aValueIfAutomatic;
+ }
+}
+
+/* static */
+void MouseScrollHandler::Device::Init() {
+ // FYI: Thinkpad's TrackPoint is Apoint of Alps and UltraNav is SynTP of
+ // Synaptics. So, those drivers' information should be initialized
+ // before calling methods of TrackPoint and UltraNav.
+ SynTP::Init();
+ Elantech::Init();
+ Apoint::Init();
+
+ sFakeScrollableWindowNeeded = GetWorkaroundPref(
+ "ui.trackpoint_hack.enabled", (TrackPoint::IsDriverInstalled() ||
+ UltraNav::IsObsoleteDriverInstalled()));
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s",
+ GetBoolName(sFakeScrollableWindowNeeded)));
+}
+
+/******************************************************************************
+ *
+ * Device::SynTP
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::SynTP::Init() {
+ if (sInitialized) {
+ return;
+ }
+
+ sInitialized = true;
+ sMajorVersion = 0;
+ sMinorVersion = -1;
+
+ wchar_t buf[40];
+ if (!WinRegistry::GetString(
+ HKEY_LOCAL_MACHINE, u"Software\\Synaptics\\SynTP\\Install"_ns,
+ u"DriverVersion"_ns, buf, WinRegistry::kLegacyWinUtilsStringFlags)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SynTP::Init(): "
+ "SynTP driver is not found"));
+ return;
+ }
+
+ sMajorVersion = wcstol(buf, nullptr, 10);
+ sMinorVersion = 0;
+ wchar_t* p = wcschr(buf, L'.');
+ if (p) {
+ sMinorVersion = wcstol(p + 1, nullptr, 10);
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SynTP::Init(): "
+ "found driver version = %d.%d",
+ sMajorVersion, sMinorVersion));
+}
+
+/******************************************************************************
+ *
+ * Device::Elantech
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::Elantech::Init() {
+ int32_t version = GetDriverMajorVersion();
+ bool needsHack = Device::GetWorkaroundPref(
+ "ui.elantech_gesture_hacks.enabled", version != 0);
+ sUseSwipeHack = needsHack && version <= 7;
+ sUsePinchHack = needsHack && version <= 8;
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::Init(): version=%d, sUseSwipeHack=%s, "
+ "sUsePinchHack=%s",
+ version, GetBoolName(sUseSwipeHack), GetBoolName(sUsePinchHack)));
+}
+
+/* static */
+int32_t MouseScrollHandler::Device::Elantech::GetDriverMajorVersion() {
+ wchar_t buf[40];
+ // The driver version is found in one of these two registry keys.
+ if (!WinRegistry::GetString(
+ HKEY_CURRENT_USER, u"Software\\Elantech\\MainOption"_ns,
+ u"DriverVersion"_ns, buf, WinRegistry::kLegacyWinUtilsStringFlags) &&
+ !WinRegistry::GetString(HKEY_CURRENT_USER, u"Software\\Elantech"_ns,
+ u"DriverVersion"_ns, buf,
+ WinRegistry::kLegacyWinUtilsStringFlags)) {
+ return 0;
+ }
+
+ // Assume that the major version number can be found just after a space
+ // or at the start of the string.
+ for (wchar_t* p = buf; *p; p++) {
+ if (*p >= L'0' && *p <= L'9' && (p == buf || *(p - 1) == L' ')) {
+ return wcstol(p, nullptr, 10);
+ }
+ }
+
+ return 0;
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::IsHelperWindow(HWND aWnd) {
+ // The helper window cannot be distinguished based on its window class, so we
+ // need to check if it is owned by the helper process, ETDCtrl.exe.
+
+ const wchar_t* filenameSuffix = L"\\etdctrl.exe";
+ const int filenameSuffixLength = 12;
+
+ DWORD pid;
+ ::GetWindowThreadProcessId(aWnd, &pid);
+
+ HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (!hProcess) {
+ return false;
+ }
+
+ bool result = false;
+ wchar_t path[256] = {L'\0'};
+ if (::GetProcessImageFileNameW(hProcess, path, ArrayLength(path))) {
+ int pathLength = lstrlenW(path);
+ if (pathLength >= filenameSuffixLength) {
+ if (lstrcmpiW(path + pathLength - filenameSuffixLength, filenameSuffix) ==
+ 0) {
+ result = true;
+ }
+ }
+ }
+ ::CloseHandle(hProcess);
+
+ return result;
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::HandleKeyMessage(nsWindow* aWidget,
+ UINT aMsg,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ // The Elantech touchpad driver understands three-finger swipe left and
+ // right gestures, and translates them into Page Up and Page Down key
+ // events for most applications. For Firefox 3.6, it instead sends
+ // Alt+Left and Alt+Right to trigger browser back/forward actions. As
+ // with the Thinkpad Driver hack in nsWindow::Create, the change in
+ // HWND structure makes Firefox not trigger the driver's heuristics
+ // any longer.
+ //
+ // The Elantech driver actually sends these messages for a three-finger
+ // swipe right:
+ //
+ // WM_KEYDOWN virtual_key = 0xCC or 0xFF ScanCode = 00
+ // WM_KEYDOWN virtual_key = VK_NEXT ScanCode = 00
+ // WM_KEYUP virtual_key = VK_NEXT ScanCode = 00
+ // WM_KEYUP virtual_key = 0xCC or 0xFF ScanCode = 00
+ //
+ // Whether 0xCC or 0xFF is sent is suspected to depend on the driver
+ // version. 7.0.4.12_14Jul09_WHQL, 7.0.5.10, and 7.0.6.0 generate 0xCC.
+ // 7.0.4.3 from Asus on EeePC generates 0xFF.
+ //
+ // On some hardware, IS_VK_DOWN(0xFF) returns true even when Elantech
+ // messages are not involved, meaning that alone is not enough to
+ // distinguish the gesture from a regular Page Up or Page Down key press.
+ // The ScanCode is therefore also tested to detect the gesture.
+ // We then pretend that we should dispatch "Go Forward" command. Similarly
+ // for VK_PRIOR and "Go Back" command.
+ if (sUseSwipeHack && (aWParam == VK_NEXT || aWParam == VK_PRIOR) &&
+ WinUtils::GetScanCode(aLParam) == 0 &&
+ (IS_VK_DOWN(0xFF) || IS_VK_DOWN(0xCC))) {
+ if (aMsg == WM_KEYDOWN) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): Dispatching "
+ "%s command event",
+ aWParam == VK_NEXT ? "Forward" : "Back"));
+
+ WidgetCommandEvent appCommandEvent(
+ true, (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back,
+ aWidget);
+
+ // In this scenario, the coordinate of the event isn't supplied, so pass
+ // nullptr as an argument to indicate using the coordinate from the last
+ // available window message.
+ InitEvent(aWidget, appCommandEvent, nullptr);
+ aWidget->DispatchWindowEvent(appCommandEvent);
+ } else {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed"));
+ }
+ return true; // consume the message (doesn't need to dispatch key events)
+ }
+
+ // Version 8 of the Elantech touchpad driver sends these messages for
+ // zoom gestures:
+ //
+ // WM_KEYDOWN virtual_key = 0xCC time = 10
+ // WM_KEYDOWN virtual_key = VK_CONTROL time = 10
+ // WM_MOUSEWHEEL time = ::GetTickCount()
+ // WM_KEYUP virtual_key = VK_CONTROL time = 10
+ // WM_KEYUP virtual_key = 0xCC time = 10
+ //
+ // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP
+ // messages first because their timestamps make them appear to have
+ // been sent before the WM_MOUSEWHEEL message. To work around this,
+ // we store the current time when we process the WM_KEYUP message and
+ // assume that any WM_MOUSEWHEEL message with a timestamp before that
+ // time is one that should be processed as if the Control key was down.
+ if (sUsePinchHack && aMsg == WM_KEYUP && aWParam == VK_CONTROL &&
+ ::GetMessageTime() == 10) {
+ // We look only at the bottom 31 bits of the system tick count since
+ // GetMessageTime returns a LONG, which is signed, so we want values
+ // that are more easily comparable.
+ sZoomUntil = ::GetTickCount() & 0x7FFFFFFF;
+
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%lu",
+ sZoomUntil));
+ }
+
+ return false;
+}
+
+/* static */
+void MouseScrollHandler::Device::Elantech::UpdateZoomUntil() {
+ if (!sZoomUntil) {
+ return;
+ }
+
+ // For the Elantech Touchpad Zoom Gesture Hack, we should check that the
+ // system time (32-bit milliseconds) hasn't wrapped around. Otherwise we
+ // might get into the situation where wheel events for the next 50 days of
+ // system uptime are assumed to be Ctrl+Wheel events. (It is unlikely that
+ // we would get into that state, because the system would already need to be
+ // up for 50 days and the Control key message would need to be processed just
+ // before the system time overflow and the wheel message just after.)
+ //
+ // We also take the chance to reset sZoomUntil if we simply have passed that
+ // time.
+ LONG msgTime = ::GetMessageTime();
+ if ((sZoomUntil >= 0x3fffffffu && DWORD(msgTime) < 0x40000000u) ||
+ (sZoomUntil < DWORD(msgTime))) {
+ sZoomUntil = 0;
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Elantech::UpdateZoomUntil(): "
+ "sZoomUntil was reset"));
+ }
+}
+
+/* static */
+bool MouseScrollHandler::Device::Elantech::IsZooming() {
+ // Assume the Control key is down if the Elantech touchpad has sent the
+ // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in
+ // OnKeyUp.)
+ return (sZoomUntil && static_cast<DWORD>(::GetMessageTime()) < sZoomUntil);
+}
+
+/******************************************************************************
+ *
+ * Device::Apoint
+ *
+ ******************************************************************************/
+
+/* static */
+void MouseScrollHandler::Device::Apoint::Init() {
+ if (sInitialized) {
+ return;
+ }
+
+ sInitialized = true;
+ sMajorVersion = 0;
+ sMinorVersion = -1;
+
+ wchar_t buf[40];
+ if (!WinRegistry::GetString(HKEY_LOCAL_MACHINE, u"Software\\Alps\\Apoint"_ns,
+ u"ProductVer"_ns, buf,
+ WinRegistry::kLegacyWinUtilsStringFlags)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Apoint::Init(): "
+ "Apoint driver is not found"));
+ return;
+ }
+
+ sMajorVersion = wcstol(buf, nullptr, 10);
+ sMinorVersion = 0;
+ wchar_t* p = wcschr(buf, L'.');
+ if (p) {
+ sMinorVersion = wcstol(p + 1, nullptr, 10);
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::Apoint::Init(): "
+ "found driver version = %d.%d",
+ sMajorVersion, sMinorVersion));
+}
+
+/******************************************************************************
+ *
+ * Device::TrackPoint
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::TrackPoint::IsDriverInstalled() {
+ if (WinRegistry::HasKey(HKEY_CURRENT_USER,
+ u"Software\\Lenovo\\TrackPoint"_ns)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Lenovo's TrackPoint driver is found"));
+ return true;
+ }
+
+ if (WinRegistry::HasKey(HKEY_CURRENT_USER,
+ u"Software\\Alps\\Apoint\\TrackPoint"_ns)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Alps's TrackPoint driver is found"));
+ return true;
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ *
+ * Device::UltraNav
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled() {
+ if (WinRegistry::HasKey(HKEY_CURRENT_USER,
+ u"Software\\Lenovo\\UltraNav"_ns)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Lenovo's UltraNav driver is found"));
+ return true;
+ }
+
+ bool installed = false;
+ if (WinRegistry::HasKey(HKEY_CURRENT_USER,
+ u"Software\\Synaptics\\SynTPEnh\\UltraNavUSB"_ns)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Synaptics's UltraNav (USB) driver is found"));
+ installed = true;
+ } else if (WinRegistry::HasKey(
+ HKEY_CURRENT_USER,
+ u"Software\\Synaptics\\SynTPEnh\\UltraNavPS2"_ns)) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Synaptics's UltraNav (PS/2) driver is found"));
+ installed = true;
+ }
+
+ if (!installed) {
+ return false;
+ }
+
+ int32_t majorVersion = Device::SynTP::GetDriverMajorVersion();
+ if (!majorVersion) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Failed to get UltraNav driver version"));
+ return false;
+ }
+ int32_t minorVersion = Device::SynTP::GetDriverMinorVersion();
+ return majorVersion < 15 || (majorVersion == 15 && minorVersion == 0);
+}
+
+/******************************************************************************
+ *
+ * Device::SetPoint
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::Device::SetPoint::IsGetMessagePosResponseValid(
+ UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ if (aMessage != WM_MOUSEHWHEEL) {
+ return false;
+ }
+
+ POINTS pts = MouseScrollHandler::GetCurrentMessagePos();
+ LPARAM messagePos = MAKELPARAM(pts.x, pts.y);
+
+ // XXX We should check whether SetPoint is installed or not by registry.
+
+ // SetPoint, Logitech (Logicool) mouse driver, (confirmed with 4.82.11 and
+ // MX-1100) always sets 0 to the lParam of WM_MOUSEHWHEEL. The driver SENDs
+ // one message at first time, this time, ::GetMessagePos() works fine.
+ // Then, we will return 0 (0 means we process it) to the message. Then, the
+ // driver will POST the same messages continuously during the wheel tilted.
+ // But ::GetMessagePos() API always returns (0, 0) for them, even if the
+ // actual mouse cursor isn't 0,0. Therefore, we cannot trust the result of
+ // ::GetMessagePos API if the sender is SetPoint.
+ if (!sMightBeUsing && !aLParam && aLParam != messagePos &&
+ ::InSendMessage()) {
+ sMightBeUsing = true;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
+ "Might using SetPoint"));
+ } else if (sMightBeUsing && aLParam != 0 && ::InSendMessage()) {
+ // The user has changed the mouse from Logitech's to another one (e.g.,
+ // the user has changed to the touchpad of the notebook.
+ sMightBeUsing = false;
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::SetPoint::IsGetMessagePosResponseValid(): "
+ "Might stop using SetPoint"));
+ }
+ return (sMightBeUsing && !aLParam && !messagePos);
+}
+
+/******************************************************************************
+ *
+ * SynthesizingEvent
+ *
+ ******************************************************************************/
+
+/* static */
+bool MouseScrollHandler::SynthesizingEvent::IsSynthesizing() {
+ return MouseScrollHandler::sInstance &&
+ MouseScrollHandler::sInstance->mSynthesizingEvent &&
+ MouseScrollHandler::sInstance->mSynthesizingEvent->mStatus !=
+ NOT_SYNTHESIZING;
+}
+
+nsresult MouseScrollHandler::SynthesizingEvent::Synthesize(
+ const POINTS& aCursorPoint, HWND aWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam, const BYTE (&aKeyStates)[256]) {
+ MOZ_LOG(
+ gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::Synthesize(): aCursorPoint: { "
+ "x: %d, y: %d }, aWnd=0x%p, aMessage=0x%04X, aWParam=0x%08zX, "
+ "aLParam=0x%08" PRIXLPTR ", IsSynthesized()=%s, mStatus=%s",
+ aCursorPoint.x, aCursorPoint.y, aWnd, aMessage, aWParam, aLParam,
+ GetBoolName(IsSynthesizing()), GetStatusName()));
+
+ if (IsSynthesizing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ ::GetKeyboardState(mOriginalKeyState);
+
+ // Note that we cannot use ::SetCursorPos() because it works asynchronously.
+ // We should SEND the message for reducing the possibility of receiving
+ // unexpected message which were not sent from here.
+ mCursorPoint = aCursorPoint;
+
+ mWnd = aWnd;
+ mMessage = aMessage;
+ mWParam = aWParam;
+ mLParam = aLParam;
+
+ memcpy(mKeyState, aKeyStates, sizeof(mKeyState));
+ ::SetKeyboardState(mKeyState);
+
+ mStatus = SENDING_MESSAGE;
+
+ // Don't assume that aWnd is always managed by nsWindow. It might be
+ // a plugin window.
+ ::SendMessage(aWnd, aMessage, aWParam, aLParam);
+
+ return NS_OK;
+}
+
+void MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(
+ nsWindow* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam) {
+ if (mStatus == SENDING_MESSAGE && mMessage == aMessage &&
+ mWParam == aWParam && mLParam == aLParam) {
+ mStatus = NATIVE_MESSAGE_RECEIVED;
+ if (aWidget && aWidget->GetWindowHandle() == mWnd) {
+ return;
+ }
+ // Otherwise, the message may not be sent by us.
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(): "
+ "aWidget=%p, aWidget->GetWindowHandle()=0x%p, mWnd=0x%p, "
+ "aMessage=0x%04X, aWParam=0x%08zX, aLParam=0x%08" PRIXLPTR
+ ", mStatus=%s",
+ aWidget, aWidget ? aWidget->GetWindowHandle() : nullptr, mWnd,
+ aMessage, aWParam, aLParam, GetStatusName()));
+
+ // We failed to receive our sent message, we failed to do the job.
+ Finish();
+
+ return;
+}
+
+void MouseScrollHandler::SynthesizingEvent::
+ NotifyNativeMessageHandlingFinished() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::"
+ "NotifyNativeMessageHandlingFinished(): IsWaitingInternalMessage=%s",
+ GetBoolName(MouseScrollHandler::IsWaitingInternalMessage())));
+
+ if (MouseScrollHandler::IsWaitingInternalMessage()) {
+ mStatus = INTERNAL_MESSAGE_POSTED;
+ return;
+ }
+
+ // If the native message handler didn't post our internal message,
+ // we our job is finished.
+ // TODO: When we post the message to plugin window, there is remaning job.
+ Finish();
+}
+
+void MouseScrollHandler::SynthesizingEvent::
+ NotifyInternalMessageHandlingFinished() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::"
+ "NotifyInternalMessageHandlingFinished()"));
+
+ Finish();
+}
+
+void MouseScrollHandler::SynthesizingEvent::Finish() {
+ if (!IsSynthesizing()) {
+ return;
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::Finish()"));
+
+ // Restore the original key state.
+ ::SetKeyboardState(mOriginalKeyState);
+
+ mStatus = NOT_SYNTHESIZING;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinMouseScrollHandler.h b/widget/windows/WinMouseScrollHandler.h
new file mode 100644
index 0000000000..ecf6c3df44
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.h
@@ -0,0 +1,567 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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_WinMouseScrollHandler_h__
+#define mozilla_widget_WinMouseScrollHandler_h__
+
+#include "nscore.h"
+#include "nsDebug.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TimeStamp.h"
+#include "Units.h"
+#include <windows.h>
+#include "nsPoint.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class ModifierKeyState;
+
+struct MSGResult;
+
+class MouseScrollHandler {
+ public:
+ static MouseScrollHandler* GetInstance();
+
+ static void Initialize();
+ static void Shutdown();
+
+ static bool NeedsMessage(UINT aMsg);
+ static bool ProcessMessage(nsWindow* aWidget, UINT msg, WPARAM wParam,
+ LPARAM lParam, MSGResult& aResult);
+
+ /**
+ * See nsIWidget::SynthesizeNativeMouseScrollEvent() for the detail about
+ * this method.
+ */
+ static nsresult SynthesizeNativeMouseScrollEvent(
+ nsWindow* aWidget, const LayoutDeviceIntPoint& aPoint,
+ uint32_t aNativeMessage, int32_t aDelta, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags);
+
+ /**
+ * IsWaitingInternalMessage() returns true if MouseScrollHandler posted
+ * an internal message for a native mouse wheel message and has not
+ * received it. Otherwise, false.
+ */
+ static bool IsWaitingInternalMessage() {
+ return sInstance && sInstance->mIsWaitingInternalMessage;
+ }
+
+ private:
+ MouseScrollHandler();
+ ~MouseScrollHandler();
+
+ bool mIsWaitingInternalMessage;
+
+ static void MaybeLogKeyState();
+
+ static MouseScrollHandler* sInstance;
+
+ /**
+ * InitEvent() initializes the aEvent. If aPoint is null, the result of
+ * GetCurrentMessagePos() will be used.
+ */
+ static void InitEvent(nsWindow* aWidget, WidgetGUIEvent& aEvent,
+ LPARAM* aPoint);
+
+ /**
+ * GetModifierKeyState() returns current modifier key state.
+ * Note that some devices need some hack for the modifier key state.
+ * This method does it automatically.
+ *
+ * @param aMessage Handling message.
+ */
+ static ModifierKeyState GetModifierKeyState(UINT aMessage);
+
+ /**
+ * MozGetMessagePos() returns the mouse cursor position when GetMessage()
+ * was called last time. However, if we're sending a native message,
+ * this returns the specified cursor position by
+ * SynthesizeNativeMouseScrollEvent().
+ */
+ static POINTS GetCurrentMessagePos();
+
+ /**
+ * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and
+ * WM_MOUSEHWHEEL. Additionally, processes WM_VSCROLL and WM_HSCROLL if they
+ * should be processed as mouse wheel message.
+ * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL,
+ * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll
+ * events. That avoids deadlock with plugin process.
+ *
+ * @param aWidget A window which receives the message.
+ * @param aMessage WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or
+ * WM_HSCROLL.
+ * @param aWParam The wParam value of the message.
+ * @param aLParam The lParam value of the message.
+ */
+ void ProcessNativeMouseWheelMessage(nsWindow* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * ProcessNativeScrollMessage() processes WM_VSCROLL and WM_HSCROLL.
+ * This method just call ProcessMouseWheelMessage() if the message should be
+ * processed as mouse wheel message. Otherwise, dispatches a content
+ * command event.
+ *
+ * @param aWidget A window which receives the message.
+ * @param aMessage WM_VSCROLL or WM_HSCROLL.
+ * @param aWParam The wParam value of the message.
+ * @param aLParam The lParam value of the message.
+ * @return TRUE if the message is processed. Otherwise, FALSE.
+ */
+ bool ProcessNativeScrollMessage(nsWindow* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * HandleMouseWheelMessage() processes MOZ_WM_MOUSEVWHEEL and
+ * MOZ_WM_MOUSEHWHEEL which are posted when one of our windows received
+ * WM_MOUSEWHEEL or WM_MOUSEHWHEEL for avoiding deadlock with OOPP.
+ *
+ * @param aWidget A window which receives the wheel message.
+ * @param aMessage MOZ_WM_MOUSEWHEEL or MOZ_WM_MOUSEHWHEEL.
+ * @param aWParam The wParam value of the original message.
+ * @param aLParam The lParam value of the original message.
+ */
+ void HandleMouseWheelMessage(nsWindow* aWidget, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ /**
+ * HandleScrollMessageAsMouseWheelMessage() processes the MOZ_WM_VSCROLL and
+ * MOZ_WM_HSCROLL which are posted when one of mouse windows received
+ * WM_VSCROLL or WM_HSCROLL and user wants them to emulate mouse wheel
+ * message's behavior.
+ *
+ * @param aWidget A window which receives the scroll message.
+ * @param aMessage MOZ_WM_VSCROLL or MOZ_WM_HSCROLL.
+ * @param aWParam The wParam value of the original message.
+ * @param aLParam The lParam value of the original message.
+ */
+ void HandleScrollMessageAsMouseWheelMessage(nsWindow* aWidget, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam);
+
+ /**
+ * ComputeMessagePos() computes the cursor position when the message was
+ * added to the queue.
+ *
+ * @param aMessage Handling message.
+ * @param aWParam Handling message's wParam.
+ * @param aLParam Handling message's lParam.
+ * @return Mouse cursor position when the message is added to
+ * the queue or current cursor position if the result of
+ * ::GetMessagePos() is broken.
+ */
+ POINT ComputeMessagePos(UINT aMessage, WPARAM aWParam, LPARAM aLParam);
+
+ class EventInfo {
+ public:
+ /**
+ * @param aWidget An nsWindow which is handling the event.
+ * @param aMessage Must be WM_MOUSEWHEEL or WM_MOUSEHWHEEL.
+ */
+ EventInfo(nsWindow* aWidget, UINT aMessage, WPARAM aWParam, LPARAM aLParam);
+
+ bool CanDispatchWheelEvent() const;
+
+ int32_t GetNativeDelta() const { return mDelta; }
+ HWND GetWindowHandle() const { return mWnd; }
+ const TimeStamp& GetTimeStamp() const { return mTimeStamp; }
+ bool IsVertical() const { return mIsVertical; }
+ bool IsPositive() const { return (mDelta > 0); }
+ bool IsPage() const { return mIsPage; }
+
+ /**
+ * @return Number of lines or pages scrolled per WHEEL_DELTA.
+ */
+ int32_t GetScrollAmount() const;
+
+ protected:
+ EventInfo()
+ : mIsVertical(false), mIsPage(false), mDelta(0), mWnd(nullptr) {}
+
+ // TRUE if event is for vertical scroll. Otherwise, FALSE.
+ bool mIsVertical;
+ // TRUE if event scrolls per page, otherwise, FALSE.
+ bool mIsPage;
+ // The native delta value.
+ int32_t mDelta;
+ // The window handle which is handling the event.
+ HWND mWnd;
+ // Timestamp of the event.
+ TimeStamp mTimeStamp;
+ };
+
+ class LastEventInfo : public EventInfo {
+ public:
+ LastEventInfo() : EventInfo(), mAccumulatedDelta(0) {}
+
+ /**
+ * CanContinueTransaction() checks whether the new event can continue the
+ * last transaction or not. Note that if there is no transaction, this
+ * returns true.
+ */
+ bool CanContinueTransaction(const EventInfo& aNewEvent);
+
+ /**
+ * ResetTransaction() resets the transaction, i.e., the instance forgets
+ * the last event information.
+ */
+ void ResetTransaction();
+
+ /**
+ * RecordEvent() saves the information of new event.
+ */
+ void RecordEvent(const EventInfo& aEvent);
+
+ /**
+ * InitWheelEvent() initializes NS_WHEEL_WHEEL event and
+ * recomputes the remaning detla for the event.
+ * This must be called only once during handling a message and after
+ * RecordEvent() is called.
+ *
+ * @param aWidget A window which will dispatch the event.
+ * @param aWheelEvent An NS_WHEEL_WHEEL event, this will be
+ * initialized.
+ * @param aModKeyState Current modifier key state.
+ * @return TRUE if the event is ready to dispatch.
+ * Otherwise, FALSE.
+ */
+ bool InitWheelEvent(nsWindow* aWidget, WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState, LPARAM aLParam);
+
+ private:
+ static int32_t RoundDelta(double aDelta);
+
+ int32_t mAccumulatedDelta;
+ };
+
+ LastEventInfo mLastEventInfo;
+
+ class SystemSettings {
+ public:
+ SystemSettings() : mInitialized(false) {}
+
+ void Init();
+ void MarkDirty();
+ void NotifyUserPrefsMayOverrideSystemSettings();
+
+ // On some environments, SystemParametersInfo() may be hooked by touchpad
+ // utility or something. In such case, when user changes active pointing
+ // device to another one, the result of SystemParametersInfo() may be
+ // changed without WM_SETTINGCHANGE message. For avoiding this trouble,
+ // we need to modify cache of system settings at every wheel message
+ // handling if we meet known device whose utility may hook the API.
+ void TrustedScrollSettingsDriver();
+
+ int32_t GetScrollAmount(bool aForVertical) const {
+ MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
+ return aForVertical ? mScrollLines : mScrollChars;
+ }
+
+ bool IsPageScroll(bool aForVertical) const {
+ MOZ_ASSERT(mInitialized, "SystemSettings must be initialized");
+ return aForVertical ? (uint32_t(mScrollLines) == WHEEL_PAGESCROLL)
+ : (uint32_t(mScrollChars) == WHEEL_PAGESCROLL);
+ }
+
+ // The default vertical and horizontal scrolling speed is 3, this is defined
+ // on the document of SystemParametersInfo in MSDN.
+ static int32_t DefaultScrollLines() { return 3; }
+
+ private:
+ bool mInitialized;
+ // The result of SystemParametersInfo() may not be reliable since it may
+ // be hooked. So, if the values are initialized with prefs, we can trust
+ // the value. Following mIsReliableScroll* are set true when mScroll* are
+ // initialized with prefs.
+ bool mIsReliableScrollLines;
+ bool mIsReliableScrollChars;
+
+ int32_t mScrollLines;
+ int32_t mScrollChars;
+
+ // Returns true if cached value is changed.
+ bool InitScrollLines();
+ bool InitScrollChars();
+
+ void RefreshCache();
+ };
+
+ SystemSettings mSystemSettings;
+
+ class UserPrefs {
+ public:
+ UserPrefs();
+ ~UserPrefs();
+
+ void MarkDirty();
+
+ bool IsScrollMessageHandledAsWheelMessage() {
+ Init();
+ return mScrollMessageHandledAsWheelMessage;
+ }
+
+ bool IsSystemSettingCacheEnabled() {
+ Init();
+ return mEnableSystemSettingCache;
+ }
+
+ bool IsSystemSettingCacheForciblyEnabled() {
+ Init();
+ return mForceEnableSystemSettingCache;
+ }
+
+ bool ShouldEmulateToMakeWindowUnderCursorForeground() {
+ Init();
+ return mEmulateToMakeWindowUnderCursorForeground;
+ }
+
+ int32_t GetOverriddenVerticalScrollAmout() {
+ Init();
+ return mOverriddenVerticalScrollAmount;
+ }
+
+ int32_t GetOverriddenHorizontalScrollAmout() {
+ Init();
+ return mOverriddenHorizontalScrollAmount;
+ }
+
+ int32_t GetMouseScrollTransactionTimeout() {
+ Init();
+ return mMouseScrollTransactionTimeout;
+ }
+
+ private:
+ void Init();
+
+ static void OnChange(const char* aPrefName, void* aSelf) {
+ static_cast<UserPrefs*>(aSelf)->MarkDirty();
+ }
+
+ bool mInitialized;
+ bool mScrollMessageHandledAsWheelMessage;
+ bool mEnableSystemSettingCache;
+ bool mForceEnableSystemSettingCache;
+ bool mEmulateToMakeWindowUnderCursorForeground;
+ int32_t mOverriddenVerticalScrollAmount;
+ int32_t mOverriddenHorizontalScrollAmount;
+ int32_t mMouseScrollTransactionTimeout;
+ };
+
+ UserPrefs mUserPrefs;
+
+ class SynthesizingEvent {
+ public:
+ SynthesizingEvent()
+ : mWnd(nullptr),
+ mMessage(0),
+ mWParam(0),
+ mLParam(0),
+ mStatus(NOT_SYNTHESIZING) {}
+
+ ~SynthesizingEvent() {}
+
+ static bool IsSynthesizing();
+
+ nsresult Synthesize(const POINTS& aCursorPoint, HWND aWnd, UINT aMessage,
+ WPARAM aWParam, LPARAM aLParam,
+ const BYTE (&aKeyStates)[256]);
+
+ void NativeMessageReceived(nsWindow* aWidget, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ void NotifyNativeMessageHandlingFinished();
+ void NotifyInternalMessageHandlingFinished();
+
+ const POINTS& GetCursorPoint() const { return mCursorPoint; }
+
+ private:
+ POINTS mCursorPoint;
+ HWND mWnd;
+ UINT mMessage;
+ WPARAM mWParam;
+ LPARAM mLParam;
+ BYTE mKeyState[256];
+ BYTE mOriginalKeyState[256];
+
+ enum Status {
+ NOT_SYNTHESIZING,
+ SENDING_MESSAGE,
+ NATIVE_MESSAGE_RECEIVED,
+ INTERNAL_MESSAGE_POSTED,
+ };
+ Status mStatus;
+
+ const char* GetStatusName() {
+ switch (mStatus) {
+ case NOT_SYNTHESIZING:
+ return "NOT_SYNTHESIZING";
+ case SENDING_MESSAGE:
+ return "SENDING_MESSAGE";
+ case NATIVE_MESSAGE_RECEIVED:
+ return "NATIVE_MESSAGE_RECEIVED";
+ case INTERNAL_MESSAGE_POSTED:
+ return "INTERNAL_MESSAGE_POSTED";
+ default:
+ return "Unknown";
+ }
+ }
+
+ void Finish();
+ }; // SynthesizingEvent
+
+ SynthesizingEvent* mSynthesizingEvent;
+
+ public:
+ class Device {
+ public:
+ // SynTP is a touchpad driver of Synaptics.
+ class SynTP {
+ public:
+ static bool IsDriverInstalled() { return sMajorVersion != 0; }
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If SynTP driver isn't installed, this returns 0.
+ */
+ static int32_t GetDriverMajorVersion() { return sMajorVersion; }
+ /**
+ * GetDriverMinorVersion() returns the installed driver's minor version.
+ * If SynTP driver isn't installed, this returns -1.
+ */
+ static int32_t GetDriverMinorVersion() { return sMinorVersion; }
+
+ static void Init();
+
+ private:
+ static bool sInitialized;
+ static int32_t sMajorVersion;
+ static int32_t sMinorVersion;
+ };
+
+ class Elantech {
+ public:
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If Elantech's driver was installed, returns 0.
+ */
+ static int32_t GetDriverMajorVersion();
+
+ /**
+ * IsHelperWindow() checks whether aWnd is a helper window of Elantech's
+ * touchpad. Returns TRUE if so. Otherwise, FALSE.
+ */
+ static bool IsHelperWindow(HWND aWnd);
+
+ /**
+ * Key message handler for Elantech's hack. Returns TRUE if the message
+ * is consumed by this handler. Otherwise, FALSE.
+ */
+ static bool HandleKeyMessage(nsWindow* aWidget, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam);
+
+ static void UpdateZoomUntil();
+ static bool IsZooming();
+
+ static void Init();
+
+ static bool IsPinchHackNeeded() { return sUsePinchHack; }
+
+ private:
+ // Whether to enable the Elantech swipe gesture hack.
+ static bool sUseSwipeHack;
+ // Whether to enable the Elantech pinch-to-zoom gesture hack.
+ static bool sUsePinchHack;
+ static DWORD sZoomUntil;
+ }; // class Elantech
+
+ // Apoint is a touchpad driver of Alps.
+ class Apoint {
+ public:
+ static bool IsDriverInstalled() { return sMajorVersion != 0; }
+ /**
+ * GetDriverMajorVersion() returns the installed driver's major version.
+ * If Apoint driver isn't installed, this returns 0.
+ */
+ static int32_t GetDriverMajorVersion() { return sMajorVersion; }
+ /**
+ * GetDriverMinorVersion() returns the installed driver's minor version.
+ * If Apoint driver isn't installed, this returns -1.
+ */
+ static int32_t GetDriverMinorVersion() { return sMinorVersion; }
+
+ static void Init();
+
+ private:
+ static bool sInitialized;
+ static int32_t sMajorVersion;
+ static int32_t sMinorVersion;
+ };
+
+ class TrackPoint {
+ public:
+ /**
+ * IsDriverInstalled() returns TRUE if TrackPoint's driver is installed.
+ * Otherwise, returns FALSE.
+ */
+ static bool IsDriverInstalled();
+ }; // class TrackPoint
+
+ class UltraNav {
+ public:
+ /**
+ * IsObsoleteDriverInstalled() checks whether obsoleted UltraNav
+ * is installed on the environment.
+ * Returns TRUE if it was installed. Otherwise, FALSE.
+ */
+ static bool IsObsoleteDriverInstalled();
+ }; // class UltraNav
+
+ class SetPoint {
+ public:
+ /**
+ * SetPoint, Logitech's mouse driver, may report wrong cursor position
+ * for WM_MOUSEHWHEEL message. See comment in the implementation for
+ * the detail.
+ */
+ static bool IsGetMessagePosResponseValid(UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam);
+
+ private:
+ static bool sMightBeUsing;
+ };
+
+ static void Init();
+
+ static bool IsFakeScrollableWindowNeeded() {
+ return sFakeScrollableWindowNeeded;
+ }
+
+ private:
+ /**
+ * Gets the bool value of aPrefName used to enable or disable an input
+ * workaround (like the Trackpoint hack). The pref can take values 0 (for
+ * disabled), 1 (for enabled) or -1 (to automatically detect whether to
+ * enable the workaround).
+ *
+ * @param aPrefName The name of the pref.
+ * @param aValueIfAutomatic Whether the given input workaround should be
+ * enabled by default.
+ */
+ static bool GetWorkaroundPref(const char* aPrefName,
+ bool aValueIfAutomatic);
+
+ static bool sFakeScrollableWindowNeeded;
+ }; // class Device
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinMouseScrollHandler_h__
diff --git a/widget/windows/WinPointerEvents.cpp b/widget/windows/WinPointerEvents.cpp
new file mode 100644
index 0000000000..57e19a0c4b
--- /dev/null
+++ b/widget/windows/WinPointerEvents.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/*
+ * WinPointerEvents - Helper functions to retrieve PointerEvent's attributes
+ */
+
+#include "nscore.h"
+#include "nsWindowDefs.h"
+#include "WinPointerEvents.h"
+#include "WinUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/MouseEventBinding.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const wchar_t WinPointerEvents::kPointerLibraryName[] = L"user32.dll";
+HMODULE WinPointerEvents::sLibraryHandle = nullptr;
+WinPointerEvents::GetPointerTypePtr WinPointerEvents::getPointerType = nullptr;
+WinPointerEvents::GetPointerInfoPtr WinPointerEvents::getPointerInfo = nullptr;
+WinPointerEvents::GetPointerPenInfoPtr WinPointerEvents::getPointerPenInfo =
+ nullptr;
+
+WinPointerEvents::WinPointerEvents() { InitLibrary(); }
+
+/* Load and shutdown */
+void WinPointerEvents::InitLibrary() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (getPointerType) {
+ // Return if we already initialized the PointerEvent related interfaces
+ return;
+ }
+ sLibraryHandle = ::LoadLibraryW(kPointerLibraryName);
+ MOZ_ASSERT(sLibraryHandle, "cannot load pointer library");
+ if (sLibraryHandle) {
+ getPointerType =
+ (GetPointerTypePtr)GetProcAddress(sLibraryHandle, "GetPointerType");
+ getPointerInfo =
+ (GetPointerInfoPtr)GetProcAddress(sLibraryHandle, "GetPointerInfo");
+ getPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(
+ sLibraryHandle, "GetPointerPenInfo");
+ }
+
+ if (!getPointerType || !getPointerInfo || !getPointerPenInfo) {
+ MOZ_ASSERT(false, "get PointerEvent interfaces failed");
+ getPointerType = nullptr;
+ getPointerInfo = nullptr;
+ getPointerPenInfo = nullptr;
+ return;
+ }
+}
+
+bool WinPointerEvents::ShouldHandleWinPointerMessages(UINT aMsg,
+ WPARAM aWParam) {
+ MOZ_ASSERT(aMsg == WM_POINTERDOWN || aMsg == WM_POINTERUP ||
+ aMsg == WM_POINTERUPDATE || aMsg == WM_POINTERLEAVE);
+ if (!sLibraryHandle) {
+ return false;
+ }
+
+ // We only handle WM_POINTER* when the input source is pen. This is because
+ // we need some information (e.g. tiltX, tiltY) which can't be retrieved by
+ // WM_*BUTTONDOWN.
+ uint32_t pointerId = GetPointerId(aWParam);
+ POINTER_INPUT_TYPE pointerType = PT_POINTER;
+ if (!GetPointerType(pointerId, &pointerType)) {
+ MOZ_ASSERT(false, "cannot find PointerType");
+ return false;
+ }
+ return (pointerType == PT_PEN);
+}
+
+bool WinPointerEvents::GetPointerType(uint32_t aPointerId,
+ POINTER_INPUT_TYPE* aPointerType) {
+ if (!getPointerType) {
+ return false;
+ }
+ return getPointerType(aPointerId, aPointerType);
+}
+
+POINTER_INPUT_TYPE
+WinPointerEvents::GetPointerType(uint32_t aPointerId) {
+ POINTER_INPUT_TYPE pointerType = PT_POINTER;
+ Unused << GetPointerType(aPointerId, &pointerType);
+ return pointerType;
+}
+
+bool WinPointerEvents::GetPointerInfo(uint32_t aPointerId,
+ POINTER_INFO* aPointerInfo) {
+ if (!getPointerInfo) {
+ return false;
+ }
+ return getPointerInfo(aPointerId, aPointerInfo);
+}
+
+bool WinPointerEvents::GetPointerPenInfo(uint32_t aPointerId,
+ POINTER_PEN_INFO* aPenInfo) {
+ if (!getPointerPenInfo) {
+ return false;
+ }
+ return getPointerPenInfo(aPointerId, aPenInfo);
+}
+
+bool WinPointerEvents::ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam) {
+ MOZ_ASSERT(aMsg == WM_POINTERDOWN);
+ // Only roll up popups when we handling WM_POINTER* to fire Gecko
+ // WidgetMouseEvent and suppress Windows WM_*BUTTONDOWN.
+ return ShouldHandleWinPointerMessages(aMsg, aWParam) &&
+ ShouldFirePointerEventByWinPointerMessages();
+}
+
+bool WinPointerEvents::ShouldFirePointerEventByWinPointerMessages() {
+ MOZ_ASSERT(sLibraryHandle);
+ return StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages();
+}
+
+WinPointerInfo* WinPointerEvents::GetCachedPointerInfo(UINT aMsg,
+ WPARAM aWParam) {
+ if (!sLibraryHandle ||
+ MOUSE_INPUT_SOURCE() != dom::MouseEvent_Binding::MOZ_SOURCE_PEN ||
+ ShouldFirePointerEventByWinPointerMessages()) {
+ return nullptr;
+ }
+ switch (aMsg) {
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ return &mPenPointerDownInfo;
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ return &mPenPointerDownInfo;
+ case WM_MOUSEMOVE:
+ return &mPenPointerUpdateInfo;
+ default:
+ MOZ_ASSERT(false);
+ }
+ return nullptr;
+}
+
+void WinPointerEvents::ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam) {
+ MOZ_ASSERT(
+ !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages());
+ // Windows doesn't support chorded buttons for pen, so we can simply keep the
+ // latest information from pen generated pointer messages and use them when
+ // handling mouse messages. Used different pointer info for pointerdown,
+ // pointerupdate, and pointerup because Windows doesn't always interleave
+ // pointer messages and mouse messages.
+ switch (aMsg) {
+ case WM_POINTERDOWN:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerDownInfo);
+ break;
+ case WM_POINTERUP:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerUpInfo);
+ break;
+ case WM_POINTERUPDATE:
+ ConvertAndCachePointerInfo(aWParam, &mPenPointerUpdateInfo);
+ break;
+ default:
+ break;
+ }
+}
+
+void WinPointerEvents::ConvertAndCachePointerInfo(WPARAM aWParam,
+ WinPointerInfo* aInfo) {
+ MOZ_ASSERT(
+ !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages());
+ aInfo->pointerId = GetPointerId(aWParam);
+ MOZ_ASSERT(GetPointerType(aInfo->pointerId) == PT_PEN);
+ POINTER_PEN_INFO penInfo;
+ GetPointerPenInfo(aInfo->pointerId, &penInfo);
+ aInfo->tiltX = penInfo.tiltX;
+ aInfo->tiltY = penInfo.tiltY;
+ // Windows defines the pen pressure is normalized to a range between 0 and
+ // 1024. Convert it to float.
+ aInfo->mPressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
+}
diff --git a/widget/windows/WinPointerEvents.h b/widget/windows/WinPointerEvents.h
new file mode 100644
index 0000000000..94179e7b4e
--- /dev/null
+++ b/widget/windows/WinPointerEvents.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WinPointerEvents_h__
+#define WinPointerEvents_h__
+
+#include "mozilla/MouseEvents.h"
+#include "touchinjection_sdk80.h"
+#include <windef.h>
+
+/******************************************************************************
+ * WinPointerInfo
+ *
+ * This is a helper class to handle WM_POINTER*. It only supports Win8 or later.
+ *
+ ******************************************************************************/
+class WinPointerInfo final : public mozilla::WidgetPointerHelper {
+ public:
+ WinPointerInfo() : WidgetPointerHelper(), mPressure(0), mButtons(0) {}
+
+ WinPointerInfo(uint32_t aPointerId, uint32_t aTiltX, uint32_t aTiltY,
+ float aPressure, int16_t aButtons)
+ : WidgetPointerHelper(aPointerId, aTiltX, aTiltY),
+ mPressure(aPressure),
+ mButtons(aButtons) {}
+
+ float mPressure;
+ int16_t mButtons;
+};
+
+class WinPointerEvents final {
+ public:
+ explicit WinPointerEvents();
+
+ public:
+ bool ShouldHandleWinPointerMessages(UINT aMsg, WPARAM aWParam);
+
+ uint32_t GetPointerId(WPARAM aWParam) {
+ return GET_POINTERID_WPARAM(aWParam);
+ }
+ bool GetPointerType(uint32_t aPointerId, POINTER_INPUT_TYPE* aPointerType);
+ POINTER_INPUT_TYPE GetPointerType(uint32_t aPointerId);
+ bool GetPointerInfo(uint32_t aPointerId, POINTER_INFO* aPointerInfo);
+ bool GetPointerPenInfo(uint32_t aPointerId, POINTER_PEN_INFO* aPenInfo);
+ bool ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam);
+ bool ShouldFirePointerEventByWinPointerMessages();
+ WinPointerInfo* GetCachedPointerInfo(UINT aMsg, WPARAM aWParam);
+ void ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam);
+ void ConvertAndCachePointerInfo(WPARAM aWParam, WinPointerInfo* aInfo);
+
+ private:
+ // Function prototypes
+ typedef BOOL(WINAPI* GetPointerTypePtr)(uint32_t aPointerId,
+ POINTER_INPUT_TYPE* aPointerType);
+ typedef BOOL(WINAPI* GetPointerInfoPtr)(uint32_t aPointerId,
+ POINTER_INFO* aPointerInfo);
+ typedef BOOL(WINAPI* GetPointerPenInfoPtr)(uint32_t aPointerId,
+ POINTER_PEN_INFO* aPenInfo);
+
+ void InitLibrary();
+
+ static HMODULE sLibraryHandle;
+ static const wchar_t kPointerLibraryName[];
+ // Static function pointers
+ static GetPointerTypePtr getPointerType;
+ static GetPointerInfoPtr getPointerInfo;
+ static GetPointerPenInfoPtr getPointerPenInfo;
+ WinPointerInfo mPenPointerDownInfo;
+ WinPointerInfo mPenPointerUpInfo;
+ WinPointerInfo mPenPointerUpdateInfo;
+};
+
+#endif // #ifndef WinPointerEvents_h__
diff --git a/widget/windows/WinRegistry.cpp b/widget/windows/WinRegistry.cpp
new file mode 100644
index 0000000000..b04ae1df45
--- /dev/null
+++ b/widget/windows/WinRegistry.cpp
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "WinRegistry.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::widget::WinRegistry {
+
+Key::Key(HKEY aParent, const nsString& aPath, KeyMode aMode, CreateFlag) {
+ MOZ_ASSERT(aParent);
+ DWORD disposition;
+ ::RegCreateKeyExW(aParent, aPath.get(), 0, nullptr, REG_OPTION_NON_VOLATILE,
+ (REGSAM)aMode, nullptr, &mKey, &disposition);
+}
+
+Key::Key(HKEY aParent, const nsString& aPath, KeyMode aMode) {
+ MOZ_ASSERT(aParent);
+ ::RegOpenKeyExW(aParent, aPath.get(), 0, (REGSAM)aMode, &mKey);
+}
+
+uint32_t Key::GetChildCount() const {
+ MOZ_ASSERT(mKey);
+ DWORD result = 0;
+ ::RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &result, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr, nullptr);
+ return result;
+}
+
+bool Key::GetChildName(uint32_t aIndex, nsAString& aResult) const {
+ MOZ_ASSERT(mKey);
+ FILETIME lastWritten;
+
+ wchar_t nameBuf[kMaxKeyNameLen + 1];
+ DWORD nameLen = std::size(nameBuf);
+
+ LONG rv = RegEnumKeyExW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
+ nullptr, &lastWritten);
+ if (rv != ERROR_SUCCESS) {
+ return false;
+ }
+ aResult.Assign(nameBuf, nameLen);
+ return true;
+}
+
+bool Key::RemoveChildKey(const nsString& aName) const {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegDeleteKeyW(mKey, aName.get()));
+}
+
+uint32_t Key::GetValueCount() const {
+ MOZ_ASSERT(mKey);
+ DWORD result = 0;
+ ::RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+ &result, nullptr, nullptr, nullptr, nullptr);
+ return result;
+}
+
+bool Key::GetValueName(uint32_t aIndex, nsAString& aResult) const {
+ MOZ_ASSERT(mKey);
+ wchar_t nameBuf[kMaxValueNameLen + 1];
+ DWORD nameLen = std::size(nameBuf);
+
+ LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
+ nullptr, nullptr);
+ if (rv != ERROR_SUCCESS) {
+ return false;
+ }
+ aResult.Assign(nameBuf, nameLen);
+ return true;
+}
+
+ValueType Key::GetValueType(const nsString& aName) const {
+ MOZ_ASSERT(mKey);
+ DWORD result;
+ LONG rv =
+ RegQueryValueExW(mKey, aName.get(), nullptr, &result, nullptr, nullptr);
+ return SUCCEEDED(rv) ? ValueType(result) : ValueType::None;
+}
+
+bool Key::RemoveValue(const nsString& aName) const {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegDeleteValueW(mKey, aName.get()));
+}
+
+Maybe<uint32_t> Key::GetValueAsDword(const nsString& aName) const {
+ MOZ_ASSERT(mKey);
+ DWORD type;
+ DWORD value = 0;
+ DWORD size = sizeof(DWORD);
+ HRESULT rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type,
+ (LPBYTE)&value, &size);
+ if (FAILED(rv) || type != REG_DWORD) {
+ return Nothing();
+ }
+ return Some(value);
+}
+
+bool Key::WriteValueAsDword(const nsString& aName, uint32_t aValue) {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegSetValueExW(mKey, aName.get(), 0, REG_DWORD,
+ (const BYTE*)&aValue, sizeof(aValue)));
+}
+
+Maybe<uint64_t> Key::GetValueAsQword(const nsString& aName) const {
+ MOZ_ASSERT(mKey);
+ DWORD type;
+ uint64_t value = 0;
+ DWORD size = sizeof(uint64_t);
+ HRESULT rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type,
+ (LPBYTE)&value, &size);
+ if (FAILED(rv) || type != REG_QWORD) {
+ return Nothing();
+ }
+ return Some(value);
+}
+
+bool Key::WriteValueAsQword(const nsString& aName, uint64_t aValue) {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegSetValueExW(mKey, aName.get(), 0, REG_QWORD,
+ (const BYTE*)&aValue, sizeof(aValue)));
+}
+
+bool Key::GetValueAsBinary(const nsString& aName,
+ nsTArray<uint8_t>& aResult) const {
+ MOZ_ASSERT(mKey);
+ DWORD type;
+ DWORD size;
+ LONG rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type, nullptr, &size);
+ if (FAILED(rv) || type != REG_BINARY) {
+ return false;
+ }
+ if (!aResult.SetLength(size, fallible)) {
+ return false;
+ }
+ rv = RegQueryValueExW(mKey, aName.get(), nullptr, nullptr, aResult.Elements(),
+ &size);
+ return SUCCEEDED(rv);
+}
+
+Maybe<nsTArray<uint8_t>> Key::GetValueAsBinary(const nsString& aName) const {
+ nsTArray<uint8_t> value;
+ Maybe<nsTArray<uint8_t>> result;
+ if (GetValueAsBinary(aName, value)) {
+ result.emplace(std::move(value));
+ }
+ return result;
+}
+
+bool Key::WriteValueAsBinary(const nsString& aName,
+ Span<const uint8_t> aValue) {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegSetValueExW(mKey, aName.get(), 0, REG_BINARY,
+ (const BYTE*)aValue.data(), aValue.size()));
+}
+
+static bool IsStringType(DWORD aType, StringFlags aFlags) {
+ switch (aType) {
+ case REG_SZ:
+ return bool(aFlags & StringFlags::Sz);
+ case REG_EXPAND_SZ:
+ return bool(aFlags & StringFlags::ExpandSz);
+ case REG_MULTI_SZ:
+ return bool(aFlags & StringFlags::LegacyMultiSz);
+ default:
+ return false;
+ }
+}
+
+bool Key::GetValueAsString(const nsString& aName, nsString& aResult,
+ StringFlags aFlags) const {
+ MOZ_ASSERT(mKey);
+ DWORD type;
+ DWORD size;
+ LONG rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type, nullptr, &size);
+ if (FAILED(rv) || !IsStringType(type, aFlags)) {
+ return false;
+ }
+ if (!size) {
+ aResult.Truncate();
+ return true;
+ }
+ // The buffer size must be a multiple of 2.
+ if (NS_WARN_IF(size % 2 != 0)) {
+ return false;
+ }
+ size_t resultLen = size / 2;
+ {
+ auto handleOrError =
+ aResult.BulkWrite(resultLen, 0, /* aAllowShrinking = */ false);
+ if (NS_WARN_IF(handleOrError.isErr())) {
+ return false;
+ }
+ auto handle = handleOrError.unwrap();
+ auto len = GetValueAsString(aName, {handle.Elements(), handle.Length() + 1},
+ aFlags & ~StringFlags::ExpandEnvironment);
+ if (NS_WARN_IF(!len)) {
+ return false;
+ }
+ handle.Finish(*len, /* aAllowShrinking = */ false);
+ if (*len && !aResult.CharAt(*len - 1)) {
+ // The string passed to us had a null terminator in the final
+ // position.
+ aResult.Truncate(*len - 1);
+ }
+ }
+ if (type == REG_EXPAND_SZ && (aFlags & StringFlags::ExpandEnvironment)) {
+ resultLen = ExpandEnvironmentStringsW(aResult.get(), nullptr, 0);
+ if (resultLen > 1) {
+ nsString expandedResult;
+ // |resultLen| includes the terminating null character
+ resultLen--;
+ if (!expandedResult.SetLength(resultLen, fallible)) {
+ return false;
+ }
+ resultLen = ExpandEnvironmentStringsW(aResult.get(), expandedResult.get(),
+ resultLen + 1);
+ if (resultLen <= 0) {
+ return false;
+ }
+ aResult = std::move(expandedResult);
+ } else if (resultLen == 1) {
+ // It apparently expands to nothing (just a null terminator).
+ resultLen = 0;
+ aResult.Truncate();
+ }
+ }
+ return true;
+}
+
+Maybe<nsString> Key::GetValueAsString(const nsString& aName,
+ StringFlags aFlags) const {
+ nsString value;
+ Maybe<nsString> result;
+ if (GetValueAsString(aName, value, aFlags)) {
+ result.emplace(std::move(value));
+ }
+ return result;
+}
+
+Maybe<uint32_t> Key::GetValueAsString(const nsString& aName,
+ Span<char16_t> aBuffer,
+ StringFlags aFlags) const {
+ MOZ_ASSERT(mKey);
+ MOZ_ASSERT(aBuffer.Length(), "Empty buffer?");
+ MOZ_ASSERT(!(aFlags & StringFlags::ExpandEnvironment),
+ "Environment expansion not performed on a single buffer");
+
+ DWORD size = aBuffer.LengthBytes();
+ DWORD type;
+ HRESULT rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type,
+ (LPBYTE)aBuffer.data(), &size);
+ if (FAILED(rv)) {
+ return Nothing();
+ }
+ if (!IsStringType(type, aFlags)) {
+ return Nothing();
+ }
+ uint32_t len = size ? size / sizeof(char16_t) - 1 : 0;
+ aBuffer[len] = 0;
+ return Some(len);
+}
+
+KeyWatcher::KeyWatcher(Key&& aKey,
+ nsISerialEventTarget* aTargetSerialEventTarget,
+ Callback&& aCallback)
+ : mKey(std::move(aKey)),
+ mEventTarget(aTargetSerialEventTarget),
+ mCallback(std::move(aCallback)) {
+ MOZ_ASSERT(mKey);
+ MOZ_ASSERT(mEventTarget);
+ MOZ_ASSERT(mCallback);
+ mEvent = CreateEvent(nullptr, /* bManualReset = */ FALSE,
+ /* bInitialState = */ FALSE, nullptr);
+ if (NS_WARN_IF(!mEvent)) {
+ return;
+ }
+
+ if (NS_WARN_IF(!Register())) {
+ return;
+ }
+
+ // The callback only dispatches to the relevant event target, so we can use
+ // WT_EXECUTEINWAITTHREAD.
+ RegisterWaitForSingleObject(&mWaitObject, mEvent, WatchCallback, this,
+ INFINITE, WT_EXECUTEINWAITTHREAD);
+}
+
+void KeyWatcher::WatchCallback(void* aContext, BOOLEAN) {
+ auto* watcher = static_cast<KeyWatcher*>(aContext);
+ watcher->Register();
+ watcher->mEventTarget->Dispatch(
+ NS_NewRunnableFunction("KeyWatcher callback", watcher->mCallback));
+}
+
+// As per the documentation in:
+// https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue
+//
+// This function detects a single change. After the caller receives a
+// notification event, it should call the function again to receive the next
+// notification.
+bool KeyWatcher::Register() {
+ MOZ_ASSERT(mEvent);
+ DWORD flags = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
+ REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY |
+ REG_NOTIFY_THREAD_AGNOSTIC;
+ HRESULT rv =
+ RegNotifyChangeKeyValue(mKey.RawKey(), /* bWatchSubtree = */ TRUE, flags,
+ mEvent, /* fAsynchronous = */ TRUE);
+ return !NS_WARN_IF(FAILED(rv));
+}
+
+KeyWatcher::~KeyWatcher() {
+ if (mWaitObject) {
+ UnregisterWait(mWaitObject);
+ CloseHandle(mWaitObject);
+ }
+ if (mEvent) {
+ CloseHandle(mEvent);
+ }
+}
+
+} // namespace mozilla::widget::WinRegistry
diff --git a/widget/windows/WinRegistry.h b/widget/windows/WinRegistry.h
new file mode 100644
index 0000000000..8ab221928e
--- /dev/null
+++ b/widget/windows/WinRegistry.h
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WinRegistry_h__
+#define mozilla_widget_WinRegistry_h__
+
+#include <windows.h>
+#include <functional>
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+
+class nsISerialEventTarget;
+
+namespace mozilla::widget::WinRegistry {
+
+// According to MSDN, the following limits apply (in characters excluding room
+// for terminating null character):
+static constexpr size_t kMaxKeyNameLen = 255;
+static constexpr size_t kMaxValueNameLen = 16383;
+
+/// https://learn.microsoft.com/en-us/windows/win32/shell/regsam
+enum class KeyMode : uint32_t {
+ AllAccess = KEY_ALL_ACCESS,
+ QueryValue = KEY_QUERY_VALUE,
+ CreateLink = KEY_CREATE_LINK,
+ CreateSubKey = KEY_CREATE_SUB_KEY,
+ EnumerateSubkeys = KEY_ENUMERATE_SUB_KEYS,
+ Execute = KEY_EXECUTE,
+ Notify = KEY_NOTIFY,
+ Read = KEY_READ,
+ SetValue = KEY_SET_VALUE,
+ Write = KEY_WRITE,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(KeyMode);
+
+enum class StringFlags : uint32_t {
+ // Whether to allow REG_SZ strings.
+ Sz = 1 << 0,
+ // Whether to read EXPAND_SZ strings.
+ ExpandSz = 1 << 1,
+ // Whether to treat MULTI_SZ values as strings. This is a historical
+ // idiosyncrasy of the nsIWindowsRegKey, but most likely not what you want.
+ LegacyMultiSz = 1 << 2,
+ // Whether to expand environment variables in EXPAND_SZ values.
+ // Only makes sense along with the ExpandSz variable.
+ ExpandEnvironment = 1 << 3,
+ // By default, only allow regular Sz, and don't perform environment expansion.
+ Default = Sz,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringFlags);
+
+// Convenience alias for legacy WinUtils callers, to preserve behavior.
+// Chances are users of these flags could just look at Sz strings, tho.
+static constexpr auto kLegacyWinUtilsStringFlags =
+ StringFlags::Sz | StringFlags::ExpandSz;
+
+// https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
+enum class ValueType : uint32_t {
+ Binary = REG_BINARY,
+ Dword = REG_DWORD,
+ ExpandSz = REG_EXPAND_SZ,
+ Link = REG_LINK,
+ MultiSz = REG_MULTI_SZ,
+ None = REG_NONE,
+ Qword = REG_QWORD,
+ Sz = REG_SZ,
+};
+
+class Key {
+ public:
+ enum CreateFlag {
+ Create,
+ };
+
+ Key() = default;
+ Key(const Key&) = delete;
+ Key(Key&& aOther) { std::swap(mKey, aOther.mKey); }
+
+ Key& operator=(const Key&) = delete;
+ Key& operator=(Key&& aOther) {
+ std::swap(mKey, aOther.mKey);
+ return *this;
+ }
+
+ Key(HKEY aParent, const nsString& aPath, KeyMode aMode, CreateFlag);
+ Key(HKEY aParent, const nsString& aPath, KeyMode aMode);
+
+ Key(const Key& aParent, const nsString& aPath, KeyMode aMode, CreateFlag)
+ : Key(aParent.mKey, aPath, aMode, Create) {}
+ Key(const Key& aParent, const nsString& aPath, KeyMode aMode)
+ : Key(aParent.mKey, aPath, aMode) {}
+
+ ~Key() {
+ if (mKey) {
+ ::RegCloseKey(mKey);
+ }
+ }
+
+ explicit operator bool() const { return !!mKey; }
+
+ uint32_t GetChildCount() const;
+ [[nodiscard]] bool GetChildName(uint32_t aIndex, nsAString& aResult) const;
+ [[nodiscard]] bool RemoveChildKey(const nsString& aName) const;
+
+ uint32_t GetValueCount() const;
+ [[nodiscard]] bool GetValueName(uint32_t aIndex, nsAString& aResult) const;
+ ValueType GetValueType(const nsString& aName) const;
+ bool RemoveValue(const nsString& aName) const;
+
+ Maybe<uint32_t> GetValueAsDword(const nsString& aName) const;
+ bool WriteValueAsDword(const nsString& aName, uint32_t aValue);
+
+ Maybe<uint64_t> GetValueAsQword(const nsString& aName) const;
+ [[nodiscard]] bool WriteValueAsQword(const nsString& aName, uint64_t aValue);
+
+ [[nodiscard]] bool GetValueAsBinary(const nsString& aName,
+ nsTArray<uint8_t>&) const;
+ Maybe<nsTArray<uint8_t>> GetValueAsBinary(const nsString& aName) const;
+ [[nodiscard]] bool WriteValueAsBinary(const nsString& aName,
+ Span<const uint8_t> aValue);
+
+ [[nodiscard]] bool GetValueAsString(const nsString& aName, nsString& aResult,
+ StringFlags = StringFlags::Default) const;
+ Maybe<nsString> GetValueAsString(const nsString& aName,
+ StringFlags = StringFlags::Default) const;
+ // Reads a string value into a buffer. Returns Some(length) if the string is
+ // read fully, in which case the passed memory region contains a
+ // null-terminated string.
+ // Doesn't perform environment expansion (and asserts if you pass the
+ // ExpandEnvironment flag).
+ [[nodiscard]] Maybe<uint32_t> GetValueAsString(
+ const nsString& aName, Span<char16_t>,
+ StringFlags = StringFlags::Default) const;
+ [[nodiscard]] Maybe<uint32_t> GetValueAsString(
+ const nsString& aName, Span<wchar_t> aBuffer,
+ StringFlags aFlags = StringFlags::Default) const {
+ return GetValueAsString(
+ aName, Span<char16_t>((char16_t*)aBuffer.data(), aBuffer.Length()),
+ aFlags);
+ }
+
+ [[nodiscard]] bool WriteValueAsString(const nsString& aName,
+ const nsString& aValue) {
+ MOZ_ASSERT(mKey);
+ return SUCCEEDED(RegSetValueExW(mKey, aName.get(), 0, REG_SZ,
+ (const BYTE*)aValue.get(),
+ (aValue.Length() + 1) * sizeof(char16_t)));
+ }
+
+ HKEY RawKey() const { return mKey; }
+
+ private:
+ HKEY mKey = nullptr;
+};
+
+inline bool HasKey(HKEY aRootKey, const nsString& aKeyName) {
+ return !!Key(aRootKey, aKeyName, KeyMode::Read);
+}
+
+// Returns a single string value from the registry into a buffer.
+[[nodiscard]] inline bool GetString(HKEY aRootKey, const nsString& aKeyName,
+ const nsString& aValueName,
+ Span<char16_t> aBuffer,
+ StringFlags aFlags = StringFlags::Default) {
+ Key k(aRootKey, aKeyName, KeyMode::QueryValue);
+ return k && k.GetValueAsString(aValueName, aBuffer, aFlags);
+}
+[[nodiscard]] inline bool GetString(HKEY aRootKey, const nsString& aKeyName,
+ const nsString& aValueName,
+ Span<wchar_t> aBuffer,
+ StringFlags aFlags = StringFlags::Default) {
+ return GetString(aRootKey, aKeyName, aValueName,
+ Span<char16_t>((char16_t*)aBuffer.data(), aBuffer.Length()),
+ aFlags);
+}
+[[nodiscard]] inline bool GetString(HKEY aRootKey, const nsString& aKeyName,
+ const nsString& aValueName,
+ nsString& aBuffer,
+ StringFlags aFlags = StringFlags::Default) {
+ Key k(aRootKey, aKeyName, KeyMode::QueryValue);
+ return k && k.GetValueAsString(aValueName, aBuffer, aFlags);
+}
+inline Maybe<nsString> GetString(HKEY aRootKey, const nsString& aKeyName,
+ const nsString& aValueName,
+ StringFlags aFlags = StringFlags::Default) {
+ Key k(aRootKey, aKeyName, KeyMode::QueryValue);
+ if (!k) {
+ return Nothing();
+ }
+ return k.GetValueAsString(aValueName, aFlags);
+}
+
+class KeyWatcher final {
+ public:
+ using Callback = std::function<void()>;
+
+ KeyWatcher(const KeyWatcher&) = delete;
+
+ const Key& GetKey() const { return mKey; }
+
+ // Start watching a key. The watching is recursive (the whole key subtree is
+ // watched), and the callback is executed every time the key or any of its
+ // descendants change until the watcher is destroyed.
+ //
+ // @param aKey the key to watch. Must have been opened with the
+ // KeyMode::Notify flag.
+ // @param aTargetSerialEventTarget the target event target to dispatch the
+ // callback to.
+ // @param aCallback the closure to run every time that registry key changes.
+ KeyWatcher(Key&& aKey, nsISerialEventTarget* aTargetSerialEventTarget,
+ Callback&& aCallback);
+
+ ~KeyWatcher();
+
+ private:
+ static void CALLBACK WatchCallback(void* aContext, BOOLEAN);
+ bool Register();
+
+ Key mKey;
+ nsCOMPtr<nsISerialEventTarget> mEventTarget;
+ Callback mCallback;
+ HANDLE mEvent = nullptr;
+ HANDLE mWaitObject = nullptr;
+};
+
+} // namespace mozilla::widget::WinRegistry
+
+#endif
diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp
new file mode 100644
index 0000000000..56608503da
--- /dev/null
+++ b/widget/windows/WinTaskbar.cpp
@@ -0,0 +1,493 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsIWinTaskbar.h"
+#include "WinTaskbar.h"
+#include "TaskbarPreview.h"
+#include "nsITaskbarPreviewController.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/widget/JumpListBuilder.h"
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsIBaseWindow.h>
+#include <nsServiceManagerUtils.h>
+#include "nsIXULAppInfo.h"
+#include "nsILegacyJumpListBuilder.h"
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "TaskbarTabPreview.h"
+#include "TaskbarWindowPreview.h"
+#include "LegacyJumpListBuilder.h"
+#include "nsWidgetsCID.h"
+#include "nsPIDOMWindow.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/Preferences.h"
+#include "nsAppRunner.h"
+#include "nsXREDirProvider.h"
+#include "mozilla/widget/WinRegistry.h"
+#include <io.h>
+#include <propvarutil.h>
+#include <propkey.h>
+#include <shellapi.h>
+
+static NS_DEFINE_CID(kLegacyJumpListBuilderCID,
+ NS_WIN_LEGACYJUMPLISTBUILDER_CID);
+
+namespace {
+
+HWND GetHWNDFromDocShell(nsIDocShell* aShell) {
+ nsCOMPtr<nsIBaseWindow> baseWindow(
+ do_QueryInterface(reinterpret_cast<nsISupports*>(aShell)));
+
+ if (!baseWindow) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWindow->GetMainWidget(getter_AddRefs(widget));
+
+ return widget ? (HWND)widget->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
+}
+
+HWND GetHWNDFromDOMWindow(mozIDOMWindow* dw) {
+ nsCOMPtr<nsIWidget> widget;
+
+ if (!dw) return nullptr;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(dw);
+ return GetHWNDFromDocShell(window->GetDocShell());
+}
+
+nsresult SetWindowAppUserModelProp(mozIDOMWindow* aParent,
+ const nsString& aIdentifier) {
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ if (aIdentifier.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ RefPtr<IPropertyStore> pPropStore;
+ if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore,
+ getter_AddRefs(pPropStore)))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROPVARIANT pv;
+ if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ if (FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv)) ||
+ FAILED(pPropStore->Commit())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ PropVariantClear(&pv);
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// default nsITaskbarPreviewController
+
+class DefaultController final : public nsITaskbarPreviewController {
+ ~DefaultController() {}
+ HWND mWnd;
+
+ public:
+ explicit DefaultController(HWND hWnd) : mWnd(hWnd) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITASKBARPREVIEWCONTROLLER
+};
+
+NS_IMETHODIMP
+DefaultController::GetWidth(uint32_t* aWidth) {
+ RECT r;
+ ::GetClientRect(mWnd, &r);
+ *aWidth = r.right;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::GetHeight(uint32_t* aHeight) {
+ RECT r;
+ ::GetClientRect(mWnd, &r);
+ *aHeight = r.bottom;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::GetThumbnailAspectRatio(float* aThumbnailAspectRatio) {
+ uint32_t width, height;
+ GetWidth(&width);
+ GetHeight(&height);
+ if (!height) height = 1;
+
+ *aThumbnailAspectRatio = width / float(height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::RequestThumbnail(nsITaskbarPreviewCallback* aCallback,
+ uint32_t width, uint32_t height) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::RequestPreview(nsITaskbarPreviewCallback* aCallback) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnClose(void) {
+ MOZ_ASSERT_UNREACHABLE(
+ "OnClose should not be called for "
+ "TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnActivate(bool* rAcceptActivation) {
+ *rAcceptActivation = true;
+ MOZ_ASSERT_UNREACHABLE(
+ "OnActivate should not be called for "
+ "TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnClick(nsITaskbarPreviewButton* button) { return NS_OK; }
+
+NS_IMPL_ISUPPORTS(DefaultController, nsITaskbarPreviewController)
+} // namespace
+
+namespace mozilla {
+namespace widget {
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIWinTaskbar
+
+NS_IMPL_ISUPPORTS(WinTaskbar, nsIWinTaskbar)
+
+bool WinTaskbar::Initialize() {
+ if (mTaskbar) return true;
+
+ ::CoInitialize(nullptr);
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ITaskbarList4, (void**)&mTaskbar);
+ if (FAILED(hr)) return false;
+
+ hr = mTaskbar->HrInit();
+ if (FAILED(hr)) {
+ // This may fail with shell extensions like blackbox installed.
+ NS_WARNING("Unable to initialize taskbar");
+ NS_RELEASE(mTaskbar);
+ return false;
+ }
+ return true;
+}
+
+WinTaskbar::WinTaskbar() : mTaskbar(nullptr) {}
+
+WinTaskbar::~WinTaskbar() {
+ if (mTaskbar) { // match successful Initialize() call
+ NS_RELEASE(mTaskbar);
+ ::CoUninitialize();
+ }
+}
+
+// static
+bool WinTaskbar::GenerateAppUserModelID(nsAString& aAppUserModelId,
+ bool aPrivateBrowsing) {
+ // If marked as such in prefs, use a hash of the profile path for the id
+ // instead of the install path hash setup by the installer.
+ if (Preferences::GetBool("taskbar.grouping.useprofile", false)) {
+ nsCOMPtr<nsIFile> profileDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ bool exists = false;
+ if (profileDir && NS_SUCCEEDED(profileDir->Exists(&exists)) && exists) {
+ nsAutoCString path;
+ if (NS_SUCCEEDED(profileDir->GetPersistentDescriptor(path))) {
+ nsAutoString id;
+ id.AppendInt(HashString(path));
+ if (!id.IsEmpty()) {
+ aAppUserModelId.Assign(id);
+ return true;
+ }
+ }
+ }
+ }
+
+ // The default value is set by the installer and is stored in the registry
+ // under (HKLM||HKCU)/Software/Mozilla/Firefox/TaskBarIDs. If for any reason
+ // hash generation operation fails, the installer will not store a value in
+ // the registry or set ids on shortcuts. A lack of an id can also occur for
+ // zipped builds.
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ nsCString appName;
+ if (appInfo && NS_SUCCEEDED(appInfo->GetName(appName))) {
+ nsAutoString regKey;
+ regKey.AssignLiteral("Software\\Mozilla\\");
+ AppendASCIItoUTF16(appName, regKey);
+ regKey.AppendLiteral("\\TaskBarIDs");
+
+ WCHAR path[MAX_PATH];
+ if (GetModuleFileNameW(nullptr, path, MAX_PATH)) {
+ wchar_t* slash = wcsrchr(path, '\\');
+ if (!slash) return false;
+ *slash = '\0'; // no trailing slash
+
+ nsDependentString pathStr(path);
+ for (auto* rootKey : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
+ if (auto aumid = WinRegistry::GetString(rootKey, regKey, pathStr)) {
+ aAppUserModelId = std::move(*aumid);
+ break;
+ }
+ }
+ }
+ }
+
+ // If we haven't found an ID yet then use the install hash. In xpcshell tests
+ // the directory provider may not have been initialized so bypass in this
+ // case.
+ if (aAppUserModelId.IsEmpty() && gDirServiceProvider) {
+ gDirServiceProvider->GetInstallHash(aAppUserModelId);
+ }
+
+ if (aPrivateBrowsing) {
+ aAppUserModelId.AppendLiteral(";PrivateBrowsingAUMID");
+ }
+
+ return !aAppUserModelId.IsEmpty();
+}
+
+// static
+bool WinTaskbar::GetAppUserModelID(nsAString& aAppUserModelId,
+ bool aPrivateBrowsing) {
+ // If an ID has already been set then use that.
+ PWSTR id;
+ if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&id))) {
+ aAppUserModelId.Assign(id);
+ CoTaskMemFree(id);
+ }
+
+ return GenerateAppUserModelID(aAppUserModelId, aPrivateBrowsing);
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetDefaultGroupId(nsAString& aDefaultGroupId) {
+ if (!GetAppUserModelID(aDefaultGroupId)) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetDefaultPrivateGroupId(nsAString& aDefaultPrivateGroupId) {
+ if (!GetAppUserModelID(aDefaultPrivateGroupId, true))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+// (static) Called from AppShell
+bool WinTaskbar::RegisterAppUserModelID() {
+ nsAutoString uid;
+ if (!GetAppUserModelID(uid)) return false;
+
+ return SUCCEEDED(SetCurrentProcessExplicitAppUserModelID(uid.get()));
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetAvailable(bool* aAvailable) {
+ // ITaskbarList4::HrInit() may fail with shell extensions like blackbox
+ // installed. Initialize early to return available=false in those cases.
+ *aAvailable = Initialize();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::CreateTaskbarTabPreview(nsIDocShell* shell,
+ nsITaskbarPreviewController* controller,
+ nsITaskbarTabPreview** _retval) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(shell);
+ NS_ENSURE_ARG_POINTER(controller);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ RefPtr<TaskbarTabPreview> preview(
+ new TaskbarTabPreview(mTaskbar, controller, toplevelHWND, shell));
+ if (!preview) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = preview->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ preview.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetTaskbarWindowPreview(nsIDocShell* shell,
+ nsITaskbarWindowPreview** _retval) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(shell);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT);
+
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+
+ nsWindow* window = WinUtils::GetNSWindowPtr(toplevelHWND);
+
+ if (!window) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsITaskbarWindowPreview> preview = window->GetTaskbarPreview();
+ if (!preview) {
+ RefPtr<DefaultController> defaultController =
+ new DefaultController(toplevelHWND);
+
+ TaskbarWindowPreview* previewRaw = new TaskbarWindowPreview(
+ mTaskbar, defaultController, toplevelHWND, shell);
+ if (!previewRaw) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ preview = previewRaw;
+
+ nsresult rv = previewRaw->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ window->SetTaskbarPreview(preview);
+ }
+
+ preview.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetTaskbarProgress(nsIDocShell* shell,
+ nsITaskbarProgress** _retval) {
+ nsCOMPtr<nsITaskbarWindowPreview> preview;
+ nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(preview, _retval);
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetOverlayIconController(
+ nsIDocShell* shell, nsITaskbarOverlayIconController** _retval) {
+ nsCOMPtr<nsITaskbarWindowPreview> preview;
+ nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(preview, _retval);
+}
+
+NS_IMETHODIMP
+WinTaskbar::CreateLegacyJumpListBuilder(
+ bool aPrivateBrowsing, nsILegacyJumpListBuilder** aJumpListBuilder) {
+ nsresult rv;
+
+ if (LegacyJumpListBuilder::sBuildingList) return NS_ERROR_ALREADY_INITIALIZED;
+
+ nsCOMPtr<nsILegacyJumpListBuilder> builder =
+ do_CreateInstance(kLegacyJumpListBuilderCID, &rv);
+ if (NS_FAILED(rv)) return NS_ERROR_UNEXPECTED;
+
+ NS_IF_ADDREF(*aJumpListBuilder = builder);
+
+ nsAutoString aumid;
+ GenerateAppUserModelID(aumid, aPrivateBrowsing);
+ builder->SetAppUserModelID(aumid);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::CreateJumpListBuilder(bool aPrivateBrowsing,
+ nsIJumpListBuilder** aJumpListBuilder) {
+ nsAutoString aumid;
+ GenerateAppUserModelID(aumid, aPrivateBrowsing);
+
+ nsCOMPtr<nsIJumpListBuilder> builder = new JumpListBuilder(aumid);
+ if (!builder) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_IF_ADDREF(*aJumpListBuilder = builder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetGroupIdForWindow(mozIDOMWindow* aParent,
+ nsAString& aIdentifier) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aParent), GA_ROOT);
+ if (!toplevelHWND) return NS_ERROR_INVALID_ARG;
+ RefPtr<IPropertyStore> pPropStore;
+ if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore,
+ getter_AddRefs(pPropStore)))) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ PROPVARIANT pv;
+ PropVariantInit(&pv);
+ auto cleanupPropVariant = MakeScopeExit([&] { PropVariantClear(&pv); });
+ if (FAILED(pPropStore->GetValue(PKEY_AppUserModel_ID, &pv))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (pv.vt != VT_LPWSTR) {
+ // This can happen when there is no window specific group ID set
+ // It's not an error case so we have to check for empty strings
+ // returned from the function.
+ return NS_OK;
+ }
+ aIdentifier.Assign(char16ptr_t(pv.pwszVal));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::SetGroupIdForWindow(mozIDOMWindow* aParent,
+ const nsAString& aIdentifier) {
+ return SetWindowAppUserModelProp(aParent, nsString(aIdentifier));
+}
+
+NS_IMETHODIMP
+WinTaskbar::PrepareFullScreen(void* aHWND, bool aFullScreen) {
+ if (!Initialize()) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(aHWND);
+
+ if (!::IsWindow((HWND)aHWND)) return NS_ERROR_INVALID_ARG;
+
+ HRESULT hr = mTaskbar->MarkFullscreenWindow((HWND)aHWND, aFullScreen);
+ if (FAILED(hr)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinTaskbar.h b/widget/windows/WinTaskbar.h
new file mode 100644
index 0000000000..a6c483a561
--- /dev/null
+++ b/widget/windows/WinTaskbar.h
@@ -0,0 +1,46 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 __WinTaskbar_h__
+#define __WinTaskbar_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+#include "nsIWinTaskbar.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace widget {
+
+class WinTaskbar final : public nsIWinTaskbar {
+ ~WinTaskbar();
+
+ public:
+ WinTaskbar();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWINTASKBAR
+
+ static bool GenerateAppUserModelID(nsAString& aAppUserModelId,
+ bool aPrivateBrowsing = false);
+ // Registers the global app user model id for the instance.
+ // See comments in WinTaskbar.cpp for more information.
+ static bool RegisterAppUserModelID();
+ static bool GetAppUserModelID(nsAString& aDefaultGroupId,
+ bool aPrivateBrowsing = false);
+
+ private:
+ bool Initialize();
+
+ ITaskbarList4* mTaskbar;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __WinTaskbar_h__ */
diff --git a/widget/windows/WinTextEventDispatcherListener.cpp b/widget/windows/WinTextEventDispatcherListener.cpp
new file mode 100644
index 0000000000..aeed8be01b
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "KeyboardLayout.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/widget/IMEData.h"
+#include "nsWindow.h"
+#include "WinIMEHandler.h"
+#include "WinTextEventDispatcherListener.h"
+
+namespace mozilla {
+namespace widget {
+
+StaticRefPtr<WinTextEventDispatcherListener>
+ WinTextEventDispatcherListener::sInstance;
+
+// static
+WinTextEventDispatcherListener* WinTextEventDispatcherListener::GetInstance() {
+ if (!sInstance) {
+ sInstance = new WinTextEventDispatcherListener();
+ }
+ return sInstance.get();
+}
+
+void WinTextEventDispatcherListener::Shutdown() { sInstance = nullptr; }
+
+NS_IMPL_ISUPPORTS(WinTextEventDispatcherListener, TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+WinTextEventDispatcherListener::WinTextEventDispatcherListener() {}
+
+WinTextEventDispatcherListener::~WinTextEventDispatcherListener() {}
+
+NS_IMETHODIMP
+WinTextEventDispatcherListener::NotifyIME(
+ TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ if (NS_WARN_IF(!window)) {
+ return NS_ERROR_FAILURE;
+ }
+ return IMEHandler::NotifyIME(window, aNotification);
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+WinTextEventDispatcherListener::GetIMENotificationRequests() {
+ return IMEHandler::GetIMENotificationRequests();
+}
+
+NS_IMETHODIMP_(void)
+WinTextEventDispatcherListener::OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) {
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+WinTextEventDispatcherListener::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ static_cast<NativeKey*>(aData)->WillDispatchKeyboardEvent(aKeyboardEvent,
+ aIndexOfKeypress);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinTextEventDispatcherListener.h b/widget/windows/WinTextEventDispatcherListener.h
new file mode 100644
index 0000000000..d799d0b280
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WinTextEventDispatcherListener_h_
+#define WinTextEventDispatcherListener_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcherListener.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * On Windows, it's enough TextEventDispatcherListener to be a singleton
+ * because we have only one input context per process (IMM can create
+ * multiple IM context but we don't support such behavior).
+ */
+
+class WinTextEventDispatcherListener final
+ : public TextEventDispatcherListener {
+ public:
+ static WinTextEventDispatcherListener* GetInstance();
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ private:
+ WinTextEventDispatcherListener();
+ virtual ~WinTextEventDispatcherListener();
+
+ static StaticRefPtr<WinTextEventDispatcherListener> sInstance;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef WinTextEventDispatcherListener_h_
diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp
new file mode 100644
index 0000000000..1d8fad16f7
--- /dev/null
+++ b/widget/windows/WinUtils.cpp
@@ -0,0 +1,2107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+#include "WinUtils.h"
+
+#include <knownfolders.h>
+#include <winioctl.h>
+
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+#include "InputDeviceUtils.h"
+#include "KeyboardLayout.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/DisplayConfigWindows.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/Unused.h"
+#include "nsIContentPolicy.h"
+#include "WindowsUIUtils.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "nsNetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+#ifdef MOZ_PLACES
+# include "nsIFaviconService.h"
+#endif
+#include "nsIDownloader.h"
+#include "nsIChannel.h"
+#include "nsIThread.h"
+#include "MainThreadUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowsHelpers.h"
+#include "WinWindowOcclusionTracker.h"
+
+#include <textstor.h>
+#include "TSFTextStore.h"
+
+#include <shellscalingapi.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+
+mozilla::LazyLogModule gWindowsLog("Widget");
+
+#define LOG_E(...) MOZ_LOG(gWindowsLog, LogLevel::Error, (__VA_ARGS__))
+#define LOG_D(...) MOZ_LOG(gWindowsLog, LogLevel::Debug, (__VA_ARGS__))
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace widget {
+
+#ifdef MOZ_PLACES
+NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver)
+NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback)
+#endif
+NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable)
+NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable)
+
+const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache";
+const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache";
+
+struct CoTaskMemFreePolicy {
+ void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); }
+};
+
+SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL;
+EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL;
+GetSystemMetricsForDpiProc WinUtils::sGetSystemMetricsForDpi = NULL;
+bool WinUtils::sHasPackageIdentity = false;
+
+using GetDpiForWindowProc = UINT(WINAPI*)(HWND);
+static GetDpiForWindowProc sGetDpiForWindow = NULL;
+
+/* static */
+void WinUtils::Initialize() {
+ // Dpi-Awareness is not supported with Win32k Lockdown enabled, so we don't
+ // initialize DPI-related members and assert later that nothing accidently
+ // uses these static members
+ if (!IsWin32kLockedDown()) {
+ HMODULE user32Dll = ::GetModuleHandleW(L"user32");
+ if (user32Dll) {
+ auto getThreadDpiAwarenessContext =
+ (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
+ user32Dll, "GetThreadDpiAwarenessContext");
+ auto areDpiAwarenessContextsEqual =
+ (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
+ user32Dll, "AreDpiAwarenessContextsEqual");
+ if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
+ areDpiAwarenessContextsEqual(
+ getThreadDpiAwarenessContext(),
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ // Only per-monitor v1 requires these workarounds.
+ sEnableNonClientDpiScaling =
+ (EnableNonClientDpiScalingProc)::GetProcAddress(
+ user32Dll, "EnableNonClientDpiScaling");
+ sSetThreadDpiAwarenessContext =
+ (SetThreadDpiAwarenessContextProc)::GetProcAddress(
+ user32Dll, "SetThreadDpiAwarenessContext");
+ }
+
+ sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
+ user32Dll, "GetSystemMetricsForDpi");
+ sGetDpiForWindow =
+ (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
+ }
+ }
+
+ sHasPackageIdentity = mozilla::HasPackageIdentity();
+}
+
+// static
+LRESULT WINAPI WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+
+ // NOTE: this function was copied out into the body of the pre-XUL skeleton
+ // UI window proc (PreXULSkeletonUI.cpp). If this function changes at any
+ // point, we should probably factor this out and use it from both locations.
+ if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
+ sEnableNonClientDpiScaling(hWnd);
+ }
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+// static
+void WinUtils::LogW(const wchar_t* fmt, ...) {
+ va_list args = nullptr;
+ if (!lstrlenW(fmt)) {
+ return;
+ }
+ va_start(args, fmt);
+ int buflen = _vscwprintf(fmt, args);
+ wchar_t* buffer = new wchar_t[buflen + 1];
+ if (!buffer) {
+ va_end(args);
+ return;
+ }
+ vswprintf(buffer, buflen, fmt, args);
+ va_end(args);
+
+ // MSVC, including remote debug sessions
+ OutputDebugStringW(buffer);
+ OutputDebugStringW(L"\n");
+
+ int len =
+ WideCharToMultiByte(CP_ACP, 0, buffer, -1, nullptr, 0, nullptr, nullptr);
+ if (len) {
+ char* utf8 = new char[len];
+ if (WideCharToMultiByte(CP_ACP, 0, buffer, -1, utf8, len, nullptr,
+ nullptr) > 0) {
+ // desktop console
+ printf("%s\n", utf8);
+ NS_ASSERTION(gWindowsLog,
+ "Called WinUtils Log() but Widget "
+ "log module doesn't exist!");
+ MOZ_LOG(gWindowsLog, LogLevel::Error, ("%s", utf8));
+ }
+ delete[] utf8;
+ }
+ delete[] buffer;
+}
+
+// static
+void WinUtils::Log(const char* fmt, ...) {
+ va_list args = nullptr;
+ if (!strlen(fmt)) {
+ return;
+ }
+ va_start(args, fmt);
+ int buflen = _vscprintf(fmt, args);
+ char* buffer = new char[buflen + 1];
+ if (!buffer) {
+ va_end(args);
+ return;
+ }
+ vsprintf(buffer, fmt, args);
+ va_end(args);
+
+ // MSVC, including remote debug sessions
+ OutputDebugStringA(buffer);
+ OutputDebugStringW(L"\n");
+
+ // desktop console
+ printf("%s\n", buffer);
+
+ NS_ASSERTION(gWindowsLog,
+ "Called WinUtils Log() but Widget "
+ "log module doesn't exist!");
+ MOZ_LOG(gWindowsLog, LogLevel::Error, ("%s", buffer));
+ delete[] buffer;
+}
+
+// static
+float WinUtils::SystemDPI() {
+ // The result of GetDeviceCaps won't change dynamically, as it predates
+ // per-monitor DPI and support for on-the-fly resolution changes.
+ // Therefore, we only need to look it up once.
+ static float dpi = 0;
+ if (dpi <= 0) {
+ HDC screenDC = GetDC(nullptr);
+ dpi = GetDeviceCaps(screenDC, LOGPIXELSY);
+ ReleaseDC(nullptr, screenDC);
+ }
+
+ // Bug 1012487 - dpi can be 0 when the Screen DC is used off the
+ // main thread on windows. For now just assume a 100% DPI for this
+ // drawing call.
+ // XXX - fixme!
+ return dpi > 0 ? dpi : 96;
+}
+
+// static
+double WinUtils::SystemScaleFactor() { return SystemDPI() / 96.0; }
+
+typedef HRESULT(WINAPI* GETDPIFORMONITORPROC)(HMONITOR, MONITOR_DPI_TYPE, UINT*,
+ UINT*);
+
+typedef HRESULT(WINAPI* GETPROCESSDPIAWARENESSPROC)(HANDLE,
+ PROCESS_DPI_AWARENESS*);
+
+GETDPIFORMONITORPROC sGetDpiForMonitor;
+GETPROCESSDPIAWARENESSPROC sGetProcessDpiAwareness;
+
+static bool SlowIsPerMonitorDPIAware() {
+ // Intentionally leak the handle.
+ HMODULE shcore = LoadLibraryEx(L"shcore", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (shcore) {
+ sGetDpiForMonitor =
+ (GETDPIFORMONITORPROC)GetProcAddress(shcore, "GetDpiForMonitor");
+ sGetProcessDpiAwareness = (GETPROCESSDPIAWARENESSPROC)GetProcAddress(
+ shcore, "GetProcessDpiAwareness");
+ }
+ PROCESS_DPI_AWARENESS dpiAwareness;
+ return sGetDpiForMonitor && sGetProcessDpiAwareness &&
+ SUCCEEDED(
+ sGetProcessDpiAwareness(GetCurrentProcess(), &dpiAwareness)) &&
+ dpiAwareness == PROCESS_PER_MONITOR_DPI_AWARE;
+}
+
+/* static */
+bool WinUtils::IsPerMonitorDPIAware() {
+ static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware();
+ return perMonitorDPIAware;
+}
+
+/* static */
+float WinUtils::MonitorDPI(HMONITOR aMonitor) {
+ if (IsPerMonitorDPIAware()) {
+ UINT dpiX, dpiY = 96;
+ sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(),
+ MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+ return dpiY;
+ }
+
+ // We're not per-monitor aware, use system DPI instead.
+ return SystemDPI();
+}
+
+/* static */
+double WinUtils::LogToPhysFactor(HMONITOR aMonitor) {
+ return MonitorDPI(aMonitor) / 96.0;
+}
+
+/* static */
+int32_t WinUtils::LogToPhys(HMONITOR aMonitor, double aValue) {
+ return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor)));
+}
+
+/* static */
+double WinUtils::LogToPhysFactor(HWND aWnd) {
+ // if there's an ancestor window, we want to share its DPI setting
+ HWND ancestor = ::GetAncestor(aWnd, GA_ROOTOWNER);
+
+ // The GetDpiForWindow api is not available everywhere where we run as
+ // per-monitor, but if it is available rely on it to tell us the scale
+ // factor of the window. See bug 1722085.
+ if (sGetDpiForWindow) {
+ UINT dpi = sGetDpiForWindow(ancestor ? ancestor : aWnd);
+ if (dpi > 0) {
+ return static_cast<double>(dpi) / 96.0;
+ }
+ }
+ return LogToPhysFactor(::MonitorFromWindow(ancestor ? ancestor : aWnd,
+ MONITOR_DEFAULTTOPRIMARY));
+}
+
+/* static */
+HMONITOR
+WinUtils::GetPrimaryMonitor() {
+ const POINT pt = {0, 0};
+ return ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
+}
+
+/* static */
+HMONITOR
+WinUtils::MonitorFromRect(const gfx::Rect& rect) {
+ // convert coordinates from desktop to device pixels for MonitorFromRect
+ double dpiScale =
+ IsPerMonitorDPIAware() ? 1.0 : LogToPhysFactor(GetPrimaryMonitor());
+
+ RECT globalWindowBounds = {NSToIntRound(dpiScale * rect.X()),
+ NSToIntRound(dpiScale * rect.Y()),
+ NSToIntRound(dpiScale * (rect.XMost())),
+ NSToIntRound(dpiScale * (rect.YMost()))};
+
+ return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST);
+}
+
+/* static */
+bool WinUtils::HasSystemMetricsForDpi() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+ return (sGetSystemMetricsForDpi != NULL);
+}
+
+/* static */
+int WinUtils::GetSystemMetricsForDpi(int nIndex, UINT dpi) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+ if (HasSystemMetricsForDpi()) {
+ return sGetSystemMetricsForDpi(nIndex, dpi);
+ } else {
+ double scale = IsPerMonitorDPIAware() ? dpi / SystemDPI() : 1.0;
+ return NSToIntRound(::GetSystemMetrics(nIndex) * scale);
+ }
+}
+
+/* static */
+gfx::MarginDouble WinUtils::GetUnwriteableMarginsForDeviceInInches(HDC aHdc) {
+ if (!aHdc) {
+ return gfx::MarginDouble();
+ }
+
+ int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY);
+ int marginTop = ::GetDeviceCaps(aHdc, PHYSICALOFFSETY);
+ int printableAreaHeight = ::GetDeviceCaps(aHdc, VERTRES);
+ int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT);
+
+ double marginTopInch = double(marginTop) / pixelsPerInchY;
+
+ double printableAreaHeightInch = double(printableAreaHeight) / pixelsPerInchY;
+ double physicalHeightInch = double(physicalHeight) / pixelsPerInchY;
+ double marginBottomInch =
+ physicalHeightInch - printableAreaHeightInch - marginTopInch;
+
+ int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX);
+ int marginLeft = ::GetDeviceCaps(aHdc, PHYSICALOFFSETX);
+ int printableAreaWidth = ::GetDeviceCaps(aHdc, HORZRES);
+ int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH);
+
+ double marginLeftInch = double(marginLeft) / pixelsPerInchX;
+
+ double printableAreaWidthInch = double(printableAreaWidth) / pixelsPerInchX;
+ double physicalWidthInch = double(physicalWidth) / pixelsPerInchX;
+ double marginRightInch =
+ physicalWidthInch - printableAreaWidthInch - marginLeftInch;
+
+ return gfx::MarginDouble(marginTopInch, marginRightInch, marginBottomInch,
+ marginLeftInch);
+}
+
+#ifdef ACCESSIBILITY
+/* static */
+a11y::LocalAccessible* WinUtils::GetRootAccessibleForHWND(HWND aHwnd) {
+ nsWindow* window = GetNSWindowPtr(aHwnd);
+ if (!window) {
+ return nullptr;
+ }
+
+ return window->GetAccessible();
+}
+#endif // ACCESSIBILITY
+
+/* static */
+bool WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption) {
+ RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
+ if (msgPump) {
+ BOOL ret = FALSE;
+ HRESULT hr = msgPump->PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage,
+ aOption, &ret);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ return ret;
+ }
+ return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
+}
+
+/* static */
+bool WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage) {
+ RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump();
+ if (msgPump) {
+ BOOL ret = FALSE;
+ HRESULT hr =
+ msgPump->GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, &ret);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+ return ret;
+ }
+ return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
+}
+
+#if defined(ACCESSIBILITY)
+static DWORD GetWaitFlags() {
+ DWORD result = MWMO_INPUTAVAILABLE;
+ if (XRE_IsContentProcess()) {
+ result |= MWMO_ALERTABLE;
+ }
+ return result;
+}
+#endif
+
+/* static */
+void WinUtils::WaitForMessage(DWORD aTimeoutMs) {
+#if defined(ACCESSIBILITY)
+ static const DWORD waitFlags = GetWaitFlags();
+#else
+ const DWORD waitFlags = MWMO_INPUTAVAILABLE;
+#endif
+
+ const DWORD waitStart = ::GetTickCount();
+ DWORD elapsed = 0;
+ while (true) {
+ if (aTimeoutMs != INFINITE) {
+ elapsed = ::GetTickCount() - waitStart;
+ }
+ if (elapsed >= aTimeoutMs) {
+ break;
+ }
+ DWORD result;
+ {
+ AUTO_PROFILER_THREAD_SLEEP;
+ result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed,
+ MOZ_QS_ALLEVENT, waitFlags);
+ }
+ NS_WARNING_ASSERTION(result != WAIT_FAILED, "Wait failed");
+ if (result == WAIT_TIMEOUT) {
+ break;
+ }
+#if defined(ACCESSIBILITY)
+ if (result == WAIT_IO_COMPLETION) {
+ if (NS_IsMainThread()) {
+ // We executed an APC that would have woken up the hang monitor. Since
+ // there are no more APCs pending and we are now going to sleep again,
+ // we should notify the hang monitor.
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ }
+ continue;
+ }
+#endif // defined(ACCESSIBILITY)
+
+ // Sent messages (via SendMessage and friends) are processed differently
+ // than queued messages (via PostMessage); the destination window procedure
+ // of the sent message is called during (Get|Peek)Message. Since PeekMessage
+ // does not tell us whether it processed any sent messages, we need to query
+ // this ahead of time.
+ bool haveSentMessagesPending =
+ (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
+
+ MSG msg = {0};
+ if (haveSentMessagesPending ||
+ ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
+ break;
+ }
+ // The message is intended for another thread that has been synchronized
+ // with our input queue; yield to give other threads an opportunity to
+ // process the message. This should prevent busy waiting if resumed due
+ // to another thread's message.
+ ::SwitchToThread();
+ }
+}
+
+/* static */
+HWND WinUtils::GetTopLevelHWND(HWND aWnd, bool aStopIfNotChild,
+ bool aStopIfNotPopup) {
+ HWND curWnd = aWnd;
+ HWND topWnd = nullptr;
+
+ while (curWnd) {
+ topWnd = curWnd;
+
+ if (aStopIfNotChild) {
+ DWORD_PTR style = ::GetWindowLongPtrW(curWnd, GWL_STYLE);
+
+ VERIFY_WINDOW_STYLE(style);
+
+ if (!(style & WS_CHILD)) // first top-level window
+ break;
+ }
+
+ HWND upWnd = ::GetParent(curWnd); // Parent or owner (if has no parent)
+
+ // GetParent will only return the owner if the passed in window
+ // has the WS_POPUP style.
+ if (!upWnd && !aStopIfNotPopup) {
+ upWnd = ::GetWindow(curWnd, GW_OWNER);
+ }
+ curWnd = upWnd;
+ }
+
+ return topWnd;
+}
+
+// Map from native window handles to nsWindow structures. Does not AddRef.
+// Inherently unsafe to access outside the main thread.
+static nsTHashMap<HWND, nsWindow*> sExtantNSWindows;
+
+/* static */
+void WinUtils::SetNSWindowPtr(HWND aWnd, nsWindow* aWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aWindow) {
+ sExtantNSWindows.Remove(aWnd);
+ } else {
+ sExtantNSWindows.InsertOrUpdate(aWnd, aWindow);
+ }
+}
+
+/* static */
+nsWindow* WinUtils::GetNSWindowPtr(HWND aWnd) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return sExtantNSWindows.Get(aWnd); // or nullptr
+}
+
+/* static */
+bool WinUtils::IsOurProcessWindow(HWND aWnd) {
+ if (!aWnd) {
+ return false;
+ }
+ DWORD processId = 0;
+ ::GetWindowThreadProcessId(aWnd, &processId);
+ return (processId == ::GetCurrentProcessId());
+}
+
+/* static */
+HWND WinUtils::FindOurProcessWindow(HWND aWnd) {
+ for (HWND wnd = ::GetParent(aWnd); wnd; wnd = ::GetParent(wnd)) {
+ if (IsOurProcessWindow(wnd)) {
+ return wnd;
+ }
+ }
+ return nullptr;
+}
+
+static bool IsPointInWindow(HWND aWnd, const POINT& aPointInScreen) {
+ RECT bounds;
+ if (!::GetWindowRect(aWnd, &bounds)) {
+ return false;
+ }
+
+ return (aPointInScreen.x >= bounds.left && aPointInScreen.x < bounds.right &&
+ aPointInScreen.y >= bounds.top && aPointInScreen.y < bounds.bottom);
+}
+
+/**
+ * FindTopmostWindowAtPoint() returns the topmost child window (topmost means
+ * forground in this context) of aWnd.
+ */
+
+static HWND FindTopmostWindowAtPoint(HWND aWnd, const POINT& aPointInScreen) {
+ if (!::IsWindowVisible(aWnd) || !IsPointInWindow(aWnd, aPointInScreen)) {
+ return nullptr;
+ }
+
+ HWND childWnd = ::GetTopWindow(aWnd);
+ while (childWnd) {
+ HWND topmostWnd = FindTopmostWindowAtPoint(childWnd, aPointInScreen);
+ if (topmostWnd) {
+ return topmostWnd;
+ }
+ childWnd = ::GetNextWindow(childWnd, GW_HWNDNEXT);
+ }
+
+ return aWnd;
+}
+
+struct FindOurWindowAtPointInfo {
+ POINT mInPointInScreen;
+ HWND mOutWnd;
+};
+
+static BOOL CALLBACK FindOurWindowAtPointCallback(HWND aWnd, LPARAM aLPARAM) {
+ if (!WinUtils::IsOurProcessWindow(aWnd)) {
+ // This isn't one of our top-level windows; continue enumerating.
+ return TRUE;
+ }
+
+ // Get the top-most child window under the point. If there's no child
+ // window, and the point is within the top-level window, then the top-level
+ // window will be returned. (This is the usual case. A child window
+ // would be returned for plugins.)
+ FindOurWindowAtPointInfo* info =
+ reinterpret_cast<FindOurWindowAtPointInfo*>(aLPARAM);
+ HWND childWnd = FindTopmostWindowAtPoint(aWnd, info->mInPointInScreen);
+ if (!childWnd) {
+ // This window doesn't contain the point; continue enumerating.
+ return TRUE;
+ }
+
+ // Return the HWND and stop enumerating.
+ info->mOutWnd = childWnd;
+ return FALSE;
+}
+
+/* static */
+HWND WinUtils::FindOurWindowAtPoint(const POINT& aPointInScreen) {
+ FindOurWindowAtPointInfo info;
+ info.mInPointInScreen = aPointInScreen;
+ info.mOutWnd = nullptr;
+
+ // This will enumerate all top-level windows in order from top to bottom.
+ EnumWindows(FindOurWindowAtPointCallback, reinterpret_cast<LPARAM>(&info));
+ return info.mOutWnd;
+}
+
+/* static */
+UINT WinUtils::GetInternalMessage(UINT aNativeMessage) {
+ switch (aNativeMessage) {
+ case WM_MOUSEWHEEL:
+ return MOZ_WM_MOUSEVWHEEL;
+ case WM_MOUSEHWHEEL:
+ return MOZ_WM_MOUSEHWHEEL;
+ case WM_VSCROLL:
+ return MOZ_WM_VSCROLL;
+ case WM_HSCROLL:
+ return MOZ_WM_HSCROLL;
+ default:
+ return aNativeMessage;
+ }
+}
+
+/* static */
+UINT WinUtils::GetNativeMessage(UINT aInternalMessage) {
+ switch (aInternalMessage) {
+ case MOZ_WM_MOUSEVWHEEL:
+ return WM_MOUSEWHEEL;
+ case MOZ_WM_MOUSEHWHEEL:
+ return WM_MOUSEHWHEEL;
+ case MOZ_WM_VSCROLL:
+ return WM_VSCROLL;
+ case MOZ_WM_HSCROLL:
+ return WM_HSCROLL;
+ default:
+ return aInternalMessage;
+ }
+}
+
+/* static */
+uint16_t WinUtils::GetMouseInputSource() {
+ int32_t inputSource = dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE;
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
+ inputSource = (lParamExtraInfo & TABLET_INK_TOUCH)
+ ? dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH
+ : dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
+ }
+ return static_cast<uint16_t>(inputSource);
+}
+
+/* static */
+uint16_t WinUtils::GetMousePointerID() {
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ return lParamExtraInfo & TABLET_INK_ID_MASK;
+}
+
+/* static */
+bool WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) {
+ const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE;
+ const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK;
+ return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown ||
+ aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick ||
+ aEventMessage == eMouseDoubleClick) &&
+ (GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH);
+}
+
+/* static */
+MSG WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) {
+ MSG msg;
+ msg.message = aMessage;
+ msg.wParam = wParam;
+ msg.lParam = lParam;
+ msg.hwnd = aWnd;
+ return msg;
+}
+
+#ifdef MOZ_PLACES
+/************************************************************************
+ * Constructs as AsyncFaviconDataReady Object
+ * @param aIOThread : the thread which performs the action
+ * @param aURLShortcut : Differentiates between (false)Jumplistcache and
+ * (true)Shortcutcache
+ * @param aRunnable : Executed in the aIOThread when the favicon cache is
+ * avaiable
+ ************************************************************************/
+
+AsyncFaviconDataReady::AsyncFaviconDataReady(
+ nsIURI* aNewURI, RefPtr<LazyIdleThread>& aIOThread, const bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable)
+ : mNewURI(aNewURI),
+ mIOThread(aIOThread),
+ mRunnable(aRunnable),
+ mURLShortcut(aURLShortcut) {}
+
+NS_IMETHODIMP
+myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader,
+ nsIRequest* request, nsresult status,
+ nsIFile* result) {
+ return NS_OK;
+}
+
+nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) {
+ if (!mURLShortcut) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv =
+ FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> mozIconURI;
+ rv = NS_NewURI(getter_AddRefs(mozIconURI), "moz-icon://.html?size=32");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), mozIconURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDownloadObserver> downloadObserver = new myDownloadObserver;
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = NS_NewDownloader(getter_AddRefs(listener), downloadObserver, icoFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return channel->AsyncOpen(listener);
+}
+
+NS_IMETHODIMP
+AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen,
+ const uint8_t* aData,
+ const nsACString& aMimeType,
+ uint16_t aWidth) {
+ if (!aDataLen || !aData) {
+ if (mURLShortcut) {
+ OnFaviconDataNotAvailable();
+ }
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv =
+ FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = icoFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Decode the image from the format it was returned to us in (probably PNG)
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
+ rv = imgtool->DecodeImageFromBuffer(reinterpret_cast<const char*>(aData),
+ aDataLen, aMimeType,
+ getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SourceSurface> surface = container->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ RefPtr<DataSourceSurface> dataSurface;
+ IntSize size;
+
+ if (mURLShortcut &&
+ (surface->GetSize().width < 48 || surface->GetSize().height < 48)) {
+ // Create a 48x48 surface and paint the icon into the central rect.
+ size.width = std::max(surface->GetSize().width, 48);
+ size.height = std::max(surface->GetSize().height, 48);
+ dataSurface =
+ Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in "
+ "CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ dt->FillRect(Rect(0, 0, size.width, size.height),
+ ColorPattern(ToDeviceColor(sRGBColor::OpaqueWhite())));
+ IntPoint point;
+ point.x = (size.width - surface->GetSize().width) / 2;
+ point.y = (size.height - surface->GetSize().height) / 2;
+ dt->DrawSurface(surface,
+ Rect(point.x, point.y, surface->GetSize().width,
+ surface->GetSize().height),
+ Rect(Point(0, 0), Size(surface->GetSize().width,
+ surface->GetSize().height)));
+
+ dataSurface->Unmap();
+ } else {
+ // By using the input image surface's size, we may end up encoding
+ // to a different size than a 16x16 (or bigger for higher DPI) ICO, but
+ // Windows will resize appropriately for us. If we want to encode ourselves
+ // one day because we like our resizing better, we'd have to manually
+ // resize the image here and use GetSystemMetrics w/ SM_CXSMICON and
+ // SM_CYSMICON. We don't support resizing images asynchronously at the
+ // moment anyway so getting the DPI aware icon size won't help.
+ size.width = surface->GetSize().width;
+ size.height = surface->GetSize().height;
+ dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ }
+
+ // Allocate a new buffer that we own and can use out of line in
+ // another thread.
+ UniquePtr<uint8_t[]> data = SurfaceToPackedBGRA(dataSurface);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t stride = 4 * size.width;
+
+ // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer
+ nsCOMPtr<nsIRunnable> event =
+ new AsyncEncodeAndWriteIcon(path, std::move(data), stride, size.width,
+ size.height, mRunnable.forget());
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+#endif
+
+// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed
+// in
+AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon(
+ const nsAString& aIconPath, UniquePtr<uint8_t[]> aBuffer, uint32_t aStride,
+ uint32_t aWidth, uint32_t aHeight, already_AddRefed<nsIRunnable> aRunnable)
+ : mIconPath(aIconPath),
+ mBuffer(std::move(aBuffer)),
+ mRunnable(aRunnable),
+ mStride(aStride),
+ mWidth(aWidth),
+ mHeight(aHeight) {}
+
+NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread.");
+
+ // Note that since we're off the main thread we can't use
+ // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()
+ RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
+ mBuffer.get(), mStride, IntSize(mWidth, mHeight),
+ SurfaceFormat::B8G8R8A8);
+
+ FILE* file = _wfopen(mIconPath.get(), L"wb");
+ if (!file) {
+ // Maybe the directory doesn't exist; try creating it, then fopen again.
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1");
+ if (comFile) {
+ rv = comFile->InitWithPath(mIconPath);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> dirPath;
+ comFile->GetParent(getter_AddRefs(dirPath));
+ if (dirPath) {
+ rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) {
+ file = _wfopen(mIconPath.get(), L"wb");
+ if (!file) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (!file) {
+ return rv;
+ }
+ }
+ nsresult rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns,
+ gfxUtils::eBinaryEncode, file);
+ fclose(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mRunnable) {
+ mRunnable->Run();
+ }
+ return rv;
+}
+
+AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() {}
+
+AsyncDeleteAllFaviconsFromDisk::AsyncDeleteAllFaviconsFromDisk(
+ bool aIgnoreRecent)
+ : mIgnoreRecent(aIgnoreRecent) {
+ // We can't call FaviconHelper::GetICOCacheSecondsTimeout() on non-main
+ // threads, as it reads a pref, so cache its value here.
+ mIcoNoDeleteSeconds = FaviconHelper::GetICOCacheSecondsTimeout() + 600;
+
+ // Prepare the profile directory cache on the main thread, to ensure we wont
+ // do this on non-main threads.
+ Unused << NS_GetSpecialDirectory("ProfLDS",
+ getter_AddRefs(mJumpListCacheDir));
+}
+
+NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run() {
+ if (!mJumpListCacheDir) {
+ return NS_ERROR_FAILURE;
+ }
+ // Construct the path of our jump list cache
+ nsresult rv = mJumpListCacheDir->AppendNative(
+ nsDependentCString(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = mJumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ nsCOMPtr<nsIFile> currFile;
+ if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
+ break;
+
+ nsAutoString path;
+ if (NS_FAILED(currFile->GetPath(path))) continue;
+
+ if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
+
+ if (mIgnoreRecent) {
+ // Check to make sure the icon wasn't just recently created.
+ // If it was created recently, don't delete it yet.
+ int64_t fileModTime = 0;
+ rv = currFile->GetLastModifiedTime(&fileModTime);
+ fileModTime /= PR_MSEC_PER_SEC;
+ // If the icon is older than the regeneration time (+ 10 min to be
+ // safe), then it's old and we can get rid of it.
+ // This code is only hit directly after a regeneration.
+ int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
+ if (NS_FAILED(rv) || (nowTime - fileModTime) < mIcoNoDeleteSeconds) {
+ continue;
+ }
+ }
+
+ // We found an ICO file that exists, so we should remove it
+ currFile->Remove(false);
+ }
+ } while (true);
+
+ return NS_OK;
+}
+
+AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() {}
+
+/*
+ * (static) If the data is available, will return the path on disk where
+ * the favicon for page aFaviconPageURI is stored. If the favicon does not
+ * exist, or its cache is expired, this method will kick off an async request
+ * for the icon so that next time the method is called it will be available.
+ * @param aFaviconPageURI The URI of the page to obtain
+ * @param aICOFilePath The path of the icon file
+ * @param aIOThread The thread to perform the Fetch on
+ * @param aURLShortcut to distinguish between jumplistcache(false) and
+ * shortcutcache(true)
+ * @param aRunnable Executed in the aIOThread when the favicon cache is
+ * avaiable
+ */
+nsresult FaviconHelper::ObtainCachedIconFile(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
+ RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+ // Obtain the ICO file path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = GetOutputIconPath(aFaviconPageURI, icoFile, aURLShortcut);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the cached ICO file already exists
+ bool exists;
+ rv = icoFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ // Obtain the file's last modification date in seconds
+ int64_t fileModTime = 0;
+ rv = icoFile->GetLastModifiedTime(&fileModTime);
+ fileModTime /= PR_MSEC_PER_SEC;
+ int32_t icoReCacheSecondsTimeout = GetICOCacheSecondsTimeout();
+ int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC);
+
+ // If the last mod call failed or the icon is old then re-cache it
+ // This check is in case the favicon of a page changes
+ // the next time we try to build the jump list, the data will be available.
+ if (NS_FAILED(rv) || (nowTime - fileModTime) > icoReCacheSecondsTimeout) {
+ CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
+ aURLShortcut, runnable.forget());
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ } else {
+ // The file does not exist yet, obtain it async from the favicon service so
+ // that the next time we try to build the jump list it'll be available.
+ CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread,
+ aURLShortcut, runnable.forget());
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The icoFile is filled with a path that exists, get its path
+ rv = icoFile->GetPath(aICOFilePath);
+ return rv;
+}
+
+// Hash a URI using a cryptographic hash function (currently SHA-256)
+// Output will be a base64-encoded string of the hash.
+static nsresult HashURI(nsIURI* aUri, nsACString& aUriHash) {
+ nsAutoCString spec;
+ nsresult rv = aUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICryptoHash> cryptoHash =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cryptoHash->Init(nsICryptoHash::SHA256);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add some context to the hash to even further reduce the chances of
+ // collision. Note that we are hashing this string with its null-terminator.
+ const char kHashUriContext[] = "firefox-uri";
+ rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(kHashUriContext),
+ sizeof(kHashUriContext));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(spec.BeginReading()),
+ spec.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cryptoHash->Finish(true, aUriHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// (static) Obtains the ICO file for the favicon at page aFaviconPageURI
+// If successful, the file path on disk is in the format:
+// <ProfLDS>\jumpListCache\<hash(aFaviconPageURI)>.ico
+//
+// We generate the name with a cryptographically secure hash function in order
+// to ensure that malicious websites can't intentionally craft URLs to collide
+// with legitimate websites.
+nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile,
+ bool aURLShortcut) {
+ nsAutoCString inputURIHash;
+ nsresult rv = HashURI(aFaviconPageURI, inputURIHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ char* cur = inputURIHash.BeginWriting();
+ char* end = inputURIHash.EndWriting();
+ for (; cur < end; ++cur) {
+ if ('/' == *cur) {
+ *cur = '_';
+ }
+ }
+
+ // Obtain the local profile directory and construct the output icon file path
+ rv = NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(aICOFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!aURLShortcut)
+ rv = aICOFile->AppendNative(nsDependentCString(kJumpListCacheDir));
+ else
+ rv = aICOFile->AppendNative(nsDependentCString(kShortcutCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append the icon extension
+ inputURIHash.AppendLiteral(".ico");
+ rv = aICOFile->AppendNative(inputURIHash);
+
+ return rv;
+}
+
+// (static) Asynchronously creates a cached ICO file on disk for the favicon of
+// page aFaviconPageURI and stores it to disk at the path of aICOFile.
+nsresult FaviconHelper::CacheIconFileFromFaviconURIAsync(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
+ RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable = aRunnable;
+#ifdef MOZ_PLACES
+ // Obtain the favicon service and get the favicon for the specified page
+ nsCOMPtr<nsIFaviconService> favIconSvc(
+ do_GetService("@mozilla.org/browser/favicon-service;1"));
+ NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFaviconDataCallback> callback =
+ new mozilla::widget::AsyncFaviconDataReady(
+ aFaviconPageURI, aIOThread, aURLShortcut, runnable.forget());
+
+ favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0);
+#endif
+ return NS_OK;
+}
+
+// Obtains the jump list 'ICO cache timeout in seconds' pref
+int32_t FaviconHelper::GetICOCacheSecondsTimeout() {
+ // Only obtain the setting at most once from the pref service.
+ // In the rare case that 2 threads call this at the same
+ // time it is no harm and we will simply obtain the pref twice.
+ // None of the taskbar list prefs are currently updated via a
+ // pref observer so I think this should suffice.
+ const int32_t kSecondsPerDay = 86400;
+ static bool alreadyObtained = false;
+ static int32_t icoReCacheSecondsTimeout = kSecondsPerDay;
+ if (alreadyObtained) {
+ return icoReCacheSecondsTimeout;
+ }
+
+ // Obtain the pref
+ const char PREF_ICOTIMEOUT[] = "browser.taskbar.lists.icoTimeoutInSeconds";
+ icoReCacheSecondsTimeout =
+ Preferences::GetInt(PREF_ICOTIMEOUT, kSecondsPerDay);
+ alreadyObtained = true;
+ return icoReCacheSecondsTimeout;
+}
+
+/* static */
+LayoutDeviceIntRegion WinUtils::ConvertHRGNToRegion(HRGN aRgn) {
+ NS_ASSERTION(aRgn, "Don't pass NULL region here");
+
+ LayoutDeviceIntRegion rgn;
+
+ DWORD size = ::GetRegionData(aRgn, 0, nullptr);
+ AutoTArray<uint8_t, 100> buffer;
+ buffer.SetLength(size);
+
+ RGNDATA* data = reinterpret_cast<RGNDATA*>(buffer.Elements());
+ if (!::GetRegionData(aRgn, size, data)) return rgn;
+
+ if (data->rdh.nCount > MAX_RECTS_IN_REGION) {
+ rgn = ToIntRect(data->rdh.rcBound);
+ return rgn;
+ }
+
+ RECT* rects = reinterpret_cast<RECT*>(data->Buffer);
+ for (uint32_t i = 0; i < data->rdh.nCount; ++i) {
+ RECT* r = rects + i;
+ rgn.Or(rgn, ToIntRect(*r));
+ }
+
+ return rgn;
+}
+
+LayoutDeviceIntRect WinUtils::ToIntRect(const RECT& aRect) {
+ return LayoutDeviceIntRect(aRect.left, aRect.top, aRect.right - aRect.left,
+ aRect.bottom - aRect.top);
+}
+
+/* static */
+bool WinUtils::IsIMEEnabled(const InputContext& aInputContext) {
+ return IsIMEEnabled(aInputContext.mIMEState.mEnabled);
+}
+
+/* static */
+bool WinUtils::IsIMEEnabled(IMEEnabled aIMEState) {
+ return aIMEState == IMEEnabled::Enabled;
+}
+
+/* static */
+void WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers, UINT aMessage) {
+ MOZ_ASSERT(!(aModifiers & nsIWidget::ALTGRAPH) ||
+ !(aModifiers & (nsIWidget::CTRL_L | nsIWidget::ALT_R)));
+ if (aMessage == WM_KEYUP) {
+ // If AltGr is released, ControlLeft key is released first, then,
+ // AltRight key is released.
+ if (aModifiers & nsIWidget::ALTGRAPH) {
+ aArray->AppendElement(
+ KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+ aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
+ }
+ for (uint32_t i = ArrayLength(sModifierKeyMap); i; --i) {
+ const uint32_t* map = sModifierKeyMap[i - 1];
+ if (aModifiers & map[0]) {
+ aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) {
+ const uint32_t* map = sModifierKeyMap[i];
+ if (aModifiers & map[0]) {
+ aArray->AppendElement(KeyPair(map[1], map[2], map[3]));
+ }
+ }
+ // If AltGr is pressed, ControlLeft key is pressed first, then,
+ // AltRight key is pressed.
+ if (aModifiers & nsIWidget::ALTGRAPH) {
+ aArray->AppendElement(
+ KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft));
+ aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight));
+ }
+ }
+}
+
+/* static */
+nsresult WinUtils::WriteBitmap(nsIFile* aFile, imgIContainer* aImage) {
+ RefPtr<SourceSurface> surface = aImage->GetFrame(
+ imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ return WriteBitmap(aFile, surface);
+}
+
+/* static */
+nsresult WinUtils::WriteBitmap(nsIFile* aFile, SourceSurface* surface) {
+ nsresult rv;
+
+ // For either of the following formats we want to set the biBitCount member
+ // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
+ // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
+ // for the BI_RGB value we use for the biCompression member.
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ int32_t bytesPerPixel = 4 * sizeof(uint8_t);
+ uint32_t bytesPerRow = bytesPerPixel * width;
+ bool hasAlpha = surface->GetFormat() == SurfaceFormat::B8G8R8A8;
+
+ // initialize these bitmap structs which we will later
+ // serialize directly to the head of the bitmap file
+ BITMAPV4HEADER bmi;
+ memset(&bmi, 0, sizeof(BITMAPV4HEADER));
+ bmi.bV4Size = sizeof(BITMAPV4HEADER);
+ bmi.bV4Width = width;
+ bmi.bV4Height = height;
+ bmi.bV4Planes = 1;
+ bmi.bV4BitCount = (WORD)bytesPerPixel * 8;
+ bmi.bV4V4Compression = hasAlpha ? BI_BITFIELDS : BI_RGB;
+ bmi.bV4SizeImage = bytesPerRow * height;
+ bmi.bV4CSType = LCS_sRGB;
+ if (hasAlpha) {
+ bmi.bV4RedMask = 0x00FF0000;
+ bmi.bV4GreenMask = 0x0000FF00;
+ bmi.bV4BlueMask = 0x000000FF;
+ bmi.bV4AlphaMask = 0xFF000000;
+ }
+
+ BITMAPFILEHEADER bf;
+ DWORD colormask[3];
+ bf.bfType = 0x4D42; // 'BM'
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV4HEADER) +
+ (hasAlpha ? sizeof(colormask) : 0);
+ bf.bfSize = bf.bfOffBits + bmi.bV4SizeImage;
+
+ // get a file output stream
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write the bitmap headers and rgb pixel data to the file
+ rv = NS_ERROR_FAILURE;
+ if (stream) {
+ uint32_t written;
+ stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
+ if (written == sizeof(BITMAPFILEHEADER)) {
+ stream->Write((const char*)&bmi, sizeof(BITMAPV4HEADER), &written);
+ if (written == sizeof(BITMAPV4HEADER)) {
+ if (hasAlpha) {
+ // color mask
+ colormask[0] = 0x00FF0000;
+ colormask[1] = 0x0000FF00;
+ colormask[2] = 0x000000FF;
+
+ stream->Write((const char*)colormask, sizeof(colormask), &written);
+ }
+ if (!hasAlpha || written == sizeof(colormask)) {
+ // write out the image data backwards because the desktop won't
+ // show bitmaps with negative heights for top-to-bottom
+ uint32_t i = map.mStride * height;
+ do {
+ i -= map.mStride;
+ stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
+ if (written == bytesPerRow) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+ }
+
+ stream->Close();
+ }
+
+ dataSurface->Unmap();
+
+ return rv;
+}
+
+// This is in use here and in dom/events/TouchEvent.cpp
+/* static */
+uint32_t WinUtils::IsTouchDeviceSupportPresent() {
+ int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER);
+ int32_t touchFlags = NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH;
+ if (StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled()) {
+ touchFlags |= NID_EXTERNAL_PEN | NID_INTEGRATED_PEN;
+ }
+ return (touchCapabilities & NID_READY) && (touchCapabilities & touchFlags);
+}
+
+/* static */
+uint32_t WinUtils::GetMaxTouchPoints() {
+ if (IsTouchDeviceSupportPresent()) {
+ return GetSystemMetrics(SM_MAXIMUMTOUCHES);
+ }
+ return 0;
+}
+
+/* static */
+POWER_PLATFORM_ROLE
+WinUtils::GetPowerPlatformRole() {
+ typedef POWER_PLATFORM_ROLE(WINAPI *
+ PowerDeterminePlatformRoleEx)(ULONG Version);
+ static PowerDeterminePlatformRoleEx power_determine_platform_role =
+ reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
+ ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
+
+ POWER_PLATFORM_ROLE powerPlatformRole = PlatformRoleUnspecified;
+ if (!power_determine_platform_role) {
+ return powerPlatformRole;
+ }
+
+ return power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
+}
+
+// static
+bool WinUtils::GetAutoRotationState(AR_STATE* aRotationState) {
+ typedef BOOL(WINAPI * GetAutoRotationStateFunc)(PAR_STATE pState);
+ static GetAutoRotationStateFunc get_auto_rotation_state_func =
+ reinterpret_cast<GetAutoRotationStateFunc>(::GetProcAddress(
+ GetModuleHandleW(L"user32.dll"), "GetAutoRotationState"));
+ if (get_auto_rotation_state_func) {
+ ZeroMemory(aRotationState, sizeof(AR_STATE));
+ return get_auto_rotation_state_func(aRotationState);
+ }
+ return false;
+}
+
+// static
+void WinUtils::GetClipboardFormatAsString(UINT aFormat, nsAString& aOutput) {
+ wchar_t buf[256] = {};
+ // Get registered format name and ensure the existence of a terminating '\0'
+ // if the registered name is more than 256 characters.
+ if (::GetClipboardFormatNameW(aFormat, buf, ARRAYSIZE(buf) - 1)) {
+ aOutput.Append(buf);
+ return;
+ }
+ // Standard clipboard formats
+ // https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
+ switch (aFormat) {
+ case CF_TEXT: // 1
+ aOutput.Append(u"CF_TEXT"_ns);
+ break;
+ case CF_BITMAP: // 2
+ aOutput.Append(u"CF_BITMAP"_ns);
+ break;
+ case CF_DIB: // 8
+ aOutput.Append(u"CF_DIB"_ns);
+ break;
+ case CF_UNICODETEXT: // 13
+ aOutput.Append(u"CF_UNICODETEXT"_ns);
+ break;
+ case CF_HDROP: // 15
+ aOutput.Append(u"CF_HDROP"_ns);
+ break;
+ case CF_DIBV5: // 17
+ aOutput.Append(u"CF_DIBV5"_ns);
+ break;
+ default:
+ aOutput.AppendPrintf("%u", aFormat);
+ break;
+ }
+}
+
+static bool IsTabletDevice() {
+ // Guarantees that:
+ // - The device has a touch screen.
+ // - It is used as a tablet which means that it has no keyboard connected.
+ // On Windows 10 it means that it is verifying with ConvertibleSlateMode.
+
+ if (WindowsUIUtils::GetInTabletMode()) {
+ return true;
+ }
+
+ if (!GetSystemMetrics(SM_MAXIMUMTOUCHES)) {
+ return false;
+ }
+
+ // If the device is docked, the user is treating the device as a PC.
+ if (GetSystemMetrics(SM_SYSTEMDOCKED)) {
+ return false;
+ }
+
+ // If the device is not supporting rotation, it's unlikely to be a tablet,
+ // a convertible or a detachable. See:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx
+ AR_STATE rotation_state;
+ if (WinUtils::GetAutoRotationState(&rotation_state) &&
+ (rotation_state & (AR_NOT_SUPPORTED | AR_LAPTOP | AR_NOSENSOR))) {
+ return false;
+ }
+
+ // PlatformRoleSlate was added in Windows 8+.
+ POWER_PLATFORM_ROLE role = WinUtils::GetPowerPlatformRole();
+ if (role == PlatformRoleMobile || role == PlatformRoleSlate) {
+ return !GetSystemMetrics(SM_CONVERTIBLESLATEMODE);
+ }
+ return false;
+}
+
+static bool SystemHasMouse() {
+ // As per MSDN, this value is rarely false because of virtual mice, and
+ // some machines report the existance of a mouse port as a mouse.
+ //
+ // We probably could try to distinguish if we wanted, but a virtual mouse
+ // might be there for a reason, and maybe we shouldn't assume we know
+ // better.
+ return !!::GetSystemMetrics(SM_MOUSEPRESENT);
+}
+
+/* static */
+PointerCapabilities WinUtils::GetPrimaryPointerCapabilities() {
+ if (IsTabletDevice()) {
+ return PointerCapabilities::Coarse;
+ }
+
+ if (SystemHasMouse()) {
+ return PointerCapabilities::Fine | PointerCapabilities::Hover;
+ }
+
+ if (IsTouchDeviceSupportPresent()) {
+ return PointerCapabilities::Coarse;
+ }
+
+ return PointerCapabilities::None;
+}
+
+static bool SystemHasTouchscreen() {
+ int digitizerMetrics = ::GetSystemMetrics(SM_DIGITIZER);
+ return (digitizerMetrics & NID_INTEGRATED_TOUCH) ||
+ (digitizerMetrics & NID_EXTERNAL_TOUCH);
+}
+
+static bool SystemHasPenDigitizer() {
+ int digitizerMetrics = ::GetSystemMetrics(SM_DIGITIZER);
+ return (digitizerMetrics & NID_INTEGRATED_PEN) ||
+ (digitizerMetrics & NID_EXTERNAL_PEN);
+}
+
+/* static */
+PointerCapabilities WinUtils::GetAllPointerCapabilities() {
+ PointerCapabilities pointerCapabilities = PointerCapabilities::None;
+
+ if (SystemHasTouchscreen()) {
+ pointerCapabilities |= PointerCapabilities::Coarse;
+ }
+
+ if (SystemHasPenDigitizer() || SystemHasMouse()) {
+ pointerCapabilities |=
+ PointerCapabilities::Fine | PointerCapabilities::Hover;
+ }
+
+ return pointerCapabilities;
+}
+
+void WinUtils::GetPointerExplanation(nsAString* aExplanation) {
+ // To support localization, we will return a comma-separated list of
+ // Fluent IDs
+ *aExplanation = u"pointing-device-none";
+
+ bool first = true;
+ auto append = [&](const char16_t* str) {
+ if (first) {
+ aExplanation->Truncate();
+ first = false;
+ } else {
+ aExplanation->Append(u",");
+ }
+ aExplanation->Append(str);
+ };
+
+ if (SystemHasTouchscreen()) {
+ append(u"pointing-device-touchscreen");
+ }
+
+ if (SystemHasPenDigitizer()) {
+ append(u"pointing-device-pen-digitizer");
+ }
+
+ if (SystemHasMouse()) {
+ append(u"pointing-device-mouse");
+ }
+}
+
+/* static */
+bool WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath) {
+ LOG_D("ResolveJunctionPointsAndSymLinks: Resolving path: %S", aPath.c_str());
+
+ wchar_t path[MAX_PATH] = {0};
+
+ nsAutoHandle handle(::CreateFileW(
+ aPath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ LOG_E("Failed to open file handle to resolve path. GetLastError=%lu",
+ GetLastError());
+ return false;
+ }
+
+ DWORD pathLen = GetFinalPathNameByHandleW(
+ handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+ if (pathLen == 0 || pathLen >= MAX_PATH) {
+ LOG_E("GetFinalPathNameByHandleW failed. GetLastError=%lu", GetLastError());
+ return false;
+ }
+ aPath = path;
+
+ // GetFinalPathNameByHandle sticks a '\\?\' in front of the path,
+ // but that confuses some APIs so strip it off. It will also put
+ // '\\?\UNC\' in front of network paths, we convert that to '\\'.
+ if (aPath.compare(0, 7, L"\\\\?\\UNC") == 0) {
+ aPath.erase(2, 6);
+ } else if (aPath.compare(0, 4, L"\\\\?\\") == 0) {
+ aPath.erase(0, 4);
+ }
+
+ LOG_D("ResolveJunctionPointsAndSymLinks: Resolved path to: %S",
+ aPath.c_str());
+ return true;
+}
+
+/* static */
+bool WinUtils::ResolveJunctionPointsAndSymLinks(nsIFile* aPath) {
+ MOZ_ASSERT(aPath);
+
+ nsAutoString filePath;
+ nsresult rv = aPath->GetPath(filePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ std::wstring resolvedPath(filePath.get());
+ if (!ResolveJunctionPointsAndSymLinks(resolvedPath)) {
+ return false;
+ }
+
+ rv = aPath->InitWithPath(nsDependentString(resolvedPath.c_str()));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+bool WinUtils::RunningFromANetworkDrive() {
+ wchar_t exePath[MAX_PATH];
+ if (!::GetModuleFileNameW(nullptr, exePath, MAX_PATH)) {
+ return false;
+ }
+
+ std::wstring exeString(exePath);
+ if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(exeString)) {
+ return false;
+ }
+
+ wchar_t volPath[MAX_PATH];
+ if (!::GetVolumePathNameW(exeString.c_str(), volPath, MAX_PATH)) {
+ return false;
+ }
+
+ return (::GetDriveTypeW(volPath) == DRIVE_REMOTE);
+}
+
+/* static */
+bool WinUtils::CanonicalizePath(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ if (!PathCanonicalizeW(tempPath,
+ (char16ptr_t)PromiseFlatString(aPath).get())) {
+ return false;
+ }
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ return true;
+}
+
+/* static */
+bool WinUtils::MakeLongPath(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ DWORD longResult =
+ GetLongPathNameW((char16ptr_t)PromiseFlatString(aPath).get(), tempPath,
+ ArrayLength(tempPath));
+ if (longResult > ArrayLength(tempPath)) {
+ // Our buffer is too short, and we're guaranteeing <= MAX_PATH results.
+ return false;
+ } else if (longResult) {
+ // Success.
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ }
+ // GetLongPathNameW returns 0 if the path is not found or is not rooted,
+ // but we shouldn't consider that a failure condition.
+ return true;
+}
+
+/* static */
+bool WinUtils::UnexpandEnvVars(nsAString& aPath) {
+ wchar_t tempPath[MAX_PATH + 1];
+ // PathUnExpandEnvStringsW returns false if it doesn't make any
+ // substitutions. Silently continue using the unaltered path.
+ if (PathUnExpandEnvStringsW((char16ptr_t)PromiseFlatString(aPath).get(),
+ tempPath, ArrayLength(tempPath))) {
+ aPath = tempPath;
+ MOZ_ASSERT(aPath.Length() <= MAX_PATH);
+ }
+ return true;
+}
+
+/* static */
+WinUtils::WhitelistVec WinUtils::BuildWhitelist() {
+ WhitelistVec result;
+
+ Unused << result.emplaceBack(
+ std::make_pair(nsString(u"%ProgramFiles%"_ns), nsDependentString()));
+
+ // When no substitution is required, set the void flag
+ result.back().second.SetIsVoid(true);
+
+ Unused << result.emplaceBack(
+ std::make_pair(nsString(u"%SystemRoot%"_ns), nsDependentString()));
+ result.back().second.SetIsVoid(true);
+
+ wchar_t tmpPath[MAX_PATH + 1] = {};
+ if (GetTempPath(MAX_PATH, tmpPath)) {
+ // GetTempPath's result always ends with a backslash, which we don't want
+ uint32_t tmpPathLen = wcslen(tmpPath);
+ if (tmpPathLen) {
+ tmpPath[tmpPathLen - 1] = 0;
+ }
+
+ nsAutoString cleanTmpPath(tmpPath);
+ if (UnexpandEnvVars(cleanTmpPath)) {
+ constexpr auto tempVar = u"%TEMP%"_ns;
+ Unused << result.emplaceBack(std::make_pair(
+ nsString(cleanTmpPath), nsDependentString(tempVar, 0)));
+ }
+ }
+
+ // If we add more items to the whitelist, ensure we still don't invoke an
+ // unnecessary heap allocation.
+ MOZ_ASSERT(result.length() <= kMaxWhitelistedItems);
+
+ return result;
+}
+
+/**
+ * This function provides an array of (system path, substitution) pairs that are
+ * considered to be acceptable with respect to privacy, for the purposes of
+ * submitting within telemetry or crash reports.
+ *
+ * The substitution string's void flag may be set. If it is, no subsitution is
+ * necessary. Otherwise, the consumer should replace the system path with the
+ * substitution.
+ *
+ * @see PreparePathForTelemetry for an example of its usage.
+ */
+/* static */
+const WinUtils::WhitelistVec& WinUtils::GetWhitelistedPaths() {
+ static WhitelistVec sWhitelist([]() -> WhitelistVec {
+ auto setClearFn = [ptr = &sWhitelist]() -> void {
+ RunOnShutdown([ptr]() -> void { ptr->clear(); },
+ ShutdownPhase::XPCOMShutdownFinal);
+ };
+
+ if (NS_IsMainThread()) {
+ setClearFn();
+ } else {
+ SchedulerGroup::Dispatch(NS_NewRunnableFunction(
+ "WinUtils::GetWhitelistedPaths", std::move(setClearFn)));
+ }
+
+ return BuildWhitelist();
+ }());
+ return sWhitelist;
+}
+
+/**
+ * This function is located here (as opposed to nsSystemInfo or elsewhere)
+ * because we need to gather this information as early as possible during
+ * startup.
+ */
+/* static */
+bool WinUtils::GetAppInitDLLs(nsAString& aOutput) {
+ aOutput.Truncate();
+ HKEY hkey = NULL;
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
+ 0, KEY_QUERY_VALUE, &hkey)) {
+ return false;
+ }
+ nsAutoRegKey key(hkey);
+ LONG status;
+ const wchar_t kLoadAppInitDLLs[] = L"LoadAppInit_DLLs";
+ DWORD loadAppInitDLLs = 0;
+ DWORD loadAppInitDLLsLen = sizeof(loadAppInitDLLs);
+ status = RegQueryValueExW(hkey, kLoadAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)&loadAppInitDLLs, &loadAppInitDLLsLen);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ if (!loadAppInitDLLs) {
+ // If loadAppInitDLLs is zero then AppInit_DLLs is disabled.
+ // In this case we'll return true along with an empty output string.
+ return true;
+ }
+ DWORD numBytes = 0;
+ const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
+ // Query for required buffer size
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, nullptr,
+ &numBytes);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ // Allocate the buffer and query for the actual data
+ mozilla::UniquePtr<wchar_t[]> data =
+ mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)data.get(), &numBytes);
+ if (status != ERROR_SUCCESS) {
+ return false;
+ }
+ // For each token, split up the filename components and then check the
+ // name of the file.
+ const wchar_t kDelimiters[] = L", ";
+ wchar_t* tokenContext = nullptr;
+ wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
+ while (token) {
+ nsAutoString cleanPath(token);
+ // Since these paths are short paths originating from the registry, we need
+ // to canonicalize them, lengthen them, and sanitize them before we can
+ // check them against the whitelist
+ if (PreparePathForTelemetry(cleanPath)) {
+ if (!aOutput.IsEmpty()) {
+ aOutput += L";";
+ }
+ aOutput += cleanPath;
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ return true;
+}
+
+/* static */
+bool WinUtils::PreparePathForTelemetry(nsAString& aPath,
+ PathTransformFlags aFlags) {
+ if (aFlags & PathTransformFlags::Canonicalize) {
+ if (!CanonicalizePath(aPath)) {
+ return false;
+ }
+ }
+ if (aFlags & PathTransformFlags::Lengthen) {
+ if (!MakeLongPath(aPath)) {
+ return false;
+ }
+ }
+ if (aFlags & PathTransformFlags::UnexpandEnvVars) {
+ if (!UnexpandEnvVars(aPath)) {
+ return false;
+ }
+ }
+
+ const WhitelistVec& whitelistedPaths = GetWhitelistedPaths();
+
+ for (uint32_t i = 0; i < whitelistedPaths.length(); ++i) {
+ const nsString& testPath = whitelistedPaths[i].first;
+ const nsDependentString& substitution = whitelistedPaths[i].second;
+ if (StringBeginsWith(aPath, testPath, nsCaseInsensitiveStringComparator)) {
+ if (!substitution.IsVoid()) {
+ aPath.Replace(0, testPath.Length(), substitution);
+ }
+ return true;
+ }
+ }
+
+ // For non-whitelisted paths, we strip the path component and just leave
+ // the filename. We can't use nsLocalFile to do this because these paths may
+ // begin with environment variables, and nsLocalFile doesn't like
+ // non-absolute paths.
+ const nsString& flatPath = PromiseFlatString(aPath);
+ LPCWSTR leafStart = ::PathFindFileNameW(flatPath.get());
+ ptrdiff_t cutLen = leafStart - flatPath.get();
+ if (cutLen) {
+ aPath.Cut(0, cutLen);
+ } else if (aFlags & PathTransformFlags::RequireFilePath) {
+ return false;
+ }
+
+ return true;
+}
+
+nsString WinUtils::GetPackageFamilyName() {
+ nsString rv;
+
+ UniquePtr<wchar_t[]> packageIdentity = mozilla::GetPackageFamilyName();
+ if (packageIdentity) {
+ rv = packageIdentity.get();
+ }
+
+ return rv;
+}
+
+bool WinUtils::GetClassName(HWND aHwnd, nsAString& aClassName) {
+ const int bufferLength = 256;
+ aClassName.SetLength(bufferLength);
+
+ int length = ::GetClassNameW(aHwnd, (char16ptr_t)aClassName.BeginWriting(),
+ bufferLength);
+ if (length == 0) {
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(length <= (bufferLength - 1));
+ aClassName.Truncate(length);
+ return true;
+}
+
+static BOOL CALLBACK EnumUpdateWindowOcclusionProc(HWND aHwnd, LPARAM aLParam) {
+ const bool* const enable = reinterpret_cast<bool*>(aLParam);
+ nsWindow* window = WinUtils::GetNSWindowPtr(aHwnd);
+ if (window) {
+ window->MaybeEnableWindowOcclusion(*enable);
+ }
+ return TRUE;
+}
+
+void WinUtils::EnableWindowOcclusion(const bool aEnable) {
+ if (aEnable) {
+ WinWindowOcclusionTracker::Ensure();
+ }
+ ::EnumWindows(EnumUpdateWindowOcclusionProc,
+ reinterpret_cast<LPARAM>(&aEnable));
+}
+
+bool WinUtils::GetTimezoneName(wchar_t* aBuffer) {
+ DYNAMIC_TIME_ZONE_INFORMATION tzInfo;
+ DWORD tzid = GetDynamicTimeZoneInformation(&tzInfo);
+
+ if (tzid == TIME_ZONE_ID_INVALID) {
+ return false;
+ }
+
+ wcscpy_s(aBuffer, 128, tzInfo.TimeZoneKeyName);
+
+ return true;
+}
+
+// There are undocumented APIs to query/change the system DPI settings found by
+// https://github.com/lihas/ . We use those APIs only for testing purpose, i.e.
+// in mochitests or some such. To avoid exposing them in our official release
+// builds unexpectedly we restrict them only in debug builds.
+#ifdef DEBUG
+
+# define DISPLAYCONFIG_DEVICE_INFO_SET_SOURCE_DPI_SCALE (int)-4
+# define DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_DPI_SCALE (int)-3
+
+// Following two struts are copied from
+// https://github.com/lihas/windows-DPI-scaling-sample/blob/master/DPIHelper/DpiHelper.h
+
+/*
+ * struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET
+ * @brief used to fetch min, max, suggested, and currently applied DPI scaling
+ * values. All values are relative to the recommended DPI scaling value Note
+ * that DPI scaling is a property of the source, and not of target.
+ */
+struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET {
+ DISPLAYCONFIG_DEVICE_INFO_HEADER header;
+ /*
+ * @brief min value of DPI scaling is always 100, minScaleRel gives no. of
+ * steps down from recommended scaling eg. if minScaleRel is -3 => 100 is 3
+ * steps down from recommended scaling => recommended scaling is 175%
+ */
+ int32_t minScaleRel;
+
+ /*
+ * @brief currently applied DPI scaling value wrt the recommended value. eg.
+ * if recommended value is 175%,
+ * => if curScaleRel == 0 the current scaling is 175%, if curScaleRel == -1,
+ * then current scale is 150%
+ */
+ int32_t curScaleRel;
+
+ /*
+ * @brief maximum supported DPI scaling wrt recommended value
+ */
+ int32_t maxScaleRel;
+};
+
+/*
+ * struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET
+ * @brief set DPI scaling value of a source
+ * Note that DPI scaling is a property of the source, and not of target.
+ */
+struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET {
+ DISPLAYCONFIG_DEVICE_INFO_HEADER header;
+ /*
+ * @brief The value we want to set. The value should be relative to the
+ * recommended DPI scaling value of source. eg. if scaleRel == 1, and
+ * recommended value is 175% => we are trying to set 200% scaling for the
+ * source
+ */
+ int32_t scaleRel;
+};
+
+static int32_t sCurRelativeScaleStep = std::numeric_limits<int32_t>::max();
+
+static LONG SetRelativeScaleStep(LUID aAdapterId, int32_t aRelativeScaleStep) {
+ DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setDPIScale = {};
+ setDPIScale.header.adapterId = aAdapterId;
+ setDPIScale.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)
+ DISPLAYCONFIG_DEVICE_INFO_SET_SOURCE_DPI_SCALE;
+ setDPIScale.header.size = sizeof(setDPIScale);
+ setDPIScale.scaleRel = aRelativeScaleStep;
+
+ return DisplayConfigSetDeviceInfo(&setDPIScale.header);
+}
+
+nsresult WinUtils::SetHiDPIMode(bool aHiDPI) {
+ auto config = GetDisplayConfig();
+ if (!config) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (config->mPaths.empty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ DISPLAYCONFIG_SOURCE_DPI_SCALE_GET dpiScale = {};
+ dpiScale.header.adapterId = config->mPaths[0].targetInfo.adapterId;
+ dpiScale.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)
+ DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_DPI_SCALE;
+ dpiScale.header.size = sizeof(dpiScale);
+ LONG result = ::DisplayConfigGetDeviceInfo(&dpiScale.header);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dpiScale.minScaleRel == dpiScale.maxScaleRel) {
+ // We can't change the setting at all.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aHiDPI && dpiScale.curScaleRel == dpiScale.maxScaleRel) {
+ // We've already at the maximum level.
+ if (sCurRelativeScaleStep == std::numeric_limits<int32_t>::max()) {
+ sCurRelativeScaleStep = dpiScale.curScaleRel;
+ }
+ return NS_OK;
+ }
+
+ if (!aHiDPI && dpiScale.curScaleRel == dpiScale.minScaleRel) {
+ // We've already at the minimum level.
+ if (sCurRelativeScaleStep == std::numeric_limits<int32_t>::max()) {
+ sCurRelativeScaleStep = dpiScale.curScaleRel;
+ }
+ return NS_OK;
+ }
+
+ result = SetRelativeScaleStep(
+ config->mPaths[0].targetInfo.adapterId,
+ aHiDPI ? dpiScale.maxScaleRel : dpiScale.minScaleRel);
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (sCurRelativeScaleStep == std::numeric_limits<int>::max()) {
+ sCurRelativeScaleStep = dpiScale.curScaleRel;
+ }
+
+ return NS_OK;
+}
+
+nsresult WinUtils::RestoreHiDPIMode() {
+ if (sCurRelativeScaleStep == std::numeric_limits<int>::max()) {
+ // The DPI setting hasn't been changed.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto config = GetDisplayConfig();
+ if (!config) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (config->mPaths.empty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LONG result = SetRelativeScaleStep(config->mPaths[0].targetInfo.adapterId,
+ sCurRelativeScaleStep);
+ sCurRelativeScaleStep = std::numeric_limits<int32_t>::max();
+ if (result != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+#endif
+
+/* static */
+const char* WinUtils::WinEventToEventName(UINT msg) {
+ const auto eventMsgInfo = mozilla::widget::gAllEvents.find(msg);
+ return eventMsgInfo != mozilla::widget::gAllEvents.end()
+ ? eventMsgInfo->second.mStr
+ : nullptr;
+}
+
+// Note to testers and/or test-authors: on Windows 10, and possibly on other
+// versions as well, supplying the `WS_EX_LAYOUTRTL` flag here has no effect
+// whatsoever on child common-dialogs **unless the system UI locale is also set
+// to an RTL language**.
+//
+// If it is, the flag is still required; otherwise, the picker dialog will be
+// presented in English (or possibly some other LTR language) as a fallback.
+ScopedRtlShimWindow::ScopedRtlShimWindow(nsIWidget* aParent) : mWnd(nullptr) {
+ NS_ENSURE_TRUE_VOID(aParent);
+
+ // Headless windows don't have HWNDs, but also probably shouldn't be launching
+ // print dialogs.
+ HWND const hwnd = (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW);
+ NS_ENSURE_TRUE_VOID(hwnd);
+
+ nsWindow* const win = WinUtils::GetNSWindowPtr(hwnd);
+ NS_ENSURE_TRUE_VOID(win);
+
+ ATOM const wclass = ::GetClassWord(hwnd, GCW_ATOM);
+ mWnd = ::CreateWindowExW(
+ win->IsRTL() ? WS_EX_LAYOUTRTL : 0, (LPCWSTR)(uintptr_t)wclass, L"",
+ WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ hwnd, nullptr, nsToolkit::mDllInstance, nullptr);
+
+ MOZ_ASSERT(mWnd);
+}
+
+ScopedRtlShimWindow::~ScopedRtlShimWindow() {
+ if (mWnd) {
+ ::DestroyWindow(mWnd);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h
new file mode 100644
index 0000000000..daef5ff4bc
--- /dev/null
+++ b/widget/windows/WinUtils.h
@@ -0,0 +1,684 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WinUtils_h__
+#define mozilla_widget_WinUtils_h__
+
+#include "nscore.h"
+#include <windows.h>
+#include <shobjidl.h>
+#include <uxtheme.h>
+#include <dwmapi.h>
+#include <unordered_map>
+#include <utility>
+
+// Undo the windows.h damage
+#undef GetMessage
+#undef CreateEvent
+#undef GetClassName
+#undef GetBinaryType
+#undef RemoveDirectory
+
+#include "nsString.h"
+#include "nsRegion.h"
+#include "nsRect.h"
+
+#include "nsIRunnable.h"
+#include "nsICryptoHash.h"
+#ifdef MOZ_PLACES
+# include "nsIFaviconService.h"
+#endif
+#include "nsIDownloader.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIThread.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/HalScreenConfiguration.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WindowsDpiAwareness.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/gfx/2D.h"
+
+/**
+ * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and
+ * implementing AddRef() and Release() of IUnknown interface.
+ * This depends on xpcom/base/nsISupportsImpl.h.
+ */
+
+#define NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(_class) \
+ public: \
+ STDMETHODIMP_(ULONG) AddRef() { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ ++mRefCnt; \
+ NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \
+ return static_cast<ULONG>(mRefCnt.get()); \
+ } \
+ STDMETHODIMP_(ULONG) Release() { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, \
+ "Release called on object that has already been released!"); \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ --mRefCnt; \
+ NS_LOG_RELEASE(this, mRefCnt, #_class); \
+ if (mRefCnt == 0) { \
+ NS_ASSERT_OWNINGTHREAD(_class); \
+ mRefCnt = 1; /* stabilize */ \
+ delete this; \
+ return 0; \
+ } \
+ return static_cast<ULONG>(mRefCnt.get()); \
+ } \
+ \
+ protected: \
+ nsAutoRefCnt mRefCnt; \
+ NS_DECL_OWNINGTHREAD \
+ public:
+
+class nsWindow;
+struct KeyPair;
+
+namespace mozilla {
+enum class PointerCapabilities : uint8_t;
+#if defined(ACCESSIBILITY)
+namespace a11y {
+class LocalAccessible;
+} // namespace a11y
+#endif // defined(ACCESSIBILITY)
+
+// Helper function: enumerate all the toplevel HWNDs attached to the current
+// thread via ::EnumThreadWindows().
+//
+// Note that this use of ::EnumThreadWindows() is, unfortunately, not an
+// abstract implementation detail.
+template <typename F>
+void EnumerateThreadWindows(F&& f)
+// requires requires(F f, HWND h) { f(h); }
+{
+ class Impl {
+ public:
+ F f;
+ explicit Impl(F&& f) : f(std::forward<F>(f)) {}
+
+ void invoke() {
+ WNDENUMPROC proc = &Impl::Callback;
+ ::EnumThreadWindows(::GetCurrentThreadId(), proc,
+ reinterpret_cast<LPARAM>(&f));
+ }
+
+ private:
+ static BOOL CALLBACK Callback(HWND hwnd, LPARAM lp) {
+ (*reinterpret_cast<F*>(lp))(hwnd);
+ return TRUE;
+ }
+ };
+
+ Impl(std::forward<F>(f)).invoke();
+}
+
+namespace widget {
+
+// More complete QS definitions for MsgWaitForMultipleObjects() and
+// GetQueueStatus() that include newer win8 specific defines.
+
+#ifndef QS_RAWINPUT
+# define QS_RAWINPUT 0x0400
+#endif
+
+#ifndef QS_TOUCH
+# define QS_TOUCH 0x0800
+# define QS_POINTER 0x1000
+#endif
+
+#define MOZ_QS_ALLEVENT \
+ (QS_KEY | QS_MOUSEMOVE | QS_MOUSEBUTTON | QS_POSTMESSAGE | QS_TIMER | \
+ QS_PAINT | QS_SENDMESSAGE | QS_HOTKEY | QS_ALLPOSTMESSAGE | QS_RAWINPUT | \
+ QS_TOUCH | QS_POINTER)
+
+// Logging macros
+#define LogFunction() mozilla::widget::WinUtils::Log(__FUNCTION__)
+#define LogThread() \
+ mozilla::widget::WinUtils::Log("%s: IsMainThread:%d ThreadId:%X", \
+ __FUNCTION__, NS_IsMainThread(), \
+ GetCurrentThreadId())
+#define LogThis() mozilla::widget::WinUtils::Log("[%X] %s", this, __FUNCTION__)
+#define LogException(e) \
+ mozilla::widget::WinUtils::Log("%s Exception:%s", __FUNCTION__, \
+ e->ToString()->Data())
+#define LogHRESULT(hr) \
+ mozilla::widget::WinUtils::Log("%s hr=%X", __FUNCTION__, hr)
+
+#ifdef MOZ_PLACES
+class myDownloadObserver final : public nsIDownloadObserver {
+ ~myDownloadObserver() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADOBSERVER
+};
+#endif
+
+class WinUtils {
+ // Function pointers for APIs that may not be available depending on
+ // the Win10 update version -- will be set up in Initialize().
+ static SetThreadDpiAwarenessContextProc sSetThreadDpiAwarenessContext;
+ static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling;
+ static GetSystemMetricsForDpiProc sGetSystemMetricsForDpi;
+
+ // Set on Initialize().
+ static bool sHasPackageIdentity;
+
+ public:
+ class AutoSystemDpiAware {
+ public:
+ AutoSystemDpiAware() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+
+ if (sSetThreadDpiAwarenessContext) {
+ mPrevContext =
+ sSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
+ }
+ }
+
+ ~AutoSystemDpiAware() {
+ if (sSetThreadDpiAwarenessContext) {
+ sSetThreadDpiAwarenessContext(mPrevContext);
+ }
+ }
+
+ private:
+ DPI_AWARENESS_CONTEXT mPrevContext;
+ };
+
+ // Wrapper for DefWindowProc that will enable non-client dpi scaling on the
+ // window during creation.
+ static LRESULT WINAPI NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam,
+ LPARAM lParam);
+
+ /**
+ * Get the system's default logical-to-physical DPI scaling factor,
+ * which is based on the primary display. Note however that unlike
+ * LogToPhysFactor(GetPrimaryMonitor()), this will not change during
+ * a session even if the displays are reconfigured. This scale factor
+ * is used by Windows theme metrics etc, which do not fully support
+ * dynamic resolution changes but are only updated on logout.
+ */
+ static double SystemScaleFactor();
+
+ static bool IsPerMonitorDPIAware();
+ /**
+ * Get the DPI of the given monitor if it's per-monitor DPI aware, otherwise
+ * return the system DPI.
+ */
+ static float MonitorDPI(HMONITOR aMonitor);
+ static float SystemDPI();
+ /**
+ * Functions to convert between logical pixels as used by most Windows APIs
+ * and physical (device) pixels.
+ */
+ static double LogToPhysFactor(HMONITOR aMonitor);
+ static double LogToPhysFactor(HWND aWnd);
+ static double LogToPhysFactor(HDC aDC) {
+ return LogToPhysFactor(::WindowFromDC(aDC));
+ }
+ static int32_t LogToPhys(HMONITOR aMonitor, double aValue);
+ static HMONITOR GetPrimaryMonitor();
+ static HMONITOR MonitorFromRect(const gfx::Rect& rect);
+
+ static bool HasSystemMetricsForDpi();
+ static int GetSystemMetricsForDpi(int nIndex, UINT dpi);
+
+ /**
+ * @param msg Windows event message
+ * @return User-friendly event name, or nullptr if no
+ * match is found.
+ */
+ static const char* WinEventToEventName(UINT msg);
+
+ /**
+ * @param aHdc HDC for printer
+ * @return unwritable margins for currently set page on aHdc or empty margins
+ * if aHdc is null
+ */
+ static gfx::MarginDouble GetUnwriteableMarginsForDeviceInInches(HDC aHdc);
+
+ static bool HasPackageIdentity() { return sHasPackageIdentity; }
+
+ /*
+ * The "family name" of a Windows app package is the full name without any of
+ * the components that might change during the life cycle of the app (such as
+ * the version number, or the architecture). This leaves only those properties
+ * which together serve to uniquely identify the app within one Windows
+ * installation, namely the base name and the publisher name. Meaning, this
+ * string is safe to use anywhere that a string uniquely identifying an app
+ * installation is called for (because multiple copies of the same app on the
+ * same system is not a supported feature in the app framework).
+ */
+ static nsString GetPackageFamilyName();
+
+ /**
+ * Logging helpers that dump output to prlog module 'Widget', console, and
+ * OutputDebugString. Note these output in both debug and release builds.
+ */
+ static void Log(const char* fmt, ...);
+ static void LogW(const wchar_t* fmt, ...);
+
+ /**
+ * PeekMessage() and GetMessage() are wrapper methods for PeekMessageW(),
+ * GetMessageW(), ITfMessageMgr::PeekMessageW() and
+ * ITfMessageMgr::GetMessageW().
+ * Don't call the native APIs directly. You MUST use these methods instead.
+ */
+ static bool PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption);
+ static bool GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage);
+
+ /**
+ * Wait until a message is ready to be processed.
+ * Prefer using this method to directly calling ::WaitMessage since
+ * ::WaitMessage will wait if there is an unread message in the queue.
+ * That can cause freezes until another message enters the queue if the
+ * message is marked read by a call to PeekMessage which the caller is
+ * not aware of (e.g., from a different thread).
+ * Note that this method may cause sync dispatch of sent (as opposed to
+ * posted) messages.
+ * @param aTimeoutMs Timeout for waiting in ms, defaults to INFINITE
+ */
+ static void WaitForMessage(DWORD aTimeoutMs = INFINITE);
+
+ /**
+ * GetTopLevelHWND() returns a window handle of the top level window which
+ * aWnd belongs to. Note that the result may not be our window, i.e., it
+ * may not be managed by nsWindow.
+ *
+ * See follwing table for the detail of the result window type.
+ *
+ * +-------------------------+-----------------------------------------------+
+ * | | aStopIfNotPopup |
+ * +-------------------------+-----------------------+-----------------------+
+ * | | TRUE | FALSE |
+ + +-----------------+-------+-----------------------+-----------------------+
+ * | | | * an independent top level window |
+ * | | TRUE | * a pupup window (WS_POPUP) |
+ * | | | * an owned top level window (like dialog) |
+ * | aStopIfNotChild +-------+-----------------------+-----------------------+
+ * | | | * independent window | * only an independent |
+ * | | FALSE | * non-popup-owned- | top level window |
+ * | | | window like dialog | |
+ * +-----------------+-------+-----------------------+-----------------------+
+ */
+ static HWND GetTopLevelHWND(HWND aWnd, bool aStopIfNotChild = false,
+ bool aStopIfNotPopup = true);
+
+ /**
+ * SetNSWindowPtr() associates aWindow with aWnd. If aWidget is nullptr, it
+ * instead dissociates any nsWindow from aWnd.
+ *
+ * No AddRef is performed. May not be used off of the main thread.
+ */
+ static void SetNSWindowPtr(HWND aWnd, nsWindow* aWindow);
+ /**
+ * GetNSWindowPtr() returns a pointer to the associated nsWindow pointer, if
+ * one exists, or nullptr, if not.
+ *
+ * No AddRef is performed. May not be used off of the main thread.
+ */
+ static nsWindow* GetNSWindowPtr(HWND aWnd);
+
+ /**
+ * IsOurProcessWindow() returns TRUE if aWnd belongs our process.
+ * Otherwise, FALSE.
+ */
+ static bool IsOurProcessWindow(HWND aWnd);
+
+ /**
+ * FindOurProcessWindow() returns the nearest ancestor window which
+ * belongs to our process. If it fails to find our process's window by the
+ * top level window, returns nullptr. And note that this is using
+ * ::GetParent() for climbing the window hierarchy, therefore, it gives
+ * up at an owned top level window except popup window (e.g., dialog).
+ */
+ static HWND FindOurProcessWindow(HWND aWnd);
+
+ /**
+ * FindOurWindowAtPoint() returns the topmost child window which belongs to
+ * our process's top level window.
+ *
+ * NOTE: the topmost child window may NOT be our process's window like a
+ * plugin's window.
+ */
+ static HWND FindOurWindowAtPoint(const POINT& aPointInScreen);
+
+ /**
+ * InitMSG() returns an MSG struct which was initialized by the params.
+ * Don't trust the other members in the result.
+ */
+ static MSG InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd);
+
+ /**
+ * GetScanCode() returns a scan code for the LPARAM of WM_KEYDOWN, WM_KEYUP,
+ * WM_CHAR and WM_UNICHAR.
+ *
+ */
+ static WORD GetScanCode(LPARAM aLParam) { return (aLParam >> 16) & 0xFF; }
+
+ /**
+ * IsExtendedScanCode() returns TRUE if the LPARAM indicates the key message
+ * is an extended key event.
+ */
+ static bool IsExtendedScanCode(LPARAM aLParam) {
+ return (aLParam & 0x1000000) != 0;
+ }
+
+ /**
+ * GetInternalMessage() converts a native message to an internal message.
+ * If there is no internal message for the given native message, returns
+ * the native message itself.
+ */
+ static UINT GetInternalMessage(UINT aNativeMessage);
+
+ /**
+ * GetNativeMessage() converts an internal message to a native message.
+ * If aInternalMessage is a native message, returns the native message itself.
+ */
+ static UINT GetNativeMessage(UINT aInternalMessage);
+
+ /**
+ * GetMouseInputSource() returns a pointing device information. The value is
+ * one of MouseEvent_Binding::MOZ_SOURCE_*. This method MUST be called during
+ * mouse message handling.
+ */
+ static uint16_t GetMouseInputSource();
+
+ /**
+ * Windows also fires mouse window messages for pens and touches, so we should
+ * retrieve their pointer ID on receiving mouse events as well. Please refer
+ * to
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
+ */
+ static uint16_t GetMousePointerID();
+
+ static bool GetIsMouseFromTouch(EventMessage aEventType);
+
+ /**
+ * ConvertHRGNToRegion converts a Windows HRGN to an LayoutDeviceIntRegion.
+ *
+ * aRgn the HRGN to convert.
+ * returns the LayoutDeviceIntRegion.
+ */
+ static LayoutDeviceIntRegion ConvertHRGNToRegion(HRGN aRgn);
+
+ /**
+ * ToIntRect converts a Windows RECT to a LayoutDeviceIntRect.
+ *
+ * aRect the RECT to convert.
+ * returns the LayoutDeviceIntRect.
+ */
+ static LayoutDeviceIntRect ToIntRect(const RECT& aRect);
+
+ /**
+ * Returns true if the context or IME state is enabled. Otherwise, false.
+ */
+ static bool IsIMEEnabled(const InputContext& aInputContext);
+ static bool IsIMEEnabled(IMEEnabled aIMEState);
+
+ /**
+ * Returns modifier key array for aModifiers. This is for
+ * nsIWidget::SynthethizeNative*Event().
+ */
+ static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers, UINT aMessage);
+
+ /**
+ * Does device have touch support
+ */
+ static uint32_t IsTouchDeviceSupportPresent();
+
+ /**
+ * The maximum number of simultaneous touch contacts supported by the device.
+ * In the case of devices with multiple digitizers (e.g. multiple touch
+ * screens), the value will be the maximum of the set of maximum supported
+ * contacts by each individual digitizer.
+ */
+ static uint32_t GetMaxTouchPoints();
+
+ /**
+ * Returns the windows power platform role, which is useful for detecting
+ * tablets.
+ */
+ static POWER_PLATFORM_ROLE GetPowerPlatformRole();
+
+ // For pointer and hover media queries features.
+ static PointerCapabilities GetPrimaryPointerCapabilities();
+ // For any-pointer and any-hover media queries features.
+ static PointerCapabilities GetAllPointerCapabilities();
+ // Returns a string containing a comma-separated list of Fluent IDs
+ // representing the currently active pointing devices
+ static void GetPointerExplanation(nsAString* aExplanation);
+
+ /**
+ * Fully resolves a path to its final path name. So if path contains
+ * junction points or symlinks to other folders, we'll resolve the path
+ * fully to the actual path that the links target.
+ *
+ * @param aPath path to be resolved.
+ * @return true if successful, including if nothing needs to be changed.
+ * false if something failed or aPath does not exist, aPath will
+ * remain unchanged.
+ */
+ static bool ResolveJunctionPointsAndSymLinks(std::wstring& aPath);
+ static bool ResolveJunctionPointsAndSymLinks(nsIFile* aPath);
+
+ /**
+ * Returns true if executable's path is on a network drive.
+ */
+ static bool RunningFromANetworkDrive();
+
+ static void Initialize();
+
+ static nsresult WriteBitmap(nsIFile* aFile,
+ mozilla::gfx::SourceSurface* surface);
+ // This function is a helper, but it cannot be called from the main thread.
+ // Use the one above!
+ static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage);
+
+ /**
+ * Wrapper for PathCanonicalize().
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool CanonicalizePath(nsAString& aPath);
+
+ /**
+ * Converts short paths (e.g. "C:\\PROGRA~1\\XYZ") to full paths.
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool MakeLongPath(nsAString& aPath);
+
+ /**
+ * Wrapper for PathUnExpandEnvStringsW().
+ * Upon success, the resulting output string length is <= MAX_PATH.
+ * @param aPath [in,out] The path to transform.
+ * @return true on success, false on failure.
+ */
+ static bool UnexpandEnvVars(nsAString& aPath);
+
+ /**
+ * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs
+ */
+ static bool GetAppInitDLLs(nsAString& aOutput);
+
+ enum class PathTransformFlags : uint32_t {
+ Canonicalize = 1,
+ Lengthen = 2,
+ UnexpandEnvVars = 4,
+ RequireFilePath = 8,
+
+ Default = 7, // Default omits RequireFilePath
+ };
+
+ /**
+ * Given a path, transforms it in preparation to be reported via telemetry.
+ * That can include canonicalization, converting short to long paths,
+ * unexpanding environment strings, and removing potentially sensitive data
+ * from the path.
+ *
+ * @param aPath [in,out] The path to transform.
+ * @param aFlags [in] Specifies which transformations to perform, allowing
+ * the caller to skip operations they know have already been
+ * performed.
+ * @return true on success, false on failure.
+ */
+ static bool PreparePathForTelemetry(
+ nsAString& aPath,
+ PathTransformFlags aFlags = PathTransformFlags::Default);
+
+ static const size_t kMaxWhitelistedItems = 3;
+ using WhitelistVec =
+ Vector<std::pair<nsString, nsDependentString>, kMaxWhitelistedItems>;
+
+ static const WhitelistVec& GetWhitelistedPaths();
+
+ static bool GetClassName(HWND aHwnd, nsAString& aName);
+
+ static void EnableWindowOcclusion(const bool aEnable);
+
+ static bool GetTimezoneName(wchar_t* aBuffer);
+
+#ifdef DEBUG
+ static nsresult SetHiDPIMode(bool aHiDPI);
+ static nsresult RestoreHiDPIMode();
+#endif
+
+ static bool GetAutoRotationState(AR_STATE* aRotationState);
+
+ static void GetClipboardFormatAsString(UINT aFormat, nsAString& aOutput);
+
+ private:
+ static WhitelistVec BuildWhitelist();
+
+ public:
+#ifdef ACCESSIBILITY
+ static a11y::LocalAccessible* GetRootAccessibleForHWND(HWND aHwnd);
+#endif
+};
+
+#ifdef MOZ_PLACES
+class AsyncFaviconDataReady final : public nsIFaviconDataCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONDATACALLBACK
+
+ AsyncFaviconDataReady(nsIURI* aNewURI, RefPtr<LazyIdleThread>& aIOThread,
+ const bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable);
+ nsresult OnFaviconDataNotAvailable(void);
+
+ private:
+ ~AsyncFaviconDataReady() {}
+
+ nsCOMPtr<nsIURI> mNewURI;
+ RefPtr<LazyIdleThread> mIOThread;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ const bool mURLShortcut;
+};
+#endif
+
+/**
+ * Asynchronously tries add the list to the build
+ */
+class AsyncEncodeAndWriteIcon : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ // Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer
+ // passed in
+ AsyncEncodeAndWriteIcon(const nsAString& aIconPath,
+ UniquePtr<uint8_t[]> aData, uint32_t aStride,
+ uint32_t aWidth, uint32_t aHeight,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ private:
+ virtual ~AsyncEncodeAndWriteIcon();
+
+ nsAutoString mIconPath;
+ UniquePtr<uint8_t[]> mBuffer;
+ nsCOMPtr<nsIRunnable> mRunnable;
+ uint32_t mStride;
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ explicit AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false);
+
+ private:
+ virtual ~AsyncDeleteAllFaviconsFromDisk();
+
+ int32_t mIcoNoDeleteSeconds;
+ bool mIgnoreRecent;
+ nsCOMPtr<nsIFile> mJumpListCacheDir;
+};
+
+class FaviconHelper {
+ public:
+ static const char kJumpListCacheDir[];
+ static const char kShortcutCacheDir[];
+ static nsresult ObtainCachedIconFile(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath,
+ RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable = nullptr);
+
+ static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile>& aICOFile,
+ bool aURLShortcut);
+
+ static nsresult CacheIconFileFromFaviconURIAsync(
+ nsCOMPtr<nsIURI> aFaviconPageURI, nsCOMPtr<nsIFile> aICOFile,
+ RefPtr<LazyIdleThread>& aIOThread, bool aURLShortcut,
+ already_AddRefed<nsIRunnable> aRunnable);
+
+ static int32_t GetICOCacheSecondsTimeout();
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(WinUtils::PathTransformFlags);
+
+// RTL shim windows are temporary child windows of our nsWindows created to
+// address RTL issues in picker dialogs. (See bug 588735.)
+class ScopedRtlShimWindow {
+ public:
+ explicit ScopedRtlShimWindow(nsIWidget* aParent);
+ ~ScopedRtlShimWindow();
+
+ ScopedRtlShimWindow(const ScopedRtlShimWindow&) = delete;
+ ScopedRtlShimWindow(ScopedRtlShimWindow&& that) noexcept : mWnd(that.mWnd) {
+ that.mWnd = nullptr;
+ };
+
+ HWND get() const { return mWnd; }
+
+ private:
+ HWND mWnd;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinUtils_h__
diff --git a/widget/windows/WinWindowOcclusionTracker.cpp b/widget/windows/WinWindowOcclusionTracker.cpp
new file mode 100644
index 0000000000..a38f950585
--- /dev/null
+++ b/widget/windows/WinWindowOcclusionTracker.cpp
@@ -0,0 +1,1471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include <queue>
+#include <windows.h>
+#include <winuser.h>
+#include <wtsapi32.h>
+
+#include "WinWindowOcclusionTracker.h"
+
+#include "base/thread.h"
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+#include "gfxConfig.h"
+#include "nsThreadUtils.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPtr.h"
+#include "nsBaseWidget.h"
+#include "nsWindow.h"
+#include "transport/runnable_utils.h"
+#include "WinUtils.h"
+
+namespace mozilla::widget {
+
+// Can be called on Main thread
+LazyLogModule gWinOcclusionTrackerLog("WinOcclusionTracker");
+#define LOG(type, ...) MOZ_LOG(gWinOcclusionTrackerLog, type, (__VA_ARGS__))
+
+// Can be called on OcclusionCalculator thread
+LazyLogModule gWinOcclusionCalculatorLog("WinOcclusionCalculator");
+#define CALC_LOG(type, ...) \
+ MOZ_LOG(gWinOcclusionCalculatorLog, type, (__VA_ARGS__))
+
+// ~16 ms = time between frames when frame rate is 60 FPS.
+const int kOcclusionUpdateRunnableDelayMs = 16;
+
+class OcclusionUpdateRunnable : public CancelableRunnable {
+ public:
+ explicit OcclusionUpdateRunnable(
+ WinWindowOcclusionTracker::WindowOcclusionCalculator*
+ aOcclusionCalculator)
+ : CancelableRunnable("OcclusionUpdateRunnable"),
+ mOcclusionCalculator(aOcclusionCalculator) {
+ mTimeStamp = TimeStamp::Now();
+ }
+
+ NS_IMETHOD Run() override {
+ if (mIsCanceled) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
+
+ uint32_t latencyMs =
+ round((TimeStamp::Now() - mTimeStamp).ToMilliseconds());
+ CALC_LOG(LogLevel::Debug,
+ "ComputeNativeWindowOcclusionStatus() latencyMs %u", latencyMs);
+
+ mOcclusionCalculator->ComputeNativeWindowOcclusionStatus();
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ mIsCanceled = true;
+ mOcclusionCalculator = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ bool mIsCanceled = false;
+ RefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
+ mOcclusionCalculator;
+ TimeStamp mTimeStamp;
+};
+
+// Used to serialize tasks related to mRootWindowHwndsOcclusionState.
+class SerializedTaskDispatcher {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher)
+
+ public:
+ SerializedTaskDispatcher();
+
+ void Destroy();
+ void PostTaskToMain(already_AddRefed<nsIRunnable> aTask);
+ void PostTaskToCalculator(already_AddRefed<nsIRunnable> aTask);
+ void PostDelayedTaskToCalculator(already_AddRefed<Runnable> aTask,
+ int aDelayMs);
+ bool IsOnCurrentThread();
+
+ private:
+ friend class DelayedTaskRunnable;
+
+ ~SerializedTaskDispatcher();
+
+ struct Data {
+ std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
+ mTasks;
+ bool mDestroyed = false;
+ RefPtr<Runnable> mCurrentRunnable;
+ };
+
+ void PostTasksIfNecessary(nsISerialEventTarget* aEventTarget,
+ const DataMutex<Data>::AutoLock& aProofOfLock);
+ void HandleDelayedTask(already_AddRefed<nsIRunnable> aTask);
+ void HandleTasks();
+
+ // Hold current EventTarget during calling nsIRunnable::Run().
+ RefPtr<nsISerialEventTarget> mCurrentEventTarget = nullptr;
+
+ DataMutex<Data> mData;
+};
+
+class DelayedTaskRunnable : public Runnable {
+ public:
+ DelayedTaskRunnable(SerializedTaskDispatcher* aSerializedTaskDispatcher,
+ already_AddRefed<Runnable> aTask)
+ : Runnable("DelayedTaskRunnable"),
+ mSerializedTaskDispatcher(aSerializedTaskDispatcher),
+ mTask(aTask) {}
+
+ NS_IMETHOD Run() override {
+ mSerializedTaskDispatcher->HandleDelayedTask(mTask.forget());
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
+ RefPtr<Runnable> mTask;
+};
+
+SerializedTaskDispatcher::SerializedTaskDispatcher()
+ : mData("SerializedTaskDispatcher::mData") {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "SerializedTaskDispatcher::SerializedTaskDispatcher() this %p", this);
+}
+
+SerializedTaskDispatcher::~SerializedTaskDispatcher() {
+#ifdef DEBUG
+ auto data = mData.Lock();
+ MOZ_ASSERT(data->mDestroyed);
+ MOZ_ASSERT(data->mTasks.empty());
+#endif
+}
+
+void SerializedTaskDispatcher::Destroy() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "SerializedTaskDispatcher::Destroy() this %p", this);
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ data->mDestroyed = true;
+ std::queue<std::pair<RefPtr<nsIRunnable>, RefPtr<nsISerialEventTarget>>>
+ empty;
+ std::swap(data->mTasks, empty);
+}
+
+void SerializedTaskDispatcher::PostTaskToMain(
+ already_AddRefed<nsIRunnable> aTask) {
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget = GetMainThreadSerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::PostTaskToCalculator(
+ already_AddRefed<nsIRunnable> aTask) {
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::PostDelayedTaskToCalculator(
+ already_AddRefed<Runnable> aTask, int aDelayMs) {
+ CALC_LOG(LogLevel::Debug,
+ "SerializedTaskDispatcher::PostDelayedTaskToCalculator()");
+
+ RefPtr<DelayedTaskRunnable> runnable =
+ new DelayedTaskRunnable(this, std::move(aTask));
+ MessageLoop* targetLoop =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop();
+ targetLoop->PostDelayedTask(runnable.forget(), aDelayMs);
+}
+
+bool SerializedTaskDispatcher::IsOnCurrentThread() {
+ return !!mCurrentEventTarget;
+}
+
+void SerializedTaskDispatcher::PostTasksIfNecessary(
+ nsISerialEventTarget* aEventTarget,
+ const DataMutex<Data>::AutoLock& aProofOfLock) {
+ MOZ_ASSERT(!aProofOfLock->mTasks.empty());
+
+ if (aProofOfLock->mCurrentRunnable) {
+ return;
+ }
+
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<SerializedTaskDispatcher>(this),
+ &SerializedTaskDispatcher::HandleTasks);
+ aProofOfLock->mCurrentRunnable = runnable;
+ aEventTarget->Dispatch(runnable.forget());
+}
+
+void SerializedTaskDispatcher::HandleDelayedTask(
+ already_AddRefed<nsIRunnable> aTask) {
+ MOZ_ASSERT(WinWindowOcclusionTracker::IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleDelayedTask()");
+
+ RefPtr<nsIRunnable> task = aTask;
+
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ nsISerialEventTarget* eventTarget =
+ WinWindowOcclusionTracker::OcclusionCalculatorLoop()->SerialEventTarget();
+ data->mTasks.push({std::move(task), eventTarget});
+
+ MOZ_ASSERT_IF(!data->mCurrentRunnable, data->mTasks.size() == 1);
+ PostTasksIfNecessary(eventTarget, data);
+}
+
+void SerializedTaskDispatcher::HandleTasks() {
+ RefPtr<nsIRunnable> frontTask;
+
+ // Get front task
+ {
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(data->mCurrentRunnable);
+ MOZ_RELEASE_ASSERT(!data->mTasks.empty());
+
+ frontTask = data->mTasks.front().first;
+
+ MOZ_RELEASE_ASSERT(!mCurrentEventTarget);
+ mCurrentEventTarget = data->mTasks.front().second;
+ }
+
+ while (frontTask) {
+ if (NS_IsMainThread()) {
+ LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
+ } else {
+ CALC_LOG(LogLevel::Debug, "SerializedTaskDispatcher::HandleTasks()");
+ }
+
+ MOZ_ASSERT_IF(NS_IsMainThread(),
+ mCurrentEventTarget == GetMainThreadSerialEventTarget());
+ MOZ_ASSERT_IF(
+ !NS_IsMainThread(),
+ mCurrentEventTarget == MessageLoop::current()->SerialEventTarget());
+
+ frontTask->Run();
+
+ // Get next task
+ {
+ auto data = mData.Lock();
+ if (data->mDestroyed) {
+ return;
+ }
+
+ frontTask = nullptr;
+ data->mTasks.pop();
+ // Check if next task could be handled on current thread
+ if (!data->mTasks.empty() &&
+ data->mTasks.front().second == mCurrentEventTarget) {
+ frontTask = data->mTasks.front().first;
+ }
+ }
+ }
+
+ MOZ_ASSERT(!frontTask);
+
+ // Post tasks to different thread if pending tasks exist.
+ {
+ auto data = mData.Lock();
+ data->mCurrentRunnable = nullptr;
+ mCurrentEventTarget = nullptr;
+
+ if (data->mDestroyed || data->mTasks.empty()) {
+ return;
+ }
+
+ PostTasksIfNecessary(data->mTasks.front().second, data);
+ }
+}
+
+// static
+StaticRefPtr<WinWindowOcclusionTracker> WinWindowOcclusionTracker::sTracker;
+
+/* static */
+WinWindowOcclusionTracker* WinWindowOcclusionTracker::Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sTracker || sTracker->mHasAttemptedShutdown) {
+ return nullptr;
+ }
+ return sTracker;
+}
+
+/* static */
+void WinWindowOcclusionTracker::Ensure() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::Ensure()");
+
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_UI;
+
+ if (sTracker) {
+ // Try to reuse the thread, which involves stopping and restarting it.
+ sTracker->mThread->Stop();
+ if (sTracker->mThread->StartWithOptions(options)) {
+ // Success!
+ sTracker->mHasAttemptedShutdown = false;
+
+ // Take this opportunity to ensure that mDisplayStatusObserver and
+ // mSessionChangeObserver exist. They might have failed to be
+ // created when sTracker was created.
+ sTracker->EnsureDisplayStatusObserver();
+ sTracker->EnsureSessionChangeObserver();
+ return;
+ }
+ // Restart failed, so null out our sTracker and try again with a new
+ // thread. This will cause the old singleton instance to be deallocated,
+ // which will destroy its mThread as well.
+ sTracker = nullptr;
+ }
+
+ UniquePtr<base::Thread> thread =
+ MakeUnique<base::Thread>("WinWindowOcclusionCalc");
+
+ if (!thread->StartWithOptions(options)) {
+ return;
+ }
+
+ sTracker = new WinWindowOcclusionTracker(std::move(thread));
+ WindowOcclusionCalculator::CreateInstance();
+
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::Initialize);
+ sTracker->mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+/* static */
+void WinWindowOcclusionTracker::ShutDown() {
+ if (!sTracker) {
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::ShutDown()");
+
+ sTracker->mHasAttemptedShutdown = true;
+ sTracker->Destroy();
+
+ // Our thread could hang while we're waiting for it to stop.
+ // Since we're shutting down, that's not a critical problem.
+ // We set a reasonable amount of time to wait for shutdown,
+ // and if it succeeds within that time, we correctly stop
+ // our thread by nulling out the refptr, which will cause it
+ // to be deallocated and join the thread. If it times out,
+ // we do nothing, which means that the thread will not be
+ // joined and sTracker memory will leak.
+ CVStatus status;
+ {
+ // It's important to hold the lock before posting the
+ // runnable. This ensures that the runnable can't begin
+ // until we've started our Wait, which prevents us from
+ // Waiting on a monitor that has already been notified.
+ MonitorAutoLock lock(sTracker->mMonitor);
+
+ static const TimeDuration TIMEOUT = TimeDuration::FromSeconds(2.0);
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::Shutdown);
+ OcclusionCalculatorLoop()->PostTask(runnable.forget());
+
+ // Monitor uses SleepConditionVariableSRW, which can have
+ // spurious wakeups which are reported as timeouts, so we
+ // check timestamps to ensure that we've waited as long we
+ // intended to. If we wake early, we don't bother calculating
+ // a precise amount for the next wait; we just wait the same
+ // amount of time. This means timeout might happen after as
+ // much as 2x the TIMEOUT time.
+ TimeStamp timeStart = TimeStamp::NowLoRes();
+ do {
+ status = sTracker->mMonitor.Wait(TIMEOUT);
+ } while ((status == CVStatus::Timeout) &&
+ ((TimeStamp::NowLoRes() - timeStart) < TIMEOUT));
+ }
+
+ if (status == CVStatus::NoTimeout) {
+ WindowOcclusionCalculator::ClearInstance();
+ sTracker = nullptr;
+ }
+}
+
+void WinWindowOcclusionTracker::Destroy() {
+ if (mDisplayStatusObserver) {
+ mDisplayStatusObserver->Destroy();
+ mDisplayStatusObserver = nullptr;
+ }
+ if (mSessionChangeObserver) {
+ mSessionChangeObserver->Destroy();
+ mSessionChangeObserver = nullptr;
+ }
+ if (mSerializedTaskDispatcher) {
+ mSerializedTaskDispatcher->Destroy();
+ }
+}
+
+/* static */
+MessageLoop* WinWindowOcclusionTracker::OcclusionCalculatorLoop() {
+ return sTracker ? sTracker->mThread->message_loop() : nullptr;
+}
+
+/* static */
+bool WinWindowOcclusionTracker::IsInWinWindowOcclusionThread() {
+ return sTracker &&
+ sTracker->mThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void WinWindowOcclusionTracker::EnsureDisplayStatusObserver() {
+ if (mDisplayStatusObserver) {
+ return;
+ }
+ if (StaticPrefs::
+ widget_windows_window_occlusion_tracking_display_state_enabled()) {
+ mDisplayStatusObserver = DisplayStatusObserver::Create(this);
+ }
+}
+
+void WinWindowOcclusionTracker::EnsureSessionChangeObserver() {
+ if (mSessionChangeObserver) {
+ return;
+ }
+ if (StaticPrefs::
+ widget_windows_window_occlusion_tracking_session_lock_enabled()) {
+ mSessionChangeObserver = SessionChangeObserver::Create(this);
+ }
+}
+
+void WinWindowOcclusionTracker::Enable(nsBaseWidget* aWindow, HWND aHwnd) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::Enable() aWindow %p aHwnd %p",
+ aWindow, aHwnd);
+
+ auto it = mHwndRootWindowMap.find(aHwnd);
+ if (it != mHwndRootWindowMap.end()) {
+ return;
+ }
+
+ nsWeakPtr weak = do_GetWeakReference(aWindow);
+ mHwndRootWindowMap.emplace(aHwnd, weak);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::EnableOcclusionTrackingForWindow, aHwnd);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::Disable(nsBaseWidget* aWindow, HWND aHwnd) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::Disable() aWindow %p aHwnd %p", aWindow,
+ aHwnd);
+
+ auto it = mHwndRootWindowMap.find(aHwnd);
+ if (it == mHwndRootWindowMap.end()) {
+ return;
+ }
+
+ mHwndRootWindowMap.erase(it);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::DisableOcclusionTrackingForWindow, aHwnd);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::OnWindowVisibilityChanged(nsBaseWidget* aWindow,
+ bool aVisible) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnWindowVisibilityChanged() aWindow %p "
+ "aVisible %d",
+ aWindow, aVisible);
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleVisibilityChanged, aVisible);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+WinWindowOcclusionTracker::WinWindowOcclusionTracker(
+ UniquePtr<base::Thread> aThread)
+ : mThread(std::move(aThread)), mMonitor("WinWindowOcclusionTracker") {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WinWindowOcclusionTracker::WinWindowOcclusionTracker()");
+
+ EnsureDisplayStatusObserver();
+ EnsureSessionChangeObserver();
+
+ mSerializedTaskDispatcher = new SerializedTaskDispatcher();
+}
+
+WinWindowOcclusionTracker::~WinWindowOcclusionTracker() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::~WinWindowOcclusionTracker()");
+}
+
+// static
+bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
+ HWND aHwnd, LayoutDeviceIntRect* aWindowRect) {
+ // Filter out windows that are not "visible", IsWindowVisible().
+ if (!::IsWindow(aHwnd) || !::IsWindowVisible(aHwnd)) {
+ return false;
+ }
+
+ // Filter out minimized windows.
+ if (::IsIconic(aHwnd)) {
+ return false;
+ }
+
+ LONG exStyles = ::GetWindowLong(aHwnd, GWL_EXSTYLE);
+ // Filter out "transparent" windows, windows where the mouse clicks fall
+ // through them.
+ if (exStyles & WS_EX_TRANSPARENT) {
+ return false;
+ }
+
+ // Filter out "tool windows", which are floating windows that do not appear on
+ // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
+ // than what is visible to the user, so by filtering them out we will avoid
+ // incorrectly marking native windows as occluded. We do not filter out the
+ // Windows Taskbar.
+ if (exStyles & WS_EX_TOOLWINDOW) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ if (!className.Equals(L"Shell_TrayWnd")) {
+ return false;
+ }
+ }
+ }
+
+ // Filter out layered windows that are not opaque or that set a transparency
+ // colorkey.
+ if (exStyles & WS_EX_LAYERED) {
+ BYTE alpha;
+ DWORD flags;
+
+ // GetLayeredWindowAttributes only works if the application has
+ // previously called SetLayeredWindowAttributes on the window.
+ // The function will fail if the layered window was setup with
+ // UpdateLayeredWindow. Treat this failure as the window being transparent.
+ // See Remarks section of
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlayeredwindowattributes
+ if (!::GetLayeredWindowAttributes(aHwnd, nullptr, &alpha, &flags)) {
+ return false;
+ }
+
+ if (flags & LWA_ALPHA && alpha < 255) {
+ return false;
+ }
+ if (flags & LWA_COLORKEY) {
+ return false;
+ }
+ }
+
+ // Filter out windows that do not have a simple rectangular region.
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ int result = GetWindowRgn(aHwnd, region);
+ ::DeleteObject(region);
+ if (result == COMPLEXREGION) {
+ return false;
+ }
+
+ // Windows 10 has cloaked windows, windows with WS_VISIBLE attribute but
+ // not displayed. explorer.exe, in particular has one that's the
+ // size of the desktop. It's usually behind Chrome windows in the z-order,
+ // but using a remote desktop can move it up in the z-order. So, ignore them.
+ DWORD reason;
+ if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd, DWMWA_CLOAKED, &reason,
+ sizeof(reason))) &&
+ reason != 0) {
+ return false;
+ }
+
+ RECT winRect;
+ // Filter out windows that take up zero area. The call to GetWindowRect is one
+ // of the most expensive parts of this function, so it is last.
+ if (!::GetWindowRect(aHwnd, &winRect)) {
+ return false;
+ }
+ if (::IsRectEmpty(&winRect)) {
+ return false;
+ }
+
+ // Ignore popup windows since they're transient unless it is the Windows
+ // Taskbar
+ // XXX Chrome Widget popup handling is removed for now.
+ if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ if (!className.Equals(L"Shell_TrayWnd")) {
+ return false;
+ }
+ }
+ }
+
+ *aWindowRect = LayoutDeviceIntRect(winRect.left, winRect.top,
+ winRect.right - winRect.left,
+ winRect.bottom - winRect.top);
+
+ WINDOWPLACEMENT windowPlacement = {0};
+ windowPlacement.length = sizeof(WINDOWPLACEMENT);
+ ::GetWindowPlacement(aHwnd, &windowPlacement);
+ if (windowPlacement.showCmd == SW_MAXIMIZE) {
+ // If the window is maximized the window border extends beyond the visible
+ // region of the screen. Adjust the maximized window rect to fit the
+ // screen dimensions to ensure that fullscreen windows, which do not extend
+ // beyond the screen boundaries since they typically have no borders, will
+ // occlude maximized windows underneath them.
+ HMONITOR hmon = ::MonitorFromWindow(aHwnd, MONITOR_DEFAULTTONEAREST);
+ if (hmon) {
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (GetMonitorInfo(hmon, &mi)) {
+ LayoutDeviceIntRect workArea(mi.rcWork.left, mi.rcWork.top,
+ mi.rcWork.right - mi.rcWork.left,
+ mi.rcWork.bottom - mi.rcWork.top);
+ // Adjust aWindowRect to fit to monitor.
+ aWindowRect->width = std::min(workArea.width, aWindowRect->width);
+ if (aWindowRect->x < workArea.x) {
+ aWindowRect->x = workArea.x;
+ } else {
+ aWindowRect->x = std::min(workArea.x + workArea.width,
+ aWindowRect->x + aWindowRect->width) -
+ aWindowRect->width;
+ }
+ aWindowRect->height = std::min(workArea.height, aWindowRect->height);
+ if (aWindowRect->y < workArea.y) {
+ aWindowRect->y = workArea.y;
+ } else {
+ aWindowRect->y = std::min(workArea.y + workArea.height,
+ aWindowRect->y + aWindowRect->height) -
+ aWindowRect->height;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+// static
+void WinWindowOcclusionTracker::CallUpdateOcclusionState(
+ std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* tracker = WinWindowOcclusionTracker::Get();
+ if (!tracker) {
+ return;
+ }
+ tracker->UpdateOcclusionState(aMap, aShowAllWindows);
+}
+
+void WinWindowOcclusionTracker::UpdateOcclusionState(
+ std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ LOG(LogLevel::Debug,
+ "WinWindowOcclusionTracker::UpdateOcclusionState() aShowAllWindows %d",
+ aShowAllWindows);
+
+ mNumVisibleRootWindows = 0;
+ for (auto& [hwnd, state] : *aMap) {
+ auto it = mHwndRootWindowMap.find(hwnd);
+ // The window was destroyed while processing occlusion.
+ if (it == mHwndRootWindowMap.end()) {
+ continue;
+ }
+ auto occlState = state;
+
+ // If the screen is locked or off, ignore occlusion state results and
+ // mark the window as occluded.
+ if (mScreenLocked || !mDisplayOn) {
+ occlState = OcclusionState::OCCLUDED;
+ } else if (aShowAllWindows) {
+ occlState = OcclusionState::VISIBLE;
+ }
+ nsCOMPtr<nsIWidget> widget = do_QueryReferent(it->second);
+ if (!widget) {
+ continue;
+ }
+ auto* baseWidget = static_cast<nsBaseWidget*>(widget.get());
+ baseWidget->NotifyOcclusionState(occlState);
+ if (baseWidget->SizeMode() != nsSizeMode_Minimized) {
+ mNumVisibleRootWindows++;
+ }
+ }
+}
+
+void WinWindowOcclusionTracker::OnSessionChange(WPARAM aStatusCode,
+ Maybe<bool> aIsCurrentSession) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIsCurrentSession.isNothing() || !*aIsCurrentSession) {
+ return;
+ }
+
+ if (aStatusCode == WTS_SESSION_UNLOCK) {
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_UNLOCK");
+
+ // UNLOCK will cause a foreground window change, which will
+ // trigger an occlusion calculation on its own.
+ mScreenLocked = false;
+ } else if (aStatusCode == WTS_SESSION_LOCK) {
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
+
+ mScreenLocked = true;
+ MarkNonIconicWindowsOccluded();
+ }
+}
+
+void WinWindowOcclusionTracker::OnDisplayStateChanged(bool aDisplayOn) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::OnDisplayStateChanged() aDisplayOn %d",
+ aDisplayOn);
+
+ if (mDisplayOn == aDisplayOn) {
+ return;
+ }
+
+ mDisplayOn = aDisplayOn;
+ if (aDisplayOn) {
+ // Notify the window occlusion calculator of the display turning on
+ // which will schedule an occlusion calculation. This must be run
+ // on the WindowOcclusionCalculator thread.
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleVisibilityChanged,
+ /* aVisible */ true);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+ } else {
+ MarkNonIconicWindowsOccluded();
+ }
+}
+
+void WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded() {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info,
+ "WinWindowOcclusionTracker::MarkNonIconicWindowsOccluded()");
+
+ // Set all visible root windows as occluded. If not visible,
+ // set them as hidden.
+ for (auto& [hwnd, weak] : mHwndRootWindowMap) {
+ nsCOMPtr<nsIWidget> widget = do_QueryReferent(weak);
+ if (!widget) {
+ continue;
+ }
+ auto* baseWidget = static_cast<nsBaseWidget*>(widget.get());
+ auto state = (baseWidget->SizeMode() == nsSizeMode_Minimized)
+ ? OcclusionState::HIDDEN
+ : OcclusionState::OCCLUDED;
+ baseWidget->NotifyOcclusionState(state);
+ }
+}
+
+void WinWindowOcclusionTracker::TriggerCalculation() {
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<WindowOcclusionCalculator>(
+ WindowOcclusionCalculator::GetInstance()),
+ &WindowOcclusionCalculator::HandleTriggerCalculation);
+ mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
+}
+
+// static
+BOOL WinWindowOcclusionTracker::DumpOccludingWindowsCallback(HWND aHWnd,
+ LPARAM aLParam) {
+ HWND hwnd = reinterpret_cast<HWND>(aLParam);
+
+ LayoutDeviceIntRect windowRect;
+ bool windowIsOccluding = IsWindowVisibleAndFullyOpaque(aHWnd, &windowRect);
+ if (windowIsOccluding) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHWnd, className)) {
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ printf_stderr(
+ "DumpOccludingWindowsCallback() aHWnd %p className %s windowRect(%d, "
+ "%d, %d, %d)\n",
+ aHWnd, name.get(), windowRect.x, windowRect.y, windowRect.width,
+ windowRect.height);
+ }
+ }
+
+ if (aHWnd == hwnd) {
+ return false;
+ }
+ return true;
+}
+
+void WinWindowOcclusionTracker::DumpOccludingWindows(HWND aHWnd) {
+ printf_stderr("DumpOccludingWindows() until aHWnd %p visible %d iconic %d\n",
+ aHWnd, ::IsWindowVisible(aHWnd), ::IsIconic(aHWnd));
+ ::EnumWindows(&DumpOccludingWindowsCallback, reinterpret_cast<LPARAM>(aHWnd));
+}
+
+// static
+StaticRefPtr<WinWindowOcclusionTracker::WindowOcclusionCalculator>
+ WinWindowOcclusionTracker::WindowOcclusionCalculator::sCalculator;
+
+WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ WindowOcclusionCalculator()
+ : mMonitor(WinWindowOcclusionTracker::Get()->mMonitor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ LOG(LogLevel::Info, "WindowOcclusionCalculator()");
+
+ mSerializedTaskDispatcher =
+ WinWindowOcclusionTracker::Get()->GetSerializedTaskDispatcher();
+}
+
+WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ~WindowOcclusionCalculator() {}
+
+// static
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::CreateInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sCalculator = new WindowOcclusionCalculator();
+}
+
+// static
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::ClearInstance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ sCalculator = nullptr;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::Initialize() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(!mVirtualDesktopManager);
+ CALC_LOG(LogLevel::Info, "Initialize()");
+
+#ifndef __MINGW32__
+ RefPtr<IVirtualDesktopManager> desktopManager;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
+ __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
+ if (FAILED(hr)) {
+ return;
+ }
+ mVirtualDesktopManager = desktopManager;
+#endif
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::Shutdown() {
+ MonitorAutoLock lock(mMonitor);
+
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "Shutdown()");
+
+ UnregisterEventHooks();
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable->Cancel();
+ mOcclusionUpdateRunnable = nullptr;
+ }
+ mVirtualDesktopManager = nullptr;
+
+ mMonitor.NotifyAll();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ EnableOcclusionTrackingForWindow(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ CALC_LOG(LogLevel::Info, "EnableOcclusionTrackingForWindow() aHwnd %p",
+ aHwnd);
+
+ MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState.find(aHwnd) ==
+ mRootWindowHwndsOcclusionState.end());
+ mRootWindowHwndsOcclusionState[aHwnd] = OcclusionState::UNKNOWN;
+
+ if (mGlobalEventHooks.empty()) {
+ RegisterEventHooks();
+ }
+
+ // Schedule an occlusion calculation so that the newly tracked window does
+ // not have a stale occlusion status.
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ DisableOcclusionTrackingForWindow(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+ CALC_LOG(LogLevel::Info, "DisableOcclusionTrackingForWindow() aHwnd %p",
+ aHwnd);
+
+ MOZ_RELEASE_ASSERT(mRootWindowHwndsOcclusionState.find(aHwnd) !=
+ mRootWindowHwndsOcclusionState.end());
+ mRootWindowHwndsOcclusionState.erase(aHwnd);
+
+ if (mMovingWindow == aHwnd) {
+ mMovingWindow = 0;
+ }
+
+ if (mRootWindowHwndsOcclusionState.empty()) {
+ UnregisterEventHooks();
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable->Cancel();
+ mOcclusionUpdateRunnable = nullptr;
+ }
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ HandleVisibilityChanged(bool aVisible) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "HandleVisibilityChange() aVisible %d", aVisible);
+
+ // May have gone from having no visible windows to having one, in
+ // which case we need to register event hooks, and make sure that an
+ // occlusion calculation is scheduled.
+ if (aVisible) {
+ MaybeRegisterEventHooks();
+ ScheduleOcclusionCalculationIfNeeded();
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ HandleTriggerCalculation() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "HandleTriggerCalculation()");
+
+ MaybeRegisterEventHooks();
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ MaybeRegisterEventHooks() {
+ if (mGlobalEventHooks.empty()) {
+ RegisterEventHooks();
+ }
+}
+
+// static
+void CALLBACK
+WinWindowOcclusionTracker::WindowOcclusionCalculator::EventHookCallback(
+ HWINEVENTHOOK aWinEventHook, DWORD aEvent, HWND aHwnd, LONG aIdObject,
+ LONG aIdChild, DWORD aEventThread, DWORD aMsEventTime) {
+ if (sCalculator) {
+ sCalculator->ProcessEventHookCallback(aWinEventHook, aEvent, aHwnd,
+ aIdObject, aIdChild);
+ }
+}
+
+// static
+BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ComputeNativeWindowOcclusionStatusCallback(HWND aHwnd, LPARAM aLParam) {
+ if (sCalculator) {
+ return sCalculator->ProcessComputeNativeWindowOcclusionStatusCallback(
+ aHwnd, reinterpret_cast<std::unordered_set<DWORD>*>(aLParam));
+ }
+ return FALSE;
+}
+
+// static
+BOOL CALLBACK WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UpdateVisibleWindowProcessIdsCallback(HWND aHwnd, LPARAM aLParam) {
+ if (sCalculator) {
+ sCalculator->ProcessUpdateVisibleWindowProcessIdsCallback(aHwnd);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UpdateVisibleWindowProcessIds() {
+ mPidsForLocationChangeHook.clear();
+ ::EnumWindows(&UpdateVisibleWindowProcessIdsCallback, 0);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ComputeNativeWindowOcclusionStatus() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_ASSERT(mSerializedTaskDispatcher->IsOnCurrentThread());
+
+ if (mOcclusionUpdateRunnable) {
+ mOcclusionUpdateRunnable = nullptr;
+ }
+
+ if (mRootWindowHwndsOcclusionState.empty()) {
+ return;
+ }
+
+ // Set up initial conditions for occlusion calculation.
+ bool shouldUnregisterEventHooks = true;
+
+ // Compute the LayoutDeviceIntRegion for the screen.
+ int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ int screenTop = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
+ int screenWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ int screenHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ LayoutDeviceIntRegion screenRegion =
+ LayoutDeviceIntRect(screenLeft, screenTop, screenWidth, screenHeight);
+ mNumRootWindowsWithUnknownOcclusionState = 0;
+
+ CALC_LOG(LogLevel::Debug,
+ "ComputeNativeWindowOcclusionStatus() screen(%d, %d, %d, %d)",
+ screenLeft, screenTop, screenWidth, screenHeight);
+
+ for (auto& [hwnd, state] : mRootWindowHwndsOcclusionState) {
+ // IsIconic() checks for a minimized window. Immediately set the state of
+ // minimized windows to HIDDEN.
+ if (::IsIconic(hwnd)) {
+ state = OcclusionState::HIDDEN;
+ } else if (IsWindowOnCurrentVirtualDesktop(hwnd) == Some(false)) {
+ // If window is not on the current virtual desktop, immediately
+ // set the state of the window to OCCLUDED.
+ state = OcclusionState::OCCLUDED;
+ // Don't unregister event hooks when not on current desktop. There's no
+ // notification when that changes, so we can't reregister event hooks.
+ shouldUnregisterEventHooks = false;
+ } else {
+ state = OcclusionState::UNKNOWN;
+ shouldUnregisterEventHooks = false;
+ mNumRootWindowsWithUnknownOcclusionState++;
+ }
+ }
+
+ // Unregister event hooks if all native windows are minimized.
+ if (shouldUnregisterEventHooks) {
+ UnregisterEventHooks();
+ } else {
+ std::unordered_set<DWORD> currentPidsWithVisibleWindows;
+ mUnoccludedDesktopRegion = screenRegion;
+ // Calculate unoccluded region if there is a non-minimized native window.
+ // Also compute |current_pids_with_visible_windows| as we enumerate
+ // the windows.
+ EnumWindows(&ComputeNativeWindowOcclusionStatusCallback,
+ reinterpret_cast<LPARAM>(&currentPidsWithVisibleWindows));
+ // Check if mPidsForLocationChangeHook has any pids of processes
+ // currently without visible windows. If so, unhook the win event,
+ // remove the pid from mPidsForLocationChangeHook and remove
+ // the corresponding event hook from mProcessEventHooks.
+ std::unordered_set<DWORD> pidsToRemove;
+ for (auto locChangePid : mPidsForLocationChangeHook) {
+ if (currentPidsWithVisibleWindows.find(locChangePid) ==
+ currentPidsWithVisibleWindows.end()) {
+ // Remove the event hook from our map, and unregister the event hook.
+ // It's possible the eventhook will no longer be valid, but if we don't
+ // unregister the event hook, a process that toggles between having
+ // visible windows and not having visible windows could cause duplicate
+ // event hooks to get registered for the process.
+ UnhookWinEvent(mProcessEventHooks[locChangePid]);
+ mProcessEventHooks.erase(locChangePid);
+ pidsToRemove.insert(locChangePid);
+ }
+ }
+ if (!pidsToRemove.empty()) {
+ // XXX simplify
+ for (auto it = mPidsForLocationChangeHook.begin();
+ it != mPidsForLocationChangeHook.end();) {
+ if (pidsToRemove.find(*it) != pidsToRemove.end()) {
+ it = mPidsForLocationChangeHook.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ }
+
+ std::unordered_map<HWND, OcclusionState>* map =
+ &mRootWindowHwndsOcclusionState;
+ bool showAllWindows = mShowingThumbnails;
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "CallUpdateOcclusionState", [map, showAllWindows]() {
+ WinWindowOcclusionTracker::CallUpdateOcclusionState(map,
+ showAllWindows);
+ });
+ mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ScheduleOcclusionCalculationIfNeeded() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ // OcclusionUpdateRunnable is already queued.
+ if (mOcclusionUpdateRunnable) {
+ return;
+ }
+
+ CALC_LOG(LogLevel::Debug, "ScheduleOcclusionCalculationIfNeeded()");
+
+ RefPtr<CancelableRunnable> task = new OcclusionUpdateRunnable(this);
+ mOcclusionUpdateRunnable = task;
+ mSerializedTaskDispatcher->PostDelayedTaskToCalculator(
+ task.forget(), kOcclusionUpdateRunnableDelayMs);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterGlobalEventHook(DWORD aEventMin, DWORD aEventMax) {
+ HWINEVENTHOOK eventHook =
+ ::SetWinEventHook(aEventMin, aEventMax, nullptr, &EventHookCallback, 0, 0,
+ WINEVENT_OUTOFCONTEXT);
+ mGlobalEventHooks.push_back(eventHook);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterEventHookForProcess(DWORD aPid) {
+ mPidsForLocationChangeHook.insert(aPid);
+ mProcessEventHooks[aPid] = SetWinEventHook(
+ EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, nullptr,
+ &EventHookCallback, aPid, 0, WINEVENT_OUTOFCONTEXT);
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ RegisterEventHooks() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ MOZ_RELEASE_ASSERT(mGlobalEventHooks.empty());
+ CALC_LOG(LogLevel::Info, "RegisterEventHooks()");
+
+ // Detects native window lost mouse capture
+ RegisterGlobalEventHook(EVENT_SYSTEM_CAPTUREEND, EVENT_SYSTEM_CAPTUREEND);
+
+ // Detects native window move (drag) and resizing events.
+ RegisterGlobalEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND);
+
+ // Detects native window minimize and restore from taskbar events.
+ RegisterGlobalEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND);
+
+ // Detects foreground window changing.
+ RegisterGlobalEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND);
+
+ // Detects objects getting shown and hidden. Used to know when the task bar
+ // and alt tab are showing preview windows so we can unocclude windows.
+ RegisterGlobalEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE);
+
+ // Detects object state changes, e.g., enable/disable state, native window
+ // maximize and native window restore events.
+ RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE);
+
+ // Cloaking and uncloaking of windows should trigger an occlusion calculation.
+ // In particular, switching virtual desktops seems to generate these events.
+ RegisterGlobalEventHook(EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED);
+
+ // Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on
+ // because otherwise event throughput is very high, as it generates events
+ // for location changes of all objects, including the mouse moving on top of a
+ // window.
+ UpdateVisibleWindowProcessIds();
+ for (DWORD pid : mPidsForLocationChangeHook) {
+ RegisterEventHookForProcess(pid);
+ }
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ UnregisterEventHooks() {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+ CALC_LOG(LogLevel::Info, "UnregisterEventHooks()");
+
+ for (const auto eventHook : mGlobalEventHooks) {
+ ::UnhookWinEvent(eventHook);
+ }
+ mGlobalEventHooks.clear();
+
+ for (const auto& [pid, eventHook] : mProcessEventHooks) {
+ ::UnhookWinEvent(eventHook);
+ }
+ mProcessEventHooks.clear();
+
+ mPidsForLocationChangeHook.clear();
+}
+
+bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessComputeNativeWindowOcclusionStatusCallback(
+ HWND aHwnd, std::unordered_set<DWORD>* aCurrentPidsWithVisibleWindows) {
+ LayoutDeviceIntRegion currUnoccludedDestkop = mUnoccludedDesktopRegion;
+ LayoutDeviceIntRect windowRect;
+ bool windowIsOccluding =
+ WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect);
+ if (windowIsOccluding) {
+ // Hook this window's process with EVENT_OBJECT_LOCATION_CHANGE, if we are
+ // not already doing so.
+ DWORD pid;
+ ::GetWindowThreadProcessId(aHwnd, &pid);
+ aCurrentPidsWithVisibleWindows->insert(pid);
+ auto it = mProcessEventHooks.find(pid);
+ if (it == mProcessEventHooks.end()) {
+ RegisterEventHookForProcess(pid);
+ }
+
+ // If no more root windows to consider, return true so we can continue
+ // looking for windows we haven't hooked.
+ if (mNumRootWindowsWithUnknownOcclusionState == 0) {
+ return true;
+ }
+
+ mUnoccludedDesktopRegion.SubOut(windowRect);
+ } else if (mNumRootWindowsWithUnknownOcclusionState == 0) {
+ // This window can't occlude other windows, but we've determined the
+ // occlusion state of all root windows, so we can return.
+ return true;
+ }
+
+ // Ignore moving windows when deciding if windows under it are occluded.
+ if (aHwnd == mMovingWindow) {
+ return true;
+ }
+
+ // Check if |hwnd| is a root window; if so, we're done figuring out
+ // if it's occluded because we've seen all the windows "over" it.
+ auto it = mRootWindowHwndsOcclusionState.find(aHwnd);
+ if (it == mRootWindowHwndsOcclusionState.end() ||
+ it->second != OcclusionState::UNKNOWN) {
+ return true;
+ }
+
+ CALC_LOG(LogLevel::Debug,
+ "ProcessComputeNativeWindowOcclusionStatusCallback() windowRect(%d, "
+ "%d, %d, %d) IsOccluding %d",
+ windowRect.x, windowRect.y, windowRect.width, windowRect.height,
+ windowIsOccluding);
+
+ // On Win7, default theme makes root windows have complex regions by
+ // default. But we can still check if their bounding rect is occluded.
+ if (!windowIsOccluding) {
+ RECT rect;
+ if (::GetWindowRect(aHwnd, &rect) != 0) {
+ LayoutDeviceIntRect windowRect(
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+ currUnoccludedDestkop.SubOut(windowRect);
+ }
+ }
+
+ it->second = (mUnoccludedDesktopRegion == currUnoccludedDestkop)
+ ? OcclusionState::OCCLUDED
+ : OcclusionState::VISIBLE;
+ mNumRootWindowsWithUnknownOcclusionState--;
+
+ return true;
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
+ HWND aHwnd, LONG aIdObject, LONG aIdChild) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ // No need to calculate occlusion if a zero HWND generated the event. This
+ // happens if there is no window associated with the event, e.g., mouse move
+ // events.
+ if (!aHwnd) {
+ return;
+ }
+
+ // We only care about events for window objects. In particular, we don't care
+ // about OBJID_CARET, which is spammy.
+ if (aIdObject != OBJID_WINDOW) {
+ return;
+ }
+
+ CALC_LOG(LogLevel::Debug,
+ "WindowOcclusionCalculator::ProcessEventHookCallback() aEvent 0x%lx",
+ aEvent);
+
+ // We generally ignore events for popup windows, except for when the taskbar
+ // is hidden or Windows Taskbar, in which case we recalculate occlusion.
+ // XXX Chrome Widget popup handling is removed for now.
+ bool calculateOcclusion = true;
+ if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ calculateOcclusion = className.Equals(L"Shell_TrayWnd");
+ }
+ }
+
+ // Detect if either the alt tab view or the task list thumbnail is being
+ // shown. If so, mark all non-hidden windows as occluded, and remember that
+ // we're in the showing_thumbnails state. This lasts until we get told that
+ // either the alt tab view or task list thumbnail are hidden.
+ if (aEvent == EVENT_OBJECT_SHOW) {
+ // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
+ // needed.
+ if (mShowingThumbnails) {
+ return;
+ }
+ nsAutoString className;
+ if (WinUtils::GetClassName(aHwnd, className)) {
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ CALC_LOG(LogLevel::Debug,
+ "ProcessEventHookCallback() EVENT_OBJECT_SHOW %s", name.get());
+
+ if (name.Equals("MultitaskingViewFrame") ||
+ name.Equals("TaskListThumbnailWnd")) {
+ CALC_LOG(LogLevel::Info,
+ "ProcessEventHookCallback() mShowingThumbnails = true");
+ mShowingThumbnails = true;
+
+ std::unordered_map<HWND, OcclusionState>* map =
+ &mRootWindowHwndsOcclusionState;
+ bool showAllWindows = mShowingThumbnails;
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "CallUpdateOcclusionState", [map, showAllWindows]() {
+ WinWindowOcclusionTracker::CallUpdateOcclusionState(
+ map, showAllWindows);
+ });
+ mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
+ }
+ }
+ return;
+ } else if (aEvent == EVENT_OBJECT_HIDE) {
+ // Avoid getting the aHwnd's class name, and recomputing occlusion, if not
+ // needed.
+ if (!mShowingThumbnails) {
+ return;
+ }
+ nsAutoString className;
+ WinUtils::GetClassName(aHwnd, className);
+ const auto name = NS_ConvertUTF16toUTF8(className);
+ CALC_LOG(LogLevel::Debug, "ProcessEventHookCallback() EVENT_OBJECT_HIDE %s",
+ name.get());
+ if (name.Equals("MultitaskingViewFrame") ||
+ name.Equals("TaskListThumbnailWnd")) {
+ CALC_LOG(LogLevel::Info,
+ "ProcessEventHookCallback() mShowingThumbnails = false");
+ mShowingThumbnails = false;
+ // Let occlusion calculation fix occlusion state, even though hwnd might
+ // be a popup window.
+ calculateOcclusion = true;
+ } else {
+ return;
+ }
+ }
+ // Don't continually calculate occlusion while a window is moving (unless it's
+ // a root window), but instead once at the beginning and once at the end.
+ // Remember the window being moved so if it's a root window, we can ignore
+ // it when deciding if windows under it are occluded.
+ else if (aEvent == EVENT_SYSTEM_MOVESIZESTART) {
+ mMovingWindow = aHwnd;
+ } else if (aEvent == EVENT_SYSTEM_MOVESIZEEND) {
+ mMovingWindow = 0;
+ } else if (mMovingWindow != 0) {
+ if (aEvent == EVENT_OBJECT_LOCATIONCHANGE ||
+ aEvent == EVENT_OBJECT_STATECHANGE) {
+ // Ignore move events if it's not a root window that's being moved. If it
+ // is a root window, we want to calculate occlusion to support tab
+ // dragging to windows that were occluded when the drag was started but
+ // are no longer occluded.
+ if (mRootWindowHwndsOcclusionState.find(aHwnd) ==
+ mRootWindowHwndsOcclusionState.end()) {
+ return;
+ }
+ } else {
+ // If we get an event that isn't a location/state change, then we probably
+ // missed the movesizeend notification, or got events out of order. In
+ // that case, we want to go back to normal occlusion calculation.
+ mMovingWindow = 0;
+ }
+ }
+
+ if (!calculateOcclusion) {
+ return;
+ }
+
+ ScheduleOcclusionCalculationIfNeeded();
+}
+
+void WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd) {
+ MOZ_ASSERT(IsInWinWindowOcclusionThread());
+
+ LayoutDeviceIntRect windowRect;
+ if (WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect)) {
+ DWORD pid;
+ ::GetWindowThreadProcessId(aHwnd, &pid);
+ mPidsForLocationChangeHook.insert(pid);
+ }
+}
+
+bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
+ HWND aHwnd, LayoutDeviceIntRect* aWindowRect) {
+ return IsWindowVisibleAndFullyOpaque(aHwnd, aWindowRect) &&
+ (IsWindowOnCurrentVirtualDesktop(aHwnd) == Some(true));
+}
+
+Maybe<bool> WinWindowOcclusionTracker::WindowOcclusionCalculator::
+ IsWindowOnCurrentVirtualDesktop(HWND aHwnd) {
+ if (!mVirtualDesktopManager) {
+ return Some(true);
+ }
+
+ BOOL onCurrentDesktop;
+ HRESULT hr = mVirtualDesktopManager->IsWindowOnCurrentVirtualDesktop(
+ aHwnd, &onCurrentDesktop);
+ if (FAILED(hr)) {
+ // In this case, we do not know the window is in which virtual desktop.
+ return Nothing();
+ }
+
+ if (onCurrentDesktop) {
+ return Some(true);
+ }
+
+ GUID workspaceGuid;
+ hr = mVirtualDesktopManager->GetWindowDesktopId(aHwnd, &workspaceGuid);
+ if (FAILED(hr)) {
+ // In this case, we do not know the window is in which virtual desktop.
+ return Nothing();
+ }
+
+ // IsWindowOnCurrentVirtualDesktop() is flaky for newly opened windows,
+ // which causes test flakiness. Occasionally, it incorrectly says a window
+ // is not on the current virtual desktop when it is. In this situation,
+ // it also returns GUID_NULL for the desktop id.
+ if (workspaceGuid == GUID_NULL) {
+ // In this case, we do not know if the window is in which virtual desktop.
+ // But we hanle it as on current virtual desktop.
+ // It does not cause a problem to window occlusion.
+ // Since if window is not on current virtual desktop, window size becomes
+ // (0, 0, 0, 0). It makes window occlusion handling explicit. It is
+ // necessary for gtest.
+ return Some(true);
+ }
+
+ return Some(false);
+}
+
+#undef LOG
+#undef CALC_LOG
+
+} // namespace mozilla::widget
diff --git a/widget/windows/WinWindowOcclusionTracker.h b/widget/windows/WinWindowOcclusionTracker.h
new file mode 100644
index 0000000000..b82a41b984
--- /dev/null
+++ b/widget/windows/WinWindowOcclusionTracker.h
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_WinWindowOcclusionTracker_h
+#define widget_windows_WinWindowOcclusionTracker_h
+
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "nsIWeakReferenceUtils.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "mozilla/widget/WindowOcclusionState.h"
+#include "mozilla/widget/WinEventObserver.h"
+#include "Units.h"
+#include "nsThreadUtils.h"
+
+class nsBaseWidget;
+struct IVirtualDesktopManager;
+class WinWindowOcclusionTrackerTest;
+class WinWindowOcclusionTrackerInteractiveTest;
+
+namespace base {
+class Thread;
+} // namespace base
+
+namespace mozilla {
+
+namespace widget {
+
+class OcclusionUpdateRunnable;
+class SerializedTaskDispatcher;
+class UpdateOcclusionStateRunnable;
+
+// This class handles window occlusion tracking by using HWND.
+// Implementation is borrowed from chromium's NativeWindowOcclusionTrackerWin.
+class WinWindowOcclusionTracker final : public DisplayStatusListener,
+ public SessionChangeListener {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinWindowOcclusionTracker)
+
+ /// Can only be called from the main thread.
+ static WinWindowOcclusionTracker* Get();
+
+ /// Can only be called from the main thread.
+ static void Ensure();
+
+ /// Can only be called from the main thread.
+ static void ShutDown();
+
+ /// Can be called from any thread.
+ static MessageLoop* OcclusionCalculatorLoop();
+
+ /// Can be called from any thread.
+ static bool IsInWinWindowOcclusionThread();
+
+ /// Can only be called from the main thread.
+ void EnsureDisplayStatusObserver();
+
+ /// Can only be called from the main thread.
+ void EnsureSessionChangeObserver();
+
+ // Enables notifying to widget via NotifyOcclusionState() when the occlusion
+ // state has been computed.
+ void Enable(nsBaseWidget* aWindow, HWND aHwnd);
+
+ // Disables notifying to widget via NotifyOcclusionState() when the occlusion
+ // state has been computed.
+ void Disable(nsBaseWidget* aWindow, HWND aHwnd);
+
+ // Called when widget's visibility is changed
+ void OnWindowVisibilityChanged(nsBaseWidget* aWindow, bool aVisible);
+
+ SerializedTaskDispatcher* GetSerializedTaskDispatcher() {
+ return mSerializedTaskDispatcher;
+ }
+
+ void TriggerCalculation();
+
+ void DumpOccludingWindows(HWND aHWnd);
+
+ private:
+ friend class ::WinWindowOcclusionTrackerTest;
+ friend class ::WinWindowOcclusionTrackerInteractiveTest;
+
+ explicit WinWindowOcclusionTracker(UniquePtr<base::Thread> aThread);
+ virtual ~WinWindowOcclusionTracker();
+
+ // This class computes the occlusion state of the tracked windows.
+ // It runs on a separate thread, and notifies the main thread of
+ // the occlusion state of the tracked windows.
+ class WindowOcclusionCalculator {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowOcclusionCalculator)
+ public:
+ // Creates WindowOcclusionCalculator instance.
+ static void CreateInstance();
+
+ // Clear WindowOcclusionCalculator instance.
+ static void ClearInstance();
+
+ // Returns existing WindowOcclusionCalculator instance.
+ static WindowOcclusionCalculator* GetInstance() { return sCalculator; }
+
+ void Initialize();
+ void Shutdown();
+
+ void EnableOcclusionTrackingForWindow(HWND hwnd);
+ void DisableOcclusionTrackingForWindow(HWND hwnd);
+
+ // If a window becomes visible, makes sure event hooks are registered.
+ void HandleVisibilityChanged(bool aVisible);
+
+ void HandleTriggerCalculation();
+
+ private:
+ WindowOcclusionCalculator();
+ ~WindowOcclusionCalculator();
+
+ // Registers event hooks, if not registered.
+ void MaybeRegisterEventHooks();
+
+ // This is the callback registered to get notified of various Windows
+ // events, like window moving/resizing.
+ static void CALLBACK EventHookCallback(HWINEVENTHOOK aWinEventHook,
+ DWORD aEvent, HWND aHwnd,
+ LONG aIdObject, LONG aIdChild,
+ DWORD aEventThread,
+ DWORD aMsEventTime);
+
+ // EnumWindows callback used to iterate over all hwnds to determine
+ // occlusion status of all tracked root windows. Also builds up
+ // |current_pids_with_visible_windows_| and registers event hooks for newly
+ // discovered processes with visible hwnds.
+ static BOOL CALLBACK
+ ComputeNativeWindowOcclusionStatusCallback(HWND hwnd, LPARAM lParam);
+
+ // EnumWindows callback used to update the list of process ids with
+ // visible hwnds, |pids_for_location_change_hook_|.
+ static BOOL CALLBACK UpdateVisibleWindowProcessIdsCallback(HWND aHwnd,
+ LPARAM aLParam);
+
+ // Determines which processes owning visible application windows to set the
+ // EVENT_OBJECT_LOCATIONCHANGE event hook for and stores the pids in
+ // |pids_for_location_change_hook_|.
+ void UpdateVisibleWindowProcessIds();
+
+ // Computes the native window occlusion status for all tracked root gecko
+ // windows in |root_window_hwnds_occlusion_state_| and notifies them if
+ // their occlusion status has changed.
+ void ComputeNativeWindowOcclusionStatus();
+
+ // Schedules an occlusion calculation , if one isn't already scheduled.
+ void ScheduleOcclusionCalculationIfNeeded();
+
+ // Registers a global event hook (not per process) for the events in the
+ // range from |event_min| to |event_max|, inclusive.
+ void RegisterGlobalEventHook(DWORD aEventMin, DWORD aEventMax);
+
+ // Registers the EVENT_OBJECT_LOCATIONCHANGE event hook for the process with
+ // passed id. The process has one or more visible, opaque windows.
+ void RegisterEventHookForProcess(DWORD aPid);
+
+ // Registers/Unregisters the event hooks necessary for occlusion tracking
+ // via calls to RegisterEventHook. These event hooks are disabled when all
+ // tracked windows are minimized.
+ void RegisterEventHooks();
+ void UnregisterEventHooks();
+
+ // EnumWindows callback for occlusion calculation. Returns true to
+ // continue enumeration, false otherwise. Currently, always returns
+ // true because this function also updates currentPidsWithVisibleWindows,
+ // and needs to see all HWNDs.
+ bool ProcessComputeNativeWindowOcclusionStatusCallback(
+ HWND aHwnd, std::unordered_set<DWORD>* aCurrentPidsWithVisibleWindows);
+
+ // Processes events sent to OcclusionEventHookCallback.
+ // It generally triggers scheduling of the occlusion calculation, but
+ // ignores certain events in order to not calculate occlusion more than
+ // necessary.
+ void ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
+ HWND aHwnd, LONG aIdObject, LONG aIdChild);
+
+ // EnumWindows callback for determining which processes to set the
+ // EVENT_OBJECT_LOCATIONCHANGE event hook for. We set that event hook for
+ // processes hosting fully visible, opaque windows.
+ void ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd);
+
+ // Returns true if the window is visible, fully opaque, and on the current
+ // virtual desktop, false otherwise.
+ bool WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
+ HWND aHwnd, LayoutDeviceIntRect* aWindowRect);
+
+ // Returns true if aHwnd is definitely on the current virtual desktop,
+ // false if it's definitely not on the current virtual desktop, and Nothing
+ // if we we can't tell for sure.
+ Maybe<bool> IsWindowOnCurrentVirtualDesktop(HWND aHwnd);
+
+ static StaticRefPtr<WindowOcclusionCalculator> sCalculator;
+
+ // Map of root app window hwnds and their occlusion state. This contains
+ // both visible and hidden windows.
+ // It is accessed from WinWindowOcclusionTracker::UpdateOcclusionState()
+ // without using mutex. The access is safe by using
+ // SerializedTaskDispatcher.
+ std::unordered_map<HWND, OcclusionState> mRootWindowHwndsOcclusionState;
+
+ // Values returned by SetWinEventHook are stored so that hooks can be
+ // unregistered when necessary.
+ std::vector<HWINEVENTHOOK> mGlobalEventHooks;
+
+ // Map from process id to EVENT_OBJECT_LOCATIONCHANGE event hook.
+ std::unordered_map<DWORD, HWINEVENTHOOK> mProcessEventHooks;
+
+ // Pids of processes for which the EVENT_OBJECT_LOCATIONCHANGE event hook is
+ // set.
+ std::unordered_set<DWORD> mPidsForLocationChangeHook;
+
+ // Used as a timer to delay occlusion update.
+ RefPtr<CancelableRunnable> mOcclusionUpdateRunnable;
+
+ // Used to determine if a window is occluded. As we iterate through the
+ // hwnds in z-order, we subtract each opaque window's rect from
+ // mUnoccludedDesktopRegion. When we get to a root window, we subtract
+ // it from mUnoccludedDesktopRegion, and if mUnoccludedDesktopRegion
+ // doesn't change, the root window was already occluded.
+ LayoutDeviceIntRegion mUnoccludedDesktopRegion;
+
+ // Keeps track of how many root windows we need to compute the occlusion
+ // state of in a call to ComputeNativeWindowOcclusionStatus. Once we've
+ // determined the state of all root windows, we can stop subtracting
+ // windows from mUnoccludedDesktopRegion;.
+ int mNumRootWindowsWithUnknownOcclusionState;
+
+ // This is true if the task bar thumbnails or the alt tab thumbnails are
+ // showing.
+ bool mShowingThumbnails = false;
+
+ // Used to keep track of the window that's currently moving. That window
+ // is ignored for calculation occlusion so that tab dragging won't
+ // ignore windows occluded by the dragged window.
+ HWND mMovingWindow = 0;
+
+ // Only used on Win10+.
+ RefPtr<IVirtualDesktopManager> mVirtualDesktopManager;
+
+ // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
+ RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
+
+ // This is an alias to the singleton WinWindowOcclusionTracker mMonitor,
+ // and is used in ShutDown().
+ Monitor& mMonitor;
+
+ friend class OcclusionUpdateRunnable;
+ };
+
+ static BOOL CALLBACK DumpOccludingWindowsCallback(HWND aHWnd, LPARAM aLParam);
+
+ // Returns true if we are interested in |hwnd| for purposes of occlusion
+ // calculation. We are interested in |hwnd| if it is a window that is
+ // visible, opaque, bounded, and not a popup or floating window. If we are
+ // interested in |hwnd|, stores the window rectangle in |window_rect|.
+ static bool IsWindowVisibleAndFullyOpaque(HWND aHwnd,
+ LayoutDeviceIntRect* aWindowRect);
+
+ void Destroy();
+
+ static void CallUpdateOcclusionState(
+ std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows);
+
+ // Updates root windows occclusion state. If aShowAllWindows is true,
+ // all non-hidden windows will be marked visible. This is used to force
+ // rendering of thumbnails.
+ void UpdateOcclusionState(std::unordered_map<HWND, OcclusionState>* aMap,
+ bool aShowAllWindows);
+
+ // This is called with session changed notifications. If the screen is locked
+ // by the current session, it marks app windows as occluded.
+ void OnSessionChange(WPARAM aStatusCode,
+ Maybe<bool> aIsCurrentSession) override;
+
+ // This is called when the display is put to sleep. If the display is sleeping
+ // it marks app windows as occluded.
+ void OnDisplayStateChanged(bool aDisplayOn) override;
+
+ // Marks all root windows as either occluded, or if hwnd IsIconic, hidden.
+ void MarkNonIconicWindowsOccluded();
+
+ static StaticRefPtr<WinWindowOcclusionTracker> sTracker;
+
+ // "WinWindowOcclusionCalc" thread.
+ UniquePtr<base::Thread> mThread;
+ Monitor mMonitor;
+
+ // Has ShutDown been called on us? We might have survived if our thread join
+ // timed out.
+ bool mHasAttemptedShutdown = false;
+
+ // Map of HWND to widget. Maintained on main thread, and used to send
+ // occlusion state notifications to Windows from
+ // mRootWindowHwndsOcclusionState.
+ std::unordered_map<HWND, nsWeakPtr> mHwndRootWindowMap;
+
+ // This is set by UpdateOcclusionState(). It is currently only used by tests.
+ int mNumVisibleRootWindows = 0;
+
+ // If the screen is locked, windows are considered occluded.
+ bool mScreenLocked = false;
+
+ // If the display is off, windows are considered occluded.
+ bool mDisplayOn = true;
+
+ RefPtr<DisplayStatusObserver> mDisplayStatusObserver;
+
+ RefPtr<SessionChangeObserver> mSessionChangeObserver;
+
+ // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
+ RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
+
+ friend class OcclusionUpdateRunnable;
+ friend class UpdateOcclusionStateRunnable;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_WinWindowOcclusionTracker_h
diff --git a/widget/windows/WindowHook.cpp b/widget/windows/WindowHook.cpp
new file mode 100644
index 0000000000..06773f21b7
--- /dev/null
+++ b/widget/windows/WindowHook.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "WindowHook.h"
+#include "nsWindow.h"
+#include "nsWindowDefs.h"
+
+namespace mozilla {
+namespace widget {
+
+nsresult WindowHook::AddHook(UINT nMsg, Callback callback, void* context) {
+ MessageData* data = LookupOrCreate(nMsg);
+
+ if (!data) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Ensure we don't overwrite another hook
+ NS_ENSURE_TRUE(nullptr == data->hook.cb, NS_ERROR_UNEXPECTED);
+
+ data->hook = CallbackData(callback, context);
+
+ return NS_OK;
+}
+
+nsresult WindowHook::RemoveHook(UINT nMsg, Callback callback, void* context) {
+ CallbackData cbdata(callback, context);
+ MessageData* data = Lookup(nMsg);
+ if (!data) return NS_ERROR_UNEXPECTED;
+ if (data->hook != cbdata) return NS_ERROR_UNEXPECTED;
+ data->hook = CallbackData();
+
+ DeleteIfEmpty(data);
+ return NS_OK;
+}
+
+nsresult WindowHook::AddMonitor(UINT nMsg, Callback callback, void* context) {
+ MessageData* data = LookupOrCreate(nMsg);
+ return (data && data->monitors.AppendElement(CallbackData(callback, context),
+ fallible))
+ ? NS_OK
+ : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult WindowHook::RemoveMonitor(UINT nMsg, Callback callback,
+ void* context) {
+ CallbackData cbdata(callback, context);
+ MessageData* data = Lookup(nMsg);
+ if (!data) return NS_ERROR_UNEXPECTED;
+ CallbackDataArray::index_type idx = data->monitors.IndexOf(cbdata);
+ if (idx == CallbackDataArray::NoIndex) return NS_ERROR_UNEXPECTED;
+ data->monitors.RemoveElementAt(idx);
+ DeleteIfEmpty(data);
+ return NS_OK;
+}
+
+WindowHook::MessageData* WindowHook::Lookup(UINT nMsg) {
+ MessageDataArray::index_type idx;
+ for (idx = 0; idx < mMessageData.Length(); idx++) {
+ MessageData& data = mMessageData[idx];
+ if (data.nMsg == nMsg) return &data;
+ }
+ return nullptr;
+}
+
+WindowHook::MessageData* WindowHook::LookupOrCreate(UINT nMsg) {
+ MessageData* data = Lookup(nMsg);
+ if (!data) {
+ data = mMessageData.AppendElement();
+
+ if (!data) return nullptr;
+
+ data->nMsg = nMsg;
+ }
+ return data;
+}
+
+void WindowHook::DeleteIfEmpty(MessageData* data) {
+ // Never remove a MessageData that has still a hook or monitor entries.
+ if (data->hook || !data->monitors.IsEmpty()) return;
+
+ MessageDataArray::index_type idx;
+ idx = data - mMessageData.Elements();
+ NS_ASSERTION(
+ idx < mMessageData.Length(),
+ "Attempted to delete MessageData that doesn't belong to this array!");
+ mMessageData.RemoveElementAt(idx);
+}
+
+bool WindowHook::Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult) {
+ MessageData* data = Lookup(nMsg);
+ if (!data) return false;
+
+ uint32_t length = data->monitors.Length();
+ for (uint32_t midx = 0; midx < length; midx++) {
+ data->monitors[midx].Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult);
+ }
+
+ aResult.mConsumed =
+ data->hook.Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult);
+ return aResult.mConsumed;
+}
+
+bool WindowHook::CallbackData::Invoke(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult) {
+ if (!cb) return false;
+ return cb(context, hWnd, msg, wParam, lParam, aResult);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WindowHook.h b/widget/windows/WindowHook.h
new file mode 100644
index 0000000000..1d1f4b02da
--- /dev/null
+++ b/widget/windows/WindowHook.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_WindowHook_h__
+#define __mozilla_WindowHook_h__
+
+#include <windows.h>
+
+#include <nsHashKeys.h>
+#include <nsClassHashtable.h>
+#include <nsTArray.h>
+
+#include "nsAppShell.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class WindowHook {
+ public:
+ // It is expected that most callbacks will return false
+ typedef bool (*Callback)(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT* aResult);
+
+ nsresult AddHook(UINT nMsg, Callback callback, void* context);
+ nsresult RemoveHook(UINT nMsg, Callback callback, void* context);
+ nsresult AddMonitor(UINT nMsg, Callback callback, void* context);
+ nsresult RemoveMonitor(UINT nMsg, Callback callback, void* context);
+
+ private:
+ struct CallbackData {
+ Callback cb;
+ void* context;
+
+ CallbackData() : cb(nullptr), context(nullptr) {}
+ CallbackData(Callback cb, void* ctx) : cb(cb), context(ctx) {}
+ bool Invoke(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam,
+ LRESULT* aResult);
+ bool operator==(const CallbackData& rhs) const {
+ return cb == rhs.cb && context == rhs.context;
+ }
+ bool operator!=(const CallbackData& rhs) const { return !(*this == rhs); }
+ explicit operator bool() const { return !!cb; }
+ };
+
+ typedef nsTArray<CallbackData> CallbackDataArray;
+ struct MessageData {
+ UINT nMsg;
+ CallbackData hook;
+ CallbackDataArray monitors;
+ };
+
+ bool Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ MessageData* Lookup(UINT nMsg);
+ MessageData* LookupOrCreate(UINT nMsg);
+ void DeleteIfEmpty(MessageData* data);
+
+ typedef nsTArray<MessageData> MessageDataArray;
+ MessageDataArray mMessageData;
+
+ // For Notify
+ friend class ::nsWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // __mozilla_WindowHook_h__
diff --git a/widget/windows/WindowsConsole.cpp b/widget/windows/WindowsConsole.cpp
new file mode 100644
index 0000000000..c8e7eb1a11
--- /dev/null
+++ b/widget/windows/WindowsConsole.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "WindowsConsole.h"
+
+#include <windows.h>
+#include <fcntl.h>
+#include <cstdio>
+#include <io.h>
+
+namespace mozilla {
+
+static void AssignStdHandle(const char* aPath, const char* aMode, FILE* aStream,
+ DWORD aStdHandle) {
+ // Visual Studio's _fileno() returns -2 for the standard
+ // streams if they aren't associated with an output stream.
+ const int fd = _fileno(aStream);
+ if (fd == -2) {
+ freopen(aPath, aMode, aStream);
+ return;
+ }
+ if (fd < 0) {
+ return;
+ }
+
+ const HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
+ if (handle == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ const HANDLE oldHandle = GetStdHandle(aStdHandle);
+ if (handle == oldHandle) {
+ return;
+ }
+
+ SetStdHandle(aStdHandle, handle);
+}
+
+// This code attaches the process to the appropriate console.
+void UseParentConsole() {
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ // Redirect the standard streams to the existing console, but
+ // only if they haven't been redirected to a valid file.
+ AssignStdHandle("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE);
+ // There is no CONERR$, so use CONOUT$ for stderr as well.
+ AssignStdHandle("CONOUT$", "w", stderr, STD_ERROR_HANDLE);
+ AssignStdHandle("CONIN$", "r", stdin, STD_INPUT_HANDLE);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/windows/WindowsConsole.h b/widget/windows/WindowsConsole.h
new file mode 100644
index 0000000000..4b8ecf1823
--- /dev/null
+++ b/widget/windows/WindowsConsole.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WindowsConsole_h
+#define mozilla_WindowsConsole_h
+
+namespace mozilla {
+
+// This code attaches the process to the appropriate console.
+void UseParentConsole();
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsConsole_h
diff --git a/widget/windows/WindowsEMF.cpp b/widget/windows/WindowsEMF.cpp
new file mode 100644
index 0000000000..71e3631bea
--- /dev/null
+++ b/widget/windows/WindowsEMF.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#include "WindowsEMF.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowsEMF::WindowsEMF() : mEmf(nullptr), mDC(nullptr) {}
+
+WindowsEMF::~WindowsEMF() { ReleaseAllResource(); }
+
+bool WindowsEMF::InitForDrawing(const wchar_t* aMetafilePath /* = nullptr */) {
+ ReleaseAllResource();
+
+ mDC = ::CreateEnhMetaFile(nullptr, aMetafilePath, nullptr, nullptr);
+ return !!mDC;
+}
+
+bool WindowsEMF::InitFromFileContents(const wchar_t* aMetafilePath) {
+ MOZ_ASSERT(aMetafilePath);
+ ReleaseAllResource();
+
+ mEmf = ::GetEnhMetaFileW(aMetafilePath);
+ return !!mEmf;
+}
+
+bool WindowsEMF::InitFromFileContents(LPBYTE aBytes, UINT aSize) {
+ MOZ_ASSERT(aBytes && aSize != 0);
+ ReleaseAllResource();
+
+ mEmf = SetEnhMetaFileBits(aSize, aBytes);
+
+ return !!mEmf;
+}
+
+bool WindowsEMF::FinishDocument() {
+ if (mDC) {
+ mEmf = ::CloseEnhMetaFile(mDC);
+ mDC = nullptr;
+ }
+ return !!mEmf;
+}
+
+void WindowsEMF::ReleaseEMFHandle() {
+ if (mEmf) {
+ ::DeleteEnhMetaFile(mEmf);
+ mEmf = nullptr;
+ }
+}
+
+void WindowsEMF::ReleaseAllResource() {
+ FinishDocument();
+ ReleaseEMFHandle();
+}
+
+bool WindowsEMF::Playback(HDC aDeviceContext, const RECT& aRect) {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ return ::PlayEnhMetaFile(aDeviceContext, mEmf, &aRect) != 0;
+}
+
+bool WindowsEMF::SaveToFile() {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ ReleaseEMFHandle();
+ return true;
+}
+
+UINT WindowsEMF::GetEMFContentSize() {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ return GetEnhMetaFileBits(mEmf, 0, NULL);
+}
+
+bool WindowsEMF::GetEMFContentBits(LPBYTE aBytes) {
+ DebugOnly<bool> result = FinishDocument();
+ MOZ_ASSERT(result, "This function should be used after InitXXX.");
+
+ UINT emfSize = GetEMFContentSize();
+ if (GetEnhMetaFileBits(mEmf, emfSize, aBytes) != emfSize) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WindowsEMF.h b/widget/windows/WindowsEMF.h
new file mode 100644
index 0000000000..3a7a20173c
--- /dev/null
+++ b/widget/windows/WindowsEMF.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_WINDOWSEMF_H
+#define MOZILLA_WIDGET_WINDOWSEMF_H
+
+/* include windows.h for the HDC definitions that we need. */
+#include <windows.h>
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * Windows Enhance Metafile: https://en.wikipedia.org/wiki/Windows_Metafile
+ * A metafile, also called a vector image, is an image that is stored as a
+ * sequence of drawing commands and settings. The commands and settings
+ * recorded in a Metafile object can be stored in memory or saved to a file.
+ *
+ * The metafile device context is used for all drawing operations required to
+ * create the picture. When the system processes a GDI function associated with
+ * a metafile DC, it converts the function into the appropriate data and stores
+ * this data in a record appended to the metafile.
+ */
+class WindowsEMF {
+ public:
+ WindowsEMF();
+ ~WindowsEMF();
+
+ /**
+ * Initializes the object with the path of a file where the EMF data stream
+ * should be stored. Callers are then expected to call GetDC() to draw output
+ * before going on to call Playback() or SaveToFile() to generate the EMF
+ * output.
+ */
+ bool InitForDrawing(const wchar_t* aMetafilePath = nullptr);
+
+ /**
+ * Initializes the object with an existing EMF file. Consumers cannot use
+ * GetDC() to obtain an HDC to modify the file. They can only use Playback().
+ */
+ bool InitFromFileContents(const wchar_t* aMetafilePath);
+
+ /**
+ * Creates the EMF from the specified data
+ *
+ * @param aByte Pointer to a buffer that contains EMF data.
+ * @param aSize Specifies the size, in bytes, of aByte.
+ */
+ bool InitFromFileContents(PBYTE aBytes, UINT aSize);
+
+ /**
+ * If this object was initiaziled using InitForDrawing() then this function
+ * returns an HDC that can be drawn to generate the EMF output. Otherwise it
+ * returns null. After finishing with the HDC, consumers could call Playback()
+ * to draw EMF onto the given DC or call SaveToFile() to finish writing the
+ * EMF file.
+ */
+ HDC GetDC() const {
+ MOZ_ASSERT(mDC,
+ "GetDC can be used only after "
+ "InitForDrawing/ InitFromFileContents and before"
+ "Playback/ SaveToFile");
+ return mDC;
+ }
+
+ /**
+ * Play the EMF's drawing commands onto the given DC.
+ */
+ bool Playback(HDC aDeviceContext, const RECT& aRect);
+
+ /**
+ * Called to generate the EMF file once a consumer has finished drawing to
+ * the HDC returned by GetDC(), if initializes the object with the path of a
+ * file.
+ */
+ bool SaveToFile();
+
+ /**
+ * Return the size of the enhanced metafile, in bytes.
+ */
+ UINT GetEMFContentSize();
+
+ /**
+ * Retrieves the contents of the EMF and copies them into a buffer.
+ *
+ * @param aByte the buffer to receive the data.
+ */
+ bool GetEMFContentBits(PBYTE aBytes);
+
+ private:
+ WindowsEMF(const WindowsEMF& aEMF) = delete;
+ bool FinishDocument();
+ void ReleaseEMFHandle();
+ void ReleaseAllResource();
+
+ /* Compiled EMF data handle. */
+ HENHMETAFILE mEmf;
+ HDC mDC;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* MOZILLA_WIDGET_WINDOWSEMF_H */
diff --git a/widget/windows/WindowsEventLog.h b/widget/windows/WindowsEventLog.h
new file mode 100644
index 0000000000..e98d5077a0
--- /dev/null
+++ b/widget/windows/WindowsEventLog.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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_WindowsEventLog_h
+#define mozilla_WindowsEventLog_h
+
+/**
+ * Report messages to the Windows Event Log.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+/**
+ * This header is intended for self-contained, header-only, utility code for
+ * Win32. It may be used outside of xul.dll, in places such as
+ * default-browser-agent.exe or notificationrouter.dll. If your code creates
+ * dependencies on Mozilla libraries, you should put it elsewhere.
+ */
+
+#define MOZ_WIN_EVENT_LOG_ERROR(source, hr) \
+ mozilla::WriteWindowsEventLogHresult(source, hr, __FUNCTION__, __LINE__)
+#define MOZ_WIN_EVENT_LOG_ERROR_MESSAGE(source, format, ...) \
+ mozilla::WriteWindowsEventLogErrorMessage(source, format, __FUNCTION__, \
+ __LINE__, ##__VA_ARGS__)
+
+namespace mozilla {
+
+static void WriteWindowsEventLogErrorBuffer(const wchar_t* eventSourceName,
+ const wchar_t* buffer,
+ DWORD eventId) {
+ HANDLE source = RegisterEventSourceW(nullptr, eventSourceName);
+ if (!source) {
+ // Not much we can do about this.
+ return;
+ }
+
+ const wchar_t* stringsArray[] = {buffer};
+ ReportEventW(source, EVENTLOG_ERROR_TYPE, 0, eventId, nullptr, 1, 0,
+ stringsArray, nullptr);
+
+ DeregisterEventSource(source);
+}
+
+inline void WriteWindowsEventLogHresult(const wchar_t* eventSourceName,
+ HRESULT hr, const char* sourceFile,
+ int sourceLine) {
+ const wchar_t* format = L"0x%X in %S:%d";
+ int bufferSize = _scwprintf(format, hr, sourceFile, sourceLine);
+ ++bufferSize; // Extra character for terminating null
+ mozilla::UniquePtr<wchar_t[]> errorStr =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+
+ _snwprintf_s(errorStr.get(), bufferSize, _TRUNCATE, format, hr, sourceFile,
+ sourceLine);
+
+ WriteWindowsEventLogErrorBuffer(eventSourceName, errorStr.get(), hr);
+}
+
+MOZ_FORMAT_WPRINTF(1, 4)
+inline void WriteWindowsEventLogErrorMessage(const wchar_t* eventSourceName,
+ const wchar_t* messageFormat,
+ const char* sourceFile,
+ int sourceLine, ...) {
+ // First assemble the passed message
+ va_list ap;
+ va_start(ap, sourceLine);
+ int bufferSize = _vscwprintf(messageFormat, ap);
+ ++bufferSize; // Extra character for terminating null
+ va_end(ap);
+ mozilla::UniquePtr<wchar_t[]> message =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+
+ va_start(ap, sourceLine);
+ vswprintf(message.get(), bufferSize, messageFormat, ap);
+ va_end(ap);
+
+ // Next, assemble the complete error message to print
+ const wchar_t* errorFormat = L"Error: %s (%S:%d)";
+ bufferSize = _scwprintf(errorFormat, message.get(), sourceFile, sourceLine);
+ ++bufferSize; // Extra character for terminating null
+ mozilla::UniquePtr<wchar_t[]> errorStr =
+ mozilla::MakeUnique<wchar_t[]>(bufferSize);
+
+ _snwprintf_s(errorStr.get(), bufferSize, _TRUNCATE, errorFormat,
+ message.get(), sourceFile, sourceLine);
+
+ WriteWindowsEventLogErrorBuffer(eventSourceName, errorStr.get(), 0);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsEventLog_h
diff --git a/widget/windows/WindowsSMTCProvider.cpp b/widget/windows/WindowsSMTCProvider.cpp
new file mode 100644
index 0000000000..04d833a8e7
--- /dev/null
+++ b/widget/windows/WindowsSMTCProvider.cpp
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* mingw currently doesn't support windows.media.h, so we disable
+ * the whole related class until this is fixed.
+ * @TODO: Maybe contact MinGW Team for inclusion?*/
+#ifndef __MINGW32__
+
+# include "WindowsSMTCProvider.h"
+
+# include <windows.h>
+# include <windows.media.h>
+# include <wrl.h>
+
+# include "nsMimeTypes.h"
+# include "mozilla/Assertions.h"
+# include "mozilla/Logging.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/WidgetUtils.h"
+# include "mozilla/ScopeExit.h"
+# include "mozilla/dom/MediaControlUtils.h"
+# include "mozilla/media/MediaUtils.h"
+# include "nsThreadUtils.h"
+
+# pragma comment(lib, "runtimeobject.lib")
+
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Media;
+using namespace ABI::Windows::Storage::Streams;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace mozilla;
+
+# ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls
+# define RuntimeClass_Windows_Media_SystemMediaTransportControls \
+ L"Windows.Media.SystemMediaTransportControls"
+# endif
+
+# ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference
+# define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \
+ L"Windows.Storage.Streams.RandomAccessStreamReference"
+# endif
+
+# ifndef ISystemMediaTransportControlsInterop
+EXTERN_C const IID IID_ISystemMediaTransportControlsInterop;
+MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a")
+ISystemMediaTransportControlsInterop : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(
+ /* [in] */ __RPC__in HWND appWindow,
+ /* [in] */ __RPC__in REFIID riid,
+ /* [iid_is][retval][out] */
+ __RPC__deref_out_opt void** mediaTransportControl) = 0;
+};
+# endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */
+
+extern mozilla::LazyLogModule gMediaControlLog;
+
+# undef LOG
+# define LOG(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__))
+
+static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode(
+ SystemMediaTransportControlsButton keycode) {
+ switch (keycode) {
+ case SystemMediaTransportControlsButton_Play:
+ return Some(mozilla::dom::MediaControlKey::Play);
+ case SystemMediaTransportControlsButton_Pause:
+ return Some(mozilla::dom::MediaControlKey::Pause);
+ case SystemMediaTransportControlsButton_Next:
+ return Some(mozilla::dom::MediaControlKey::Nexttrack);
+ case SystemMediaTransportControlsButton_Previous:
+ return Some(mozilla::dom::MediaControlKey::Previoustrack);
+ case SystemMediaTransportControlsButton_Stop:
+ return Some(mozilla::dom::MediaControlKey::Stop);
+ case SystemMediaTransportControlsButton_FastForward:
+ return Some(mozilla::dom::MediaControlKey::Seekforward);
+ case SystemMediaTransportControlsButton_Rewind:
+ return Some(mozilla::dom::MediaControlKey::Seekbackward);
+ default:
+ return Nothing(); // Not supported Button
+ }
+}
+
+static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) {
+ MOZ_ASSERT(aAsyncOp);
+ IAsyncInfo* asyncInfo;
+ HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo,
+ reinterpret_cast<void**>(&asyncInfo));
+ // The assertion always works since IAsyncOperation implements IAsyncInfo
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ MOZ_ASSERT(asyncInfo);
+ return asyncInfo;
+}
+
+WindowsSMTCProvider::WindowsSMTCProvider() {
+ LOG("Creating an empty and invisible window");
+
+ // In order to create a SMTC-Provider, we need a hWnd, which shall be created
+ // dynamically from an invisible window. This leads to the following
+ // boilerplate code.
+ WNDCLASS wnd{};
+ wnd.lpszClassName = L"Firefox-MediaKeys";
+ wnd.hInstance = nullptr;
+ wnd.lpfnWndProc = DefWindowProc;
+ GetLastError(); // Clear the error
+ RegisterClass(&wnd);
+ MOZ_ASSERT(!GetLastError());
+
+ mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0,
+ CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr,
+ nullptr, nullptr, nullptr);
+ MOZ_ASSERT(mWindow);
+ MOZ_ASSERT(!GetLastError());
+}
+
+WindowsSMTCProvider::~WindowsSMTCProvider() {
+ // Dispose the window
+ MOZ_ASSERT(mWindow);
+ if (!DestroyWindow(mWindow)) {
+ LOG("Failed to destroy the hidden window. Error Code: %lu", GetLastError());
+ }
+ if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) {
+ // Note that this is logged when the class wasn't even registered.
+ LOG("Failed to unregister the class. Error Code: %lu", GetLastError());
+ }
+}
+
+bool WindowsSMTCProvider::IsOpened() const { return mInitialized; }
+
+bool WindowsSMTCProvider::Open() {
+ LOG("Opening Source");
+ MOZ_ASSERT(!mInitialized);
+
+ if (!InitDisplayAndControls()) {
+ LOG("Failed to initialize the SMTC and its display");
+ return false;
+ }
+
+ if (!UpdateButtons()) {
+ LOG("Failed to initialize the buttons");
+ return false;
+ }
+
+ if (!RegisterEvents()) {
+ LOG("Failed to register SMTC key-event listener");
+ return false;
+ }
+
+ if (!EnableControl(true)) {
+ LOG("Failed to enable SMTC control");
+ return false;
+ }
+
+ mInitialized = true;
+ SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
+ return mInitialized;
+}
+
+void WindowsSMTCProvider::Close() {
+ MediaControlKeySource::Close();
+ // Prevent calling Set methods when init failed
+ if (mInitialized) {
+ SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None);
+ UnregisterEvents();
+ ClearMetadata();
+ // We have observed an Windows issue, if we modify `mControls` , (such as
+ // setting metadata, disable buttons) before disabling control, and those
+ // operations are not done sequentially within a same main thread task,
+ // then it would cause a problem where the SMTC wasn't clean up completely
+ // and show the executable name.
+ EnableControl(false);
+ mInitialized = false;
+ }
+}
+
+void WindowsSMTCProvider::SetPlaybackState(
+ mozilla::dom::MediaSessionPlaybackState aState) {
+ MOZ_ASSERT(mInitialized);
+ MediaControlKeySource::SetPlaybackState(aState);
+
+ HRESULT hr;
+
+ // Note: we can't return the status of put_PlaybackStatus, but we can at least
+ // assert it.
+ switch (aState) {
+ case mozilla::dom::MediaSessionPlaybackState::Paused:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Paused);
+ break;
+ case mozilla::dom::MediaSessionPlaybackState::Playing:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Playing);
+ break;
+ case mozilla::dom::MediaSessionPlaybackState::None:
+ hr = mControls->put_PlaybackStatus(
+ ABI::Windows::Media::MediaPlaybackStatus_Stopped);
+ break;
+ // MediaPlaybackStatus still supports Closed and Changing, which we don't
+ // use (yet)
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Enum Inconsitency between PlaybackState and WindowsSMTCProvider");
+ break;
+ }
+
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+}
+
+void WindowsSMTCProvider::SetMediaMetadata(
+ const mozilla::dom::MediaMetadataBase& aMetadata) {
+ MOZ_ASSERT(mInitialized);
+ SetMusicMetadata(aMetadata.mArtist, aMetadata.mTitle);
+ LoadThumbnail(aMetadata.mArtwork);
+}
+
+void WindowsSMTCProvider::ClearMetadata() {
+ MOZ_ASSERT(mDisplay);
+ if (FAILED(mDisplay->ClearAll())) {
+ LOG("Failed to clear SMTC display");
+ }
+ mImageFetchRequest.DisconnectIfExists();
+ CancelPendingStoreAsyncOperation();
+ mThumbnailUrl.Truncate();
+ mProcessingUrl.Truncate();
+ mNextImageIndex = 0;
+ mSupportedKeys = 0;
+}
+
+void WindowsSMTCProvider::SetSupportedMediaKeys(
+ const MediaKeysArray& aSupportedKeys) {
+ MOZ_ASSERT(mInitialized);
+
+ uint32_t supportedKeys = 0;
+ for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
+ supportedKeys |= GetMediaKeyMask(key);
+ }
+
+ if (supportedKeys == mSupportedKeys) {
+ LOG("Supported keys stay the same");
+ return;
+ }
+
+ LOG("Update supported keys");
+ mSupportedKeys = supportedKeys;
+ UpdateButtons();
+}
+
+void WindowsSMTCProvider::UnregisterEvents() {
+ if (mControls && mButtonPressedToken.value != 0) {
+ mControls->remove_ButtonPressed(mButtonPressedToken);
+ }
+}
+
+bool WindowsSMTCProvider::RegisterEvents() {
+ MOZ_ASSERT(mControls);
+ auto self = RefPtr<WindowsSMTCProvider>(this);
+ auto callbackbtnPressed = Callback<
+ ITypedEventHandler<SystemMediaTransportControls*,
+ SystemMediaTransportControlsButtonPressedEventArgs*>>(
+ [this, self](ISystemMediaTransportControls*,
+ ISystemMediaTransportControlsButtonPressedEventArgs* pArgs)
+ -> HRESULT {
+ MOZ_ASSERT(pArgs);
+ SystemMediaTransportControlsButton btn;
+
+ if (FAILED(pArgs->get_Button(&btn))) {
+ LOG("SystemMediaTransportControls: ButtonPressedEvent - Could "
+ "not get Button.");
+ return S_OK; // Propagating the error probably wouldn't help.
+ }
+
+ Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn);
+ if (keyCode.isSome() && IsOpened()) {
+ OnButtonPressed(keyCode.value());
+ }
+ return S_OK;
+ });
+
+ if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(),
+ &mButtonPressedToken))) {
+ LOG("SystemMediaTransportControls: Failed at "
+ "registerEvents().add_ButtonPressed()");
+ return false;
+ }
+
+ return true;
+}
+
+void WindowsSMTCProvider::OnButtonPressed(
+ mozilla::dom::MediaControlKey aKey) const {
+ if (!IsKeySupported(aKey)) {
+ LOG("key: %s is not supported", ToMediaControlKeyStr(aKey));
+ return;
+ }
+
+ for (auto& listener : mListeners) {
+ listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
+ }
+}
+
+bool WindowsSMTCProvider::EnableControl(bool aEnabled) const {
+ MOZ_ASSERT(mControls);
+ return SUCCEEDED(mControls->put_IsEnabled(aEnabled));
+}
+
+bool WindowsSMTCProvider::UpdateButtons() const {
+ static const mozilla::dom::MediaControlKey kKeys[] = {
+ mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause,
+ mozilla::dom::MediaControlKey::Previoustrack,
+ mozilla::dom::MediaControlKey::Nexttrack,
+ mozilla::dom::MediaControlKey::Stop};
+
+ bool success = true;
+ for (const mozilla::dom::MediaControlKey& key : kKeys) {
+ if (!EnableKey(key, IsKeySupported(key))) {
+ success = false;
+ LOG("Failed to set %s=%s", ToMediaControlKeyStr(key),
+ IsKeySupported(key) ? "true" : "false");
+ }
+ }
+
+ return success;
+}
+
+bool WindowsSMTCProvider::IsKeySupported(
+ mozilla::dom::MediaControlKey aKey) const {
+ return mSupportedKeys & GetMediaKeyMask(aKey);
+}
+
+bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey,
+ bool aEnable) const {
+ MOZ_ASSERT(mControls);
+ switch (aKey) {
+ case mozilla::dom::MediaControlKey::Play:
+ return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Pause:
+ return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Previoustrack:
+ return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Nexttrack:
+ return SUCCEEDED(mControls->put_IsNextEnabled(aEnable));
+ case mozilla::dom::MediaControlKey::Stop:
+ return SUCCEEDED(mControls->put_IsStopEnabled(aEnable));
+ default:
+ LOG("No button for %s", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+}
+
+bool WindowsSMTCProvider::InitDisplayAndControls() {
+ // As Open() might be called multiple times, "cache" the results of the COM
+ // API
+ if (mControls && mDisplay) {
+ return true;
+ }
+ ComPtr<ISystemMediaTransportControlsInterop> interop;
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls)
+ .Get(),
+ interop.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("SystemMediaTransportControls: Failed at instantiating the "
+ "Interop object");
+ return false;
+ }
+ MOZ_ASSERT(interop);
+
+ if (!mControls && FAILED(interop->GetForWindow(
+ mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) {
+ LOG("SystemMediaTransportControls: Failed at GetForWindow()");
+ return false;
+ }
+ MOZ_ASSERT(mControls);
+
+ if (!mDisplay &&
+ FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) {
+ LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()");
+ }
+
+ MOZ_ASSERT(mDisplay);
+ return true;
+}
+
+bool WindowsSMTCProvider::SetMusicMetadata(const nsString& aArtist,
+ const nsString& aTitle) {
+ MOZ_ASSERT(mDisplay);
+ ComPtr<IMusicDisplayProperties> musicProps;
+
+ HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to get music properties");
+ return false;
+ }
+
+ hr = musicProps->put_Artist(HStringReference(aArtist.get()).Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set the music's artist");
+ return false;
+ }
+
+ hr = musicProps->put_Title(HStringReference(aTitle.get()).Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set the music's title");
+ return false;
+ }
+
+ hr = mDisplay->Update();
+ if (FAILED(hr)) {
+ LOG("Failed to refresh the display");
+ return false;
+ }
+
+ return true;
+}
+
+void WindowsSMTCProvider::LoadThumbnail(
+ const nsTArray<mozilla::dom::MediaImage>& aArtwork) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // TODO: Sort the images by the preferred size or format.
+ mArtwork = aArtwork;
+ mNextImageIndex = 0;
+
+ // Abort the loading if
+ // 1) thumbnail is being updated, and one in processing is in the artwork
+ // 2) thumbnail is not being updated, and one in use is in the artwork
+ if (!mProcessingUrl.IsEmpty()) {
+ LOG("Load thumbnail while image: %s is being processed",
+ NS_ConvertUTF16toUTF8(mProcessingUrl).get());
+ if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) {
+ LOG("No need to load thumbnail. The one being processed is in the "
+ "artwork");
+ return;
+ }
+ } else if (!mThumbnailUrl.IsEmpty()) {
+ if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) {
+ LOG("No need to load thumbnail. The one in use is in the artwork");
+ return;
+ }
+ }
+
+ // If there is a pending image store operation, that image must be different
+ // from the new image will be loaded below, so the pending one should be
+ // cancelled.
+ CancelPendingStoreAsyncOperation();
+ // Remove the current thumbnail on the interface
+ ClearThumbnail();
+ // Then load the new thumbnail asynchronously
+ LoadImageAtIndex(mNextImageIndex++);
+}
+
+void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIndex >= mArtwork.Length()) {
+ LOG("Stop loading thumbnail. No more available images");
+ mImageFetchRequest.DisconnectIfExists();
+ mProcessingUrl.Truncate();
+ return;
+ }
+
+ const mozilla::dom::MediaImage& image = mArtwork[aIndex];
+
+ // TODO: No need to fetch the default image and do image processing since the
+ // the default image is local file and it's trustworthy. For the default
+ // image, we can use `CreateFromFile` to create the IRandomAccessStream. We
+ // should probably cache it since it could be used very often (Bug 1643102)
+
+ if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
+ LOG("Skip the image with invalid URL. Try next image");
+ mImageFetchRequest.DisconnectIfExists();
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ mImageFetchRequest.DisconnectIfExists();
+ mProcessingUrl = image.mSrc;
+
+ mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
+ RefPtr<WindowsSMTCProvider> self = this;
+ mImageFetcher->FetchImage()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ [this, self](const nsCOMPtr<imgIContainer>& aImage) {
+ LOG("The image is fetched successfully");
+ mImageFetchRequest.Complete();
+
+ // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a
+ // png image with transparent background will be converted into a
+ // jpeg/bmp file with a colored background. IMAGE_PNG format seems
+ // to be the best choice for now.
+ uint32_t size = 0;
+ char* src = nullptr;
+ // Only used to hold the image data
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mozilla::dom::GetEncodedImageBuffer(
+ aImage, nsLiteralCString(IMAGE_PNG),
+ getter_AddRefs(inputStream), &size, &src);
+ if (NS_FAILED(rv) || !inputStream || size == 0 || !src) {
+ LOG("Failed to get the image buffer info. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ LoadImage(src, size);
+ },
+ [this, self](bool) {
+ LOG("Failed to fetch image. Try next image");
+ mImageFetchRequest.Complete();
+ LoadImageAtIndex(mNextImageIndex++);
+ })
+ ->Track(mImageFetchRequest);
+}
+
+void WindowsSMTCProvider::LoadImage(const char* aImageData,
+ uint32_t aDataSize) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1. Use mImageDataWriter to write the binary data of image into mImageStream
+ // 2. Refer the image by mImageStreamReference and then set it to the SMTC
+ // In case of the race condition between they are being destroyed and the
+ // async operation for image loading, mImageDataWriter, mImageStream, and
+ // mImageStreamReference are member variables
+
+ HRESULT hr = ActivateInstance(
+ HStringReference(
+ RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream)
+ .Get(),
+ mImageStream.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to make mImageStream refer to an instance of "
+ "InMemoryRandomAccessStream");
+ return;
+ }
+
+ ComPtr<IOutputStream> outputStream;
+ hr = mImageStream.As(&outputStream);
+ if (FAILED(hr)) {
+ LOG("Failed when query IOutputStream interface from mImageStream");
+ return;
+ }
+
+ ComPtr<IDataWriterFactory> dataWriterFactory;
+ hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(),
+ dataWriterFactory.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to get an activation factory for IDataWriterFactory");
+ return;
+ }
+
+ hr = dataWriterFactory->CreateDataWriter(outputStream.Get(),
+ mImageDataWriter.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to create mImageDataWriter that writes data to mImageStream");
+ return;
+ }
+
+ hr = mImageDataWriter->WriteBytes(
+ aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData)));
+ if (FAILED(hr)) {
+ LOG("Failed to write data to mImageStream");
+ return;
+ }
+
+ hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation);
+ if (FAILED(hr)) {
+ LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation");
+ return;
+ }
+
+ // Upon the image is stored in mImageStream, set the image to the SMTC
+ // interface
+ auto onStoreCompleted = Callback<
+ IAsyncOperationCompletedHandler<unsigned int>>(
+ [this, self = RefPtr<WindowsSMTCProvider>(this),
+ aImageUrl = nsString(mProcessingUrl)](
+ IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aStatus != AsyncStatus::Completed) {
+ LOG("Asynchronous operation is not completed");
+ return E_ABORT;
+ }
+
+ HRESULT hr = S_OK;
+ IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp);
+ asyncInfo->get_ErrorCode(&hr);
+ if (FAILED(hr)) {
+ LOG("Failed to get termination status of the asynchronous operation");
+ return hr;
+ }
+
+ if (!UpdateThumbnail(aImageUrl)) {
+ LOG("Failed to update thumbnail");
+ }
+
+ // If an error occurs above:
+ // - If aImageUrl is not mProcessingUrl. It's fine.
+ // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset.
+ // Therefore the thumbnail will remain empty until a new image whose
+ // url is different from mProcessingUrl is loaded.
+
+ return S_OK;
+ });
+
+ hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get());
+ if (FAILED(hr)) {
+ LOG("Failed to set callback on completeing the asynchronous operation");
+ }
+}
+
+bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) {
+ MOZ_ASSERT(mDisplay);
+ MOZ_ASSERT(mImageStream);
+ MOZ_ASSERT(!aUrl.IsEmpty());
+
+ ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory;
+
+ HRESULT hr = GetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference)
+ .Get(),
+ streamRefFactory.GetAddressOf());
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] {
+ LOG("Clean mThumbnailUrl");
+ mThumbnailUrl.Truncate();
+ });
+
+ if (FAILED(hr)) {
+ LOG("Failed to get an activation factory for "
+ "IRandomAccessStreamReferenceStatics type");
+ return false;
+ }
+
+ hr = streamRefFactory->CreateFromStream(mImageStream.Get(),
+ mImageStreamReference.GetAddressOf());
+ if (FAILED(hr)) {
+ LOG("Failed to create mImageStreamReference from mImageStream");
+ return false;
+ }
+
+ hr = mDisplay->put_Thumbnail(mImageStreamReference.Get());
+ if (FAILED(hr)) {
+ LOG("Failed to update thumbnail");
+ return false;
+ }
+
+ hr = mDisplay->Update();
+ if (FAILED(hr)) {
+ LOG("Failed to refresh display");
+ return false;
+ }
+
+ // No need to clean mThumbnailUrl since thumbnail is set successfully
+ cleanup.release();
+ mThumbnailUrl = aUrl;
+
+ return true;
+}
+
+void WindowsSMTCProvider::ClearThumbnail() {
+ MOZ_ASSERT(mDisplay);
+ HRESULT hr = mDisplay->put_Thumbnail(nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ hr = mDisplay->Update();
+ MOZ_ASSERT(SUCCEEDED(hr));
+ Unused << hr;
+ mThumbnailUrl.Truncate();
+}
+
+bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsOpened()) {
+ LOG("Abort the thumbnail update: SMTC is closed");
+ return false;
+ }
+
+ if (aUrl != mProcessingUrl) {
+ LOG("Abort the thumbnail update: The image from %s is out of date",
+ NS_ConvertUTF16toUTF8(aUrl).get());
+ return false;
+ }
+
+ mProcessingUrl.Truncate();
+
+ if (!SetThumbnail(aUrl)) {
+ LOG("Failed to update thumbnail");
+ return false;
+ }
+
+ MOZ_ASSERT(mThumbnailUrl == aUrl);
+ LOG("The thumbnail is updated to the image from: %s",
+ NS_ConvertUTF16toUTF8(mThumbnailUrl).get());
+ return true;
+}
+
+void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const {
+ if (mStoreAsyncOperation) {
+ IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get());
+ asyncInfo->Cancel();
+ }
+}
+
+#endif // __MINGW32__
diff --git a/widget/windows/WindowsSMTCProvider.h b/widget/windows/WindowsSMTCProvider.h
new file mode 100644
index 0000000000..3926618d1f
--- /dev/null
+++ b/widget/windows/WindowsSMTCProvider.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
+#define WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
+
+#ifndef __MINGW32__
+
+# include <functional>
+# include <Windows.Media.h>
+# include <wrl.h>
+
+# include "mozilla/dom/FetchImageHelper.h"
+# include "mozilla/dom/MediaController.h"
+# include "mozilla/dom/MediaControlKeySource.h"
+# include "mozilla/UniquePtr.h"
+
+using ISMTC = ABI::Windows::Media::ISystemMediaTransportControls;
+using SMTCProperty = ABI::Windows::Media::SystemMediaTransportControlsProperty;
+using ISMTCDisplayUpdater =
+ ABI::Windows::Media::ISystemMediaTransportControlsDisplayUpdater;
+
+using ABI::Windows::Foundation::IAsyncOperation;
+using ABI::Windows::Storage::Streams::IDataWriter;
+using ABI::Windows::Storage::Streams::IRandomAccessStream;
+using ABI::Windows::Storage::Streams::IRandomAccessStreamReference;
+using Microsoft::WRL::ComPtr;
+
+class WindowsSMTCProvider final : public mozilla::dom::MediaControlKeySource {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowsSMTCProvider, override)
+
+ public:
+ WindowsSMTCProvider();
+
+ bool IsOpened() const override;
+ bool Open() override;
+ void Close() override;
+
+ void SetPlaybackState(
+ mozilla::dom::MediaSessionPlaybackState aState) override;
+
+ void SetMediaMetadata(
+ const mozilla::dom::MediaMetadataBase& aMetadata) override;
+
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override;
+
+ private:
+ ~WindowsSMTCProvider();
+ void UnregisterEvents();
+ bool RegisterEvents();
+
+ void OnButtonPressed(mozilla::dom::MediaControlKey aKey) const;
+ // Enable the SMTC interface
+ bool EnableControl(bool aEnabled) const;
+ // Sets the play, pause, next, previous buttons on the SMTC interface by
+ // mSupportedKeys
+ bool UpdateButtons() const;
+ bool IsKeySupported(mozilla::dom::MediaControlKey aKey) const;
+ bool EnableKey(mozilla::dom::MediaControlKey aKey, bool aEnable) const;
+
+ bool InitDisplayAndControls();
+
+ // Sets the Metadata for the currently playing media and sets the playback
+ // type to "MUSIC"
+ bool SetMusicMetadata(const nsString& aArtist, const nsString& aTitle);
+
+ // Sets one of the artwork to the SMTC interface asynchronously
+ void LoadThumbnail(const nsTArray<mozilla::dom::MediaImage>& aArtwork);
+ // Stores the image at index aIndex of the mArtwork to the Thumbnail
+ // asynchronously
+ void LoadImageAtIndex(const size_t aIndex);
+ // Stores the raw binary data of an image to mImageStream and set it to the
+ // Thumbnail asynchronously
+ void LoadImage(const char* aImageData, uint32_t aDataSize);
+ // Sets the Thumbnail to the image stored in mImageStream
+ bool SetThumbnail(const nsAString& aUrl);
+ void ClearThumbnail();
+
+ bool UpdateThumbnail(const nsAString& aUrl);
+ void CancelPendingStoreAsyncOperation() const;
+
+ void ClearMetadata();
+
+ bool mInitialized = false;
+
+ // A bit table indicating what keys are enabled
+ uint32_t mSupportedKeys = 0;
+
+ ComPtr<ISMTC> mControls;
+ ComPtr<ISMTCDisplayUpdater> mDisplay;
+
+ // Use mImageDataWriter to write the binary data of image into mImageStream
+ // and refer the image by mImageStreamReference and then set it to the SMTC
+ // interface
+ ComPtr<IDataWriter> mImageDataWriter;
+ ComPtr<IRandomAccessStream> mImageStream;
+ ComPtr<IRandomAccessStreamReference> mImageStreamReference;
+ ComPtr<IAsyncOperation<unsigned int>> mStoreAsyncOperation;
+
+ // mThumbnailUrl is the url of the current Thumbnail
+ // mProcessingUrl is the url that is being processed. The process starts from
+ // fetching an image from the url and then storing the fetched image to the
+ // mImageStream. If mProcessingUrl is not empty, it means there is an image is
+ // in processing
+ // mThumbnailUrl and mProcessingUrl won't be set at the same time and they can
+ // only be touched on main thread
+ nsString mThumbnailUrl;
+ nsString mProcessingUrl;
+
+ // mArtwork can only be used in main thread in case of data racing
+ CopyableTArray<mozilla::dom::MediaImage> mArtwork;
+ size_t mNextImageIndex;
+
+ mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher;
+ mozilla::MozPromiseRequestHolder<mozilla::dom::ImagePromise>
+ mImageFetchRequest;
+
+ HWND mWindow; // handle to the invisible window
+
+ // EventRegistrationTokens are used to have a handle on a callback (to remove
+ // it again)
+ EventRegistrationToken mButtonPressedToken;
+};
+
+#endif // __MINGW32__
+#endif // WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_
diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
new file mode 100644
index 0000000000..3d5cff7e23
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -0,0 +1,809 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <windows.h>
+#include <wrl.h>
+
+#include "nsServiceManagerUtils.h"
+
+#include "WindowsUIUtils.h"
+
+#include "nsIObserverService.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/media/MediaUtils.h"
+#include "nsString.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsPIDOMWindow.h"
+#include "nsWindowGfx.h"
+#include "Units.h"
+
+/* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it
+ * until it's fixed. */
+
+// See
+// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/winrt/windows.ui.viewmanagement.h
+// for the source of some of these definitions for older SDKs.
+#ifndef __MINGW32__
+
+# include <inspectable.h>
+# include <roapi.h>
+# include <windows.ui.viewmanagement.h>
+
+# pragma comment(lib, "runtimeobject.lib")
+
+using namespace ABI::Windows::UI;
+using namespace ABI::Windows::UI::ViewManagement;
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::ApplicationModel::DataTransfer;
+
+# ifndef RuntimeClass_Windows_UI_ViewManagement_UIViewSettings
+# define RuntimeClass_Windows_UI_ViewManagement_UIViewSettings \
+ L"Windows.UI.ViewManagement.UIViewSettings"
+# endif
+
+# ifndef IUIViewSettingsInterop
+
+using IUIViewSettingsInterop = interface IUIViewSettingsInterop;
+
+MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
+IUIViewSettingsInterop : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid,
+ void** ppv) = 0;
+};
+# endif
+
+# ifndef __IDataTransferManagerInterop_INTERFACE_DEFINED__
+# define __IDataTransferManagerInterop_INTERFACE_DEFINED__
+
+using IDataTransferManagerInterop = interface IDataTransferManagerInterop;
+
+MIDL_INTERFACE("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8")
+IDataTransferManagerInterop : public IUnknown {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(
+ HWND appWindow, REFIID riid, void** dataTransferManager) = 0;
+ virtual HRESULT STDMETHODCALLTYPE ShowShareUIForWindow(HWND appWindow) = 0;
+};
+
+# endif
+
+# if !defined( \
+ ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__)
+# define ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__
+
+MIDL_INTERFACE("13a24ec8-9382-536f-852a-3045e1b29a3b")
+IDataPackage4 : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE add_ShareCanceled(
+ __FITypedEventHandler_2_Windows__CApplicationModel__CDataTransfer__CDataPackage_IInspectable *
+ handler,
+ EventRegistrationToken * token) = 0;
+ virtual HRESULT STDMETHODCALLTYPE remove_ShareCanceled(
+ EventRegistrationToken token) = 0;
+};
+
+# endif
+
+# ifndef RuntimeClass_Windows_UI_ViewManagement_UISettings
+# define RuntimeClass_Windows_UI_ViewManagement_UISettings \
+ L"Windows.UI.ViewManagement.UISettings"
+# endif
+# if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION < 0x80000
+namespace ABI {
+namespace Windows {
+namespace UI {
+namespace ViewManagement {
+
+class UISettings;
+class UISettingsAutoHideScrollBarsChangedEventArgs;
+interface IUISettingsAutoHideScrollBarsChangedEventArgs;
+MIDL_INTERFACE("87afd4b2-9146-5f02-8f6b-06d454174c0f")
+IUISettingsAutoHideScrollBarsChangedEventArgs : public IInspectable{};
+
+} // namespace ViewManagement
+} // namespace UI
+} // namespace Windows
+} // namespace ABI
+
+namespace ABI {
+namespace Windows {
+namespace Foundation {
+
+template <>
+struct __declspec(uuid("808aef30-2660-51b0-9c11-f75dd42006b4"))
+ ITypedEventHandler<ABI::Windows::UI::ViewManagement::UISettings*,
+ ABI::Windows::UI::ViewManagement::
+ UISettingsAutoHideScrollBarsChangedEventArgs*>
+ : ITypedEventHandler_impl<
+ ABI::Windows::Foundation::Internal::AggregateType<
+ ABI::Windows::UI::ViewManagement::UISettings*,
+ ABI::Windows::UI::ViewManagement::IUISettings*>,
+ ABI::Windows::Foundation::Internal::AggregateType<
+ ABI::Windows::UI::ViewManagement::
+ UISettingsAutoHideScrollBarsChangedEventArgs*,
+ ABI::Windows::UI::ViewManagement::
+ IUISettingsAutoHideScrollBarsChangedEventArgs*>> {
+ static const wchar_t* z_get_rc_name_impl() {
+ return L"Windows.Foundation.TypedEventHandler`2<Windows.UI.ViewManagement."
+ L"UISettings, "
+ L"Windows.UI.ViewManagement."
+ L"UISettingsAutoHideScrollBarsChangedEventArgs>";
+ }
+};
+// Define a typedef for the parameterized interface specialization's mangled
+// name. This allows code which uses the mangled name for the parameterized
+// interface to access the correct parameterized interface specialization.
+typedef ITypedEventHandler<ABI::Windows::UI::ViewManagement::UISettings*,
+ ABI::Windows::UI::ViewManagement::
+ UISettingsAutoHideScrollBarsChangedEventArgs*>
+ __FITypedEventHandler_2_Windows__CUI__CViewManagement__CUISettings_Windows__CUI__CViewManagement__CUISettingsAutoHideScrollBarsChangedEventArgs_t;
+# define __FITypedEventHandler_2_Windows__CUI__CViewManagement__CUISettings_Windows__CUI__CViewManagement__CUISettingsAutoHideScrollBarsChangedEventArgs \
+ ABI::Windows::Foundation:: \
+ __FITypedEventHandler_2_Windows__CUI__CViewManagement__CUISettings_Windows__CUI__CViewManagement__CUISettingsAutoHideScrollBarsChangedEventArgs_t
+
+} // namespace Foundation
+} // namespace Windows
+} // namespace ABI
+
+namespace ABI {
+namespace Windows {
+namespace UI {
+namespace ViewManagement {
+class UISettings;
+class UISettingsAutoHideScrollBarsChangedEventArgs;
+interface IUISettings5;
+MIDL_INTERFACE("5349d588-0cb5-5f05-bd34-706b3231f0bd")
+IUISettings5 : public IInspectable {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE get_AutoHideScrollBars(boolean * value) = 0;
+ virtual HRESULT STDMETHODCALLTYPE add_AutoHideScrollBarsChanged(
+ __FITypedEventHandler_2_Windows__CUI__CViewManagement__CUISettings_Windows__CUI__CViewManagement__CUISettingsAutoHideScrollBarsChangedEventArgs *
+ handler,
+ EventRegistrationToken * token) = 0;
+ virtual HRESULT STDMETHODCALLTYPE remove_AutoHideScrollBarsChanged(
+ EventRegistrationToken token) = 0;
+};
+} // namespace ViewManagement
+} // namespace UI
+} // namespace Windows
+} // namespace ABI
+# endif
+#endif
+
+using namespace mozilla;
+
+enum class TabletModeState : uint8_t { Unknown, Off, On };
+static TabletModeState sInTabletModeState;
+
+WindowsUIUtils::WindowsUIUtils() = default;
+WindowsUIUtils::~WindowsUIUtils() = default;
+
+NS_IMPL_ISUPPORTS(WindowsUIUtils, nsIWindowsUIUtils)
+
+NS_IMETHODIMP
+WindowsUIUtils::GetSystemSmallIconSize(int32_t* aSize) {
+ NS_ENSURE_ARG(aSize);
+
+ mozilla::LayoutDeviceIntSize size =
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon);
+ *aSize = std::max(size.width, size.height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::GetSystemLargeIconSize(int32_t* aSize) {
+ NS_ENSURE_ARG(aSize);
+
+ mozilla::LayoutDeviceIntSize size =
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon);
+ *aSize = std::max(size.width, size.height);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::SetWindowIcon(mozIDOMWindowProxy* aWindow,
+ imgIContainer* aSmallIcon,
+ imgIContainer* aBigIcon) {
+ NS_ENSURE_ARG(aWindow);
+
+ nsCOMPtr<nsIWidget> widget =
+ nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget();
+ nsWindow* window = static_cast<nsWindow*>(widget.get());
+
+ nsresult rv;
+
+ if (aSmallIcon) {
+ HICON hIcon = nullptr;
+ rv = nsWindowGfx::CreateIcon(
+ aSmallIcon, false, mozilla::LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ window->SetSmallIcon(hIcon);
+ }
+
+ if (aBigIcon) {
+ HICON hIcon = nullptr;
+ rv = nsWindowGfx::CreateIcon(
+ aBigIcon, false, mozilla::LayoutDeviceIntPoint(),
+ nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ window->SetBigIcon(hIcon);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::SetWindowIconFromExe(mozIDOMWindowProxy* aWindow,
+ const nsAString& aExe, uint16_t aIndex) {
+ NS_ENSURE_ARG(aWindow);
+
+ nsCOMPtr<nsIWidget> widget =
+ nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget();
+ nsWindow* window = static_cast<nsWindow*>(widget.get());
+
+ HICON icon = ::LoadIconW(::GetModuleHandleW(PromiseFlatString(aExe).get()),
+ MAKEINTRESOURCEW(aIndex));
+ window->SetBigIcon(icon);
+ window->SetSmallIcon(icon);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::SetWindowIconNoData(mozIDOMWindowProxy* aWindow) {
+ NS_ENSURE_ARG(aWindow);
+
+ nsCOMPtr<nsIWidget> widget =
+ nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget();
+ nsWindow* window = static_cast<nsWindow*>(widget.get());
+
+ window->SetSmallIconNoData();
+ window->SetBigIconNoData();
+
+ return NS_OK;
+}
+
+bool WindowsUIUtils::GetInTabletMode() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ if (sInTabletModeState == TabletModeState::Unknown) {
+ UpdateInTabletMode();
+ }
+ return sInTabletModeState == TabletModeState::On;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::GetInTabletMode(bool* aResult) {
+ *aResult = GetInTabletMode();
+ return NS_OK;
+}
+
+static IInspectable* GetUISettings() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+#ifndef __MINGW32__
+ // We need to keep this alive for ~ever so that change callbacks work as
+ // expected, sigh.
+ static StaticRefPtr<IInspectable> sUiSettingsAsInspectable;
+
+ if (!sUiSettingsAsInspectable) {
+ ComPtr<IInspectable> uiSettingsAsInspectable;
+ ::RoActivateInstance(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_UISettings)
+ .Get(),
+ &uiSettingsAsInspectable);
+ if (NS_WARN_IF(!uiSettingsAsInspectable)) {
+ return nullptr;
+ }
+
+ ComPtr<IUISettings5> uiSettings5;
+ if (SUCCEEDED(uiSettingsAsInspectable.As(&uiSettings5))) {
+ EventRegistrationToken unusedToken;
+ auto callback = Callback<ITypedEventHandler<
+ UISettings*, UISettingsAutoHideScrollBarsChangedEventArgs*>>(
+ [](auto...) {
+ // Scrollbar sizes change layout.
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::StyleAndLayout);
+ return S_OK;
+ });
+ (void)NS_WARN_IF(FAILED(uiSettings5->add_AutoHideScrollBarsChanged(
+ callback.Get(), &unusedToken)));
+ }
+
+ ComPtr<IUISettings2> uiSettings2;
+ if (SUCCEEDED(uiSettingsAsInspectable.As(&uiSettings2))) {
+ EventRegistrationToken unusedToken;
+ auto callback =
+ Callback<ITypedEventHandler<UISettings*, IInspectable*>>([](auto...) {
+ // Text scale factor changes style and layout.
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::StyleAndLayout);
+ return S_OK;
+ });
+ (void)NS_WARN_IF(FAILED(uiSettings2->add_TextScaleFactorChanged(
+ callback.Get(), &unusedToken)));
+ }
+
+ ComPtr<IUISettings3> uiSettings3;
+ if (SUCCEEDED(uiSettingsAsInspectable.As(&uiSettings3))) {
+ EventRegistrationToken unusedToken;
+ auto callback =
+ Callback<ITypedEventHandler<UISettings*, IInspectable*>>([](auto...) {
+ // System color changes change style only.
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::Style);
+ return S_OK;
+ });
+ (void)NS_WARN_IF(FAILED(
+ uiSettings3->add_ColorValuesChanged(callback.Get(), &unusedToken)));
+ }
+
+ ComPtr<IUISettings4> uiSettings4;
+ if (SUCCEEDED(uiSettingsAsInspectable.As(&uiSettings4))) {
+ EventRegistrationToken unusedToken;
+ auto callback =
+ Callback<ITypedEventHandler<UISettings*, IInspectable*>>([](auto...) {
+ // Transparent effects changes change media queries only.
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::MediaQueriesOnly);
+ return S_OK;
+ });
+ (void)NS_WARN_IF(FAILED(uiSettings4->add_AdvancedEffectsEnabledChanged(
+ callback.Get(), &unusedToken)));
+ }
+
+ sUiSettingsAsInspectable = dont_AddRef(uiSettingsAsInspectable.Detach());
+ ClearOnShutdown(&sUiSettingsAsInspectable);
+ }
+
+ return sUiSettingsAsInspectable.get();
+#else
+ return nullptr;
+#endif
+}
+
+Maybe<nscolor> WindowsUIUtils::GetAccentColor(int aTone) {
+ MOZ_ASSERT(aTone >= -3);
+ MOZ_ASSERT(aTone <= 3);
+#ifndef __MINGW32__
+ ComPtr<IInspectable> settings = GetUISettings();
+ if (NS_WARN_IF(!settings)) {
+ return Nothing();
+ }
+ ComPtr<IUISettings3> uiSettings3;
+ if (NS_WARN_IF(FAILED(settings.As(&uiSettings3)))) {
+ return Nothing();
+ }
+ Color color;
+ auto colorType = UIColorType(int(UIColorType_Accent) + aTone);
+ if (NS_WARN_IF(FAILED(uiSettings3->GetColorValue(colorType, &color)))) {
+ return Nothing();
+ }
+ return Some(NS_RGBA(color.R, color.G, color.B, color.A));
+#else
+ return Nothing();
+#endif
+}
+
+Maybe<nscolor> WindowsUIUtils::GetSystemColor(ColorScheme aScheme,
+ int aSysColor) {
+#ifndef __MINGW32__
+ if (!StaticPrefs::widget_windows_uwp_system_colors_enabled()) {
+ return Nothing();
+ }
+
+ // https://docs.microsoft.com/en-us/windows/apps/design/style/color
+ // Is a useful resource to see which values have decent contrast.
+ if (StaticPrefs::widget_windows_uwp_system_colors_highlight_accent()) {
+ if (aSysColor == COLOR_HIGHLIGHT) {
+ int tone = aScheme == ColorScheme::Light ? 0 : -1;
+ if (auto c = GetAccentColor(tone)) {
+ return c;
+ }
+ }
+ if (aSysColor == COLOR_HIGHLIGHTTEXT && GetAccentColor()) {
+ return Some(NS_RGBA(255, 255, 255, 255));
+ }
+ }
+
+ if (aScheme == ColorScheme::Dark) {
+ // There are no explicitly dark colors in UWP, other than the highlight
+ // colors above.
+ return Nothing();
+ }
+
+ auto knownType = [&]() -> Maybe<UIElementType> {
+# define MAP(_win32, _uwp) \
+ case COLOR_##_win32: \
+ return Some(UIElementType_##_uwp)
+ switch (aSysColor) {
+ MAP(HIGHLIGHT, Highlight);
+ MAP(HIGHLIGHTTEXT, HighlightText);
+ MAP(ACTIVECAPTION, ActiveCaption);
+ MAP(BTNFACE, ButtonFace);
+ MAP(BTNTEXT, ButtonText);
+ MAP(CAPTIONTEXT, CaptionText);
+ MAP(GRAYTEXT, GrayText);
+ MAP(HOTLIGHT, Hotlight);
+ MAP(INACTIVECAPTION, InactiveCaption);
+ MAP(INACTIVECAPTIONTEXT, InactiveCaptionText);
+ MAP(WINDOW, Window);
+ MAP(WINDOWTEXT, WindowText);
+ default:
+ return Nothing();
+ }
+# undef MAP
+ }();
+ if (!knownType) {
+ return Nothing();
+ }
+ ComPtr<IInspectable> settings = GetUISettings();
+ if (NS_WARN_IF(!settings)) {
+ return Nothing();
+ }
+ ComPtr<IUISettings> uiSettings;
+ if (NS_WARN_IF(FAILED(settings.As(&uiSettings)))) {
+ return Nothing();
+ }
+ Color color;
+ if (NS_WARN_IF(FAILED(uiSettings->UIElementColor(*knownType, &color)))) {
+ return Nothing();
+ }
+ return Some(NS_RGBA(color.R, color.G, color.B, color.A));
+#else
+ return Nothing();
+#endif
+}
+bool WindowsUIUtils::ComputeOverlayScrollbars() {
+#ifndef __MINGW32__
+ if (!IsWin11OrLater()) {
+ // While in theory Windows 10 supports overlay scrollbar settings, it's off
+ // by default and it's untested whether our Win10 scrollbar drawing code
+ // deals with it properly.
+ return false;
+ }
+ if (!StaticPrefs::widget_windows_overlay_scrollbars_enabled()) {
+ return false;
+ }
+ ComPtr<IInspectable> settings = GetUISettings();
+ if (NS_WARN_IF(!settings)) {
+ return false;
+ }
+ ComPtr<IUISettings5> uiSettings5;
+ if (NS_WARN_IF(FAILED(settings.As(&uiSettings5)))) {
+ return false;
+ }
+ boolean autoHide = false;
+ if (NS_WARN_IF(FAILED(uiSettings5->get_AutoHideScrollBars(&autoHide)))) {
+ return false;
+ }
+ return autoHide;
+#else
+ return false;
+#endif
+}
+
+double WindowsUIUtils::ComputeTextScaleFactor() {
+#ifndef __MINGW32__
+ ComPtr<IInspectable> settings = GetUISettings();
+ if (NS_WARN_IF(!settings)) {
+ return 1.0;
+ }
+ ComPtr<IUISettings2> uiSettings2;
+ if (NS_WARN_IF(FAILED(settings.As(&uiSettings2)))) {
+ return false;
+ }
+ double scaleFactor = 1.0;
+ if (NS_WARN_IF(FAILED(uiSettings2->get_TextScaleFactor(&scaleFactor)))) {
+ return 1.0;
+ }
+ return scaleFactor;
+#else
+ return 1.0;
+#endif
+}
+
+bool WindowsUIUtils::ComputeTransparencyEffects() {
+ constexpr bool kDefault = true;
+#ifndef __MINGW32__
+ ComPtr<IInspectable> settings = GetUISettings();
+ if (NS_WARN_IF(!settings)) {
+ return kDefault;
+ }
+ ComPtr<IUISettings4> uiSettings4;
+ if (NS_WARN_IF(FAILED(settings.As(&uiSettings4)))) {
+ return kDefault;
+ }
+ boolean transparencyEffects = kDefault;
+ if (NS_WARN_IF(FAILED(
+ uiSettings4->get_AdvancedEffectsEnabled(&transparencyEffects)))) {
+ return kDefault;
+ }
+ return transparencyEffects;
+#else
+ return kDefault;
+#endif
+}
+
+void WindowsUIUtils::UpdateInTabletMode() {
+#ifndef __MINGW32__
+ nsresult rv;
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> widget;
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+
+ rv = winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (NS_FAILED(rv) || !navWin) {
+ // Fall back to the hidden window
+ nsCOMPtr<nsIAppShellService> appShell(
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+
+ rv = appShell->GetHiddenDOMWindow(getter_AddRefs(navWin));
+ if (NS_FAILED(rv) || !navWin) {
+ return;
+ }
+ }
+
+ nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
+ widget = widget::WidgetUtils::DOMWindowToWidget(win);
+
+ if (!widget) {
+ return;
+ }
+
+ HWND winPtr = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ ComPtr<IUIViewSettingsInterop> uiViewSettingsInterop;
+
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_UIViewSettings)
+ .Get(),
+ &uiViewSettingsInterop);
+ if (FAILED(hr)) {
+ return;
+ }
+ ComPtr<IUIViewSettings> uiViewSettings;
+ hr = uiViewSettingsInterop->GetForWindow(winPtr,
+ IID_PPV_ARGS(&uiViewSettings));
+ if (FAILED(hr)) {
+ return;
+ }
+ UserInteractionMode mode;
+ hr = uiViewSettings->get_UserInteractionMode(&mode);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ TabletModeState oldTabletModeState = sInTabletModeState;
+ sInTabletModeState = mode == UserInteractionMode_Touch ? TabletModeState::On
+ : TabletModeState::Off;
+ if (sInTabletModeState != oldTabletModeState) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(nullptr, "tablet-mode-change",
+ sInTabletModeState == TabletModeState::On
+ ? u"tablet-mode"
+ : u"normal-mode");
+ }
+#endif
+}
+
+#ifndef __MINGW32__
+struct HStringDeleter {
+ using pointer = HSTRING;
+ void operator()(pointer aString) { WindowsDeleteString(aString); }
+};
+
+using HStringUniquePtr = UniquePtr<HSTRING, HStringDeleter>;
+
+Result<HStringUniquePtr, HRESULT> ConvertToWindowsString(
+ const nsAString& aStr) {
+ HSTRING rawStr;
+ HRESULT hr = WindowsCreateString(PromiseFlatString(aStr).get(), aStr.Length(),
+ &rawStr);
+ if (FAILED(hr)) {
+ return Err(hr);
+ }
+ return HStringUniquePtr(rawStr);
+}
+
+static Result<Ok, nsresult> RequestShare(
+ const std::function<HRESULT(IDataRequestedEventArgs* pArgs)>& aCallback) {
+ HWND hwnd = GetForegroundWindow();
+ if (!hwnd) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ ComPtr<IDataTransferManagerInterop> dtmInterop;
+ ComPtr<IDataTransferManager> dtm;
+
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(
+ RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager)
+ .Get(),
+ IID_PPV_ARGS(&dtmInterop));
+ if (FAILED(hr) ||
+ FAILED(dtmInterop->GetForWindow(hwnd, IID_PPV_ARGS(&dtm)))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ auto callback = Callback<
+ ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
+ [aCallback](IDataTransferManager*,
+ IDataRequestedEventArgs* pArgs) -> HRESULT {
+ return aCallback(pArgs);
+ });
+
+ EventRegistrationToken dataRequestedToken;
+ if (FAILED(dtm->add_DataRequested(callback.Get(), &dataRequestedToken)) ||
+ FAILED(dtmInterop->ShowShareUIForWindow(hwnd))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ return Ok();
+}
+
+static Result<Ok, nsresult> AddShareEventListeners(
+ const RefPtr<mozilla::media::Refcountable<MozPromiseHolder<SharePromise>>>&
+ aPromiseHolder,
+ const ComPtr<IDataPackage>& aDataPackage) {
+ ComPtr<IDataPackage3> spDataPackage3;
+
+ if (FAILED(aDataPackage.As(&spDataPackage3))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ auto completedCallback =
+ Callback<ITypedEventHandler<DataPackage*, ShareCompletedEventArgs*>>(
+ [aPromiseHolder](IDataPackage*,
+ IShareCompletedEventArgs*) -> HRESULT {
+ aPromiseHolder->Resolve(true, __func__);
+ return S_OK;
+ });
+
+ EventRegistrationToken dataRequestedToken;
+ if (FAILED(spDataPackage3->add_ShareCompleted(completedCallback.Get(),
+ &dataRequestedToken))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ ComPtr<IDataPackage4> spDataPackage4;
+ if (SUCCEEDED(aDataPackage.As(&spDataPackage4))) {
+ // Use SharedCanceled API only on supported versions of Windows
+ // So that the older ones can still use ShareUrl()
+
+ auto canceledCallback =
+ Callback<ITypedEventHandler<DataPackage*, IInspectable*>>(
+ [aPromiseHolder](IDataPackage*, IInspectable*) -> HRESULT {
+ aPromiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return S_OK;
+ });
+
+ if (FAILED(spDataPackage4->add_ShareCanceled(canceledCallback.Get(),
+ &dataRequestedToken))) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ return Ok();
+}
+#endif
+
+RefPtr<SharePromise> WindowsUIUtils::Share(nsAutoString aTitle,
+ nsAutoString aText,
+ nsAutoString aUrl) {
+ auto promiseHolder = MakeRefPtr<
+ mozilla::media::Refcountable<MozPromiseHolder<SharePromise>>>();
+ RefPtr<SharePromise> promise = promiseHolder->Ensure(__func__);
+
+#ifndef __MINGW32__
+ auto result = RequestShare([promiseHolder, title = std::move(aTitle),
+ text = std::move(aText), url = std::move(aUrl)](
+ IDataRequestedEventArgs* pArgs) {
+ ComPtr<IDataRequest> spDataRequest;
+ ComPtr<IDataPackage> spDataPackage;
+ ComPtr<IDataPackage2> spDataPackage2;
+ ComPtr<IDataPackagePropertySet> spDataPackageProperties;
+
+ if (FAILED(pArgs->get_Request(&spDataRequest)) ||
+ FAILED(spDataRequest->get_Data(&spDataPackage)) ||
+ FAILED(spDataPackage.As(&spDataPackage2)) ||
+ FAILED(spDataPackage->get_Properties(&spDataPackageProperties))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ /*
+ * Windows always requires a title, and an empty string does not work.
+ * Thus we trick the API by passing a whitespace when we have no title.
+ * https://docs.microsoft.com/en-us/windows/uwp/app-to-app/share-data
+ */
+ auto wTitle = ConvertToWindowsString((title.IsVoid() || title.Length() == 0)
+ ? nsAutoString(u" "_ns)
+ : title);
+ if (wTitle.isErr() ||
+ FAILED(spDataPackageProperties->put_Title(wTitle.unwrap().get()))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ // Assign even if empty, as Windows requires some data to share
+ auto wText = ConvertToWindowsString(text);
+ if (wText.isErr() || FAILED(spDataPackage->SetText(wText.unwrap().get()))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ if (!url.IsVoid()) {
+ auto wUrl = ConvertToWindowsString(url);
+ if (wUrl.isErr()) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return wUrl.unwrapErr();
+ }
+
+ ComPtr<IUriRuntimeClassFactory> uriFactory;
+ ComPtr<IUriRuntimeClass> uri;
+
+ auto hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(),
+ &uriFactory);
+
+ if (FAILED(hr) ||
+ FAILED(uriFactory->CreateUri(wUrl.unwrap().get(), &uri)) ||
+ FAILED(spDataPackage2->SetWebLink(uri.Get()))) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+ }
+
+ if (!StaticPrefs::widget_windows_share_wait_action_enabled()) {
+ promiseHolder->Resolve(true, __func__);
+ } else if (AddShareEventListeners(promiseHolder, spDataPackage).isErr()) {
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+ return E_FAIL;
+ }
+
+ return S_OK;
+ });
+ if (result.isErr()) {
+ promiseHolder->Reject(result.unwrapErr(), __func__);
+ }
+#else
+ promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
+#endif
+
+ return promise;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::ShareUrl(const nsAString& aUrlToShare,
+ const nsAString& aShareTitle) {
+ nsAutoString text;
+ text.SetIsVoid(true);
+ WindowsUIUtils::Share(nsAutoString(aShareTitle), text,
+ nsAutoString(aUrlToShare));
+ return NS_OK;
+}
diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h
new file mode 100644
index 0000000000..a55f92c8da
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsString.h"
+#include "nsColor.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+
+using SharePromise =
+ mozilla::MozPromise<bool, nsresult, /* IsExclusive */ true>;
+
+namespace mozilla {
+enum class ColorScheme : uint8_t;
+}
+
+class WindowsUIUtils final : public nsIWindowsUIUtils {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWSUIUTILS
+
+ WindowsUIUtils();
+
+ static RefPtr<SharePromise> Share(nsAutoString aTitle, nsAutoString aText,
+ nsAutoString aUrl);
+
+ static void UpdateInTabletMode();
+ static bool GetInTabletMode();
+
+ // Gets the system accent color, or one of the darker / lighter variants
+ // (darker = -1/2/3, lighter=+1/2/3, values outside of that range are
+ // disallowed).
+ static mozilla::Maybe<nscolor> GetAccentColor(int aTone = 0);
+ static mozilla::Maybe<nscolor> GetSystemColor(mozilla::ColorScheme, int);
+
+ // Use LookAndFeel for a cached getter.
+ static bool ComputeOverlayScrollbars();
+ static double ComputeTextScaleFactor();
+ static bool ComputeTransparencyEffects();
+
+ protected:
+ ~WindowsUIUtils();
+};
+
+#endif // mozilla_widget_WindowsUIUtils_h__
diff --git a/widget/windows/components.conf b/widget/windows/components.conf
new file mode 100644
index 0000000000..e5089ed9e1
--- /dev/null
+++ b/widget/windows/components.conf
@@ -0,0 +1,220 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Headers = [
+ '/widget/windows/nsWidgetFactory.h',
+]
+
+InitFunc = 'nsWidgetWindowsModuleCtor'
+UnloadFunc = 'nsWidgetWindowsModuleDtor'
+
+Classes = [
+ {
+ 'cid': '{4c9dee4a-b083-4261-8bbe-c6883d2a6bc9}',
+ 'contract_ids': ['@mozilla.org/gfx/parent/screenmanager;1'],
+ 'singleton': True,
+ 'type': 'mozilla::widget::ScreenManager',
+ 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton',
+ 'headers': ['/widget/ScreenManager.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS,
+ },
+ {
+ 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}',
+ 'contract_ids': ['@mozilla.org/widget/appshell/win;1'],
+ 'headers': ['/widget/windows/nsWidgetFactory.h'],
+ 'legacy_constructor': 'nsAppShellConstructor',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS,
+ },
+ {
+ 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}',
+ 'contract_ids': ['@mozilla.org/widget/useridleservice;1'],
+ 'singleton': True,
+ 'type': 'nsUserIdleServiceWin',
+ 'constructor': 'nsUserIdleServiceWin::GetInstance',
+ 'headers': ['/widget/windows/nsUserIdleServiceWin.h', 'nsUserIdleService.h'],
+ },
+ {
+ 'cid': '{b148eed2-236d-11d3-b35c-00a0cc3c1cde}',
+ 'contract_ids': ['@mozilla.org/sound;1'],
+ 'singleton': True,
+ 'type': 'nsISound',
+ 'constructor': 'nsSound::GetInstance',
+ 'headers': ['/widget/windows/nsSound.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{b8e5bc54-a22f-4eb2-b061-24cb6d19c15f}',
+ 'contract_ids': ['@mozilla.org/windows-taskbar;1'],
+ 'type': 'mozilla::widget::WinTaskbar',
+ 'headers': ['/widget/windows/WinTaskbar.h'],
+ },
+ {
+ 'cid': '{73a5946f-608d-454f-9d33-0b8f8c7294b6}',
+ 'contract_ids': ['@mozilla.org/windows-legacyjumplistbuilder;1'],
+ 'type': 'mozilla::widget::LegacyJumpListBuilder',
+ 'headers': ['/widget/windows/LegacyJumpListBuilder.h'],
+ },
+ {
+ 'cid': '{2b9a1f2c-27ce-45b6-8d4e-755d0e34f8db}',
+ 'contract_ids': ['@mozilla.org/windows-legacyjumplistitem;1'],
+ 'type': 'mozilla::widget::LegacyJumpListItem',
+ 'headers': ['/widget/windows/LegacyJumpListItem.h'],
+ },
+ {
+ 'cid': '{21f1f13b-f75a-42ad-867a-d91ad694447e}',
+ 'contract_ids': ['@mozilla.org/windows-legacyjumplistseparator;1'],
+ 'type': 'mozilla::widget::LegacyJumpListSeparator',
+ 'headers': ['/widget/windows/LegacyJumpListItem.h'],
+ },
+ {
+ 'cid': '{f72c5dc4-5a12-47be-be28-ab105f33b08f}',
+ 'contract_ids': ['@mozilla.org/windows-legacyjumplistlink;1'],
+ 'type': 'mozilla::widget::LegacyJumpListLink',
+ 'headers': ['/widget/windows/LegacyJumpListItem.h'],
+ },
+ {
+ 'cid': '{b16656b2-5187-498f-abf4-56346126bfdb}',
+ 'contract_ids': ['@mozilla.org/windows-legacyjumplistshortcut;1'],
+ 'type': 'mozilla::widget::LegacyJumpListShortcut',
+ 'headers': ['/widget/windows/LegacyJumpListItem.h'],
+ },
+ {
+ 'cid': '{e04a55e8-fee3-4ea2-a98b-41d2621adc3c}',
+ 'contract_ids': ['@mozilla.org/windows-ui-utils;1'],
+ 'type': 'WindowsUIUtils',
+ 'headers': ['/widget/windows/WindowsUIUtils.h'],
+ },
+ {
+ 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/transferable;1'],
+ 'type': 'nsTransferable',
+ 'headers': ['/widget/nsTransferable.h'],
+ },
+ {
+ 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'],
+ 'type': 'nsHTMLFormatConverter',
+ 'headers': ['/widget/nsHTMLFormatConverter.h'],
+ },
+ {
+ 'cid': '{f92e733e-33a3-4752-90e5-25801ddeaf7b}',
+ 'contract_ids': ['@mozilla.org/widget/parent/dragservice;1'],
+ 'type': 'nsDragService',
+ 'headers': ['/widget/windows/nsDragService.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{9a0cb62b-d638-4faf-9588-ae96f5e29093}',
+ 'contract_ids': ['@mozilla.org/widget/taskbar-preview-callback;1'],
+ 'type': 'mozilla::widget::TaskbarPreviewCallback',
+ 'headers': ['/widget/windows/TaskbarPreview.h'],
+ },
+ {
+ 'name': 'GfxInfo',
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/windows/GfxInfo.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS,
+ },
+ {
+ 'cid': '{e2fc3e45-c893-4b34-8f6d-b87faf65a897}',
+ 'contract_ids': ['@mozilla.org/parent/filepicker;1'],
+ 'type': 'nsFilePicker',
+ 'headers': ['/widget/windows/nsFilePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{035d92f3-3802-4cf5-87cb-1758bfc5d4da}',
+ 'contract_ids': ['@mozilla.org/parent/colorpicker;1'],
+ 'type': 'nsColorPicker',
+ 'headers': ['/widget/windows/nsColorPicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{1201d357-8417-4926-a694-e6408fbedcf8}',
+ 'contract_ids': ['@mozilla.org/sharepicker;1'],
+ 'type': 'nsSharePicker',
+ 'headers': ['/widget/windows/nsSharePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{25b4efa0-7054-4787-9cd6-630efb3fe6fa}',
+ 'contract_ids': ['@mozilla.org/widget/parent/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{b6e1a890-b2b8-4883-a65f-9476f6185313}',
+ 'contract_ids': ['@mozilla.org/widget/systemstatusbar;1'],
+ 'singleton': True,
+ 'init_method': 'Init',
+ 'type': 'mozilla::widget::SystemStatusBar',
+ 'constructor': 'mozilla::widget::SystemStatusBar::GetAddRefedSingleton',
+ 'headers': ['/widget/windows/SystemStatusBar.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+]
+
+if buildconfig.substs['CC_TYPE'] == 'clang-cl':
+ Classes += [
+ {
+ 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/system-alerts-service;1'],
+ 'type': 'mozilla::widget::ToastNotification',
+ 'headers': ['/widget/windows/ToastNotification.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{a46c385b-a45c-4b48-ab7c-aaed1252bb83}',
+ 'contract_ids': ['@mozilla.org/windows-alert-notification;1'],
+ 'type': 'mozilla::widget::WindowsAlertNotification',
+ 'headers': ['/widget/windows/ToastNotification.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ }
+ ]
+
+if defined('NS_PRINTING'):
+ Classes += [
+ {
+ 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}',
+ 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'],
+ 'type': 'nsDeviceContextSpecWin',
+ 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}',
+ 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'],
+ 'type': 'nsPrintDialogServiceWin',
+ 'headers': ['/widget/windows/nsPrintDialogWin.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}',
+ 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'],
+ 'type': 'nsPrintSettingsServiceWin',
+ 'headers': ['/widget/windows/nsPrintSettingsServiceWin.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{a6cf9129-15b3-11d2-932e-00805f8add32}',
+ 'contract_ids': ['@mozilla.org/gfx/printerlist;1'],
+ 'type': 'nsPrinterListWin',
+ 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ ]
diff --git a/widget/windows/docs/blocklist.rst b/widget/windows/docs/blocklist.rst
new file mode 100644
index 0000000000..5450faaa1b
--- /dev/null
+++ b/widget/windows/docs/blocklist.rst
@@ -0,0 +1,347 @@
+========================
+Windows DLL Blocklisting
+========================
+
+--------
+Overview
+--------
+
+There are many applications which interact with another application, which means
+they run their code as a DLL in a different process. This technique is used, for
+example, when an antivirus software tries to monitor/block navigation to a
+malicious website, or a screen reader tries to access UI parts. If such an
+application injects their code into Firefox, and if there is a bug in their code
+running in our firefox.exe, it will emerge as Firefox’s bug even though it’s
+not.
+
+Firefox for Windows has a feature to prevent DLLs from being loaded into our
+processes. If we are aware that a particular DLL causes a problem in our
+processes such as a crash or performance degradation, we can stop the problem by
+blocking the DLL from being loaded.
+
+This blocklist is about a third-party application which runs outside Firefox but
+interacts with Firefox. For add-ons, there is `a different process
+<https://extensionworkshop.com/documentation/publish/add-ons-blocking-process/>`_.
+
+This page explains how to request to block a DLL which you think we should block
+it as well as technical details about the feature.
+
+-----------------------
+Two types of blocklists
+-----------------------
+
+There are two types of blocklists in Firefox:
+
+1. A static blocklist that is compiled in to Firefox. This consists of DLLs
+ known to cause problems with Firefox, and this blocklist cannot be disabled
+ by the user. For more information and instructions on how to add a new DLL
+ to this list, see :ref:`Process for blocking a DLL in the static blocklist
+ <how-to-block-dll-in-static-blocklist>` below.
+2. A dynamic blocklist that users can use to block DLLs that are giving them
+ problems. This was added in
+ `bug 1744362 <https://bugzilla.mozilla.org/show_bug.cgi?id=1744362>`_.
+
+The static blocklist has ways to specify if only certain versions of a DLL
+should be blocked, or only for certain Firefox processes, etc. The dynamic
+blocklist does not have this capability; if a DLL is on the list it will always
+be blocked.
+
+Regardless of which blocklist the DLL is on, if it meets the criteria for being
+blocked Firefox uses the same mechanism to block it. There are more details
+below in :ref:`How the blocklist blocks a DLL <how-the-blocklist-blocks-a-dll>`.
+
+.. _how-to-block-dll-in-static-blocklist:
+
+--------------------------------------------------
+Process for blocking a DLL in the static blocklist
+--------------------------------------------------
+
+But wait, should we really block it?
+------------------------------------
+
+Blocking a DLL with the static blocklist should be our last resort to fix a
+problem because doing it normally breaks functionality of an application which
+installed the DLL. If there is another option, we should always go for it.
+Sometimes we can safely bypass a third-party’s problem by changing our code even
+though its root cause is not on our side.
+
+When we decide to block it, we must be certain that the issue at hand is so
+great that it outweighs the user's choice to install the software, the utility
+it provides, and the vendor's freedom to distribute and control their software.
+
+How to request to block a DLL
+-----------------------------
+
+Our codebase has the file named
+`WindowsDllBlocklistDefs.in <https://searchfox.org/mozilla-central/source/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in>`_ from which our build process generates DLL blocklists as C++ header files and compiles them. To block a new DLL, you create a patch to update WindowsDllBlocklistDefs.in and land it on our codebase, following our standard development process. Moreover, you need to fill out a form specific to the DLL blockling request so that reviewers can review the impact and risk as well as the patch itself.
+
+Here are the steps:
+
+1. File `a bug
+ <https://bugzilla.mozilla.org/enter_bug.cgi?format=__default__&bug_type=defect&product=Toolkit&component=Blocklist%20Policy%20Requests&op_sys=Windows&short_desc=DLL%20block%20request%3A%20%3CDLL%20name%3E&comment=Please%20go%20through%20https%3A%2F%2Fwiki.mozilla.org%2FBlocklisting%2FDLL%20before%20filing%20a%20new%20bug.>`_
+ if it does not exist.
+2. Answer all the questions in `this questionnaire
+ <https://msmania.github.io/assets/mozilla/third-party-modules/questionnaire.txt>`_,
+ and attach it to the bug as a plaintext.
+3. Make a patch and start a code review via Phabricator as usual.
+
+How to edit WindowsDllBlocklistDefs.in
+--------------------------------------
+
+WindowsDllBlocklistDefs.in defines several variables as a Python Array. When you
+add a new entry in the blocklists, you pick one of the variables and add an
+entry in the following syntax:
+
+Syntax
+******
+
+::
+
+ Variable += [
+ ...
+ # One-liner comment including a bug number
+ EntryType(Name, Version, Flags),
+ ...
+ ]
+
+Parameters
+**********
+
++-----------+--------------------------------------------------------------------------------+
+| Parameter | Value |
++===========+================================================================================+
+| Variable | ALL_PROCESSES \| BROWSER_PROCESS \| CHILD_PROCESSES \| GMPLUGIN_PROCESSES \| |
+| | GPU_PROCESSES \| SOCKET_PROCESSES \| UTILITY_PROCESSES |
++-----------+--------------------------------------------------------------------------------+
+| EntryType | DllBlocklistEntry \| A11yBlocklistEntry \| RedirectToNoOpEntryPoint |
++-----------+--------------------------------------------------------------------------------+
+| Name | A case-insensitive string representing a DLL's filename to block |
++-----------+--------------------------------------------------------------------------------+
+| Version | One of the following formats: |
+| | |
+| | - ALL_VERSIONS \| UNVERSIONED |
+| | - A tuple consisting of four digits |
+| | - A 32-bit integer representing a Unix timestamp with PETimeStamp |
++-----------+--------------------------------------------------------------------------------+
+
+Variable
+********
+
+Choose one of the following predefined variables.
+
+- **ALL_PROCESSES**: DLLs defined here are blocked in BROWSER_PROCESS +
+ CHILD_PROCESSES
+- **BROWSER_PROCESS**: DLLs defined here are blocked in the browser process
+- **CHILD_PROCESSES**: DLLs defined here are blocked in non-browser processes
+- **GMPLUGIN_PROCESSES**: DLLs defined here are blocked in GMPlugin processes
+- **GPU_PROCESSES**: DLLs defined here are blocked in GPU processes
+- **SOCKET_PROCESSES**: DLLs defined here are blocked in socket processes
+- **UTILITY_PROCESSES**: DLLs defined here are blocked in utility processes
+
+EntryType
+*********
+Choose one of the following predefined EntryTypes.
+
+- **DllBlocklistEntry**: Use this EntryType unless your case matches the other
+ EntryTypes.
+- **A11yBlocklistEntry**: If you want to block a module only when it’s loaded by
+ an accessibility application such as a screen reader, you can use this
+ EntryType.
+- **RedirectToNoOpEntryPoint**: If a modules is injected via Import Directory
+ Table, adding the module as DllBlocklistEntry breaks process launch, meaning
+ DllBlocklistEntry is not an option. You can use RedirectToNoOpEntryPoint
+ instead.
+
+Name
+****
+A case-insensitive string representing a DLL's filename to block. Don’t include a directory name.
+
+Version
+*******
+
+A maximum version to be blocked. If you specify a value, a module with the
+specified version, older versions, and a module with no version are blocked.
+
+| If you want to block a module regardless of its version, use ALL_VERSIONS.
+| If you want to block a module with no version, use UNVERSIONED.
+
+
+To specify a version, you can use either of the following formats:
+
+- | A tuple consisting of four digits. This is compared to the version that is embedded in a DLL as a version resource.
+ | Example: (1, 2, 3, 4)
+- | A 32-bit integer representing a Unix timestamp with PETimeStamp. This is compared to an integer of IMAGE_FILE_HEADER::TimeDateStamp.
+ | Example: PETimeStamp(0x12345678)
+
+
+-----------------
+Technical details
+-----------------
+
+.. _how-the-blocklist-blocks-a-dll:
+
+How the blocklist blocks a DLL
+------------------------------
+
+Briefly speaking, we make ntdll!NtMapViewOfSection return
+``STATUS_ACCESS_DENIED`` if a given module is on the blocklist, thereby a
+third-party’s code, or even Firefox’s legitimate code, which tries to load a DLL
+in our processes in any way such as LoadLibrary API fails and receives an
+access-denied error.
+
+Cases where we should not block a module
+----------------------------------------
+
+As our blocklist works as explained above, there are the cases where we should not block a module.
+
+- | A module is loaded via `Import Directory Table <https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table>`_
+ | Blocking this type of module blocks even a process from launching. You may be able to block this type of module with RedirectToNoOpEntryPoint.
+- | A module is loaded as a `Layered Service Provider <https://docs.microsoft.com/en-us/windows/win32/winsock/categorizing-layered-service-providers-and-applications>`_
+ | Blocking this type of module on Windows 8 or newer breaks networking. Blocking a LSP on Windows 7 is ok.
+
+(we used to have to avoid blocking modules loaded via a
+`Window hook <https://docs.microsoft.com/en-us/windows/win32/winmsg/hooks>`_ because blocking this type of
+module would cause repetitive attempts to load a module, resulting in slow performance
+like `Bug 1633718 <https://bugzilla.mozilla.org/show_bug.cgi?id=1633718>`_, but this should be fixed
+as of `Bug 1823412 <https://bugzilla.mozilla.org/show_bug.cgi?id=1823412>`_.)
+
+Third-party-module ping
+-----------------------
+
+We’re collecting the :ref:`third-party-module ping <third-party-modules-ping>`
+which captures a moment when a third-party module is loaded into the
+Browser/Tab/RDD process. As it’s asked in the request form, it’s important to
+check the third-party-module ping and see whether a module we want to block
+appears in the ping or not. If it appears, you may be able to know how a module
+is loaded by looking at a callstack in the ping.
+
+How to view callstacks in the ping
+**********************************
+
+1. You can run a query on BigQuery console or STMO. (BigQuery console is much
+ faster and can handle larger data.)
+
+ - BigQuery console (visit
+ `here <https://docs.telemetry.mozilla.org/cookbooks/bigquery.html#gcp-bigquery-console>`_
+ to request access): https://console.cloud.google.com/bigquery
+ - STMO: https://sql.telemetry.mozilla.org/
+
+2. Make your own query based on `this template
+ <https://msmania.github.io/assets/mozilla/third-party-modules/query-template.txt>`_.
+3. Run the query.
+4. Save the result as a JSON file.
+
+ - In BigQuery console, click [SAVE RESULTS] and choose [JSON (local file)].
+ - In STMO, click [...] at the right-top corner and select [Show API Key],
+ then you can download a JSON from a URL shown in the [Results in JSON format].
+
+5. | Go to https://msmania.github.io/assets/mozilla/third-party-modules/
+ | (A temporal link. Need to find a permanent place.)
+6. Click [Upload JSON] and select the file you saved at the step 4.
+7. Click a row in the table to view a callstack
+
+
+How to see the versions of a specific module in the ping
+********************************************************
+
+You can use `this template query
+<https://msmania.github.io/assets/mozilla/third-party-modules/query-groupby-template.txt>`_
+to query which versions of a specific module are captured in the ping. This
+tells the product versions which are actively used including the crashing
+versions and the working versions.
+
+You can also get the crashing versions by querying the crash reports or the
+Socorro table. Having two version lists, you can decide whether you can specify
+the Version parameter in a blocklist entry.
+
+Initialization
+--------------
+
+In order to have the most effective blocking of DLLs, the blocklist is
+initialized very early during browser startup. If the :ref:`launcher process
+<launcher-process>` is available, the steps are:
+
+- Launcher process loads dynamic blocklist from disk (see
+ `DynamicBlocklist::LoadFile()
+ <https://searchfox.org/mozilla-central/search?q=DynamicBlocklist%3A%3ALoadFile&path=&case=false&regexp=false>`_)
+- Launcher process puts dynamic blocklist data in shared section (see
+ `SharedSection::AddBlocklist()
+ <https://searchfox.org/mozilla-central/search?q=SharedSection%3A%3AAddBlocklist&path=&case=false&regexp=false>`_)
+- Launcher process creates the browser process in a suspended mode, sets up its
+ dynamic blocklist, then starts it. (see `LauncherMain()
+ <https://searchfox.org/mozilla-central/search?q=LauncherMain&path=&case=false&regexp=false>`_)
+
+ - This is so (ideally) no DLLs can be injected before the blocklist is set up.
+
+If the launcher process is not available, a different blocklist is used, defined
+in `mozglue/WindowsDllBlocklist.cpp
+<https://searchfox.org/mozilla-central/source/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp>`_.
+This code does not currently support the dynamic blocklist. This is intended to
+only be used in testing and other non-deployed scenarios, so this shouldn't be
+a problem for users.
+
+Note that the mozglue blocklist also has a feature to block threads that start
+in ``LoadLibrary`` and variants. This code is currently only turned on in
+Nightly builds because it breaks some third-party DLP products.
+
+Dynamic blocklist file location
+-------------------------------
+
+Because the blocklist is loaded so early during startup, we don't have access to
+what profile is going to be loaded, so the blocklist file can't be stored there.
+Instead, by default the blocklist file is stored in the Windows user's roaming
+app data directory, specifically
+
+``<Roaming AppData directory>\Mozilla\Firefox\blocklist-<install hash>``
+
+Note that the install hash here is what is returned by `GetInstallHash()
+<https://searchfox.org/mozilla-central/source/toolkit/mozapps/update/common/commonupdatedir.cpp#404>`_,
+and is suitable for uniquely identifying the particular Firefox installation
+that is running.
+
+On first launch, this location will be written to the registry, and can be
+overriden by setting that key to a different file location. The registry key is
+``HKEY_CURRENT_USER\Software\Mozilla\Firefox\Launcher``, and the name is the
+full path to firefox.exe with "\|Blocklist" appended. This code is in
+`LauncherRegistryInfo
+<https://searchfox.org/mozilla-central/source/toolkit/xre/LauncherRegistryInfo.cpp>`_.
+
+Adding to and removing from the dynamic blocklist
+-------------------------------------------------
+
+Users can add or remove DLLs from the dynamic blocklist by navigating to
+``about:third-party``, finding the entry for the DLL they are interested in, and
+clicking on the dash icon. They will then be prompted to restart the browser, as
+the change will only take effect after the browser restarts.
+
+Disabling the dynamic blocklist
+-------------------------------
+
+It is possible that users can get Firefox into a bad state by putting a DLL on
+the dynamic blocklist. One possibility is that the user blocks only one of a set
+of DLLs that interact, which could make Firefox behave in unpredictable ways or
+crash.
+
+By launching Firefox with ``--disableDynamicBlocklist``\, the dynamic blocklist
+will be loaded but not used to block DLLs. This lets the user go to
+``about:third-party`` and attempt to fix the problem by unblocking or blocking
+DLLs.
+
+Similarly, in safe mode the dynamic blocklist is also disabled.
+
+Enterprise policy
+-----------------
+
+The dynamic blocklist can be disabled by setting a registry key at
+``HKEY_CURRENT_USER\Software\Policies\Mozilla\Firefox`` with a name of
+DisableThirdPartyModuleBlocking and a DWORD value of 1. This will have the
+effect of not loading the dynamic blocklist, and no icons will show up in
+``about:third-party`` to allow blocking DLLs.
+
+-------
+Contact
+-------
+
+Any questions or feedback are welcome!
+
+**Matrix**: `#hardening <https://app.element.io/#/room/#hardening:mozilla.org>`_
diff --git a/widget/windows/docs/index.rst b/widget/windows/docs/index.rst
new file mode 100644
index 0000000000..9a24cb9cdb
--- /dev/null
+++ b/widget/windows/docs/index.rst
@@ -0,0 +1,9 @@
+==================
+Firefox on Windows
+==================
+
+.. toctree::
+ :maxdepth: 2
+
+ blocklist
+ windows-pointing-device/index
diff --git a/widget/windows/docs/windows-pointing-device/apple_vision.jpg b/widget/windows/docs/windows-pointing-device/apple_vision.jpg
new file mode 100644
index 0000000000..9515f4d4fb
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/apple_vision.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/apple_vision_user.webp b/widget/windows/docs/windows-pointing-device/apple_vision_user.webp
new file mode 100644
index 0000000000..64f8afc0e5
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/apple_vision_user.webp
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/index.rst b/widget/windows/docs/windows-pointing-device/index.rst
new file mode 100644
index 0000000000..eda552b3dd
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/index.rst
@@ -0,0 +1,1384 @@
+################################################################################
+Windows Pointing Device Support in Firefox
+################################################################################
+
+.. contents:: Table of Contents
+ :depth: 4
+
+================================================================================
+Introduction
+================================================================================
+
+This document is intended to provide the reader with a quick primer and/or
+refresher on pointing devices and the various operating system APIs, user
+experience guidelines, and Web standards that contribute to the way Firefox
+handles input devices on Microsoft Windows.
+
+The documentation for these things is scattered across the web and has varying
+levels of detail and completeness; some of it is missing or ambiguous and was
+only determined experimentally or by reading about other people's experiences
+through forum posts. An explicit goal of this document is to gather this
+information into a cohesive picture.
+
+We will then discuss the ways in which Firefox currently (as of early 2023)
+produces incorrect or suboptimal behavior when implementing those standards
+and guidelines.
+
+Finally, we will raise some thoughts and questions to spark discussion on how
+we might improve the situation and handle corner cases. Some of
+these issues are intrinsically "opinion based" or "policy based", so clear
+direction on these is desirable before engineering effort is invested into
+reimplementation.
+
+
+================================================================================
+Motivation
+================================================================================
+
+A quick look at the `pile of defects <https://bugzilla.mozilla.orgbuglist.cgi?query_format=advanced&status_whiteboard=%5Bwin%3Atouch%5D&list_id=16586149&status_whiteboard_type=allwordssubstr>`__
+on *bugzilla.mozilla.org* marked with *[win:touch]* will show anyone that
+Firefox's input stack for pointer devices has issues, but the bugs recorded
+there don't begin to capture the full range of unreported glitches and
+difficult-to-reproduce hiccups that users run into while using touchscreen
+hardware and pen digitizers on Firefox, nor does it capture the ways that
+Firefox misbehaves according to various W3C standards that are (luckily) either
+rarely used or worked around in web apps (and thus go undetected or
+unreported).
+
+These bugs primarily manifest in a few ways that will each be discussed in
+their own section:
+
+1. Firefox failing to return the proper values for the ``pointer``,
+ ``any-pointer``, ``hover``, and ``any-hover`` CSS Media Queries
+
+2. Firefox failing to fire the correct pointer-related DOM events at the
+ correct time (or at all)
+
+3. Firefox's inconsistent handling of touch-related gestures like scrolling,
+ where certain machines (like the Surface Pro) fail to meet the expected
+ behavior of scrolling inertia and overscroll. This leads to a weird touch
+ experience where the page comes to a choppy, dead-stop when using
+ single-finger scrolling
+
+
+It's worth noting that Firefox is not alone in having these types of issues,
+and that handling input devices is a notoriously difficult task for many
+applications; even a substantial amount of Microsoft's own software has trouble
+navigating this minefield on their own Microsoft Surface devices. Defects are
+instigated by a combination of the *intrinsic complexity* of the problem domain
+and the *accidential complexity* introduced by device vendors and Windows
+itself.
+
+The *intrinsic complexity* comes from the simple fact that human-machine
+interaction is difficult. A person must attempt to convey complex
+and abstract goals through a series of simple movements involving a few pieces
+of physical hardware. The devices can send signals that are unclear
+or even contradictory, and the software must decide how to handle
+this.
+
+As a trivial example, every software engineer that's ever written
+page scrolling logic has to answer the question, "What should my
+program do if the user hits 'Page Up' and 'Page Down' at the same time?".
+While it may seem obvious that the answer is "Do nothing.", naively-written
+keyboard input logic might assume the two are mutually-exclusive and only
+process whichever key is handled first in program order.
+
+Occasionally, a new device will be invented that doesn't obviously map to
+existing abstractions and input pipelines. There will be a period of time where
+applications will want to support the new device, but it won't be well
+understood by either the application developers nor the device vendor
+themselves what ideal integration would look like. The new Apple Vision VR
+headset is such a device; traditional VR headsets have used controllers to
+point at things, but Apple insists that the entire thing should be done using
+only hand tracking and eye tracking. Developers of VR video games and other
+apps (like Firefox) will inevitably make many mistakes on the road to
+supporting this new headset.
+
+A major source of defect-causing *accidental complexity* is the lack of clear
+expectations and documentation from Microsoft for apps (like Firefox) that are
+not using their Universal Windows Platform (UWP). The Microsoft Developer
+Network (MSDN) mentions concepts like inertia, overscroll, elastic bounce,
+single-finger panning, etc., but the solution is presented in the context
+of UWP, and the solution for non-UWP apps is either unclear or undocumented.
+
+Adding to this complexity is the fact that Windows itself has gone through
+several iterations of input APIs for different classes of devices, and
+these APIs interact with each other in ways that are surprising or
+unintuitive. Again, the advice given on MSDN pertains to UWP apps, and the
+documentation about the newer "pointer" based window messages is
+a mix of incomplete and inaccurate.
+
+Finally, individual input devices have bugs in their driver software that
+would disrupt even applications that are using the Windows input APIs perfectly.
+Handling all of these deviations is impossible and would result in fragile,
+unmaintainable code, but Firefox inevitably has to work around common ones to
+avoid alienating large portions of the userbase.
+
+
+================================================================================
+Technical Background
+================================================================================
+
+
+A Quick Primer on Pointing Devices
+======================================
+
+
+Traditionally, web browsers were designed to accommodate computer mice and
+devices that behave in a similar way, like trackballs and touchpads on
+laptops. Generally, it was assumed that there would be one such device attached
+to the computer, and it would be used to control a hovering "cursor" whose
+movements would be changed by relative movement of the physical input device.
+
+However, modern computers can be controlled using a variety of different
+pointing devices, all with different characteristics. Many allow
+multiple concurrent targets to be pointed at and have multiple sensors,
+buttons, and other actuators.
+
+For example, the screen of the Microsoft Surface Pro has dual capabilities
+of being a touch sensor and a digitizer for a tablet pen. When being used as a
+workstation, it's not uncommon for a user to also connect the "keyboard +
+touchpad" cover and a mouse (via USB or Bluetooth) to provide the more
+productivity-oriented "keyboard and mouse" setup. In that configuration, there
+are 4 pointer devices connected to the machine simultaneously: a touch screen,
+a pen digitizer, a touchpad, and a mouse.
+
+The next section will give a quick overview of common pointing devices.
+Many will be familiar to the reader, but they are still mentioned to establish
+common terminology and to avoid making assumptions about familiarity with every
+input device.
+
+
+Common Pointing Devices
+---------------------------
+
+Here are some descriptions of a few pointing device types that demonstrate
+the diversity of hardware:
+
+**Touchscreen**
+
+ A touchscreen is a computer display that is able to sense the
+ location of (possibly-multiple) fingers (or stylus) making contact with its
+ surface. Software can then respond to the touches by changing the displayed
+ objects quickly, giving the user a sense of actually physically manipulating
+ them on screen with their hands.
+
+ .. image:: touchscreen.jpg
+ :width: 25%
+
+
+**Digitizing Tablet + Pen Stylus**
+
+ These advanced pointing devices tend to
+ exist in two forms: as an external sensing "pad" that can be plugged into a
+ computer and sits on a desk or in someone's lap, or as a sensor built right
+ into a computer display. Both use a "stylus", which is a pen-shaped
+ electronic device that is detectable by the surface. Common features
+ include the ability to distinguish proximity to the surface ("hovering")
+ versus actual contact, pressure sensitivity, angle/tilt detection, multiple
+ "ends" such as a tip and an eraser, and one-or-more buttons/switch
+ actuators.
+
+ .. image:: wacom_tablet.png
+ :width: 25%
+
+
+**Joystick/Pointer Stick**
+
+ Pointer sticks are most often seen in laptop
+ computers made by IBM/Lenovo, where they exist as a little red nub located
+ between the G, H, and B keys on a standard QWERTY keyboard. They function
+ similarly to the analog sticks on a game controller -- The user displaces
+ the stick from its center position, and that is interpreted as a relative
+ direction to move the on-screen cursor. A greater displacement from center
+ is interpreted as increased velocity of movement.
+
+ .. image:: trackpoint.jpg
+ :width: 25%
+
+
+**Touchpad**
+
+ A touchpad is a rectangular surface (often found on laptop
+ computers) that detects touch and motion of a finger and moves an on-screen
+ cursor relative to the motion. Modern touchpads often support multiple
+ touches simultaneously, and therefore offer functionality that is quite
+ similar to a touchscreen, albeit with different movement semantics because
+ of their physical separation from the screen (discussed below).
+
+ .. image:: touchpad.jpg
+ :width: 25%
+
+
+**VR Controllers**
+
+ VR controllers (and other similar devices like the
+ Wiimote from the Nintendo Wii) allow users to point at objects in a
+ three-dimensional virtual world by moving a real-world controller and
+ "projecting" the controller's position into the virtual space. They often
+ also include sensors to detect the yaw, pitch, and roll of the sensors.
+ There are often other inputs in the controller device, like analog sticks
+ and buttons.
+
+ .. image:: vrcontroller.jpg
+ :width: 25%
+
+
+**Hand Tracking**
+
+ Devices like the Apple Vision (introduced during the
+ time this document was being written) and (to a lesser extent) the Meta
+ Quest have the ability to track the wearer's hand and directly interpret
+ gestures and movements as input. As the human hand can assume a staggering
+ number of orientations and configurations, a finite list of specific shapes
+ and movements must be identified and labelled to allow for clear
+ software-user interaction.
+
+ .. image:: apple_vision_user.webp
+ :width: 25%
+
+ .. image:: apple_vision.jpg
+ :width: 25%
+
+
+**Mouse**
+
+ A pointing device that needs no introduction. Moving a physical
+ clam-shaped device across a surface translates to relative movement of a
+ cursor on screen.
+
+ .. image:: mouse.jpg
+ :width: 25%
+
+
+The Buxton Three-State Model
+-------------------------------
+
+
+Bill Buxton, an early pioneer in the field of human-computer interaction,
+came up with a three-state model for pointing devices; a device can be
+"Out of Range", "Tracking", or "Dragging". Not all devices support all three
+states, and some devices have multiple actuators that can have the three-state
+model individually applied.
+
+.. mermaid::
+
+ stateDiagram-v2
+ direction LR
+ state "State 0" as s0
+ state "State 1" as s1
+ state "State 2" as s2
+ s0 --> s0 : Out Of Range
+ s1 --> s1 : Tracking
+ s2 --> s2 : Dragging
+ s0 --> s1 : Stylus On
+ s1 --> s0 : Stylus Lift
+ s1 --> s2 : Tip Switch Close
+ s2 --> s1 : Tip Switch Open
+
+
+For demonstration, here is the model applied to a few devices:
+
+**Computer Mouse**
+
+ A mouse is never in the "Out of Range" state. Even though it can technically
+ be lifted off its surface, the mouse does not report this as a separate
+ condition; instead, it behaves as-if it is stationary until it can once
+ again sense the surface moving underneath.
+
+ The remaining two states apply to each button individually; when a button is
+ not being pressed, the mouse is considered in the "tracking" state with
+ respect to that button. When a button is held down, the mouse is "dragging"
+ with respect to that button. A "click" is simply considered a zero-length
+ drag under this model.
+
+ In the case of a two-button mouse, this means that the mouse can be in a
+ total of 4 different states: tracking, left button dragging, right button
+ dragging, and two-button dragging. In practice, very little software
+ actually does anything meaningful with two-button dragging.
+
+**Touch Screen**
+
+ Applying the model to a touch screen, one can observe that current hardware
+ has no way to sense that a finger that is "hovering, but not quite making
+ contact with the screen". This means that the "Tracking" state can be ruled
+ out, leaving only the "Out of Range" and "Dragging" states. Since many touch
+ screens can support multiple fingers touching the screen concurrently, and
+ each finger can be in one of two states, there are potentially 2^N different
+ "states" that a touchscreen can be in. Windows assigns meaning to many two,
+ three, and four-finger gestures.
+
+**Tablet Digitizer**
+
+ A tablet digitizer supports all three states: when the stylus is far away
+ from the surface, it is considered "out of range"; when it is located
+ slightly above the surface, it is "tracking"; and when it is making contact
+ with the surface, it is "dragging".
+
+The W3C standards for pointing devices are based on this three-state model, but
+applied to each individual web element instead of the entire system. This
+makes things like "Out-of-Range" possible for the mouse, since it can be
+out of range of a web element.
+
+The W3C uses the terms "over" and "out" to convey the transition between
+"out-of-range" and "tracking" (which the W3C calls "hover"), and the terms
+"down" and "up" convey the transition between "tracking" and "dragging".
+
+The standard also address some of the known shortcomings of the model to
+improve portability and consistency; these improvements will be discussed more
+below.
+
+The Windows Pointer API is *supposedly* based around this model,
+but unfortunately real-world testing shows that the model is not followed
+very consistently with respect to the actual signals sent to the application.
+
+
+Gestures
+=====================================
+
+
+In contrast to the sort-of "anything goes" UI designs of the past,
+modern operating systems like Windows, Mac OS X, iOS, Android, and even
+modern Linux DEs have an "opinionated" idea of how user interaction
+should behave across all apps on the platform (the so-called "look and feel"
+of the operating system).
+
+Users expect gestures like swipes, pinches, and taps to act the same way
+across all apps for a given operating system, and they expect things like
+on-screen keyboards or handwriting recognition to pop up in certain contexts.
+Failing to meet those expectations makes an app look less polished, and
+(especially as far as accessibility is concerned) it frustrates the user
+and makes it more difficult for them to interact with the app.
+
+Microsoft defines guidelines for various behaviours that Windows applications
+should ideally adhere to in the `Input and Interactions <https://learn.microsoft.com/en-us/windows/apps/design/input/>`__
+section on MSDN. Some of these are summarized quickly below:
+
+**Drag and Drop**
+
+ Drag and drop allows a user to transfer data from one application to
+ another. The gesture begins when a pointer device moves into the "Dragging"
+ state over top of a UI element, usually as a result of holding down a mouse
+ button or pressing a finger on a touchscreen. The user moves the pointer
+ over top of the receiver of the data, and then ends the gesture by releasing
+ the mouse button or lifting their finger off the touchscreen. Window
+ interprets this transition out of the "Dragging" state as permission to
+ initiate the data transfer.
+
+ Firefox has supported Drag and Drop for a very long time, so it will not be
+ discussed further.
+
+
+**Pan and Zoom**
+
+ When using touchscreens (and multi-touch touchpads), users expect to be able
+ to cause the viewport to "pan" left/right/up/down by pressing two fingers on
+ the screen (creating two pointers in "Dragging" state) and moving their
+ fingers in the direction of movement. When they are done, they can release
+ both fingers (changing both pointers to "Out of Bounds").
+
+ A zoom can be signalled by moving the two fingers apart or together
+ in a "pinch" or "reverse pinch" gesture.
+
+
+**Single Pointer Panning**
+
+ Applications that are based on a UI model of the user interacting with a
+ "page" often allow a single pointer "Dragging" over the viewport to cause
+ the viewport to pan, similarly to the two-finger panning discussed in the
+ previous section.
+
+ Note that this gesture is not as universal as two-finger panning is -- as a
+ counterexample, graphics programs tend to treat one-finger dragging as
+ object manipulation and two-finger dragging as viewport panning.
+
+
+**Inertia**
+
+ When a user is done panning, they may lift their finger/pen off the screen
+ while the viewport is still in motion. Users expect that the page will
+ continue to move for a little while, as-if the user had "tossed" the page
+ when they let go. Effectively, the page behaves as though it has "momentum"
+ that needs to be gradually lost before the page comes to a full stop.
+
+ Modern operating systems provide this behavior via their various native
+ widget toolkits, and the curve that objects follow as they slow to a stop
+ are different across OSes. In that way, they can be considered part of the
+ unique "look and feel" of the OS. Users expect the scrolling of pages in
+ their web browser to behave this way, and so when Firefox fails to provide
+ this behavior it can be jarring.
+
+
+**Overscroll and Elastic Bounce**
+
+ When a user is panning the page and reaches the outer edges, Microsoft
+ recommends that the app should begin an "elastic bounce" animation, where
+ the page will allow the user to scroll past the end ("overscroll"),
+ show empty space underneath the page, and then sort of "snap back" like a
+ rubber band that's been stretched and then released. You can see a
+ demonstration in `this article <https://www.windowslatest.com/2020/05/21/microsoft-is-adding-elastic-scrolling-to-chrome-on-windows-10/>`__,
+ which discusses Microsoft adding it to Chromium.
+
+
+History of Web Standards and Windows APIs
+===========================================
+
+The World-Wide Web Consortium (W3C) and the Web Hypertext Application
+Technology Working Group (WHATWG) manage the standards that detail the
+interface between a user agent (like Firefox) and applications designed to run
+on the Web Platform. The user agent, in turn, must rely on the operating system
+(Windows, in this case) to provide the necessary APIs to implement the
+standards required by the Web Platform.
+
+As a result of that relationship, a Web Standard is unlikely to be created
+until all widely-used operating systems provide the required APIs. That allows
+us to build a linear timeline with a predictable pattern: a new type of device
+becomes popular, the APIs to support it are introduced into operating systems,
+and eventually a cross-platform standard is introduced into the Web Platform.
+
+The following sections detail the history of input devices supported by
+Windows and the Web Platform:
+
+
+**1985 - Computer Mouse Support (Windows 1.0)**
+
+ The first version of Windows (1985) supported a computer mouse. Support
+ for other input devices is not well-documented, but probably non-existant.
+
+
+**1991 - Third-Party De-facto Pen Support (Wintab)**
+
+ In the late 80s and early 90s, any tablet pen hardware vendor that wanted
+ to support Windows would need to write a device driver and design a
+ proprietary user-mode API to expose the device to user applications. In
+ turn, application developers would have to write and maintain code to
+ support the APIs of every relevant device vendor.
+
+ In 1991, a company named LCS/Telegraphics released an API for Windows
+ called "Wintab", which was designed in collaboration with hardware and
+ software vendors to define a general API that could be targetted by
+ device drivers and applications.
+
+ It would take Microsoft more than a decade to include first-party support
+ for tablet pens in Windows, which allowed Wintab to become the de-facto
+ standard for pen support on Windows. The Wintab API continues to be
+ supported by virtually all artist tablets to this day. Notable companies
+ include Wacom, Huion, XP-Pen, etc.
+
+
+**1992 - Early Windows Pen Support (Windows for Pen Computing)**
+
+ The earliest Windows operating system to support non-mouse pointing devices
+ was Windows 3.1 with the "Windows for Pen Computing" add-on (1992).
+ (`For the curious <https://socket3.wordpress.com/2019/07/31/windows-for-pen-computing-1-0/>`__,
+ and I'm certain `this book <https://www.amazon.com/Microsoft-Windows-Pen-Computing-Programmers/dp/1556154690>`__
+ is a must-read!). Pen support was mostly implemented by translating actions
+ into the existing ``WM_MOUSExxx`` messages, but also "upgraded" any
+ application's ``EDIT`` controls into ``HEDIT`` controls, which looked the
+ same but were capable of being handwritten into using a pen. This was not
+ very user-friendly, as the controls stayed the same size and the UI was not
+ adapted to the input method. This add-on never achieved much popularity.
+
+ It is not documented whether Netscape Navigator (the ancestor of Mozilla
+ Firefox) supported this add-on or not, but there is no trace of it in modern
+ Firefox code.
+
+
+**1995 - Introduction of JavaScript and Mouse Events (De-facto Web Standard)**
+
+ The introduction of JavaScript in 1995 by Netscape Communications added a
+ programmable, event-driven scripting environment to the Web Platform.
+ Browser vendors quickly added the ability for scripts to listen for and
+ react to mouse events. These are the well-known events like ``mouseover``,
+ ``mouseenter``, ``mousedown``, etc. that are ubiquitous on the web, and are
+ known by basically anyone who has ever written front-end JavaScript.
+
+ This ubiquity created a de-facto standard for mouse input, which would
+ eventually be formally standardized by the W3C in the HTML Living Standard
+ in 2001.
+
+ The Mouse Event APIs assume that the computer has one single pointing device
+ which is always present, has a single cursor capable of "hovering" over an
+ element, and has between one and three buttons.
+
+ When support for other pointing devices like touchscreen and pen first
+ became available in operating systems, it was exposed to the web by
+ interpreting user actions into equivalent mouse events. Unfortunately, this
+ is unable to handle multiple concurrent pointers (like one would get from
+ multitouch screens) or report the kind of rich information a pen digitizer
+ can provide, like tilt angle, pressure, etc. This eventually lead the W3C
+ to develop the new "Touch Events" standard to expose touch functionality,
+ and eventually the "Pointer Events" to expose more of the rich information
+ provided by pens.
+
+
+**2005 - Mainstream Pen Support (Windows XP Tablet PC Edition)**
+
+ It was the release of Windows XP Tablet PC Edition (2005) that allowed
+ Windows applications to directly support tablet pens by using the new COM
+ "`Windows Tablet PC <https://learn.microsoft.com/en-us/windows/win32/tablet/tablet-pc-development-guide>`__"
+ APIs, most of which are provided through the main `InkCollector <https://learn.microsoft.com/en-us/windows/win32/tablet/inkcollector-class>`__
+ class. The ``InkCollector`` functionality would eventually be "mainlined"
+ into Windows XP Professional Service Pack 2, and continues to exist in
+ modern Windows releases.
+
+ The Tablet PC APIs consist of a large group of COM objects that work
+ together to facilitate enumerating attached pens, detecting pen movement and
+ pen strokes, and analyzing them to provide:
+
+ 1. **Cursor Movement**: translates the movements of the pen into the
+ standard mouse events that applications expect from mouse cursor
+ movement, namely ``WM_NCHITTEST``, ``WM_SETCURSOR`` and
+ ``WM_MOUSEMOVE``.
+
+ 2. **Gesture Recognition**: detects common user actions, like "tap",
+ "double-tap", "press-and-hold", and "drag". The `InkCollector` delivers
+ these events via COM `SystemGesture <https://learn.microsoft.com/en-us/windows/win32/tablet/inkcollector-systemgesture>`__
+ events using the `InkSystemGesture <https://learn.microsoft.com/en-us/windows/win32/api/msinkaut/ne-msinkaut-inksystemgesture>`__
+ enumeration. It will also translate them into common Win32 messages; for
+ example, a "drag" gesture would be translated into a ``WM_LBUTTONDOWN``
+ message, several ``WM_MOUSEMOVE`` messages, and finally a
+ ``WM_LBUTTONUP`` message.
+
+ An application that is using ``InkCollector`` will receive both types of
+ messages: traditional mouse input through the Win32 message queue, and
+ "Tablet PC API" events through COM callbacks. It is up to the
+ application to determine which events matter to it in a given context,
+ as the two types of events are not guaranteed by Microsoft to correspond
+ in any predictable way.
+
+ 3. **Shape and Text Recognition**: allows the app to
+ recognize letters, numbers, punctuation, and other `common shapes <https://learn.microsoft.com/en-us/windows/win32/api/msinkaut/ne-msinkaut-inkapplicationgesture>`__
+ the user might make using their pen. Supported shapes include circles,
+ squares, arrows, and motions like "scratch out" to correct a misspelled
+ word. Custom recognizers exist that allow recognition of other symbols,
+ like music notes or mathematical notation.
+
+ 4. **Flick Recognition**: allows the user to invoke actions via quick,
+ linear motions that are recognized by Windows and sent to the app as
+ ``WM_TABLET_FLICK`` messages. The app can choose to handle the window
+ message or pass it on to the default window procedure, which will
+ translate it to scrolling messages or mouse messages.
+
+ For example, a quick upward 'flick' corresponds to "Page up", and
+ a quick sideways flick in a web browser would be "back". Flicks were
+ never widely used by Windows apps, and they may have been removed in
+ more recent versions of Windows, as the existing Control Panel menus
+ for configuring them seem to no longer exist as of Windows 10 22H2.
+
+
+ Firefox does not appear to have ever used these APIs to allow tablet pen
+ input, with the exception of `one piece of code <https://searchfox.org/mozilla-central/rev/e6cb503ac22402421186e7488d4250cc1c5fecab/widget/windows/InkCollector.cpp>`__
+ to detect when the pen leaves the Firefox window to solve
+ `Bug 1016232 <https://bugzilla.mozilla.org/show_bug.cgi?id=1016232>`__.
+
+
+**2009 - Touch Support: WM_GESTURE (Windows 7)**
+
+ While attempts were made with the release of Windows Vista (2007) to support
+ touchscreens through the existing tablet APIs, it was ultimately the release
+ of Windows 7 (2009) that brought first-class support for Touchscreen devices
+ to Windows with new Win32 APIs and two main window messages: ``WM_TOUCH``
+ and ``WM_GESTURE``.
+
+ These two messages are mutually-exclusive, and all applications are
+ initially set to receive only ``WM_GESTURE`` messages. Under this
+ configuration, Windows will attempt to recognize specific movements on a
+ touch digitizer and post "gesture" messages to the application's message
+ queue. These gestures are similar to (but, somewhat-confusingly, not
+ identical to) the gestures provided by the "Windows Tablet PC" APIs
+ mentioned above. The main gesture messages are: zoom, pan, rotate,
+ two-finger-tap, and press-and-tap (one finger presses, another finger
+ quickly taps the screen).
+
+ In contrast to the behavior of the ``InkCollector`` APIs, which will send
+ both gesture events and translated mouse messages, the ``WM_GESTURE``
+ message is truly "upstream" of the translated mouse messages; the translated
+ mouse messages will only be generated if the application forwards the
+ ``WM_GESTURE`` message to the default window procedure. This makes
+ programming against this API simpler than the ``InkCollector`` API, as
+ there is no need to state-fully "remember" that an action has already been
+ serviced by one codepath and needs to be ignored by the other.
+
+ Firefox current supports the ``WM_GESTURE`` message when Asynchronous Pan
+ and Zoom (APZ) is not enabled (although we do not handle inertia in this
+ case, so the page comes to a dead-stop immediately when the user stops
+ scrolling).
+
+
+**2009 - Touch Support: WM_TOUCH (Windows 7)**
+
+ Also introduced in Windows 7, an application that needs full control over
+ touchscreen events can use `RegisterTouchWindow <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registertouchwindow>`__
+ to change any of its windows to receive ``WM_TOUCH`` messages instead of the
+ more high-level ``WM_GESTURE`` messages. These messages explicitly notify
+ the application about every finger that contacts or breaks contact with the
+ digitizer (as well as each finger's movement over time). This provides
+ absolute control over touch interpretation, but also means that the burden
+ of handling touch behavior falls completely on the application.
+
+ To help ease this burden, Microsoft provides two COM APIs to interpret
+ touch messages, ``IManipulationProcessor`` and ``IInertiaProcessor``.
+
+ ``IManipulationProcessor`` can be considered a superset of the functionality
+ available through normal gestures. The application feeds ``WM_TOUCH`` data
+ into it (along with other state, such as pivot points and timestamps), and
+ it allows for manipulations like: two-finger rotation around a pivot,
+ single-finger rotation around a pivot, simultaneous rotation and translation
+ (for example, 'dragging' a single corner of a square).
+ `These MSDN diagrams <https://learn.microsoft.com/en-us/windows/win32/wintouch/advanced-manipulations-overview>`__
+ give a good overview of the kinds of advanced manipulations an app might
+ support.
+
+ ``IInertiaProcessor`` works with ``IManipulationProcessor`` to add inertia
+ to objects in a standard way across the operating system. It is likely that
+ later APIs that provide this (like DirectManipulation) are using these COM
+ objects under the hood to accomplish their inertia handling.
+
+ Firefox currently handles the ``WM_TOUCH`` event when Asynchronous Pan and
+ Zoom (APZ) is enabled, but we do not use either the ``IInertiaProcessor``
+ nor the ``IManipulationProcessor``.
+
+
+**2012 - Unified Pointer API (Windows 8)**
+
+ Windows 8 (2012) was Microsoft's initial attempt to make a touch-first,
+ mobile-first operating system that (ideally) would make it easy for app
+ developers to treat touch, pen, and mouse as first-class input devices.
+
+ By this point, the Windows Tablet APIs would allow tablet pens to draw
+ text and shapes like squares, triangles, and music notes, and those shapes
+ would be recognizable by the Windows Ink subsystem.
+
+ At the same time, Windows Touch allowed touchscreens to have advanced
+ manipulation, like rotate + translate, or simultaneous pan and zoom, and it
+ allowed objects manipulated by touch to have momentum and angular velocity.
+
+ The shortcomings of having separate input stacks for these various devices
+ starts to be become apparent after a while: Why shouldn't a touchscreen be
+ able to recognize a circle or a triangle? Why shouldn't a pen be able to
+ have complex rotation and zoom functionality? How do we handle these newer
+ laptop touchpads that are starting to handle multi-touch gestures like a
+ touchscreen, but still cause relative cursor movement like a mouse? Why does
+ my program have to have 3 separate codepaths for different pointing devices
+ that are all very similar?
+
+ The Windows Pointer Device Input Stack introduces new APIs and window
+ messages that generalize the various types of pointing devices under a
+ single API while still falling back to the legacy touch and tablet input
+ stacks in the event that the API is unused. (Note that the touch and tablet
+ stacks themselves fall back to the traditional mouse input stack when they
+ are unused.)
+
+ Microsoft based their pointer APIs off the Buxton Three-State Model
+ (discussed earlier), where changes between "Out-of-Range" and "Tracking" are
+ signalled by ``WM_POINTERENTER`` AND ``WM_POINTERLEAVE`` messages, and
+ changes between "Tracking" and "Dragging" are signalled by
+ ``WM_POINTERDOWN`` and ``WM_POINTERUP``. Movement is indicated via
+ ``WM_POINTERUPDATE`` messages.
+
+ If these messages are unhandled (the message is forwarded to
+ ``DefWindowProc``), the Win32 subsystem will translate them
+ into touch or gesture messages. If unhandled, those will be further
+ translated into mouse and system messages.
+
+ While the Pointer API is not without some unfortunate pitfalls (which will
+ be discussed later), it still provides several advantages over the
+ previously available APIs: it can allow a mostly-unified codepath for
+ handling pointing devices, it circumvents many of the often-complex
+ interactions between the previous APIs, and it provides the ability to
+ simulate pointing devices to help facilitate end-to-end automated testing.
+
+ Firefox currently uses the Pointer APIs to handle tablet stylus input only,
+ while other input methods still use the historical mouse and touch input
+ APIs above.
+
+
+**2013 - DirectManipulation (Windows 8.1)**
+
+ DirectManipulation is a DirectX based API that was added during the release
+ of Windows 8.1 (2013). This API allows an app to create a series of
+ "viewports" inside a window and have scrollable content within each of these
+ viewports. The manipulation engine will then take care of automatically
+ reading Pointer API messages from the window's event queue and generating
+ pan and zoom events to be consumed by the app.
+
+ In the case that the app is also using DirectComposition to draw its window,
+ DirectManipulation can pipe the events directly into it, causing the app
+ to essentially get asynchronous pan and zoom with proper handling of inertia
+ and overscroll with very little coding.
+
+ DirectManipulation is only used in Firefox to handle data coming from
+ Precision Touchpads, as Microsoft provides no other convenient API for
+ obtaining data from such devices. Firefox creates fake content inside of
+ a fake viewport to capture the incoming events from the touchpad and
+ translates them into the standard Asynchronous Pan and Zoom (APZ) events
+ that the rest of the input pipeline uses.
+
+
+**2013 - Touch Events (Web Standard)**
+
+ "`Touch Events <https://www.w3.org/TR/touch-events/>`__" became a W3C
+ recommendation in October, 2013.
+
+ At this point, Microsoft's first operating system to include touch support
+ (Windows 7) was the most popular desktop operating system, and the ubiquity
+ of smart phones brought a huge uptick in users with touchscreen inputs. All
+ major browsers included some API that allowed reading touch input,
+ prompting the W3C to formalize a new standard to ensure interoperability.
+
+ With the Touch Events API, multiple touch interactions may be reported
+ simultaneously, each with their own separate identifier for tracking and
+ their own coordinates within the screen, viewport, and client area. A
+ touch is reported by: a ``touchstart`` event with a unique ID for each
+ contact, zero-or-more ``touchmove`` events with that ID, and finally a
+ ``touchend`` event to signal the end of that specific contact.
+
+ The API also has some amount of support for pen styluses, but it lacks
+ important features necessary to truly support them: hovering, pressure,
+ tilt, or multiple cursors like an erasure. Ultimately, its functionality
+ has been superceded by the newer "Pointer Events" API, discussed below.
+
+
+**2016 - Precision Touchpads (Windows 10)**
+
+ Early touchpads emulated a computer mouse by directly using the same IBM
+ PS/2 interface that most computer mice used and translating relative
+ movement of the user's finger into equivalent movements of a mouse on a
+ surface.
+
+ As touchpad technology advanced and more powerful interface standards like
+ USB begun to take over the consumer market, touchpad vendors started adding
+ extra features to their hardware, like tap-to-click, tap-and-drag, and
+ tap-and-hold (to simulate a right click). These behaviors were implemented
+ by touchpad vendors either in hardware drivers and/or user mode "hooks" that
+ injected equivalent Win32 messages into the appropriate target.
+
+ As expected, each touchpad vendor's driver had its own subtly-different
+ behavior from others, its own bugs, and its own negative interactions with
+ other software.
+
+ During the later years of Windows 8, Microsoft and touchpad company
+ Synaptics co-developed the "Precision Touchpad" standard, which defines an
+ interface for touchpad hardware to report its physical measurements,
+ precision, and sensor configuration to Windows and allows it to deliver raw
+ touch data. Windows then interprets the data and generates gestures and
+ window messages in a standard way, removing the burden of implementing these
+ behaviors from the touchpad vendor and providing the OS with rich
+ information about the user's movements.
+
+ It wasn't until the 2016 release of Windows 10 14946 that Microsoft would
+ support all the standard gestures through the new standard. Although
+ adoption by vendors has been a bit slow, the fact that
+ `it is a requirement for Windows 11 <https://pocketnow.com/all-windows-11-pcs-will-be-required-to-have-a-precision-touchpad-and-webcam/>`__
+ means that vendor support for this standard is imminent.
+
+ Unfortunately, there's a piece of bad news: Microsoft did not
+ implement the above "Unified Pointer API" for use with touchpads, as the
+ developers of Blender discovered when `they moved to the Pointer API <https://archive.blender.org/developer/D7660>`__.
+ Instead, Microsoft expects developers to either use DirectManipulation to
+ automatically get pan/zoom enabled for their app, or the RawInput API to
+ directly read touchpad data.
+
+
+**2019 - Pointer Events (Web Standard)**
+
+ "`Pointer Events <https://www.w3.org/TR/pointerevents/>`__" became a level 2
+ W3C recommendation in April, 2019. They considered `the work done by Microsoft <https://www.w3.org/Submission/2012/SUBM-pointer-events-20120907/>`__
+ as part of the design of their own Pointer API, and in many ways the W3C
+ standard resembles an improved, better specified, more consistent, and
+ easier-to-use version of the APIs provided by the Win32 subsystem.
+
+ The Pointer Events API generalizes devices like touchscreens, mice, tablet
+ pens, VR controllers, etc. into a "thing that points". A pointer has
+ (optional) properties: a width and height (big for a finger, 1px for a
+ mouse), an amount of pressure, a tilt angle relative to the surface, some
+ buttons, etc. This helps applications maximize code reuse for handling
+ pointer input by having a common codebase written against these generalized
+ traits. If needed, the application may also have smaller, specialized
+ sections of code for each concrete pointer type.
+
+ Certain types of pointers (like pens and touchscreens) have a behavior where
+ they are always "captured" by the first object that they interact with. For
+ example, if a user puts their finger on an empty part of a web page and
+ starts to scroll, their finger is now "captured" by the web page itself.
+ "Captured" means that even if their finger moves over an element in
+ the web page, that element will not receive events from the finger -- the
+ page itself will until the entire interaction stops.
+
+ The events themselves very closely follow the Buxton Three-State Model
+ (discussed earlier), where ``pointerover/pointerout`` messages indicate
+ transitions from "Out of Range" to "Tracking" and visa-versa, and
+ ``pointerdown/pointerup`` messages transition between "Tracking" and
+ "Dragging". ``pointermove`` updates the position of the pointer, and a
+ special ``pointercancel`` message is sent to inform the page that the
+ browser is "cancelling" a ``pointerdown`` event because it has decided to
+ consume it for a gesture or because the operating system cancelled the
+ pointer for its own reasons.
+
+
+CSS "interaction" Media Queries
+==========================================
+
+(Note that this section is **not** about the `pointer-events <https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events>`__
+CSS property, which defines the circumstances where an element can be the target
+of pointer events.)
+
+The W3C defines the interaction-related media queries in the
+`Media Queries Level 4 - Interaction Media Features <https://www.w3.org/TR/mediaqueries-4/#mf-interaction>`__
+document.
+
+To summarize, the main interaction-related CSS Media Queries that Firefox must
+support are ``pointer``, ``any-pointer``, ``hover`` and ``any-hover``.
+
+
+``pointer``
+
+ Allows the webpage to query the existence of a pointing device on
+ the machine, and (if available) the assumed "pointing accuracy" of the
+ "primary" pointing device. The device considered "primary" on a machine with
+ multiple input devices is a policy decision that must be made by the web
+ browser; Windows simply provides the APIs to query information about
+ attached devices.
+
+ The browser is expected to return one of three strings to this media query:
+
+ ``none``
+
+ There is no pointing device attached to the computer.
+
+ ``coarse``
+
+ The primary pointing device is capable of approximately
+ pointing at a relatively large target (like a finger on a
+ touchscreen).
+
+ ``fine``
+
+ The primary pointing device is capable of near-pixel-level
+ accuracy (like a computer mouse or a tablet pen).
+
+
+``any-pointer``
+
+ Similar to ``pointer``, but represents the union of
+ capabilities of all pointers attached to the system, such that the meanings
+ become:
+
+ ``none``
+
+ There is no pointing device attached to the computer.
+
+ ``coarse``
+
+ There is at-least one "coarse" pointer attached.
+
+ ``fine``
+
+ There is at-least one "fine" pointer attached.
+
+
+``hover``
+
+ Allows the webpage to query whether the primary pointer is
+ capable of "hovering" over top of elements on the page. Computer mice,
+ touchpad cursors, and higher-end pen tablets all support this, whereas
+ current touchscreens are "touch" or "no touch", and they cannot detect a
+ finger hovering over the screen.
+
+ ``hover``
+
+ The primary pointer is capable of reporting hovering.
+
+ ``none``
+
+ The primary pointer is not capable of reporting hovering.
+
+``any-hover``
+
+ Indicates whether any pointer attached to the system has the
+ ``hover`` capability.
+
+
+Selection of the Primary Pointing Device
+--------------------------------------------
+
+To illustrate the complexity of this topic, consider the Microsoft Surface Pro.
+
+The Surface Pro has an advanced screen that is capable of receiving touch
+input, but it can also behave like a pen digitizer and receive input from a
+stylus with advanced pen capabilities, like hover sensing, pressure
+sensitivity, multiple buttons, and even multiple "tips" (a pen and eraser end).
+
+In this case, what should Firefox consider the primary pointing device?
+
+Perhaps the user intends to use their Surface Pro like a touchscreen tablet,
+at which point Firefox should report ``pointer: coarse`` and ``hover: none``
+capabilities.
+
+But what if, instead, the user wants to sketch art or take notes using a pen on
+their Surface Pro? In this case, Firefox should be reporting ``pointer: fine``
+and ``hover: hover``.
+
+Imagine that the user then attaches the "keyboard + touchpad" cover attachment
+to their Surface Pro; naturally, we will consider that the user's intent is for
+the touchpad to become the primary pointing device, and so it is fairly clear
+that we should return ``pointer: fine`` and ``hover: hover`` in this state.
+
+However, what if the user tucks the keyboard/touchpad attachment behind the
+tablet and begins exclusively operating the device with their finger?
+
+This example shows that complex, multi-input machines can resist classification
+and blur the lines between labels like "touch device", "laptop", "drawing
+tablet", etc. It also illustrates that identifying the "primary" pointing
+device using only machine configuration may yield unintuitive and suboptimal
+results.
+
+While we can almost-certainly improve our hardware detection heuristics to
+better answer this question (and we should, at the very least), perhaps it
+makes more sense for Firefox to incorporate user intentions into the decision.
+Intentions could be communicated directly by the user through some sort of
+setting or indirectly through the user's actions.
+
+For example, if the user intends to draw on the screen with a pen, perhaps
+Firefox provides something like a "drawing mode" that the user can toggle to
+change the primary pointing device to the pen. Or perhaps it's better for
+Firefox to interpret the mere fact of receiving pen input as evidence of the
+user's intent and switch the reported primary pointing device automatically.
+
+If we wanted to switch automatically, there are predictable traps and pitfalls
+we need to think about: we need to ensure that we don't create frustrating user
+experiences where web pages may "pop" beneath the user suddenly, and
+we should likely incorporate some kind of "settling time" so we don't
+oscillate between devices.
+
+It's worth noting that Chromium doesn't seem to incorporate anything like
+what's being suggested here, so if this is well-designed it may be an
+opportunity for Firefox to try something novel.
+
+
+
+
+================================================================================
+State of the Browser
+================================================================================
+
+Pan and Zoom, Inertia, Overscroll, and Elastic Bounce
+=========================================================
+
+As can be seen in the videos below, Firefox's support for inertia, overscroll,
+and elastic bounce works well on all platforms when a stylus pen is used
+as the input device, and it also works just fine with the touchscreen on the
+Dell XPS 15. However, it completely fails when the touchscreen is used on
+the Microsoft Surface Pro. While more investigation is needed to completely
+understand these issues, the fact that the correctly-behaving digitizing pens
+use the Pointer API and the misbehaving input devices do not may be related.
+
+- `Video 1 <https://drive.google.com/file/d/1Z1QRSf2RluNhJwkKCzPb6-14vRtkqK8s/view?usp=sharing>`__
+ showcasing overscroll and bounce not working on Surface Pro with touch, but
+ other devices/inputs are working
+
+- `Video 2 <https://drive.google.com/file/d/1bOgpVGBeZtwelvPJzYdA6uFRpubGtu4W/view?usp=sharing>`__
+ showing that everything works just fine with an external Wacom digitizer
+
+
+Pointer Media Queries
+=========================================================
+
+**"any-pointer" Queries**
+
+Unlike the ``pointer`` media queries, which rely on the browser to make a policy
+decision about what should be considered the "primary" pointer in a given
+system configuration, the ``any-pointer`` queries are much more objective and
+binary: the computer either has a type of device attached to it, or it
+doesn't.
+
+**any-pointer: coarse**
+
+Firefox reports that there are "coarse" pointing devices present if either of
+these two points is true:
+
+1. ``GetSystemMetrics(SM_DIGITIZER)`` reports that a device that supports
+ touch or pen is present.
+
+2. Based on heuristics, Firefox concludes that it is running on a computer it
+ considers a "tablet".
+
+Point #1 is incorrect, as a pen is not a "coarse" pointing device. Note that
+this is a recent regression in `Bug 1811303 <https://bugzilla.mozilla.org/show_bug.cgi?id=1811303>`__
+that was uplifted to Firefox 112, so this actually regressed as this document
+was being written! This is responsible for the incorrect "Windows 10 Desktop +
+Wacom USB Tablet" issue in the table.
+
+Point #2 is a clear case of the `XY Problem <https://en.wikipedia.org/wiki/XY_problem>`__,
+where Firefox is trying to determine if a coarse pointing device is present
+by determining whether it is running on a tablet, when instead it should be
+directly testing for coarse pointing devices (since, of course, those can exist
+on machines that wouldn't normally be considered a "tablet"). This is
+responsible for the incorrect "Windows 10 Dell XPS 15 (Touch Disabled) + Wacom
+USB Tablet" issue in the table below.
+
+**any-pointer: fine**
+
+Firefox reports that there are "fine" pointing devices present if and only if
+it detects a mouse. This is clearly already wrong. Firefox determines that the
+computer has a mouse using the following algorithm:
+
+1. If ``GetSystemMetrics(SM_MOUSEPRESENT)`` returns false, report no mouse.
+
+2. If Firefox does not consider the current computer to be a tablet, report a
+ mouse if there is at-least one "mouse" device driver running on the
+ computer.
+
+3. If Firefox considers the current computer to be a tablet or a touch system,
+ only report a mouse if there are at-least two "mouse" device drivers
+ running. This exists because some tablet pens and touch digitizers report
+ themselves as computer mice.
+
+This algorithm also suffers from the XY problem -- Firefox is trying to
+determine whether a fine pointing device exists by determining if there is
+a computer mouse present, when instead it should be directly testing for
+fine pointing devices, since mice are not the only fine pointing
+devices.
+
+Because of this proxy question, this algorithm is completely dependent on any
+attached fine pointing device (like a pen tablet) to report itself as a mouse.
+Point #3 makes the problem even worse, because if a computer that resembles a
+tablet fails to report its digitizers as mice, the algorithm will completely
+ignore an actual computer mouse attached to the system because it expects two
+of them to be reported!
+
+Unfortunately, the Surface Pro has both a pen digitizer and a touch digitizer,
+and it reports neither as a mouse. As a result, this algorithm completely falls
+apart on the Surface Pro, failing to report any "fine" pointing device even
+when a computer mouse is plugged in, a pen is plugged in, or even when
+the tablet is docked because its touchpad is only one mouse and it expects
+at least two.
+
+This is also responsible for failing to report the trackpad on the Dell XPS 15
+as "fine", because the Dell XPS 15 has a touchscreen and therefore looks like
+a "tablet", but doesn't report 2 mouse drivers.
+
+**any-pointer: hover**
+
+
+Firefox reports that any device that is a "fine" pointer also supports "hover",
+which does generally hold true, but isn't necessarily true for lower-end pens
+that only support tapping. It would be better for Firefox to directly
+query the operating system instead of just assuming.
+
+**"pointer" media query**
+
+As discussed previously at length, this media query relies on a "primary"
+designation made by the browser. Below is the current algorithm used to
+determine this:
+
+1. If the computer is considered a "tablet" (see below), report primary
+ pointer as "coarse" (this is clearly already the wrong behavior).
+
+2. Otherwise, if the computer has a mouse plugged in, report "fine".
+
+3. Otherwise, if the computer has a touchscreen or pen digitizer, report
+ "coarse" (this is wrong in the case of the digitizer).
+
+4. Otherwise, report "fine" (this is wrong; should report "None").
+
+Firefox uses the following algorithm to determine if the computer is a
+"tablet" for point #1 above:
+
+1. It is not a tablet if it's not at-least running Windows 8.
+
+2. If Windows "Tablet Mode" is enabled, it is a tablet no matter what.
+
+3. If no touch-capable digitizers are attached, it is not a tablet.
+
+4. If the system doesn't support auto-rotation, perhaps because it has
+ no rotation sensor, or perhaps because it's docked and operating in
+ "laptop mode" where rotation won't happen, it's not a tablet.
+
+5. If the vendor that made the computer reports to Windows that it supports
+ "convertible slate mode" and it is currently operating in "slate mode",
+ it's a tablet.
+
+6. Otherwise, it's not a tablet.
+
+
+**Table with comparison to Chromium**
+
+The following table shows how Firefox and Chromium respond to various pointer
+queries. The "any-pointer" and "any-hover" columns are not subjective and
+therefore are always either green or red to indicate "pass" or "fail", but the
+"pointer" and "hover" may also be yellow to indicate that it's "open to
+interpretation" because of the aforementioned difficulty in determining the
+"primary pointer".
+
+.. image:: touch_media_queries.png
+ :width: 100%
+
+
+**Related Bugs**
+
+- Bug 1813979 - For Surface Pro media query "any-pointer: fine" is true only
+ when both the Type Cover and mouse are connected
+
+- Bug 1747942 - Incorrect CSS media query matches for pointer, any-pointer,
+ hover and any-hover on Surface Laptop
+
+- Bug 1528441 - @media (hover) and (any-hover) does not work on Firefox 64/65
+ where certain dual inputs are present
+
+- Bug 1697294 - Content processes unable to detect Windows 10 Tablet Mode
+
+- Bug 1806259 - CSS media queries wrongly detect a Win10 desktop computer
+ with a mouse and a touchscreen, as a device with no mouse (hover: none)
+ and a touchscreen (pointer: coarse)
+
+
+Web Events
+=====================
+
+The pen stylus worked well on all tested systems -- The correct pointer events
+were fired in the correct order, and mouse events were properly simulated in
+case the default behavior was allowed.
+
+The touchscreen input was less reliable. On the Dell XPS 15, the
+"Pointer Events" were flawless, but the "Touch Events" were missing
+an important step: the ``touchstart`` and ``touchmove`` messages were sent just
+fine, but Firefox never sends the ``touchend`` message! (Hopefully that isn't
+too difficult to fix!)
+
+Unfortunately, everything really falls apart on the Surface Pro using the
+touchscreen -- neither the "Pointer Events" nor the "Touch Events" fire at all!
+Instead, the touch is completely absorbed by pan and zoom gestures, and nothing
+is sent to the web page. The website's request for ``touch-action: none`` is
+ignored, and the web page is never given any opportunity to call
+``Event.preventDefault()`` to cancel the pan/zoom behavior.
+
+
+Operating System Interfaces
+================================
+
+As was discussed above, Windows has multiple input APIs that were each
+introduced in newer version of Windows to handle devices that were not
+well-served by existing APIs.
+
+Backward compatibility with applications designed against older APIs is
+realized when applications call the default event handler (``DefWindowProc``)
+upon receiving an event type that they don't recognize (which is what apps have
+always been instructed to do if they receive events they don't recognize).
+The unrecognized newer events will be translated by the default event handler
+into older events and sent back to the application. A very old application may
+have this process repeat through several generations of APIs until it finally
+sees events that it recognizes.
+
+Firefox currently uses a mix of the older and newer APIs, which complicates
+the input handling logic and may be responsible for some of the
+difficult-to-explain bugs that we see reported by users.
+
+Here is an explanation of the codepaths Firefox uses to handle pointer input:
+
+1. Firefox handles the ``WM_POINTER[LEAVE|DOWN|UP|UPDATE]`` messages if the
+ input device is a tablet pen and an Asynchronous Pan and Zoom (APZ)
+ compositor is available. Note that this already may not be ideal, as
+ Microsoft warns (`here <https://learn.microsoft.com/en-us/windows/win32/inputmsg/wm-pointercapturechanged>`__)
+ that handling some pointer messages and passing other pointer messages to
+ ``DefWindowProc`` has unspecified behavior (meaning that Win32 may do
+ something unexpected or nonsensical).
+
+ If the above criteria aren't met, Firefox will call ``DefWindowProc``, which
+ will re-post the pointer messages as either touch messages or mouse
+ messages.
+
+2. If DirectManipulation is being used for APZ, it will output the
+ ``WM_POINTERCAPTURECHANGED`` if it detects a pan or zoom gesture it can
+ handle. It will then handle the rest of the gesture itself.
+
+ DirectManipulation is used for all top-level and popup windows as long as
+ it isn't disabled via the ``apz.allow_zooming``,
+ ``apz.windows.use_direct_manipulation``, or
+ ``apz.windows.force_disable_direct_manipulation`` prefs.
+
+3. If the pointing device is touch, the next action depends on
+ whether an Asynchronous Pan and Zoom (APZ) compositor is available. If it
+ is, the window will have been registered using ``RegisterTouchWindow``, and
+ Firefox will receive ``WM_TOUCH`` messages, which will be sent to the
+ "Touch Event" API and handled directly by the APZ compositor.
+
+ If there is no APZ compositor, it will instead be received as a
+ ``WM_GESTURE`` message or a mouse message, depending on the movement. Note
+ that these will be more basic gestures, like tap-and-hold.
+
+4. If none of the above apply, the message will be converted into standard
+ ``WM_MOUSExxx`` messages via a call to ``DefWindowProc``.
+
+
+================================================================================
+Discussion
+================================================================================
+
+Here is where some of the outstanding thoughts or questions can be listed.
+This can be updated as more questions come about and (hopefully) as answers to
+questions become apparent.
+
+CSS "pointer" Media Queries
+===============================
+
+- The logic for the ``any-pointer`` and ``any-hover`` queries are objectively
+ incorrect and should be rewritten altogether. That is not as
+ big of a job as it sounds, as the code is fairly straightforward and
+ self-contained. (Note: Improvements have already been made in
+ `Bug 1813979 <https://bugzilla.mozilla.org/show_bug.cgi?id=1813979>`__)
+
+- There are a few behaviors for ``pointer`` and ``hover`` that are
+ objectively wrong (such as reporting a ``coarse`` pointer when the
+ Surface Pro is docked with a touchpad). Those should be fixable with a
+ code change similar to the previous bullet.
+
+- Do we want to continue to use only machine configuration to decide what
+ the "primary" pointer is, or do we also want to incorporate user intent
+ into the algorithm? Or, alternatively:
+
+ 1. Do we create a way for the user to override? For example, a "Drawing
+ Mode" button if a tablet digitizer is sensed.
+
+ 2. Do we attempt to change automatically in response to user action?
+
+ - An example was used above of a docked Surface Pro computer, where
+ the user may use the keyboard and touchpad for a while, then perhaps
+ tuck that behind and use the device as a touchscreen, and then
+ perhaps draw on it with a tablet stylus.
+
+ - We would need to be careful to avoid careless "popping" or
+ "oscillating" if we react too quickly to changing input types.
+
+- On a separate-but-related note, the `W3C suggested <https://www.w3.org/TR/mediaqueries-5/#descdef-media-pointer>`__
+ that it might be beneficial to allow users to at-least disable all
+ reporting of ``fine`` pointing devices for users who may have a disability
+ that prevents them from being able to click small objects, even with a fine
+ pointing device.
+
+
+Pan-and-Zoom, Inertia, Overscroll, and Elastic Bounce
+=========================================================
+
+- Inertia, overscroll, and elastic bounce are just plain broken on the
+ Surface Pro. That should definitely be investigated.
+
+- We can see from the video below that Microsoft Edge has quite a bit more
+ overscroll and a more elastic bounce than Firefox does, and it also
+ allows elastic bounce in directions that the page itself doesn't scroll.
+
+ Edge's way seems more similar to the user experience I'd expect from using
+ Firefox on an iPhone or Android device. Perhaps we should consider
+ following suit?
+
+ (`Link to video <https://drive.google.com/file/d/14XVLT6CNn2RaXcHHCRIrQmRwoMYjj6fu/view?usp=sharing>`__)
+
+
+Web Events
+==============
+
+- It's worth investigating why the ``touchend`` message never seems
+ to be sent by Firefox on any tested devices.
+
+- It's very disappointing that neither the Pointer Events API nor the
+ Touch Events API works at all on Firefox on the Surface Pro. That should
+ be investigated very soon!
+
+
+Operating System Interfaces
+================================
+
+- With the upcoming sun-setting of Windows 7 support, Firefox has an
+ opportunity to revisit the implementation of our input handling and try to
+ simplify our codepaths and eliminate some of the workarounds that exist to
+ handle some of these complex interactions, as well as fix entire classes of
+ bugs - both reported and unreported - that currently exist as a result.
+
+- Does it make sense to combine the touchscreen and pen handling together
+ and use the ``WM_POINTERXXX`` messages for both?
+
+ - This would eliminate the need to handle the ``WM_TOUCH`` and
+ ``WM_GESTURE`` messages at all.
+
+ - Note that there is precedent for this, as `GTK <https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/1563>`__
+ has already done so. It appears that `Blender <https://archive.blender.org/developer/D7660>`__
+ has plans to move toward this as well.
+
+ - Tablet pens seemed to do very well in most of the testing,
+ and they are also the part of the code that mainly exercises the
+ ``WM_POINTERXXX`` codepaths. That may imply increased reliability in
+ that codepath?
+
+ - The Pointer APIs also have good device simulation for integration
+ testing.
+
+ - Would we also want to roll mouse handling into it using the
+ `EnableMouseInPointer <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablemouseinpointer>` __
+ call? That would allow us to also get rid of handling
+ ``WM_MOUSE[MOVE/WHEEL/HWHEEL]`` and ``WM_[LRM]BUTTON[UP|DOWN]``
+ messages. Truly one codepath (with a few minor branches) to rule them
+ all!
+
+ - Nick Rishel sent `this link <http://the-witness.net/news/2012/10/wm_touch-is-totally-bananas/>`__
+ that details the troubles that the developers of The Witness (a video
+ game) ran into when using the ``WM_TOUCH`` API. It argues that the API
+ is poorly-designed, and advises that if Windows 7 support is not
+ needed, the API should be avoided.
+
+- Should we exclusively use DirectManipulation for Pan/Zoom?
+
+ - Multitouch touchpads bypass all of the ``WM_POINTER`` machinery
+ for anything gesture-related and directly send their messages to
+ DirectManipulation. We then "capture" all the DirectManipulation events
+ and pump them into our events pipeline, as explained above.
+
+ - DirectManipulation also handles "overscroll + elastic bounce" in a way
+ that aligns with Windows look-and-feel.
+
+ - Perhaps it makes sense to just use DirectManipulation for all APZ
+ handling and eliminate any attempt at handling this through other
+ codepaths.
+
+High-Frequency Input
+================================
+
+"High-Frequency Input" refers to the ability for an app to be able to still
+perceive input events despite them happening at a rate faster than the app
+itself actually handles them.
+
+Consider a mouse that moves through several points: "A->B->C->D->E". If the
+application processes input when the mouse is at "A" and doesn't poll again
+until the mouse is at point "E", the default behavior of all modern operating
+systems is to "coalesce" these events and simply report "A->E". This is fine
+for the majority of use cases, but certain workloads (such as digital
+handwriting and video games) can benefit from knowing the complete path that
+was taken to get from the start point to the end point.
+
+Generally, solutions to this involve the operating system keeping a history of
+pointer movements that can be retrieved through an API. For example,
+Android provides the `MotionEvent <https://developer.android.com/reference/android/view/MotionEvent.html>`__
+API that batches historal movements.
+
+Unfortunately, the APIs to do this in Windows are terribly broken. As
+`this blog <https://blog.getpaint.net/2019/11/14/paint-net-4-2-6-alpha-build-7258/>`__
+makes clear, `GetMouseMovePointsEx <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex>`__
+has so many issues that they had to remove its usage from their program because
+of the burden. That same blog entry also details that the newer Pointer API has
+the `GetPointerInfoHistory <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpointerinfohistory>`__
+that is *supposed* to support tracking pointer history, but it only ever tracks
+a single entry!
+
+Perhaps luckily, there is currently no web standard for high-frequency input,
+although it `has been asked about in the past <https://lists.w3.org/Archives/Public/public-pointer-events/2014AprJun/0057.html>`__.
+
+If such a standard was ever created, it would likely be very difficult for
+Firefox on Windows to support it.
+
+
+DirectManipulation and Pens
+=============================
+
+- This is a todo item, but it needs to be investigated whether or not
+ DirectManipulation can directly scoop up pen input, or whether it has
+ to be handled by the application (and forwarded to DM if desired).
diff --git a/widget/windows/docs/windows-pointing-device/mouse.jpg b/widget/windows/docs/windows-pointing-device/mouse.jpg
new file mode 100644
index 0000000000..c4fca9ba31
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/mouse.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/touch_media_queries.png b/widget/windows/docs/windows-pointing-device/touch_media_queries.png
new file mode 100644
index 0000000000..f0de661d7f
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/touch_media_queries.png
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/touchpad.jpg b/widget/windows/docs/windows-pointing-device/touchpad.jpg
new file mode 100644
index 0000000000..1327ec5b1c
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/touchpad.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/touchscreen.jpg b/widget/windows/docs/windows-pointing-device/touchscreen.jpg
new file mode 100644
index 0000000000..90246ca02e
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/touchscreen.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/trackpoint.jpg b/widget/windows/docs/windows-pointing-device/trackpoint.jpg
new file mode 100644
index 0000000000..9eae5b5c21
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/trackpoint.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/vrcontroller.jpg b/widget/windows/docs/windows-pointing-device/vrcontroller.jpg
new file mode 100644
index 0000000000..20f2e90bfe
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/vrcontroller.jpg
Binary files differ
diff --git a/widget/windows/docs/windows-pointing-device/wacom_tablet.png b/widget/windows/docs/windows-pointing-device/wacom_tablet.png
new file mode 100644
index 0000000000..2bc30a3b4f
--- /dev/null
+++ b/widget/windows/docs/windows-pointing-device/wacom_tablet.png
Binary files differ
diff --git a/widget/windows/filedialog/PWinFileDialog.ipdl b/widget/windows/filedialog/PWinFileDialog.ipdl
new file mode 100644
index 0000000000..812db7e103
--- /dev/null
+++ b/widget/windows/filedialog/PWinFileDialog.ipdl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=ipdl : */
+/* 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/. */
+
+include WinFileDialogCommandsDefn;
+using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
+using mozilla::widget::filedialog::FileDialogType from "mozilla/widget/filedialog/WinFileDialogCommands.h";
+
+namespace mozilla {
+namespace widget {
+namespace filedialog {
+
+[ChildProc=Utility]
+protocol PWinFileDialog {
+
+child:
+ // Exactly one Show function should be called per instance. Further calls will
+ // result in IPC failure.
+ //
+ // Each will return `Nothing` iff the operation was canceled by the user.
+
+ async ShowFileDialog(WindowsHandle parentHwnd, FileDialogType type, Command[] commands)
+ returns (Results? results);
+ async ShowFolderDialog(WindowsHandle parentHwnd, Command[] commands)
+ returns (nsString? path);
+};
+
+} // namespace filedialog
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/filedialog/WinFileDialogChild.cpp b/widget/windows/filedialog/WinFileDialogChild.cpp
new file mode 100644
index 0000000000..1a2903f8ec
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogChild.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/widget/filedialog/WinFileDialogChild.h"
+
+#include <combaseapi.h>
+#include <objbase.h>
+#include <shobjidl.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/widget/filedialog/WinFileDialogCommands.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::widget::filedialog {
+
+/* extern */ mozilla::LazyLogModule sLogFileDialog("FileDialog");
+
+WinFileDialogChild::WinFileDialogChild() {
+ MOZ_LOG(sLogFileDialog, LogLevel::Info, ("%s %p", __PRETTY_FUNCTION__, this));
+};
+
+WinFileDialogChild::~WinFileDialogChild() {
+ MOZ_LOG(sLogFileDialog, LogLevel::Info, ("%s %p", __PRETTY_FUNCTION__, this));
+};
+
+#define MOZ_ABORT_IF_ALREADY_USED() \
+ do { \
+ MOZ_RELEASE_ASSERT( \
+ !mUsed, "called Show* twice on a single WinFileDialog instance"); \
+ MOZ_LOG( \
+ sLogFileDialog, LogLevel::Info, \
+ ("%s %p: first call to a Show* function", __PRETTY_FUNCTION__, this)); \
+ mUsed = true; \
+ } while (0)
+
+template <size_t N>
+WinFileDialogChild::IPCResult WinFileDialogChild::MakeIpcFailure(
+ HRESULT hr, const char (&what)[N]) {
+ // The crash-report annotator stringifies integer values anyway. We do so
+ // eagerly here to avoid questions about C int/long conversion semantics.
+ nsPrintfCString data("%lu", hr);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::WindowsFileDialogErrorCode, data);
+
+ return IPC_FAIL(this, what);
+}
+
+#define MOZ_IPC_ENSURE_HRESULT_OK(hr, what) \
+ do { \
+ MOZ_LOG(sLogFileDialog, LogLevel::Verbose, \
+ ("checking HRESULT for %s", what)); \
+ HRESULT const _hr_ = (hr); \
+ if (FAILED(_hr_)) { \
+ MOZ_LOG(sLogFileDialog, LogLevel::Error, \
+ ("HRESULT %8lX while %s", (hr), (what))); \
+ return MakeIpcFailure(_hr_, (what)); \
+ } \
+ } while (0)
+
+WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFileDialog(
+ uintptr_t parentHwnd, FileDialogType type, nsTArray<Command> commands,
+ FileResolver&& resolver) {
+ MOZ_ABORT_IF_ALREADY_USED();
+
+ SpawnFilePicker(HWND(parentHwnd), type, std::move(commands))
+ ->Then(
+ GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [resolver = std::move(resolver)](Maybe<Results> const& res) {
+ resolver(res);
+ },
+ [self = RefPtr(this)](HRESULT hr) {
+ // this doesn't need to be returned anywhere; it'll crash the
+ // process as a side effect of construction
+ self->MakeIpcFailure(hr, "SpawnFilePicker");
+ });
+
+ return IPC_OK();
+}
+
+WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFolderDialog(
+ uintptr_t parentHwnd, nsTArray<Command> commands,
+ FolderResolver&& resolver) {
+ MOZ_ABORT_IF_ALREADY_USED();
+
+ SpawnFolderPicker(HWND(parentHwnd), std::move(commands))
+ ->Then(
+ GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [resolver = std::move(resolver)](Maybe<nsString> const& res) {
+ resolver(res);
+ },
+ [self = RefPtr(this), resolver](HRESULT hr) {
+ // this doesn't need to be returned anywhere; it'll crash the
+ // process as a side effect of construction
+ self->MakeIpcFailure(hr, "SpawnFolderPicker");
+ });
+
+ return IPC_OK();
+}
+
+#undef MOZ_IPC_ENSURE_HRESULT_OK
+
+void WinFileDialogChild::ProcessingError(Result aCode, const char* aReason) {
+ detail::LogProcessingError(sLogFileDialog, this, aCode, aReason);
+}
+
+} // namespace mozilla::widget::filedialog
diff --git a/widget/windows/filedialog/WinFileDialogChild.h b/widget/windows/filedialog/WinFileDialogChild.h
new file mode 100644
index 0000000000..b0939ce2ed
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogChild.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_filedialog_WinFileDialogChild_h__
+#define widget_windows_filedialog_WinFileDialogChild_h__
+
+#include "mozilla/widget/filedialog/PWinFileDialogChild.h"
+
+// forward declaration of native Windows interface-struct
+struct IFileDialog;
+
+namespace mozilla::widget::filedialog {
+
+class WinFileDialogChild : public PWinFileDialogChild {
+ public:
+ using Command = mozilla::widget::filedialog::Command;
+ using IPCResult = mozilla::ipc::IPCResult;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinFileDialogChild, override);
+
+ WinFileDialogChild();
+
+ public:
+ using FileResolver = PWinFileDialogChild::ShowFileDialogResolver;
+ IPCResult RecvShowFileDialog(uintptr_t parentHwnd, FileDialogType,
+ nsTArray<Command>, FileResolver&&);
+
+ using FolderResolver = PWinFileDialogChild::ShowFolderDialogResolver;
+ IPCResult RecvShowFolderDialog(uintptr_t parentHwnd, nsTArray<Command>,
+ FolderResolver&&);
+
+ private:
+ ~WinFileDialogChild();
+
+ void ProcessingError(Result aCode, const char* aReason) override;
+
+ // Defined and used only in WinFileDialogChild.cpp.
+ template <size_t N>
+ IPCResult MakeIpcFailure(HRESULT hr, const char (&what)[N]);
+
+ // This flag properly _should_ be static (_i.e._, per-process) rather than
+ // per-instance; but we can't presently instantiate two separate utility
+ // processes with the same sandbox type, so we have to reuse the existing
+ // utility process if there is one.
+ bool mUsed = false;
+};
+
+} // namespace mozilla::widget::filedialog
+
+#endif // widget_windows_filedialog_WinFileDialogChild_h__
diff --git a/widget/windows/filedialog/WinFileDialogCommands.cpp b/widget/windows/filedialog/WinFileDialogCommands.cpp
new file mode 100644
index 0000000000..f0503ab8f0
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogCommands.cpp
@@ -0,0 +1,460 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/widget/filedialog/WinFileDialogCommands.h"
+
+#include <type_traits>
+#include <shobjidl.h>
+#include <shtypes.h>
+#include <winerror.h>
+#include "WinUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/mscom/ApartmentRegion.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::widget::filedialog {
+
+// Visitor to apply commands to the dialog.
+struct Applicator {
+ IFileDialog* dialog = nullptr;
+
+ HRESULT Visit(Command const& c) {
+ switch (c.type()) {
+ default:
+ case Command::T__None:
+ return E_INVALIDARG;
+
+ case Command::TSetOptions:
+ return Apply(c.get_SetOptions());
+ case Command::TSetTitle:
+ return Apply(c.get_SetTitle());
+ case Command::TSetOkButtonLabel:
+ return Apply(c.get_SetOkButtonLabel());
+ case Command::TSetFolder:
+ return Apply(c.get_SetFolder());
+ case Command::TSetFileName:
+ return Apply(c.get_SetFileName());
+ case Command::TSetDefaultExtension:
+ return Apply(c.get_SetDefaultExtension());
+ case Command::TSetFileTypes:
+ return Apply(c.get_SetFileTypes());
+ case Command::TSetFileTypeIndex:
+ return Apply(c.get_SetFileTypeIndex());
+ }
+ }
+
+ HRESULT Apply(SetOptions const& c) { return dialog->SetOptions(c.options()); }
+ HRESULT Apply(SetTitle const& c) { return dialog->SetTitle(c.title().get()); }
+ HRESULT Apply(SetOkButtonLabel const& c) {
+ return dialog->SetOkButtonLabel(c.label().get());
+ }
+ HRESULT Apply(SetFolder const& c) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(SHCreateItemFromParsingName(
+ c.path().get(), nullptr, IID_IShellItem, getter_AddRefs(folder)))) {
+ return dialog->SetFolder(folder);
+ }
+ // graciously accept that the provided path may have been nonsense
+ return S_OK;
+ }
+ HRESULT Apply(SetFileName const& c) {
+ return dialog->SetFileName(c.filename().get());
+ }
+ HRESULT Apply(SetDefaultExtension const& c) {
+ return dialog->SetDefaultExtension(c.extension().get());
+ }
+ HRESULT Apply(SetFileTypes const& c) {
+ std::vector<COMDLG_FILTERSPEC> vec;
+ for (auto const& filter : c.filterList()) {
+ vec.push_back(
+ {.pszName = filter.name().get(), .pszSpec = filter.spec().get()});
+ }
+ return dialog->SetFileTypes(vec.size(), vec.data());
+ }
+ HRESULT Apply(SetFileTypeIndex const& c) {
+ return dialog->SetFileTypeIndex(c.index());
+ }
+};
+
+namespace {
+static HRESULT GetShellItemPath(IShellItem* aItem, nsString& aResultString) {
+ NS_ENSURE_TRUE(aItem, E_INVALIDARG);
+
+ mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> str;
+ HRESULT const hr =
+ aItem->GetDisplayName(SIGDN_FILESYSPATH, getter_Transfers(str));
+ if (SUCCEEDED(hr)) {
+ aResultString.Assign(str.get());
+ }
+ return hr;
+}
+} // namespace
+
+#define MOZ_ENSURE_HRESULT_OK(call_) \
+ do { \
+ HRESULT const _tmp_hr_ = (call_); \
+ if (FAILED(_tmp_hr_)) return Err(_tmp_hr_); \
+ } while (0)
+
+mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(
+ FileDialogType type) {
+ RefPtr<IFileDialog> dialog;
+
+ CLSID const clsid = type == FileDialogType::Open ? CLSID_FileOpenDialog
+ : CLSID_FileSaveDialog;
+ HRESULT const hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IFileDialog, getter_AddRefs(dialog));
+ MOZ_ENSURE_HRESULT_OK(hr);
+
+ return std::move(dialog);
+}
+
+HRESULT ApplyCommands(::IFileDialog* dialog,
+ nsTArray<Command> const& commands) {
+ Applicator applicator{.dialog = dialog};
+ for (auto const& cmd : commands) {
+ HRESULT const hr = applicator.Visit(cmd);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ }
+ return S_OK;
+}
+
+mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) {
+ FILEOPENDIALOGOPTIONS fos;
+ MOZ_ENSURE_HRESULT_OK(dialog->GetOptions(&fos));
+
+ using widget::WinUtils;
+
+ // Extract which filter type the user selected
+ UINT index;
+ MOZ_ENSURE_HRESULT_OK(dialog->GetFileTypeIndex(&index));
+
+ // single selection
+ if ((fos & FOS_ALLOWMULTISELECT) == 0) {
+ RefPtr<IShellItem> item;
+ MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item)));
+ if (!item) {
+ return Err(E_FAIL);
+ }
+
+ nsAutoString path;
+ MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, path));
+
+ return Results({path}, index);
+ }
+
+ // multiple selection
+ RefPtr<IFileOpenDialog> openDlg;
+ dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg));
+ if (!openDlg) {
+ MOZ_ASSERT(false, "a file-save dialog was given FOS_ALLOWMULTISELECT?");
+ return Err(E_UNEXPECTED);
+ }
+
+ RefPtr<IShellItemArray> items;
+ MOZ_ENSURE_HRESULT_OK(openDlg->GetResults(getter_AddRefs(items)));
+ if (!items) {
+ return Err(E_FAIL);
+ }
+
+ nsTArray<nsString> paths;
+
+ DWORD count = 0;
+ MOZ_ENSURE_HRESULT_OK(items->GetCount(&count));
+ for (DWORD idx = 0; idx < count; idx++) {
+ RefPtr<IShellItem> item;
+ MOZ_ENSURE_HRESULT_OK(items->GetItemAt(idx, getter_AddRefs(item)));
+
+ nsAutoString str;
+ MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str));
+
+ paths.EmplaceBack(str);
+ }
+
+ return Results(std::move(paths), std::move(index));
+}
+
+mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) {
+ RefPtr<IShellItem> item;
+ MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item)));
+ if (!item) {
+ // shouldn't happen -- probably a precondition failure on our part, but
+ // might be due to misbehaving shell extensions?
+ MOZ_ASSERT(false,
+ "unexpected lack of item: was `Show`'s return value checked?");
+ return Err(E_FAIL);
+ }
+
+ // If the user chose a Win7 Library, resolve to the library's
+ // default save folder.
+ RefPtr<IShellLibrary> shellLib;
+ RefPtr<IShellItem> folderPath;
+ MOZ_ENSURE_HRESULT_OK(
+ CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLibrary, getter_AddRefs(shellLib)));
+
+ if (shellLib && SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) &&
+ SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
+ getter_AddRefs(folderPath)))) {
+ item.swap(folderPath);
+ }
+
+ // get the folder's file system path
+ nsAutoString str;
+ MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str));
+ return str;
+}
+
+#undef MOZ_ENSURE_HRESULT_OK
+
+namespace detail {
+void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller,
+ ipc::HasResultCodes::Result aCode,
+ const char* aReason) {
+ LogLevel const level = [&]() {
+ switch (aCode) {
+ case ipc::HasResultCodes::MsgProcessed:
+ // Normal operation. (We probably never actually get this code.)
+ return LogLevel::Verbose;
+
+ case ipc::HasResultCodes::MsgDropped:
+ return LogLevel::Verbose;
+
+ default:
+ return LogLevel::Error;
+ }
+ }();
+
+ // Processing errors are sometimes unhelpfully formatted. We can't fix that
+ // directly because the unhelpful formatting has made its way to telemetry
+ // (table `telemetry.socorro_crash`, column `ipc_channel_error`) and is being
+ // aggregated on. :(
+ nsCString reason(aReason);
+ if (reason.Last() == '\n') {
+ reason.Truncate(reason.Length() - 1);
+ }
+
+ if (MOZ_LOG_TEST(aModule, level)) {
+ const char* const side = [&]() {
+ switch (aCaller->GetSide()) {
+ case ipc::ParentSide:
+ return "parent";
+ case ipc::ChildSide:
+ return "child";
+ case ipc::UnknownSide:
+ return "unknown side";
+ default:
+ return "<illegal value>";
+ }
+ }();
+
+ const char* const errorStr = [&]() {
+ switch (aCode) {
+ case ipc::HasResultCodes::MsgProcessed:
+ return "Processed";
+ case ipc::HasResultCodes::MsgDropped:
+ return "Dropped";
+ case ipc::HasResultCodes::MsgNotKnown:
+ return "NotKnown";
+ case ipc::HasResultCodes::MsgNotAllowed:
+ return "NotAllowed";
+ case ipc::HasResultCodes::MsgPayloadError:
+ return "PayloadError";
+ case ipc::HasResultCodes::MsgProcessingError:
+ return "ProcessingError";
+ case ipc::HasResultCodes::MsgRouteError:
+ return "RouteError";
+ case ipc::HasResultCodes::MsgValueError:
+ return "ValueError";
+ default:
+ return "<illegal error type>";
+ }
+ }();
+
+ MOZ_LOG(aModule, level,
+ ("%s [%s]: IPC error (%s): %s", aCaller->GetProtocolName(), side,
+ errorStr, reason.get()));
+ }
+
+ if (level == LogLevel::Error) {
+ // kill the child process...
+ if (aCaller->GetSide() == ipc::ParentSide) {
+ // ... which isn't us
+ ipc::UtilityProcessManager::GetSingleton()->CleanShutdown(
+ ipc::SandboxingKind::WINDOWS_FILE_DIALOG);
+ } else {
+ // ... which (presumably) is us
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::ipc_channel_error, reason);
+
+ MOZ_CRASH("IPC error");
+ }
+ }
+}
+
+// Given a (synchronous) Action returning a Result<T, HRESULT>, perform that
+// action on a new single-purpose "File Dialog" thread, with COM initialized as
+// STA. (The thread will be destroyed afterwards.)
+//
+// Returns a Promise which will resolve to T (if the action returns Ok) or
+// reject with an HRESULT (if the action either returns Err or couldn't be
+// performed).
+template <typename Res, typename Action, size_t N>
+RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N],
+ Action action) {
+ RefPtr<nsIThread> thread;
+ {
+ nsresult rv = NS_NewNamedThread("File Dialog", getter_AddRefs(thread),
+ nullptr, {.isUiThread = true});
+ if (NS_FAILED(rv)) {
+ return Promise<Res>::CreateAndReject((HRESULT)rv, where);
+ }
+ }
+ // `thread` is single-purpose, and should not perform any additional work
+ // after `action`. Shut it down after we've dispatched that.
+ auto close_thread_ = MakeScopeExit([&]() {
+ auto const res = thread->AsyncShutdown();
+ static_assert(
+ std::is_same_v<uint32_t, std::underlying_type_t<decltype(res)>>);
+ if (NS_FAILED(res)) {
+ MOZ_LOG(sLogFileDialog, LogLevel::Warning,
+ ("thread->AsyncShutdown() failed: res=0x%08" PRIX32,
+ static_cast<uint32_t>(res)));
+ }
+ });
+
+ // our eventual return value
+ RefPtr promise = MakeRefPtr<typename Promise<Res>::Private>(where);
+
+ // alias to reduce indentation depth
+ auto const dispatch = [&](auto closure) {
+ return thread->DispatchToQueue(
+ NS_NewRunnableFunction(where, std::move(closure)),
+ mozilla::EventQueuePriority::Normal);
+ };
+
+ dispatch([thread, promise, where, action = std::move(action)]() {
+ // Like essentially all COM UI components, the file dialog is STA: it must
+ // be associated with a specific thread to create its HWNDs and receive
+ // messages for them. If it's launched from a thread in the multithreaded
+ // apartment (including via implicit MTA), COM will proxy out to the
+ // process's main STA thread, and the file-dialog's modal loop will run
+ // there.
+ //
+ // This of course would completely negate any point in using a separate
+ // thread, since behind the scenes the dialog would still be running on the
+ // process's main thread. In particular, under that arrangement, file
+ // dialogs (and other nested modal loops, like those performed by
+ // `SpinEventLoopUntil`) will resolve in strictly LIFO order, effectively
+ // remaining suspended until all later modal loops resolve.
+ //
+ // To avoid this, we initialize COM as STA, so that it (rather than the main
+ // STA thread) is the file dialog's "home" thread and the IFileDialog's home
+ // apartment.
+
+ mozilla::mscom::STARegion staRegion;
+ if (!staRegion) {
+ MOZ_LOG(sLogFileDialog, LogLevel::Error,
+ ("COM init failed on file dialog thread: hr = %08lx",
+ staRegion.GetHResult()));
+
+ APTTYPE at;
+ APTTYPEQUALIFIER atq;
+ HRESULT const hr = ::CoGetApartmentType(&at, &atq);
+ MOZ_LOG(sLogFileDialog, LogLevel::Error,
+ (" current COM apartment state: hr = %08lX, APTTYPE = "
+ "%08X, APTTYPEQUALIFIER = %08X",
+ hr, at, atq));
+
+ // If this happens in the utility process, crash so we learn about it.
+ // (TODO: replace this with a telemetry ping.)
+ if (!XRE_IsParentProcess()) {
+ // Preserve relevant data on the stack for later analysis.
+ std::tuple volatile info{staRegion.GetHResult(), hr, at, atq};
+ MOZ_CRASH("Could not initialize COM STA in utility process");
+ }
+
+ // If this happens in the parent process, don't crash; just fall back to a
+ // nested modal loop. This isn't ideal, but it will probably still work
+ // well enough for the common case, wherein no other modal loops are
+ // active.
+ //
+ // (TODO: replace this with a telemetry ping, too.)
+ }
+
+ // Actually invoke the action and report the result.
+ Result<Res, HRESULT> val = action();
+ if (val.isErr()) {
+ promise->Reject(val.unwrapErr(), where);
+ } else {
+ promise->Resolve(val.unwrap(), where);
+ }
+ });
+
+ return promise;
+}
+
+// For F returning `Result<T, E>`, yields the type `T`.
+template <typename F, typename... Args>
+using inner_result_of =
+ typename std::remove_reference_t<decltype(std::declval<F>()(
+ std::declval<Args>()...))>::ok_type;
+
+template <typename ExtractorF,
+ typename RetT = inner_result_of<ExtractorF, IFileDialog*>>
+auto SpawnPickerT(HWND parent, FileDialogType type, ExtractorF&& extractor,
+ nsTArray<Command> commands) -> RefPtr<Promise<Maybe<RetT>>> {
+ return detail::SpawnFileDialogThread<Maybe<RetT>>(
+ __PRETTY_FUNCTION__,
+ [=, commands = std::move(commands)]() -> Result<Maybe<RetT>, HRESULT> {
+ // On Win10, the picker doesn't support per-monitor DPI, so we create it
+ // with our context set temporarily to system-dpi-aware.
+ WinUtils::AutoSystemDpiAware dpiAwareness;
+
+ RefPtr<IFileDialog> dialog;
+ MOZ_TRY_VAR(dialog, MakeFileDialog(type));
+
+ if (HRESULT const rv = ApplyCommands(dialog, commands); FAILED(rv)) {
+ return mozilla::Err(rv);
+ }
+
+ if (HRESULT const rv = dialog->Show(parent); FAILED(rv)) {
+ if (rv == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
+ return Result<Maybe<RetT>, HRESULT>(Nothing());
+ }
+ return mozilla::Err(rv);
+ }
+
+ RetT res;
+ MOZ_TRY_VAR(res, extractor(dialog.get()));
+
+ return Some(res);
+ });
+}
+
+} // namespace detail
+
+RefPtr<Promise<Maybe<Results>>> SpawnFilePicker(HWND parent,
+ FileDialogType type,
+ nsTArray<Command> commands) {
+ return detail::SpawnPickerT(parent, type, GetFileResults,
+ std::move(commands));
+}
+
+RefPtr<Promise<Maybe<nsString>>> SpawnFolderPicker(HWND parent,
+ nsTArray<Command> commands) {
+ return detail::SpawnPickerT(parent, FileDialogType::Open, GetFolderResults,
+ std::move(commands));
+}
+
+} // namespace mozilla::widget::filedialog
diff --git a/widget/windows/filedialog/WinFileDialogCommands.h b/widget/windows/filedialog/WinFileDialogCommands.h
new file mode 100644
index 0000000000..ca4561a8f2
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogCommands.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_filedialog_WinFileDialogCommands_h__
+#define widget_windows_filedialog_WinFileDialogCommands_h__
+
+#include "ipc/EnumSerializer.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ipc/MessageLink.h"
+#include "mozilla/widget/filedialog/WinFileDialogCommandsDefn.h"
+
+// Windows interface types, defined in <shobjidl.h>
+struct IFileDialog;
+struct IFileOpenDialog;
+
+namespace mozilla::widget::filedialog {
+
+extern LazyLogModule sLogFileDialog;
+
+enum class FileDialogType : uint8_t { Open, Save };
+
+// Create a file-dialog of the relevant type. Requires MSCOM to be initialized.
+mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(FileDialogType);
+
+// Apply the selected commands to the IFileDialog, in preparation for showing
+// it. (The actual showing step is left to the caller.)
+[[nodiscard]] HRESULT ApplyCommands(::IFileDialog*,
+ nsTArray<Command> const& commands);
+
+// Extract one or more results from the file-picker dialog.
+//
+// Requires that Show() has been called and has returned S_OK.
+mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog*);
+
+// Extract the chosen folder from the folder-picker dialog.
+//
+// Requires that Show() has been called and has returned S_OK.
+mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog*);
+
+namespace detail {
+// Log the error. If it's a notable error, kill the child process.
+void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller,
+ ipc::HasResultCodes::Result aCode, const char* aReason);
+
+} // namespace detail
+
+template <typename R>
+using Promise = MozPromise<R, HRESULT, true>;
+
+// Show a file-picker on another thread in the current process.
+RefPtr<Promise<Maybe<Results>>> SpawnFilePicker(HWND parent,
+ FileDialogType type,
+ nsTArray<Command> commands);
+
+// Show a folder-picker on another thread in the current process.
+RefPtr<Promise<Maybe<nsString>>> SpawnFolderPicker(HWND parent,
+ nsTArray<Command> commands);
+
+} // namespace mozilla::widget::filedialog
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::widget::filedialog::FileDialogType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::widget::filedialog::FileDialogType,
+ mozilla::widget::filedialog::FileDialogType::Open,
+ mozilla::widget::filedialog::FileDialogType::Save> {};
+} // namespace IPC
+
+#endif // widget_windows_filedialog_WinFileDialogCommands_h__
diff --git a/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh
new file mode 100644
index 0000000000..dd85942f24
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=ipdl : */
+/* 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/. */
+
+namespace mozilla {
+namespace widget {
+namespace filedialog {
+
+// Commands corresponding to the various functions in IFileDialog (or at least
+// the ones we actually make use of).
+//
+// All commands' semantics are direct parallels of their equivalently-named
+// functions on IFileDialog, with the only changes being those necessary to use
+// IPDLable representation-datatypes. (Thus, e.g., `SetOptions` effectively
+// takes a `FILEOPENDIALOGOPTIONS`, and `SetFileTypeIndex` is 1-based.)
+struct SetOptions { uint32_t options; };
+struct SetTitle { nsString title; };
+struct SetOkButtonLabel { nsString label; };
+struct SetFolder { nsString path; };
+struct SetFileName { nsString filename; };
+struct SetDefaultExtension { nsString extension; };
+struct ComDlgFilterSpec { nsString name; nsString spec; };
+struct SetFileTypes { ComDlgFilterSpec[] filterList; };
+struct SetFileTypeIndex { uint32_t index; };
+
+// Union of the above.
+union Command {
+ SetOptions;
+ SetTitle;
+ SetOkButtonLabel;
+ SetFolder;
+ SetFileName;
+ SetDefaultExtension;
+ SetFileTypes;
+ SetFileTypeIndex;
+};
+
+// The results from opening a file dialog. (Note that folder selection only
+// returns an nsString.)
+struct Results {
+ nsString[] paths;
+ uint32_t selectedFileTypeIndex;
+};
+
+} // namespace filedialog
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/filedialog/WinFileDialogParent.cpp b/widget/windows/filedialog/WinFileDialogParent.cpp
new file mode 100644
index 0000000000..2c256a1506
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogParent.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/widget/filedialog/WinFileDialogParent.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Result.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "nsISupports.h"
+
+namespace mozilla::widget::filedialog {
+
+// Count of currently-open file dialogs (not just open-file dialogs).
+static size_t sOpenDialogActors = 0;
+
+WinFileDialogParent::WinFileDialogParent() {
+ MOZ_LOG(sLogFileDialog, LogLevel::Debug,
+ ("%s %p", __PRETTY_FUNCTION__, this));
+}
+
+WinFileDialogParent::~WinFileDialogParent() {
+ MOZ_LOG(sLogFileDialog, LogLevel::Debug,
+ ("%s %p", __PRETTY_FUNCTION__, this));
+}
+
+PWinFileDialogParent::nsresult WinFileDialogParent::BindToUtilityProcess(
+ mozilla::ipc::UtilityProcessParent* aUtilityParent) {
+ Endpoint<PWinFileDialogParent> parentEnd;
+ Endpoint<PWinFileDialogChild> childEnd;
+ nsresult rv = PWinFileDialog::CreateEndpoints(base::GetCurrentProcId(),
+ aUtilityParent->OtherPid(),
+ &parentEnd, &childEnd);
+
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Protocol endpoints failure");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aUtilityParent->SendStartWinFileDialogService(std::move(childEnd))) {
+ MOZ_ASSERT(false, "SendStartWinFileDialogService failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!parentEnd.Bind(this)) {
+ MOZ_ASSERT(false, "parentEnd.Bind failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ sOpenDialogActors++;
+ return NS_OK;
+}
+
+void WinFileDialogParent::ProcessingError(Result aCode, const char* aReason) {
+ detail::LogProcessingError(sLogFileDialog, this, aCode, aReason);
+}
+
+ProcessProxy::ProcessProxy(RefPtr<WFDP>&& obj)
+ : data(MakeRefPtr<Contents>(std::move(obj))) {}
+
+ProcessProxy::Contents::Contents(RefPtr<WFDP>&& obj) : ptr(std::move(obj)) {}
+
+ProcessProxy::Contents::~Contents() {
+ AssertIsOnMainThread();
+
+ // destroy the actor...
+ ptr->Close();
+
+ // ... and possibly the process
+ if (!--sOpenDialogActors) {
+ StopProcess();
+ }
+}
+
+void ProcessProxy::Contents::StopProcess() {
+ auto const upm = ipc::UtilityProcessManager::GetSingleton();
+ if (!upm) {
+ // This is only possible when the UtilityProcessManager has shut down -- in
+ // which case the file-dialog process has also already been directed to shut
+ // down, and there's nothing we need to do here.
+ return;
+ }
+
+ MOZ_LOG(sLogFileDialog, LogLevel::Debug,
+ ("%s: killing the WINDOWS_FILE_DIALOG process (no more live "
+ "actors)",
+ __PRETTY_FUNCTION__));
+ upm->CleanShutdown(ipc::SandboxingKind::WINDOWS_FILE_DIALOG);
+}
+
+} // namespace mozilla::widget::filedialog
diff --git a/widget/windows/filedialog/WinFileDialogParent.h b/widget/windows/filedialog/WinFileDialogParent.h
new file mode 100644
index 0000000000..a2c1197c55
--- /dev/null
+++ b/widget/windows/filedialog/WinFileDialogParent.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 widget_windows_filedialog_WinFileDialogParent_h__
+#define widget_windows_filedialog_WinFileDialogParent_h__
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ProcInfo.h"
+#include "mozilla/Result.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/widget/filedialog/PWinFileDialogParent.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::widget::filedialog {
+
+class WinFileDialogParent : public PWinFileDialogParent {
+ public:
+ using UtilityActorName = ::mozilla::UtilityActorName;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinFileDialogParent, override);
+
+ public:
+ WinFileDialogParent();
+ nsresult BindToUtilityProcess(
+ mozilla::ipc::UtilityProcessParent* aUtilityParent);
+
+ UtilityActorName GetActorName() {
+ return UtilityActorName::WindowsFileDialog;
+ }
+
+ private:
+ ~WinFileDialogParent();
+
+ void ProcessingError(Result aCode, const char* aReason) override;
+};
+
+// Proxy for the WinFileDialog process and actor.
+//
+// The IPC subsystem holds a strong reference to all IPC actors, so releasing
+// the last RefPtr for such an actor does not actually cause the actor to be
+// destroyed. Similarly, the UtilityProcessManager owns the host process for an
+// actor, and merely destroying all actors within that host process will not
+// cause it to be reaped.
+//
+// This object, then, acts as a proxy for those objects' lifetimes: when the
+// last reference to `Contents` is released, the necessary explicit cleanup of
+// the actor (and, if possible, the host process) will be performed.
+class ProcessProxy {
+ public:
+ using WFDP = WinFileDialogParent;
+
+ explicit ProcessProxy(RefPtr<WFDP>&& obj);
+ ~ProcessProxy() = default;
+
+ explicit operator bool() const { return data->ptr && data->ptr->CanSend(); }
+ bool operator!() const { return !bool(*this); }
+
+ WFDP& operator*() const { return *data->ptr; }
+ WFDP* operator->() const { return data->ptr; }
+ WFDP* get() const { return data->ptr; }
+
+ ProcessProxy(ProcessProxy const& that) = default;
+ ProcessProxy(ProcessProxy&&) = default;
+
+ private:
+ struct Contents {
+ NS_INLINE_DECL_REFCOUNTING(Contents);
+
+ public:
+ explicit Contents(RefPtr<WFDP>&& obj);
+ RefPtr<WFDP> const ptr;
+
+ private:
+ ~Contents();
+ void StopProcess();
+ };
+ // guaranteed nonnull
+ RefPtr<Contents> data;
+};
+
+} // namespace mozilla::widget::filedialog
+
+#endif // widget_windows_filedialog_WinFileDialogParent_h__
diff --git a/widget/windows/filedialog/moz.build b/widget/windows/filedialog/moz.build
new file mode 100644
index 0000000000..d2732faf78
--- /dev/null
+++ b/widget/windows/filedialog/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+IPDL_SOURCES += [
+ "PWinFileDialog.ipdl",
+ "WinFileDialogCommandsDefn.ipdlh",
+]
+
+UNIFIED_SOURCES += [
+ "WinFileDialogChild.cpp",
+ "WinFileDialogCommands.cpp",
+ "WinFileDialogParent.cpp",
+]
+
+EXPORTS.mozilla.widget.filedialog += [
+ "WinFileDialogChild.h",
+ "WinFileDialogCommands.h",
+ "WinFileDialogParent.h",
+]
+
+# needed for IPC header files
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/widget/windows/metrics.yaml b/widget/windows/metrics.yaml
new file mode 100644
index 0000000000..e9d3b4f5cf
--- /dev/null
+++ b/widget/windows/metrics.yaml
@@ -0,0 +1,58 @@
+# 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/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: Widget: Win32'
+
+file_dialog:
+ fallback:
+ type: event
+ description: >
+ Records the result of an attempt to open and use the out-of-process file
+ dialog when the in-process file-dialog is available as a fallback.
+ metadata:
+ # mostly technical, but includes timing data that may derive from user
+ # interactions
+ data-sensitivity: [technical, interaction]
+ notification_emails:
+ - rkraesig@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1872397
+ # this event may alternatively be manually expired once bug 1677170 is
+ # closed
+ expires: 135
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1872397#c7
+ extra_keys:
+ succeeded:
+ type: boolean
+ description: >
+ Whether the out-of-process dialog succeeded or failed. (Note that
+ user-induced cancellation is considered a form of success.)
+ time_remote:
+ type: quantity
+ description: >
+ The time between the out-of-process file dialog's instantiation
+ attempt and its failure, in milliseconds.
+ hresult_remote:
+ type: string
+ description: >
+ The failure code produced by the out-of-process file dialog, formatted
+ as eight hexdigits. Only present when `!succeeded`.
+ time_local:
+ type: quantity
+ description: >
+ The time between the in-process file dialog's instantiation attempt
+ and its conclusion (successfully or otherwise), in milliseconds. Only
+ present when `!succeeded`.
+ hresult_local:
+ type: string
+ description: >
+ The return code produced by the in-process file dialog, formatted as
+ eight hexdigits. Only present when `!succeeded`.
diff --git a/widget/windows/moz.build b/widget/windows/moz.build
new file mode 100644
index 0000000000..f19a46caf1
--- /dev/null
+++ b/widget/windows/moz.build
@@ -0,0 +1,213 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Win32")
+ SCHEDULES.exclusive = ["windows"]
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*IMEHandler*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*IMMHandler*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*KeyboardLayout*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("OSK*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*TSFTextStore*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+DIRS += [
+ "filedialog",
+]
+
+TEST_DIRS += ["tests"]
+
+EXPORTS += [
+ "nsdefs.h",
+ "WindowHook.h",
+ "WinUtils.h",
+]
+
+EXPORTS.mozilla += [
+ "ShellHeaderOnlyUtils.h",
+ "ToastNotificationHeaderOnlyUtils.h",
+ "UrlmonHeaderOnlyUtils.h",
+ "WindowsConsole.h",
+ "WindowsEventLog.h",
+ "WinHeaderOnlyUtils.h",
+]
+
+EXPORTS.mozilla.widget += [
+ "AudioSession.h",
+ "CompositorWidgetChild.h",
+ "CompositorWidgetParent.h",
+ "InProcessWinCompositorWidget.h",
+ "JumpListBuilder.h",
+ "nsWindowLoggedMessages.h",
+ "WinCompositorWidget.h",
+ "WinCompositorWindowThread.h",
+ "WindowsEMF.h",
+ "WindowsSMTCProvider.h",
+ "WinEventObserver.h",
+ "WinMessages.h",
+ "WinModifierKeyState.h",
+ "WinRegistry.h",
+ "WinTaskbar.h",
+ "WinWindowOcclusionTracker.h",
+]
+
+UNIFIED_SOURCES += [
+ "AudioSession.cpp",
+ "CompositorWidgetChild.cpp",
+ "DirectManipulationOwner.cpp",
+ "GfxInfo.cpp",
+ "IEnumFE.cpp",
+ "IMMHandler.cpp",
+ "JumpListBuilder.cpp",
+ "KeyboardLayout.cpp",
+ "LegacyJumpListItem.cpp",
+ "LSPAnnotator.cpp",
+ "nsAppShell.cpp",
+ "nsClipboard.cpp",
+ "nsColorPicker.cpp",
+ "nsDataObj.cpp",
+ "nsDataObjCollection.cpp",
+ "nsDragService.cpp",
+ "nsLookAndFeel.cpp",
+ "nsNativeDragSource.cpp",
+ "nsNativeDragTarget.cpp",
+ "nsNativeThemeWin.cpp",
+ "nsSound.cpp",
+ "nsToolkit.cpp",
+ "nsUserIdleServiceWin.cpp",
+ "nsUXThemeData.cpp",
+ "nsWindow.cpp",
+ "nsWindowDbg.cpp",
+ "nsWindowGfx.cpp",
+ "nsWindowLoggedMessages.cpp",
+ "nsWindowTaskbarConcealer.cpp",
+ "nsWinGesture.cpp",
+ "OSKTabTipManager.cpp",
+ "OSKVRManager.cpp",
+ "RemoteBackbuffer.cpp",
+ "ScreenHelperWin.cpp",
+ "SystemStatusBar.cpp",
+ "TaskbarPreview.cpp",
+ "TaskbarPreviewButton.cpp",
+ "TaskbarTabPreview.cpp",
+ "TaskbarWindowPreview.cpp",
+ "WidgetTraceEvent.cpp",
+ "WinCompositorWindowThread.cpp",
+ "WindowHook.cpp",
+ "WindowsConsole.cpp",
+ "WinEventObserver.cpp",
+ "WinIMEHandler.cpp",
+ "WinPointerEvents.cpp",
+ "WinRegistry.cpp",
+ "WinTaskbar.cpp",
+ "WinTextEventDispatcherListener.cpp",
+ "WinUtils.cpp",
+ "WinWindowOcclusionTracker.cpp",
+]
+
+# The following files cannot be built in unified mode because of name clashes.
+SOURCES += [
+ "CompositorWidgetParent.cpp",
+ "InProcessWinCompositorWidget.cpp",
+ "LegacyJumpListBuilder.cpp",
+ "MediaKeysEventSourceFactory.cpp",
+ "nsBidiKeyboard.cpp",
+ "nsFilePicker.cpp",
+ "nsSharePicker.cpp",
+ "nsWidgetFactory.cpp",
+ "OSKInputPaneManager.cpp",
+ "WinCompositorWidget.cpp",
+ "WindowsSMTCProvider.cpp",
+ "WindowsUIUtils.cpp",
+ "WinMouseScrollHandler.cpp",
+]
+
+# Needs INITGUID and we don't allow INITGUID in unified sources since bug 970429.
+SOURCES += [
+ "InputDeviceUtils.cpp",
+ "TSFTextStore.cpp",
+]
+
+if CONFIG["NS_PRINTING"]:
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecWin.cpp",
+ "nsPrintDialogWin.cpp",
+ "nsPrinterWin.cpp",
+ "nsPrintSettingsServiceWin.cpp",
+ "nsPrintSettingsWin.cpp",
+ ]
+ SOURCES += [
+ "nsPrintDialogUtil.cpp",
+ ]
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ UNIFIED_SOURCES += [
+ "WindowsEMF.cpp",
+ ]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_ENABLE_SKIA_PDF"]:
+ LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
+
+LOCAL_INCLUDES += [
+ "/gfx/cairo/cairo/src",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/toolkit/components/jsoncpp/include",
+ "/toolkit/xre",
+ "/widget",
+ "/widget/headless",
+ "/xpcom/base",
+]
+
+DEFINES["MOZ_UNICODE"] = True
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+# Turn `firefox` into `Firefox`.
+DEFINES["MOZ_TOAST_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"].title()
+
+for var in ("MOZ_ENABLE_D3D10_LAYER",):
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+USE_LIBS += [
+ "jsoncpp",
+]
+
+OS_LIBS += [
+ "ktmw32",
+ "rpcrt4",
+ "urlmon",
+]
+
+# mingw is missing Windows toast notification definitions.
+if CONFIG["CC_TYPE"] == "clang-cl":
+ SOURCES += [
+ "ToastNotification.cpp",
+ "ToastNotificationHandler.cpp",
+ ]
+
+SPHINX_TREES["/widget/windows"] = "docs"
diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp
new file mode 100644
index 0000000000..314176766d
--- /dev/null
+++ b/widget/windows/nsAppShell.cpp
@@ -0,0 +1,1000 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/WindowsMessageLoop.h"
+#include "nsAppShell.h"
+#include "nsToolkit.h"
+#include "nsThreadUtils.h"
+#include "WinUtils.h"
+#include "WinTaskbar.h"
+#include "WinMouseScrollHandler.h"
+#include "nsWindowDefs.h"
+#include "nsWindow.h"
+#include "nsString.h"
+#include "WinIMEHandler.h"
+#include "mozilla/widget/AudioSession.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsComponentManagerUtils.h"
+#include "ScreenHelperWin.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+#include <winternl.h>
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+
+#if defined(ACCESSIBILITY)
+# include "mozilla/a11y/Compatibility.h"
+# include "mozilla/a11y/Platform.h"
+#endif // defined(ACCESSIBILITY)
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define WAKE_LOCK_LOG(...) \
+ MOZ_LOG(gWinWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gWinWakeLockLog("WinWakeLock");
+
+// This wakelock listener is used for Window7 and above.
+class WinWakeLockListener final : public nsIDOMMozWakeLockListener {
+ public:
+ NS_DECL_ISUPPORTS
+ WinWakeLockListener() { MOZ_ASSERT(XRE_IsParentProcess()); }
+
+ private:
+ ~WinWakeLockListener() {
+ ReleaseWakelockIfNeeded(PowerRequestDisplayRequired);
+ ReleaseWakelockIfNeeded(PowerRequestExecutionRequired);
+ }
+
+ void SetHandle(HANDLE aHandle, POWER_REQUEST_TYPE aType) {
+ switch (aType) {
+ case PowerRequestDisplayRequired: {
+ if (!aHandle && mDisplayHandle) {
+ CloseHandle(mDisplayHandle);
+ }
+ mDisplayHandle = aHandle;
+ return;
+ }
+ case PowerRequestExecutionRequired: {
+ if (!aHandle && mNonDisplayHandle) {
+ CloseHandle(mNonDisplayHandle);
+ }
+ mNonDisplayHandle = aHandle;
+ return;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return;
+ }
+ }
+
+ HANDLE GetHandle(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return mDisplayHandle;
+ case PowerRequestExecutionRequired:
+ return mNonDisplayHandle;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return nullptr;
+ }
+ }
+
+ HANDLE CreateHandle(POWER_REQUEST_TYPE aType) {
+ MOZ_ASSERT(!GetHandle(aType));
+ REASON_CONTEXT context = {0};
+ context.Version = POWER_REQUEST_CONTEXT_VERSION;
+ context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
+ context.Reason.SimpleReasonString = RequestTypeLPWSTR(aType);
+ HANDLE handle = PowerCreateRequest(&context);
+ if (!handle) {
+ WAKE_LOCK_LOG("Failed to create handle for %s, error=%lu",
+ RequestTypeStr(aType), GetLastError());
+ return nullptr;
+ }
+ SetHandle(handle, aType);
+ return handle;
+ }
+
+ LPWSTR RequestTypeLPWSTR(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return const_cast<LPWSTR>(L"display request"); // -Wwritable-strings
+ case PowerRequestExecutionRequired:
+ return const_cast<LPWSTR>(
+ L"non-display request"); // -Wwritable-strings
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return const_cast<LPWSTR>(L"unknown"); // -Wwritable-strings
+ }
+ }
+
+ const char* RequestTypeStr(POWER_REQUEST_TYPE aType) const {
+ switch (aType) {
+ case PowerRequestDisplayRequired:
+ return "display request";
+ case PowerRequestExecutionRequired:
+ return "non-display request";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid request type");
+ return "unknown";
+ }
+ }
+
+ void RequestWakelockIfNeeded(POWER_REQUEST_TYPE aType) {
+ if (GetHandle(aType)) {
+ WAKE_LOCK_LOG("Already requested lock for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ WAKE_LOCK_LOG("Prepare a wakelock for %s", RequestTypeStr(aType));
+ HANDLE handle = CreateHandle(aType);
+ if (!handle) {
+ WAKE_LOCK_LOG("Failed due to no handle for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ if (PowerSetRequest(handle, aType)) {
+ WAKE_LOCK_LOG("Requested %s lock", RequestTypeStr(aType));
+ } else {
+ WAKE_LOCK_LOG("Failed to request %s lock, error=%lu",
+ RequestTypeStr(aType), GetLastError());
+ SetHandle(nullptr, aType);
+ }
+ }
+
+ void ReleaseWakelockIfNeeded(POWER_REQUEST_TYPE aType) {
+ if (!GetHandle(aType)) {
+ WAKE_LOCK_LOG("Already released lock for %s", RequestTypeStr(aType));
+ return;
+ }
+
+ WAKE_LOCK_LOG("Prepare to release wakelock for %s", RequestTypeStr(aType));
+ if (!PowerClearRequest(GetHandle(aType), aType)) {
+ WAKE_LOCK_LOG("Failed to release %s lock, error=%lu",
+ RequestTypeStr(aType), GetLastError());
+ return;
+ }
+ SetHandle(nullptr, aType);
+ WAKE_LOCK_LOG("Released wakelock for %s", RequestTypeStr(aType));
+ }
+
+ NS_IMETHOD Callback(const nsAString& aTopic,
+ const nsAString& aState) override {
+ WAKE_LOCK_LOG("topic=%s, state=%s", NS_ConvertUTF16toUTF8(aTopic).get(),
+ NS_ConvertUTF16toUTF8(aState).get());
+ if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") &&
+ !aTopic.EqualsASCII("video-playing")) {
+ return NS_OK;
+ }
+
+ const bool isNonDisplayLock = aTopic.EqualsASCII("audio-playing");
+ bool requestLock = false;
+ if (isNonDisplayLock) {
+ requestLock = aState.EqualsASCII("locked-foreground") ||
+ aState.EqualsASCII("locked-background");
+ } else {
+ requestLock = aState.EqualsASCII("locked-foreground");
+ }
+
+ if (isNonDisplayLock) {
+ if (requestLock) {
+ RequestWakelockIfNeeded(PowerRequestExecutionRequired);
+ } else {
+ ReleaseWakelockIfNeeded(PowerRequestExecutionRequired);
+ }
+ } else {
+ if (requestLock) {
+ RequestWakelockIfNeeded(PowerRequestDisplayRequired);
+ } else {
+ ReleaseWakelockIfNeeded(PowerRequestDisplayRequired);
+ }
+ }
+ return NS_OK;
+ }
+
+ // Handle would only exist when we request wakelock successfully.
+ HANDLE mDisplayHandle = nullptr;
+ HANDLE mNonDisplayHandle = nullptr;
+};
+NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener)
+StaticRefPtr<nsIDOMMozWakeLockListener> sWakeLockListener;
+
+static void AddScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new WinWakeLockListener();
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void RemoveScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+class SingleNativeEventPump final : public nsIThreadObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ SingleNativeEventPump() {
+ MOZ_ASSERT(!XRE_UseNativeEventProcessing(),
+ "Should only be used when not properly processing events.");
+ }
+
+ private:
+ ~SingleNativeEventPump() {}
+};
+
+NS_IMPL_ISUPPORTS(SingleNativeEventPump, nsIThreadObserver)
+
+NS_IMETHODIMP
+SingleNativeEventPump::OnDispatchedEvent() { return NS_OK; }
+
+NS_IMETHODIMP
+SingleNativeEventPump::OnProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait) {
+ MSG msg;
+ bool gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SingleNativeEventPump::AfterProcessNextEvent(nsIThreadInternal* aThread,
+ bool aMayWait) {
+ return NS_OK;
+}
+
+// RegisterWindowMessage values
+// Native event callback message
+const wchar_t* kAppShellGeckoEventId = L"nsAppShell:EventID";
+UINT sAppShellGeckoMsgId = 0x10001; // initialize to invalid message ID
+// Taskbar button creation message
+const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated";
+UINT sTaskbarButtonCreatedMsg = 0x10002; // initialize to invalid message ID
+
+/* static */
+UINT nsAppShell::GetTaskbarButtonCreatedMessage() {
+ return sTaskbarButtonCreatedMsg;
+}
+
+namespace mozilla {
+namespace crashreporter {
+void LSPAnnotate();
+} // namespace crashreporter
+} // namespace mozilla
+
+using mozilla::crashreporter::LSPAnnotate;
+
+//-------------------------------------------------------------------------
+
+// Note that since we're on x86-ish processors here, ReleaseAcquire is the
+// semantics that normal loads and stores would use anyway.
+static Atomic<size_t, ReleaseAcquire> sOutstandingNativeEventCallbacks;
+
+/*static*/ LRESULT CALLBACK nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ NativeEventLogger eventLogger("AppShell", hwnd, uMsg, wParam, lParam);
+
+ if (uMsg == sAppShellGeckoMsgId) {
+ // The app shell might have been destroyed between this message being
+ // posted and being executed, so be extra careful.
+ if (!sOutstandingNativeEventCallbacks) {
+ return TRUE;
+ }
+
+ nsAppShell* as = reinterpret_cast<nsAppShell*>(lParam);
+ as->NativeEventCallback();
+ --sOutstandingNativeEventCallbacks;
+ return TRUE;
+ }
+
+ LRESULT ret = DefWindowProc(hwnd, uMsg, wParam, lParam);
+ eventLogger.SetResult(ret, false);
+ return ret;
+}
+
+nsAppShell::~nsAppShell() {
+ hal::Shutdown();
+
+ if (mEventWnd) {
+ // DestroyWindow doesn't do anything when called from a non UI thread.
+ // Since mEventWnd was created on the UI thread, it must be destroyed on
+ // the UI thread.
+ SendMessage(mEventWnd, WM_CLOSE, 0, 0);
+ }
+
+ // Cancel any outstanding native event callbacks.
+ sOutstandingNativeEventCallbacks = 0;
+}
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+ if (!strcmp(aTopic, "sessionstore-restoring-on-startup")) {
+ nsWindow::SetIsRestoringSession(true);
+ // Now that we've handled the observer notification, we can remove it
+ obsServ->RemoveObserver(this, "sessionstore-restoring-on-startup");
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "sessionstore-windows-restored")) {
+ nsWindow::SetIsRestoringSession(false);
+ // Now that we've handled the observer notification, we can remove it
+ obsServ->RemoveObserver(this, "sessionstore-windows-restored");
+ return NS_OK;
+ }
+ }
+
+ return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+}
+
+namespace {
+
+// Struct storing the visible, loggable error-state of a Windows thread.
+// Approximately `std:pair(::GetLastError(), ::RtlGetLastNtStatus())`.
+//
+// Uses sentinel values rather than a proper `Maybe` type to simplify
+// minidump-analysis.
+struct WinErrorState {
+ // Last error, as provided by ::GetLastError().
+ DWORD error = ~0;
+ // Last NTSTATUS, as provided by the TIB.
+ NTSTATUS ntStatus = ~0;
+
+ private:
+ // per WINE et al.; stable since NT 3.51
+ constexpr static size_t kLastNtStatusOffset =
+ sizeof(size_t) == 8 ? 0x1250 : 0xbf4;
+
+ static void SetLastNtStatus(NTSTATUS status) {
+ auto* teb = ::NtCurrentTeb();
+ *reinterpret_cast<NTSTATUS*>(reinterpret_cast<char*>(teb) +
+ kLastNtStatusOffset) = status;
+ }
+
+ static NTSTATUS GetLastNtStatus() {
+ auto const* teb = ::NtCurrentTeb();
+ return *reinterpret_cast<NTSTATUS const*>(
+ reinterpret_cast<char const*>(teb) + kLastNtStatusOffset);
+ }
+
+ public:
+ // Restore (or just set) the error state of the current thread.
+ static void Apply(WinErrorState const& state) {
+ SetLastNtStatus(state.ntStatus);
+ ::SetLastError(state.error);
+ }
+
+ // Clear the error-state of the current thread.
+ static void Clear() { Apply({.error = 0, .ntStatus = 0}); }
+
+ // Get the error-state of the current thread.
+ static WinErrorState Get() {
+ return WinErrorState{
+ .error = ::GetLastError(),
+ .ntStatus = GetLastNtStatus(),
+ };
+ }
+
+ bool operator==(WinErrorState const& that) const {
+ return this->error == that.error && this->ntStatus == that.ntStatus;
+ }
+
+ bool operator!=(WinErrorState const& that) const { return !operator==(that); }
+};
+
+// Struct containing information about the user atom table. (See
+// DiagnoseUserAtomTable(), below.)
+struct AtomTableInformation {
+ // Number of atoms in use. (Exactly 0x4000 == 16384, if all are.)
+ UINT in_use = 0;
+ // Number of atoms confirmed not in use.
+ UINT free = 0;
+ // Number of atoms which gave errors when checked.
+ UINT errors = 0;
+
+ // Last atom which gave an unexpected error...
+ UINT lastErrorAtom = ~0u;
+ // ... and the error it gave.
+ WinErrorState lastErrorState;
+};
+
+// Return a summary of the state of the atom table.
+MOZ_NEVER_INLINE static AtomTableInformation DiagnoseUserAtomTable() {
+ // Restore error state on exit, for the sake of automated minidump analyses.
+ auto const _restoreErrState =
+ mozilla::MakeScopeExit([oldErrState = WinErrorState::Get()]() {
+ WinErrorState::Apply(oldErrState);
+ });
+
+ AtomTableInformation retval;
+
+ // Expected error-state on failure-return when the atom is assigned, but not
+ // enough space was provided for the full string.
+ constexpr WinErrorState kBufferTooSmall = {
+ .error = ERROR_INSUFFICIENT_BUFFER,
+ .ntStatus = ((NTSTATUS)0xC0000023), // == STATUS_BUFFER_TOO_SMALL
+ };
+ // Expected error-state on failure-return when the atom is not assigned.
+ constexpr WinErrorState kInvalidAtom = {
+ .error = ERROR_INVALID_HANDLE,
+ .ntStatus = ((NTSTATUS)STATUS_INVALID_HANDLE),
+ };
+
+ // Iterate over only the dynamic portion of the atom table.
+ for (UINT atom = 0xC000; atom <= 0xFFFF; ++atom) {
+ // The actual atom values are PII. Don't acquire them in their entirety, and
+ // don't keep more information about them than is needed.
+ WCHAR buf[2] = {};
+ // USE OF UNDOCUMENTED BEHAVIOR: The user atom table is shared by message
+ // names, window-class names, and clipboard-format names. Only the last has
+ // a documented getter-mechanism.
+ BOOL const ok = ::GetClipboardFormatNameW(atom, buf, 1);
+ WinErrorState const errState = WinErrorState::Get();
+ if (ok || errState == kBufferTooSmall) {
+ ++retval.in_use;
+ } else if (errState == kInvalidAtom) {
+ ++retval.free;
+ } else {
+ // Unexpected error-state.
+ ++retval.errors;
+ retval.lastErrorAtom = atom;
+ retval.lastErrorState = errState;
+ }
+ }
+
+ return retval;
+}
+
+} // namespace
+
+#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && defined(_M_X64)
+MOZ_NEVER_INLINE MOZ_NAKED void EnableTrapFlag() {
+ asm volatile(
+ "pushfq;"
+ "orw $0x100,(%rsp);"
+ "popfq;"
+ "retq;");
+}
+
+MOZ_NEVER_INLINE MOZ_NAKED void DisableTrapFlag() { asm volatile("retq;"); }
+
+# define SSD_MAX_USER32_STEPS 0x1800
+# define SSD_MAX_ERROR_STATES 0x200
+struct SingleStepData {
+ uint32_t mUser32StepsLog[SSD_MAX_USER32_STEPS]{};
+ WinErrorState mErrorStatesLog[SSD_MAX_ERROR_STATES];
+ uint16_t mUser32StepsAtErrorState[SSD_MAX_ERROR_STATES]{};
+};
+
+struct SingleStepStaticState {
+ SingleStepData* mData{};
+ uintptr_t mUser32Start{};
+ uintptr_t mUser32End{};
+ uint32_t mUser32Steps{};
+ uint32_t mErrorStates{};
+ WinErrorState mLastRecordedErrorState;
+
+ constexpr void Reset() { *this = SingleStepStaticState{}; }
+};
+
+static SingleStepStaticState sSingleStepStaticState{};
+
+LONG SingleStepExceptionHandler(_EXCEPTION_POINTERS* aExceptionInfo) {
+ auto& state = sSingleStepStaticState;
+ if (state.mData &&
+ aExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
+ auto instructionPointer = aExceptionInfo->ContextRecord->Rip;
+ if (instructionPointer == reinterpret_cast<uintptr_t>(&DisableTrapFlag)) {
+ // Stop handling any exception in this handler
+ state.mData = nullptr;
+ } else {
+ // Record data for the current step, if in user32
+ if (state.mUser32Start <= instructionPointer &&
+ instructionPointer < state.mUser32End) {
+ // We record the instruction pointer
+ if (state.mUser32Steps < SSD_MAX_USER32_STEPS) {
+ state.mData->mUser32StepsLog[state.mUser32Steps] =
+ static_cast<uint32_t>(instructionPointer - state.mUser32Start);
+ }
+
+ // We record changes in the error state
+ auto currentErrorState{WinErrorState::Get()};
+ if (currentErrorState != state.mLastRecordedErrorState) {
+ state.mLastRecordedErrorState = currentErrorState;
+
+ if (state.mErrorStates < SSD_MAX_ERROR_STATES) {
+ state.mData->mErrorStatesLog[state.mErrorStates] =
+ currentErrorState;
+ state.mData->mUser32StepsAtErrorState[state.mErrorStates] =
+ state.mUser32Steps;
+ }
+
+ ++state.mErrorStates;
+ }
+
+ ++state.mUser32Steps;
+ }
+
+ // Continue single-stepping
+ aExceptionInfo->ContextRecord->EFlags |= 0x100;
+ }
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+enum CSSD_RESULT {
+ CSSD_SUCCESS = 0,
+ CSSD_ERROR_DEBUGGER_PRESENT = 1,
+ CSSD_ERROR_GET_MODULE_HANDLE = 2,
+ CSSD_ERROR_PARSING_USER32 = 3,
+ CSSD_ERROR_ADD_VECTORED_EXCEPTION_HANDLER = 4,
+};
+
+template <typename CallbackToRun, typename PostCollectionCallback>
+[[clang::optnone]] MOZ_NEVER_INLINE CSSD_RESULT
+CollectSingleStepData(CallbackToRun aCallbackToRun,
+ PostCollectionCallback aPostCollectionCallback) {
+ if (::IsDebuggerPresent()) {
+ return CSSD_ERROR_DEBUGGER_PRESENT;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!sSingleStepStaticState.mData,
+ "Single-stepping is already active");
+ HANDLE user32 = ::GetModuleHandleW(L"user32.dll");
+ if (!user32) {
+ return CSSD_ERROR_GET_MODULE_HANDLE;
+ }
+
+ nt::PEHeaders user32Headers{user32};
+ auto bounds = user32Headers.GetBounds();
+ if (bounds.isNothing()) {
+ return CSSD_ERROR_PARSING_USER32;
+ }
+
+ SingleStepData singleStepData{};
+
+ sSingleStepStaticState.Reset();
+ sSingleStepStaticState.mUser32Start =
+ reinterpret_cast<uintptr_t>(bounds.ref().begin().get());
+ sSingleStepStaticState.mUser32End =
+ reinterpret_cast<uintptr_t>(bounds.ref().end().get());
+ sSingleStepStaticState.mData = &singleStepData;
+ auto veh = ::AddVectoredExceptionHandler(TRUE, SingleStepExceptionHandler);
+ if (!veh) {
+ sSingleStepStaticState.mData = nullptr;
+ return CSSD_ERROR_ADD_VECTORED_EXCEPTION_HANDLER;
+ }
+
+ EnableTrapFlag();
+ aCallbackToRun();
+ DisableTrapFlag();
+ ::RemoveVectoredExceptionHandler(veh);
+ sSingleStepStaticState.mData = nullptr;
+
+ aPostCollectionCallback();
+
+ return CSSD_SUCCESS;
+}
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED && _M_X64
+
+// Collect data for bug 1571516. We don't automatically send up `GetLastError`
+// or `GetLastNtStatus` data for beta/release builds, so extract the relevant
+// error values and store them on the stack, where they can be viewed in
+// minidumps -- in fact, do so after each individual API call. This takes the
+// form of various local variables whose initial character is an underscore,
+// most of which are also marked [[maybe_unused]].
+//
+// We tag this function `[[clang::optnone]]` to prevent the compiler from
+// eliding those values as _actually_ unused, as well as to generally simplify
+// the haruspex's task once the minidumps are in. (As this function should be
+// called at most once per process, the minor performance hit is not a concern.)
+//
+[[clang::optnone]] MOZ_NEVER_INLINE nsresult nsAppShell::InitHiddenWindow() {
+ // note the incoming error-state; this may be relevant to errors we get later
+ auto _initialErr [[maybe_unused]] = WinErrorState::Get();
+ // reset the error-state, to avoid ambiguity below
+ WinErrorState::Clear();
+
+ // Diagnostic variable. Only collected in the event of a failure in one of the
+ // functions that attempts to register an atom.
+ AtomTableInformation _atomTableInfo [[maybe_unused]];
+
+ // Attempt to register the window message. On failure, retain the initial
+ // value of `sAppShellGeckoMsgId`.
+ auto const _msgId = ::RegisterWindowMessageW(kAppShellGeckoEventId);
+ if (_msgId) {
+ sAppShellGeckoMsgId = _msgId;
+ }
+ auto const _sAppShellGeckoMsgId [[maybe_unused]] = sAppShellGeckoMsgId;
+ auto const _rwmErr [[maybe_unused]] = WinErrorState::Get();
+ if (!_msgId) _atomTableInfo = DiagnoseUserAtomTable();
+ NS_ASSERTION(sAppShellGeckoMsgId,
+ "Could not register hidden window event message!");
+
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+
+ WNDCLASSW wc;
+ HINSTANCE const module = GetModuleHandle(nullptr);
+
+ constexpr const wchar_t* kWindowClass = L"nsAppShell:EventWindowClass";
+ // (Undocumented behavior note: on success, this will specifically be the
+ // window-class atom. We don't rely on this.)
+ BOOL const _gciwRet = ::GetClassInfoW(module, kWindowClass, &wc);
+ auto const _gciwErr [[maybe_unused]] = WinErrorState::Get();
+ WinErrorState::Clear();
+
+ WinErrorState _rcErr [[maybe_unused]];
+ if (!_gciwRet) {
+ wc.style = 0;
+ wc.lpfnWndProc = EventWindowProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = module;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = (HBRUSH) nullptr;
+ wc.lpszMenuName = (LPCWSTR) nullptr;
+ wc.lpszClassName = kWindowClass;
+
+ ATOM _windowClassAtom = ::RegisterClassW(&wc);
+ _rcErr = WinErrorState::Get();
+
+ if (!_windowClassAtom) _atomTableInfo = DiagnoseUserAtomTable();
+
+#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && defined(_M_X64)
+ if (!_windowClassAtom) {
+ // Retry with single-step data collection
+ auto cssdResult = CollectSingleStepData(
+ [&wc, &_windowClassAtom]() {
+ _windowClassAtom = ::RegisterClassW(&wc);
+ },
+ [&_windowClassAtom]() {
+ // Crashing here gives access to the single step data on stack
+ MOZ_DIAGNOSTIC_ASSERT(
+ _windowClassAtom,
+ "RegisterClassW for EventWindowClass failed twice");
+ });
+ auto const _cssdErr [[maybe_unused]] = WinErrorState::Get();
+ MOZ_DIAGNOSTIC_ASSERT(
+ cssdResult == CSSD_SUCCESS,
+ "Failed to collect single step data for RegisterClassW");
+ // If we reach this point then somehow the single-stepped call succeeded
+ // and we can proceed
+ }
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED && _M_X64
+
+ MOZ_DIAGNOSTIC_ASSERT(_windowClassAtom,
+ "RegisterClassW for EventWindowClass failed");
+ WinErrorState::Clear();
+ }
+
+ mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", 0, 0, 0,
+ 10, 10, HWND_MESSAGE, nullptr, module, nullptr);
+ auto const _cwErr [[maybe_unused]] = WinErrorState::Get();
+
+#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && defined(_M_X64)
+ if (!mEventWnd) {
+ // Retry with single-step data collection
+ HWND eventWnd{};
+ auto cssdResult = CollectSingleStepData(
+ [module, &eventWnd]() {
+ eventWnd =
+ CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", 0, 0, 0,
+ 10, 10, HWND_MESSAGE, nullptr, module, nullptr);
+ },
+ [&eventWnd]() {
+ // Crashing here gives access to the single step data on stack
+ MOZ_DIAGNOSTIC_ASSERT(eventWnd,
+ "CreateWindowW for EventWindow failed twice");
+ });
+ auto const _cssdErr [[maybe_unused]] = WinErrorState::Get();
+ MOZ_DIAGNOSTIC_ASSERT(
+ cssdResult == CSSD_SUCCESS,
+ "Failed to collect single step data for CreateWindowW");
+ // If we reach this point then somehow the single-stepped call succeeded and
+ // we can proceed
+ mEventWnd = eventWnd;
+ }
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED && _M_X64
+
+ MOZ_DIAGNOSTIC_ASSERT(mEventWnd, "CreateWindowW for EventWindow failed");
+ NS_ENSURE_STATE(mEventWnd);
+
+ return NS_OK;
+}
+
+nsresult nsAppShell::Init() {
+ LSPAnnotate();
+
+ hal::Init();
+
+ if (XRE_IsParentProcess()) {
+ sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
+ NS_ASSERTION(sTaskbarButtonCreatedMsg,
+ "Could not register taskbar button creation message");
+ }
+
+ // The hidden message window is used for interrupting the processing of native
+ // events, so that we can process gecko events. Therefore, we only need it if
+ // we are processing native events. Disabling this is required for win32k
+ // syscall lockdown.
+ if (XRE_UseNativeEventProcessing()) {
+ if (nsresult rv = this->InitHiddenWindow(); NS_FAILED(rv)) {
+ return rv;
+ }
+ } else if (XRE_IsContentProcess() && !IsWin32kLockedDown()) {
+ // We're not generally processing native events, but still using GDI and we
+ // still have some internal windows, e.g. from calling CoInitializeEx.
+ // So we use a class that will do a single event pump where previously we
+ // might have processed multiple events to make sure any occasional messages
+ // to these windows are processed. This also allows any internal Windows
+ // messages to be processed to ensure the GDI data remains fresh.
+ nsCOMPtr<nsIThreadInternal> threadInt =
+ do_QueryInterface(NS_GetCurrentThread());
+ if (threadInt) {
+ threadInt->SetObserver(new SingleNativeEventPump());
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperWin>());
+ ScreenHelperWin::RefreshScreens();
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ(
+ mozilla::services::GetObserverService());
+
+ obsServ->AddObserver(this, "sessionstore-restoring-on-startup", false);
+ obsServ->AddObserver(this, "sessionstore-windows-restored", false);
+ }
+
+ if (!WinUtils::GetTimezoneName(mTimezoneName)) {
+ NS_WARNING("Unable to get system timezone name, timezone may be invalid\n");
+ }
+
+ return nsBaseAppShell::Init();
+}
+
+NS_IMETHODIMP
+nsAppShell::Run(void) {
+ bool wantAudio = true;
+ if (XRE_IsParentProcess()) {
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ wantAudio = false;
+ }
+#endif
+ if (MOZ_LIKELY(wantAudio)) {
+ mozilla::widget::StartAudioSession();
+ }
+
+ // Add an observer that disables the screen saver when requested by Gecko.
+ // For example when we're playing video in the foreground tab. Whole firefox
+ // only needs one wakelock instance, so we would only create one listener in
+ // chrome process to prevent requesting unnecessary wakelock.
+ AddScreenWakeLockListener();
+ }
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ if (XRE_IsParentProcess()) {
+ RemoveScreenWakeLockListener();
+
+ if (MOZ_LIKELY(wantAudio)) {
+ mozilla::widget::StopAudioSession();
+ }
+ }
+
+ return rv;
+}
+
+void nsAppShell::DoProcessMoreGeckoEvents() {
+ // Called by nsBaseAppShell's NativeEventCallback() after it has finished
+ // processing pending gecko events and there are still gecko events pending
+ // for the thread. (This can happen if NS_ProcessPendingEvents reached it's
+ // starvation timeout limit.) The default behavior in nsBaseAppShell is to
+ // call ScheduleNativeEventCallback to post a follow up native event callback
+ // message. This triggers an additional call to NativeEventCallback for more
+ // gecko event processing.
+
+ // There's a deadlock risk here with certain internal Windows modal loops. In
+ // our dispatch code, we prioritize messages so that input is handled first.
+ // However Windows modal dispatch loops often prioritize posted messages. If
+ // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents
+ // takes longer than the timer duration, NS_HasPendingEvents(thread) will
+ // always be true. ScheduleNativeEventCallback will be called on every
+ // NativeEventCallback callback, and in a Windows modal dispatch loop, the
+ // callback message will be processed first -> input gets starved, dead lock.
+
+ // To avoid, don't post native callback messages from NativeEventCallback
+ // when we're in a modal loop. This gets us back into the Windows modal
+ // dispatch loop dispatching input messages. Once we drop out of the modal
+ // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback
+ // if we need it, which insures NS_ProcessPendingEvents gets called and all
+ // gecko events get processed.
+ if (mEventloopNestingLevel < 2) {
+ OnDispatchedEvent();
+ mNativeCallbackPending = false;
+ } else {
+ mNativeCallbackPending = true;
+ }
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ MOZ_ASSERT(mEventWnd,
+ "We should have created mEventWnd in Init, if this is called.");
+
+ // Post a message to the hidden message window
+ ++sOutstandingNativeEventCallbacks;
+ {
+ MutexAutoLock lock(mLastNativeEventScheduledMutex);
+ // Time stamp this event so we can detect cases where the event gets
+ // dropping in sub classes / modal loops we do not control.
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+ }
+ ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0,
+ reinterpret_cast<LPARAM>(this));
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ // Notify ipc we are spinning a (possibly nested) gecko event loop.
+ mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch();
+
+ bool gotMessage = false;
+
+ do {
+ MSG msg;
+
+ // For avoiding deadlock between our process and plugin process by
+ // mouse wheel messages, we're handling actually when we receive one of
+ // following internal messages which is posted by native mouse wheel
+ // message handler. Any other events, especially native modifier key
+ // events, should not be handled between native message and posted
+ // internal message because it may make different modifier key state or
+ // mouse cursor position between them.
+ if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) {
+ gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST,
+ MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE);
+ NS_ASSERTION(gotMessage,
+ "waiting internal wheel message, but it has not come");
+ }
+
+ if (!gotMessage) {
+ gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
+ }
+
+ if (gotMessage) {
+ if (msg.message == WM_QUIT) {
+ ::PostQuitMessage(msg.wParam);
+ Exit();
+ } else {
+ // If we had UI activity we would be processing it now so we know we
+ // have either kUIActivity or kActivityNoUIAVail.
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
+ IMEHandler::ProcessRawKeyMessage(msg)) {
+ continue; // the message is consumed.
+ }
+
+#if defined(_X86_)
+ // Store Printer dialog messages for reposting on x86, because on x86
+ // Windows 7 they are not processed by a window procedure, but are
+ // explicitly waited for in the winspool.drv code that will be further
+ // up the stack (winspool!WaitForCompletionMessage). These are
+ // undocumented Windows Message identifiers found in winspool.drv.
+ if (msg.message == 0x5b7a || msg.message == 0x5b7f ||
+ msg.message == 0x5b80 || msg.message == 0x5b81) {
+ mMsgsToRepost.push_back(msg);
+ continue;
+ }
+#endif
+
+ // Windows documentation suggets that WM_SETTINGSCHANGE is the message
+ // to watch for timezone changes, but experimentation showed that it
+ // doesn't fire on changing the timezone, but that WM_TIMECHANGE does,
+ // even if there's no immediate effect on the clock (e.g., changing
+ // from Pacific Daylight at UTC-7 to Arizona at UTC-7).
+ if (msg.message == WM_TIMECHANGE) {
+ // The message may not give us sufficient information to determine
+ // if the timezone changed, so keep track of it ourselves.
+ wchar_t systemTimezone[128];
+ bool getSystemTimeSucceeded =
+ WinUtils::GetTimezoneName(systemTimezone);
+ if (getSystemTimeSucceeded && wcscmp(systemTimezone, mTimezoneName)) {
+ nsBaseAppShell::OnSystemTimezoneChange();
+
+ wcscpy_s(mTimezoneName, 128, systemTimezone);
+ }
+ }
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ } else if (mayWait) {
+ // Block and wait for any posted application message
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ {
+ AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent::Wait", IDLE);
+ WinUtils::WaitForMessage();
+ }
+ }
+ } while (!gotMessage && mayWait);
+
+ // See DoProcessNextNativeEvent, mEventloopNestingLevel will be
+ // one when a modal loop unwinds.
+ if (mNativeCallbackPending && mEventloopNestingLevel == 1)
+ DoProcessMoreGeckoEvents();
+
+ // Check for starved native callbacks. If we haven't processed one
+ // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off.
+ static const mozilla::TimeDuration nativeEventStarvationLimit =
+ mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT);
+
+ TimeDuration timeSinceLastNativeEventScheduled;
+ {
+ MutexAutoLock lock(mLastNativeEventScheduledMutex);
+ timeSinceLastNativeEventScheduled =
+ TimeStamp::NowLoRes() - mLastNativeEventScheduled;
+ }
+ if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) {
+ ScheduleNativeEventCallback();
+ }
+
+ return gotMessage;
+}
+
+nsresult nsAppShell::AfterProcessNextEvent(nsIThreadInternal* /* unused */,
+ bool /* unused */) {
+ if (!mMsgsToRepost.empty()) {
+ for (MSG msg : mMsgsToRepost) {
+ ::PostMessageW(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+ }
+ mMsgsToRepost.clear();
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsAppShell.h b/widget/windows/nsAppShell.h
new file mode 100644
index 0000000000..20ea65e834
--- /dev/null
+++ b/widget/windows/nsAppShell.h
@@ -0,0 +1,64 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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 nsAppShell_h__
+#define nsAppShell_h__
+
+#include "nsBaseAppShell.h"
+#include <windows.h>
+#include <vector>
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Mutex.h"
+
+// The maximum time we allow before forcing a native event callback.
+// In seconds.
+#define NATIVE_EVENT_STARVATION_LIMIT 1
+
+/**
+ * Native Win32 Application shell wrapper
+ */
+class nsAppShell final : public nsBaseAppShell {
+ public:
+ nsAppShell()
+ : mEventWnd(nullptr),
+ mNativeCallbackPending(false),
+ mLastNativeEventScheduledMutex(
+ "nsAppShell::mLastNativeEventScheduledMutex") {}
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::Mutex Mutex;
+
+ nsresult Init();
+ void DoProcessMoreGeckoEvents();
+
+ static UINT GetTaskbarButtonCreatedMessage();
+
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread,
+ bool eventWasProcessed) final;
+
+ protected:
+ NS_IMETHOD Run() override;
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool mayWait);
+ virtual ~nsAppShell();
+
+ static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM);
+
+ protected:
+ nsresult InitHiddenWindow();
+ HWND mEventWnd;
+ bool mNativeCallbackPending;
+
+ Mutex mLastNativeEventScheduledMutex MOZ_UNANNOTATED;
+ TimeStamp mLastNativeEventScheduled;
+ std::vector<MSG> mMsgsToRepost;
+
+ private:
+ wchar_t mTimezoneName[128];
+};
+
+#endif // nsAppShell_h__
diff --git a/widget/windows/nsBidiKeyboard.cpp b/widget/windows/nsBidiKeyboard.cpp
new file mode 100644
index 0000000000..87d81d458e
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include <stdio.h>
+#include "nsBidiKeyboard.h"
+#include "WidgetUtils.h"
+#include "nsIWidget.h"
+#include <tchar.h>
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard() { Reset(); }
+
+nsBidiKeyboard::~nsBidiKeyboard() {}
+
+NS_IMETHODIMP nsBidiKeyboard::Reset() {
+ mInitialized = false;
+ mHaveBidiKeyboards = false;
+ mLTRKeyboard[0] = '\0';
+ mRTLKeyboard[0] = '\0';
+ mCurrentLocaleName[0] = '\0';
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool* aIsRTL) {
+ *aIsRTL = false;
+
+ nsresult result = SetupBidiKeyboards();
+ if (NS_FAILED(result)) return result;
+
+ HKL currentLocale;
+
+ currentLocale = ::GetKeyboardLayout(0);
+ *aIsRTL = IsRTLLanguage(currentLocale);
+
+ if (!::GetKeyboardLayoutNameW(mCurrentLocaleName)) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(*mCurrentLocaleName,
+ "GetKeyboardLayoutName return string length == 0");
+ NS_ASSERTION((wcslen(mCurrentLocaleName) < KL_NAMELENGTH),
+ "GetKeyboardLayoutName return string length >= KL_NAMELENGTH");
+
+ // The language set by the user overrides the default language for that
+ // direction
+ if (*aIsRTL) {
+ wcsncpy(mRTLKeyboard, mCurrentLocaleName, KL_NAMELENGTH);
+ mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ } else {
+ wcsncpy(mLTRKeyboard, mCurrentLocaleName, KL_NAMELENGTH);
+ mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ }
+
+ NS_ASSERTION((wcslen(mRTLKeyboard) < KL_NAMELENGTH),
+ "mLTRKeyboard has string length >= KL_NAMELENGTH");
+ NS_ASSERTION((wcslen(mLTRKeyboard) < KL_NAMELENGTH),
+ "mRTLKeyboard has string length >= KL_NAMELENGTH");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult result = SetupBidiKeyboards();
+ if (NS_FAILED(result)) return result;
+
+ *aResult = mHaveBidiKeyboards;
+ return NS_OK;
+}
+
+// Get the list of keyboard layouts available in the system
+// Set mLTRKeyboard to the first LTR keyboard in the list and mRTLKeyboard to
+// the first RTL keyboard in the list These defaults will be used unless the
+// user explicitly sets something else.
+nsresult nsBidiKeyboard::SetupBidiKeyboards() {
+ if (mInitialized) return mHaveBidiKeyboards ? NS_OK : NS_ERROR_FAILURE;
+
+ int keyboards;
+ HKL far* buf;
+ HKL locale;
+ wchar_t localeName[KL_NAMELENGTH];
+ bool isLTRKeyboardSet = false;
+ bool isRTLKeyboardSet = false;
+
+ // GetKeyboardLayoutList with 0 as first parameter returns the number of
+ // keyboard layouts available
+ keyboards = ::GetKeyboardLayoutList(0, nullptr);
+ if (!keyboards) return NS_ERROR_FAILURE;
+
+ // allocate a buffer to hold the list
+ buf = (HKL far*)malloc(keyboards * sizeof(HKL));
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Call again to fill the buffer
+ if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) {
+ free(buf);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Go through the list and pick a default LTR and RTL keyboard layout
+ while (keyboards--) {
+ locale = buf[keyboards];
+ if (IsRTLLanguage(locale)) {
+ _snwprintf(mRTLKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1,
+ LANGIDFROMLCID((DWORD_PTR)locale));
+ isRTLKeyboardSet = true;
+ } else {
+ _snwprintf(mLTRKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1,
+ LANGIDFROMLCID((DWORD_PTR)locale));
+ isLTRKeyboardSet = true;
+ }
+ }
+ free(buf);
+ mInitialized = true;
+
+ // If there is not at least one keyboard of each directionality, Bidi
+ // keyboard functionality will be disabled.
+ mHaveBidiKeyboards = (isRTLKeyboardSet && isLTRKeyboardSet);
+ if (!mHaveBidiKeyboards) return NS_ERROR_FAILURE;
+
+ // Get the current keyboard layout and use it for either mRTLKeyboard or
+ // mLTRKeyboard as appropriate. If the user has many keyboard layouts
+ // installed this prevents us from arbitrarily resetting the current
+ // layout (bug 80274)
+ locale = ::GetKeyboardLayout(0);
+ if (!::GetKeyboardLayoutNameW(localeName)) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(*localeName, "GetKeyboardLayoutName return string length == 0");
+ NS_ASSERTION((wcslen(localeName) < KL_NAMELENGTH),
+ "GetKeyboardLayout return string length >= KL_NAMELENGTH");
+
+ if (IsRTLLanguage(locale)) {
+ wcsncpy(mRTLKeyboard, localeName, KL_NAMELENGTH);
+ mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ } else {
+ wcsncpy(mLTRKeyboard, localeName, KL_NAMELENGTH);
+ mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate
+ }
+
+ NS_ASSERTION(*mRTLKeyboard, "mLTRKeyboard has string length == 0");
+ NS_ASSERTION(*mLTRKeyboard, "mLTRKeyboard has string length == 0");
+
+ return NS_OK;
+}
+
+// Test whether the language represented by this locale identifier is a
+// right-to-left language, using bit 123 of the Unicode subset bitfield in
+// the LOCALESIGNATURE
+// See
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_63ub.asp
+bool nsBidiKeyboard::IsRTLLanguage(HKL aLocale) {
+ LOCALESIGNATURE localesig;
+ return (::GetLocaleInfoW(PRIMARYLANGID((DWORD_PTR)aLocale),
+ LOCALE_FONTSIGNATURE, (LPWSTR)&localesig,
+ (sizeof(localesig) / sizeof(WCHAR))) &&
+ (localesig.lsUsb[3] & 0x08000000));
+}
+
+// static
+void nsBidiKeyboard::OnLayoutChange() {
+ mozilla::widget::WidgetUtils::SendBidiKeyboardInfoToContent();
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ return do_AddRef(new nsBidiKeyboard());
+}
diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h
new file mode 100644
index 0000000000..584d4d2dee
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 __nsBidiKeyboard
+#define __nsBidiKeyboard
+#include "nsIBidiKeyboard.h"
+#include <windows.h>
+
+class nsBidiKeyboard : public nsIBidiKeyboard {
+ virtual ~nsBidiKeyboard();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+ static void OnLayoutChange();
+
+ protected:
+ nsresult SetupBidiKeyboards();
+ bool IsRTLLanguage(HKL aLocale);
+
+ bool mInitialized;
+ bool mHaveBidiKeyboards;
+ wchar_t mLTRKeyboard[KL_NAMELENGTH];
+ wchar_t mRTLKeyboard[KL_NAMELENGTH];
+ wchar_t mCurrentLocaleName[KL_NAMELENGTH];
+};
+
+#endif // __nsBidiKeyboard
diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp
new file mode 100644
index 0000000000..8783affd1e
--- /dev/null
+++ b/widget/windows/nsClipboard.cpp
@@ -0,0 +1,1472 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsClipboard.h"
+
+#include <shlobj.h>
+#include <intshcut.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include <functional>
+#include <thread>
+#include <chrono>
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Compatibility.h"
+#endif
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_clipboard.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WindowsVersion.h"
+#include "SpecialSystemDirectory.h"
+
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDataObj.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIInputStream.h"
+#include "nsITransferable.h"
+#include "nsXPCOM.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsEscape.h"
+#include "nsIObserverService.h"
+#include "nsMimeTypes.h"
+#include "imgITools.h"
+#include "imgIContainer.h"
+#include "WinUtils.h"
+
+/* static */
+UINT nsClipboard::GetClipboardFileDescriptorFormatA() {
+ static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORA);
+ MOZ_ASSERT(format);
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetClipboardFileDescriptorFormatW() {
+ static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW);
+ MOZ_ASSERT(format);
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetHtmlClipboardFormat() {
+ static UINT format = ::RegisterClipboardFormatW(L"HTML Format");
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetCustomClipboardFormat() {
+ static UINT format =
+ ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
+ return format;
+}
+
+//-------------------------------------------------------------------------
+//
+// nsClipboard constructor
+//
+//-------------------------------------------------------------------------
+nsClipboard::nsClipboard()
+ : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
+ false /* supportsSelectionClipboard */,
+ false /* supportsFindClipboard */,
+ false /* supportsSelectionCache */)) {
+ mWindow = nullptr;
+
+ // Register for a shutdown notification so that we can flush data
+ // to the OS clipboard.
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+ false);
+ }
+}
+
+//-------------------------------------------------------------------------
+// nsClipboard destructor
+//-------------------------------------------------------------------------
+nsClipboard::~nsClipboard() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // This will be called on shutdown.
+ ::OleFlushClipboard();
+ ::CloseClipboard();
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
+ UINT format;
+
+ if (strcmp(aMimeStr, kTextMime) == 0) {
+ format = CF_UNICODETEXT;
+ } else if (strcmp(aMimeStr, kRTFMime) == 0) {
+ format = ::RegisterClipboardFormat(L"Rich Text Format");
+ } else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
+ strcmp(aMimeStr, kJPGImageMime) == 0 ||
+ strcmp(aMimeStr, kPNGImageMime) == 0) {
+ format = CF_DIBV5;
+ } else if (strcmp(aMimeStr, kFileMime) == 0 ||
+ strcmp(aMimeStr, kFilePromiseMime) == 0) {
+ format = CF_HDROP;
+ } else if ((strcmp(aMimeStr, kNativeHTMLMime) == 0) ||
+ (aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)) {
+ format = GetHtmlClipboardFormat();
+ } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
+ format = GetCustomClipboardFormat();
+ } else {
+ format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
+ }
+
+ return format;
+}
+
+//-------------------------------------------------------------------------
+// static
+nsresult nsClipboard::CreateNativeDataObject(
+ nsITransferable* aTransferable, IDataObject** aDataObj, nsIURI* aUri,
+ MightNeedToFlush* aMightNeedToFlush) {
+ MOZ_ASSERT(aTransferable);
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create our native DataObject that implements the OLE IDataObject interface
+ RefPtr<nsDataObj> dataObj = new nsDataObj(aUri);
+
+ // Now set it up with all the right data flavors & enums
+ nsresult res =
+ SetupNativeDataObject(aTransferable, dataObj, aMightNeedToFlush);
+ if (NS_SUCCEEDED(res)) {
+ dataObj.forget(aDataObj);
+ }
+ return res;
+}
+
+static nsresult StoreValueInDataObject(nsDataObj* aObj,
+ LPCWSTR aClipboardFormat, DWORD value) {
+ HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
+ if (!hGlobalMemory) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ DWORD* pdw = (DWORD*)::GlobalLock(hGlobalMemory);
+ *pdw = value;
+ ::GlobalUnlock(hGlobalMemory);
+
+ STGMEDIUM stg;
+ stg.tymed = TYMED_HGLOBAL;
+ stg.pUnkForRelease = nullptr;
+ stg.hGlobal = hGlobalMemory;
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, ::RegisterClipboardFormat(aClipboardFormat), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ aObj->SetData(&fe, &stg, TRUE);
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SetupNativeDataObject(
+ nsITransferable* aTransferable, IDataObject* aDataObj,
+ MightNeedToFlush* aMightNeedToFlush) {
+ MOZ_ASSERT(aTransferable);
+ MOZ_ASSERT(aDataObj);
+ if (!aTransferable || !aDataObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto* dObj = static_cast<nsDataObj*>(aDataObj);
+ if (aMightNeedToFlush) {
+ *aMightNeedToFlush = MightNeedToFlush::No;
+ }
+
+ // Now give the Transferable to the DataObject
+ // for getting the data out of it
+ dObj->SetTransferable(aTransferable);
+
+ // Get the transferable list of data flavors
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanExport(flavors);
+
+ // Walk through flavors that contain data and register them
+ // into the DataObj as supported flavors
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ // When putting data onto the clipboard, we want to maintain kHTMLMime
+ // ("text/html") and not map it to CF_HTML here since this will be done
+ // below.
+ UINT format = GetFormat(flavorStr.get(), false);
+
+ // Now tell the native IDataObject about both our mime type and
+ // the native data format
+ FORMATETC fe;
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(flavorStr.get(), &fe);
+
+ // Do various things internal to the implementation, like map one
+ // flavor to another or add additional flavors based on what's required
+ // for the win32 impl.
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ // if we find text/plain, also add CF_TEXT, but we can add it for
+ // text/plain as well.
+ FORMATETC textFE;
+ SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kTextMime, &textFE);
+ if (aMightNeedToFlush) {
+ *aMightNeedToFlush = MightNeedToFlush::Yes;
+ }
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ // if we find text/html, also advertise win32's html flavor (which we will
+ // convert on our own in nsDataObj::GetText().
+ FORMATETC htmlFE;
+ SET_FORMATETC(htmlFE, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kHTMLMime, &htmlFE);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // if we're a url, in addition to also being text, we need to register
+ // the "file" flavors so that the win32 shell knows to create an internet
+ // shortcut when it sees one of these beasts.
+ FORMATETC shortcutFE;
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
+ 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kURLMime, &shortcutFE);
+ } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ // if we're an image, register the native bitmap flavor
+ FORMATETC imageFE;
+ // Add DIBv5
+ SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr.get(), &imageFE);
+ // Add DIBv3
+ SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr.get(), &imageFE);
+ } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ // if we're a file promise flavor, also register the
+ // CFSTR_PREFERREDDROPEFFECT format. The data object
+ // returns a value of DROPEFFECTS_MOVE to the drop target
+ // when it asks for the value of this format. This causes
+ // the file to be moved from the temporary location instead
+ // of being copied. The right thing to do here is to call
+ // SetData() on the data object and set the value of this format
+ // to DROPEFFECTS_MOVE on this particular data object. But,
+ // since all the other clipboard formats follow the model of setting
+ // data on the data object only when the drop object calls GetData(),
+ // I am leaving this format's value hard coded in the data object.
+ // We can change this if other consumers of this format get added to this
+ // codebase and they need different values.
+ FORMATETC shortcutFE;
+ SET_FORMATETC(shortcutFE,
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
+ }
+ }
+
+ if (!mozilla::StaticPrefs::
+ clipboard_copyPrivateDataToClipboardCloudOrHistory()) {
+ // Let Clipboard know that data is sensitive and must not be copied to
+ // the Cloud Clipboard, Clipboard History and similar.
+ // https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats
+ if (aTransferable->GetIsPrivateData()) {
+ nsresult rv =
+ StoreValueInDataObject(dObj, TEXT("CanUploadToCloudClipboard"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ StoreValueInDataObject(dObj, TEXT("CanIncludeInClipboardHistory"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreValueInDataObject(
+ dObj, TEXT("ExcludeClipboardContentFromMonitorProcessing"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+// See methods listed at
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
+static void IDataObjectMethodResultToString(const HRESULT aHres,
+ nsACString& aResult) {
+ switch (aHres) {
+ case E_INVALIDARG:
+ aResult = "E_INVALIDARG";
+ break;
+ case E_UNEXPECTED:
+ aResult = "E_UNEXPECTED";
+ break;
+ case E_OUTOFMEMORY:
+ aResult = "E_OUTOFMEMORY";
+ break;
+ case DV_E_LINDEX:
+ aResult = "DV_E_LINDEX";
+ break;
+ case DV_E_FORMATETC:
+ aResult = "DV_E_FORMATETC";
+ break;
+ case DV_E_TYMED:
+ aResult = "DV_E_TYMED";
+ break;
+ case DV_E_DVASPECT:
+ aResult = "DV_E_DVASPECT";
+ break;
+ case OLE_E_NOTRUNNING:
+ aResult = "OLE_E_NOTRUNNING";
+ break;
+ case STG_E_MEDIUMFULL:
+ aResult = "STG_E_MEDIUMFULL";
+ break;
+ case DV_E_CLIPFORMAT:
+ aResult = "DV_E_CLIPFORMAT";
+ break;
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
+static void OleGetClipboardResultToString(const HRESULT aHres,
+ nsACString& aResult) {
+ switch (aHres) {
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ case CLIPBRD_E_CANT_OPEN:
+ aResult = "CLIPBRD_E_CANT_OPEN";
+ break;
+ case CLIPBRD_E_CANT_CLOSE:
+ aResult = "CLIPBRD_E_CANT_CLOSE";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
+static void LogOleGetClipboardResult(const HRESULT aHres) {
+ if (MOZ_CLIPBOARD_LOG_ENABLED()) {
+ nsAutoCString hresString;
+ OleGetClipboardResultToString(aHres, hresString);
+ MOZ_CLIPBOARD_LOG("OleGetClipboard result: %s", hresString.get());
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
+static void OleSetClipboardResultToString(HRESULT aHres, nsACString& aResult) {
+ switch (aHres) {
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ case CLIPBRD_E_CANT_OPEN:
+ aResult = "CLIPBRD_E_CANT_OPEN";
+ break;
+ case CLIPBRD_E_CANT_EMPTY:
+ aResult = "CLIPBRD_E_CANT_EMPTY";
+ break;
+ case CLIPBRD_E_CANT_CLOSE:
+ aResult = "CLIPBRD_E_CANT_CLOSE";
+ break;
+ case CLIPBRD_E_CANT_SET:
+ aResult = "CLIPBRD_E_CANT_SET";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
+static void LogOleSetClipboardResult(const HRESULT aHres) {
+ if (MOZ_CLIPBOARD_LOG_ENABLED()) {
+ nsAutoCString hresString;
+ OleSetClipboardResultToString(aHres, hresString);
+ MOZ_CLIPBOARD_LOG("OleSetClipboard result: %s", hresString.get());
+ }
+}
+
+template <typename Function, typename LogFunction, typename... Args>
+static HRESULT RepeatedlyTry(Function aFunction, LogFunction aLogFunction,
+ Args... aArgs) {
+ // These are magic values based on local testing. They are chosen not higher
+ // to avoid jank (<https://developer.mozilla.org/en-US/docs/Glossary/Jank>).
+ // When changing them, be careful.
+ static constexpr int kNumberOfTries = 3;
+ static constexpr int kDelayInMs = 3;
+
+ HRESULT hres;
+ for (int i = 0; i < kNumberOfTries; ++i) {
+ hres = aFunction(aArgs...);
+ aLogFunction(hres);
+
+ if (hres == S_OK) {
+ break;
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(kDelayInMs));
+ }
+
+ return hres;
+}
+
+// Other apps can block access to the clipboard. This repeatedly
+// calls `::OleSetClipboard` for a fixed number of times and should be called
+// instead of `::OleSetClipboard`.
+static void RepeatedlyTryOleSetClipboard(IDataObject* aDataObj) {
+ RepeatedlyTry(::OleSetClipboard, LogOleSetClipboardResult, aDataObj);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::SetNativeClipboardData(
+ nsITransferable* aTransferable, int32_t aWhichClipboard) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure we have a good transferable
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::Compatibility::SuppressA11yForClipboardCopy();
+#endif
+
+ RefPtr<IDataObject> dataObj;
+ auto mightNeedToFlush = MightNeedToFlush::No;
+ if (NS_SUCCEEDED(CreateNativeDataObject(aTransferable,
+ getter_AddRefs(dataObj), nullptr,
+ &mightNeedToFlush))) {
+ RepeatedlyTryOleSetClipboard(dataObj);
+
+ const bool doFlush = [&] {
+ switch (mozilla::StaticPrefs::widget_windows_sync_clipboard_flush()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ // Bug 1774285: Windows Suggested Actions (introduced in Windows 11
+ // 22H2) walks the entire a11y tree using UIA if something is placed
+ // on the clipboard using delayed rendering. (The OLE clipboard always
+ // uses delayed rendering.) This a11y tree walk causes an unacceptable
+ // hang, particularly when the a11y cache is disabled. We choose the
+ // lesser of the two performance/memory evils here and force immediate
+ // rendering as part of our workaround.
+ return mightNeedToFlush == MightNeedToFlush::Yes &&
+ mozilla::IsWin1122H2OrLater();
+ }
+ }();
+ if (doFlush) {
+ RepeatedlyTry(::OleFlushClipboard, [](HRESULT) {});
+ }
+ } else {
+ // Clear the native clipboard
+ RepeatedlyTryOleSetClipboard(nullptr);
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
+ uint32_t* aLen) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ // Allocate a new memory buffer and copy the data from global memory.
+ // Recall that win98 allocates to nearest DWORD boundary. As a safety
+ // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
+ // and null them out to ensure that all of our NS_strlen calls will succeed.
+ // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
+ // a full NUL char16_t when |*aLen| is odd.
+ nsresult result = NS_ERROR_FAILURE;
+ if (aHGBL != nullptr) {
+ LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
+ mozilla::CheckedInt<uint32_t> allocSize =
+ mozilla::CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
+ if (!allocSize.isValid()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ char* data = static_cast<char*>(malloc(allocSize.value()));
+ if (data) {
+ uint32_t size = allocSize.value() - 3;
+ memcpy(data, lpStr, size);
+ // null terminate for safety
+ data[size] = data[size + 1] = data[size + 2] = '\0';
+
+ GlobalUnlock(aHGBL);
+ *aData = data;
+ *aLen = size;
+
+ result = NS_OK;
+ }
+ } else {
+ // We really shouldn't ever get here
+ // but just in case
+ *aData = nullptr;
+ *aLen = 0;
+ LPVOID lpMsgBuf;
+
+ FormatMessageW(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+ (LPWSTR)&lpMsgBuf, 0, nullptr);
+
+ // Display the string.
+ MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
+ MB_OK | MB_ICONINFORMATION);
+
+ // Free the buffer.
+ LocalFree(lpMsgBuf);
+ }
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
+ UINT /*aIndex*/, UINT aFormat,
+ void** aData, uint32_t* aLen) {
+ MOZ_CLIPBOARD_LOG("%s: overload taking nsIWidget*.", __FUNCTION__);
+
+ HGLOBAL hglb;
+ nsresult result = NS_ERROR_FAILURE;
+
+ HWND nativeWin = nullptr;
+ if (::OpenClipboard(nativeWin)) {
+ hglb = ::GetClipboardData(aFormat);
+ result = GetGlobalData(hglb, aData, aLen);
+ ::CloseClipboard();
+ }
+ return result;
+}
+
+// See methods listed at
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
+static void LogIDataObjectMethodResult(const HRESULT aHres,
+ const nsCString& aMethodName) {
+ if (MOZ_CLIPBOARD_LOG_ENABLED()) {
+ nsAutoCString hresString;
+ IDataObjectMethodResultToString(aHres, hresString);
+ MOZ_CLIPBOARD_LOG("IDataObject::%s result : %s", aMethodName.get(),
+ hresString.get());
+ }
+}
+
+// Other apps can block access to the clipboard. This repeatedly calls
+// `GetData` for a fixed number of times and should be called instead of
+// `GetData`. See
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-idataobject-getdata>.
+// While Microsoft's documentation doesn't include `CLIPBRD_E_CANT_OPEN`
+// explicitly, it allows it implicitly and in local experiments it was indeed
+// returned.
+static HRESULT RepeatedlyTryGetData(IDataObject& aDataObject, LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ return RepeatedlyTry(
+ [&aDataObject, &pFE, &pSTM]() { return aDataObject.GetData(pFE, pSTM); },
+ std::bind(LogIDataObjectMethodResult, std::placeholders::_1,
+ "GetData"_ns));
+}
+
+//-------------------------------------------------------------------------
+// static
+HRESULT nsClipboard::FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
+ LPFORMATETC pFE, LPSTGMEDIUM pSTM,
+ DWORD aTymed) {
+ SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
+
+ // Starting by querying for the data to see if we can get it as from global
+ // memory
+ HRESULT hres = S_FALSE;
+ hres = aDataObject->QueryGetData(pFE);
+ LogIDataObjectMethodResult(hres, "QueryGetData"_ns);
+ if (S_OK == hres) {
+ hres = RepeatedlyTryGetData(*aDataObject, pFE, pSTM);
+ }
+ return hres;
+}
+
+//-------------------------------------------------------------------------
+// If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
+// an image encoder (e.g. image/png).
+// For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
+nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
+ UINT aIndex, UINT aFormat,
+ const char* aMIMEImageFormat,
+ void** aData, uint32_t* aLen) {
+ MOZ_CLIPBOARD_LOG("%s: overload taking IDataObject*.", __FUNCTION__);
+
+ nsresult result = NS_ERROR_FAILURE;
+ *aData = nullptr;
+ *aLen = 0;
+
+ if (!aDataObject) {
+ return result;
+ }
+
+ UINT format = aFormat;
+ HRESULT hres = S_FALSE;
+
+ // XXX at the moment we only support global memory transfers
+ // It is here where we will add support for native images
+ // and IStream
+ FORMATETC fe;
+ STGMEDIUM stm;
+ hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
+
+ // If the format is CF_HDROP and we haven't found any files we can try looking
+ // for virtual files with FILEDESCRIPTOR.
+ if (FAILED(hres) && format == CF_HDROP) {
+ hres = FillSTGMedium(aDataObject,
+ nsClipboard::GetClipboardFileDescriptorFormatW(), &fe,
+ &stm, TYMED_HGLOBAL);
+ if (FAILED(hres)) {
+ hres = FillSTGMedium(aDataObject,
+ nsClipboard::GetClipboardFileDescriptorFormatA(),
+ &fe, &stm, TYMED_HGLOBAL);
+ }
+ }
+
+ // Currently this is only handling TYMED_HGLOBAL data
+ // For Text, Dibs, Files, and generic data (like HTML)
+ if (S_OK == hres) {
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT fileFlavor =
+ ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ static CLIPFORMAT preferredDropEffect =
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
+
+ switch (stm.tymed) {
+ case TYMED_HGLOBAL: {
+ switch (fe.cfFormat) {
+ case CF_TEXT: {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_TEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ *aLen = strlen(reinterpret_cast<char*>(*aData));
+ result = NS_OK;
+ }
+ } break;
+
+ case CF_UNICODETEXT: {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_UNICODETEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
+ result = NS_OK;
+ }
+ } break;
+
+ case CF_DIBV5:
+ if (aMIMEImageFormat) {
+ uint32_t allocLen = 0;
+ const char* clipboardData;
+ if (NS_SUCCEEDED(GetGlobalData(
+ stm.hGlobal, (void**)&clipboardData, &allocLen))) {
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ result = imgTools->DecodeImageFromBuffer(
+ clipboardData, allocLen,
+ nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
+ getter_AddRefs(container));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ nsAutoCString mimeType;
+ if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
+ mimeType.Assign(IMAGE_JPEG);
+ } else {
+ mimeType.Assign(aMIMEImageFormat);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ result = imgTools->EncodeImage(container, mimeType, u""_ns,
+ getter_AddRefs(inputStream));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ if (!inputStream) {
+ result = NS_ERROR_FAILURE;
+ break;
+ }
+
+ *aData = inputStream.forget().take();
+ *aLen = sizeof(nsIInputStream*);
+ }
+ }
+ break;
+
+ case CF_HDROP: {
+ // in the case of a file drop, multiple files are stashed within a
+ // single data object. In order to match mozilla's D&D apis, we
+ // just pull out the file at the requested index, pretending as
+ // if there really are multiple drag items.
+ HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
+
+ UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
+ NS_ASSERTION(numFiles > 0,
+ "File drop flavor, but no files...hmmmm");
+ NS_ASSERTION(aIndex < numFiles,
+ "Asked for a file index out of range of list");
+ if (numFiles > 0) {
+ UINT fileNameLen =
+ ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
+ wchar_t* buffer = reinterpret_cast<wchar_t*>(
+ moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
+ ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
+ *aData = buffer;
+ *aLen = fileNameLen * sizeof(char16_t);
+ result = NS_OK;
+ }
+ GlobalUnlock(stm.hGlobal);
+
+ } break;
+
+ default: {
+ if (fe.cfFormat == fileDescriptorFlavorA ||
+ fe.cfFormat == fileDescriptorFlavorW) {
+ nsAutoString tempPath;
+
+ LPFILEGROUPDESCRIPTOR fgdesc =
+ static_cast<LPFILEGROUPDESCRIPTOR>(GlobalLock(stm.hGlobal));
+ if (fgdesc) {
+ result = GetTempFilePath(
+ nsDependentString((fgdesc->fgd)[aIndex].cFileName),
+ tempPath);
+ GlobalUnlock(stm.hGlobal);
+ }
+ if (NS_FAILED(result)) {
+ break;
+ }
+ result = SaveStorageOrStream(aDataObject, aIndex, tempPath);
+ if (NS_FAILED(result)) {
+ break;
+ }
+ wchar_t* buffer = reinterpret_cast<wchar_t*>(
+ moz_xmalloc((tempPath.Length() + 1) * sizeof(wchar_t)));
+ wcscpy(buffer, tempPath.get());
+ *aData = buffer;
+ *aLen = tempPath.Length() * sizeof(wchar_t);
+ result = NS_OK;
+ } else if (fe.cfFormat == fileFlavor) {
+ NS_WARNING(
+ "Mozilla doesn't yet understand how to read this type of "
+ "file flavor");
+ } else {
+ // Get the data out of the global data handle. The size we
+ // return should not include the null because the other
+ // platforms don't use nulls, so just return the length we get
+ // back from strlen(), since we know CF_UNICODETEXT is null
+ // terminated. Recall that GetGlobalData() returns the size of
+ // the allocated buffer, not the size of the data (on 98, these
+ // are not the same) so we can't use that.
+ //
+ // NOTE: we are assuming that anything that falls into this
+ // default case is unicode. As we start to get more
+ // kinds of binary data, this may become an incorrect
+ // assumption. Stay tuned.
+ uint32_t allocLen = 0;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
+ if (fe.cfFormat == GetHtmlClipboardFormat()) {
+ // CF_HTML is actually UTF8, not unicode, so disregard the
+ // assumption above. We have to check the header for the
+ // actual length, and we'll do that in FindPlatformHTML().
+ // For now, return the allocLen. This case is mostly to
+ // ensure we don't try to call strlen on the buffer.
+ *aLen = allocLen;
+ } else if (fe.cfFormat == GetCustomClipboardFormat()) {
+ // Binary data
+ *aLen = allocLen;
+ } else if (fe.cfFormat == preferredDropEffect) {
+ // As per the MSDN doc entitled: "Shell Clipboard Formats"
+ // CFSTR_PREFERREDDROPEFFECT should return a DWORD
+ // Reference:
+ // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
+ NS_ASSERTION(
+ allocLen == sizeof(DWORD),
+ "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
+ *aLen = allocLen;
+ } else {
+ *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
+ sizeof(char16_t);
+ }
+ result = NS_OK;
+ }
+ }
+ } break;
+ } // switch
+ } break;
+
+ case TYMED_GDI: {
+#ifdef DEBUG
+ MOZ_CLIPBOARD_LOG("*********************** TYMED_GDI");
+#endif
+ } break;
+
+ default:
+ break;
+ } // switch
+
+ ReleaseStgMedium(&stm);
+ }
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
+ UINT anIndex, nsIWidget* aWindow,
+ nsITransferable* aTransferable) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ // make sure we have a good transferable
+ if (!aTransferable) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult res = NS_ERROR_FAILURE;
+
+ // get flavor list that includes all flavors that can be written (including
+ // ones obtained through conversion)
+ nsTArray<nsCString> flavors;
+ res = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Walk through flavors and see which flavor is on the clipboard them on the
+ // native clipboard,
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+ UINT format = GetFormat(flavorStr.get());
+
+ // Try to get the data using the desired flavor. This might fail, but all is
+ // not lost.
+ void* data = nullptr;
+ uint32_t dataLen = 0;
+ bool dataFound = false;
+ if (nullptr != aDataObject) {
+ if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
+ flavorStr.get(), &data,
+ &dataLen))) {
+ dataFound = true;
+ }
+ } else if (nullptr != aWindow) {
+ if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
+ &data, &dataLen))) {
+ dataFound = true;
+ }
+ }
+
+ // This is our second chance to try to find some data, having not found it
+ // when directly asking for the flavor. Let's try digging around in other
+ // flavors to help satisfy our craving for data.
+ if (!dataFound) {
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ dataFound =
+ FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // drags from other windows apps expose the native
+ // CFSTR_INETURL{A,W} flavor
+ dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
+ if (!dataFound) {
+ dataFound =
+ FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
+ }
+ }
+ } // if we try one last ditch effort to find our data
+
+ // Hopefully by this point we've found it and can go about our business
+ if (dataFound) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ // we have a file path in |data|. Create an nsLocalFile object.
+ nsDependentString filepath(reinterpret_cast<char16_t*>(data));
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(
+ NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
+ genericDataWrapper = do_QueryInterface(file);
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
+ uint32_t dummy;
+ // the editor folks want CF_HTML exactly as it's on the clipboard, no
+ // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
+ // a wrapper and hand it back to them.
+ if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
+ } else {
+ free(data);
+ continue; // something wrong with this flavor, keep looking for other
+ // data
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ uint32_t startOfData = 0;
+ // The JS folks want CF_HTML exactly as it is on the clipboard, but
+ // minus the CF_HTML header index information.
+ // It also needs to be converted to UTF16 and have linebreaks changed.
+ if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
+ &dataLen)) {
+ dataLen -= startOfData;
+ nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
+ static_cast<char*>(data) + startOfData, &dataLen,
+ getter_AddRefs(genericDataWrapper));
+ } else {
+ free(data);
+ continue; // something wrong with this flavor, keep looking for other
+ // data
+ }
+ free(data);
+ } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime)) {
+ nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
+ genericDataWrapper = do_QueryInterface(imageStream);
+ NS_IF_RELEASE(imageStream);
+ } else {
+ // Treat custom types as a string of bytes.
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ bool isRTF = flavorStr.EqualsLiteral(kRTFMime);
+ // we probably have some form of text. The DOM only wants LF, so
+ // convert from Win32 line endings to DOM line endings.
+ int32_t signedLen = static_cast<int32_t>(dataLen);
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(isRTF, &data,
+ &signedLen);
+ dataLen = signedLen;
+
+ if (isRTF) {
+ // RTF on Windows is known to sometimes deliver an extra null byte.
+ if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
+ dataLen--;
+ }
+ }
+ }
+
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
+ free(data);
+ }
+
+ NS_ASSERTION(genericDataWrapper,
+ "About to put null data into the transferable");
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ res = NS_OK;
+
+ // we found one, get out of the loop
+ break;
+ }
+ } // foreach flavor
+
+ return res;
+}
+
+//
+// FindPlatformHTML
+//
+// Someone asked for the OS CF_HTML flavor. We give it back to them exactly
+// as-is.
+//
+bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outStartOfData,
+ uint32_t* outDataLen) {
+ // Reference: MSDN doc entitled "HTML Clipboard Format"
+ // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
+ // CF_HTML is UTF8, not unicode. We also can't rely on it being
+ // null-terminated so we have to check the CF_HTML header for the correct
+ // length. The length we return is the bytecount from the beginning of the
+ // selected data to the end of the selected data, without the null
+ // termination. Because it's UTF8, we're guaranteed the header is ASCII.
+
+ if (!outData || !*outData) {
+ return false;
+ }
+
+ char version[8] = {0};
+ int32_t startOfData = 0;
+ int32_t endOfData = 0;
+ int numFound =
+ sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
+ &startOfData, &endOfData);
+
+ if (numFound != 3 || startOfData < -1 || endOfData < -1) {
+ return false;
+ }
+
+ // Fixup the start and end markers if they have no context (set to -1)
+ if (startOfData == -1) {
+ startOfData = 0;
+ }
+ if (endOfData == -1) {
+ endOfData = *outDataLen;
+ }
+
+ // Make sure we were passed sane values within our buffer size.
+ // (Note that we've handled all cases of negative endOfData above, so we can
+ // safely cast it to be unsigned here.)
+ if (!endOfData || startOfData >= endOfData ||
+ static_cast<uint32_t>(endOfData) > *outDataLen) {
+ return false;
+ }
+
+ // We want to return the buffer not offset by startOfData because it will be
+ // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
+ // in CF_HTML format.
+
+ // We return the byte offset from the start of the data buffer to where the
+ // HTML data starts. The caller might want to extract the HTML only.
+ *outStartOfData = startOfData;
+ *outDataLen = endOfData;
+ return true;
+}
+
+//
+// FindUnicodeFromPlainText
+//
+// Looks for CF_TEXT on the clipboard and converts it into an UTF-16 string
+// if present. Returns this string in outData, and its length in outDataLen.
+// XXXndeakin Windows converts between CF_UNICODE and CF_TEXT automatically
+// so it doesn't seem like this is actually needed.
+//
+bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
+ UINT inIndex, void** outData,
+ uint32_t* outDataLen) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ // We are looking for text/plain and we failed to find it on the clipboard
+ // first, so try again with CF_TEXT. If that is present, convert it to
+ // unicode.
+ nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, CF_TEXT,
+ nullptr, outData, outDataLen);
+ if (NS_FAILED(rv) || !*outData) {
+ return false;
+ }
+
+ const char* castedText = static_cast<char*>(*outData);
+ nsAutoString tmp;
+ rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
+ tmp);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // out with the old, in with the new
+ free(*outData);
+ *outData = ToNewUnicode(tmp);
+ *outDataLen = tmp.Length() * sizeof(char16_t);
+
+ return true;
+
+} // FindUnicodeFromPlainText
+
+//
+// FindURLFromLocalFile
+//
+// we are looking for a URL and couldn't find it, try again with looking for
+// a local file. If we have one, it may either be a normal file or an internet
+// shortcut. In both cases, however, we can get a URL (it will be a file:// url
+// in the local file case).
+//
+bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ bool dataFound = false;
+
+ nsresult loadResult =
+ GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
+ nullptr, outData, outDataLen);
+ if (NS_SUCCEEDED(loadResult) && *outData) {
+ // we have a file path in |data|. Is it an internet shortcut or a normal
+ // file?
+ const nsDependentString filepath(static_cast<char16_t*>(*outData));
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ free(*outData);
+ return dataFound;
+ }
+
+ if (IsInternetShortcut(filepath)) {
+ free(*outData);
+ nsAutoCString url;
+ ResolveShortcut(file, url);
+ if (!url.IsEmpty()) {
+ // convert it to unicode and pass it out
+ NS_ConvertUTF8toUTF16 urlString(url);
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. We can guess the title from the file's name.
+ nsAutoString title;
+ file->GetLeafName(title);
+ // We rely on IsInternetShortcut check that file has a .url extension.
+ title.SetLength(title.Length() - 4);
+ if (title.IsEmpty()) {
+ title = urlString;
+ }
+ *outData = ToNewUnicode(urlString + u"\n"_ns + title);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+
+ dataFound = true;
+ }
+ } else {
+ // we have a normal file, use some Necko objects to get our file path
+ nsAutoCString urlSpec;
+ NS_GetURLSpecFromFile(file, urlSpec);
+
+ // convert it to unicode and pass it out
+ free(*outData);
+ *outData = UTF8ToNewUnicode(urlSpec);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ dataFound = true;
+ } // else regular file
+ }
+
+ return dataFound;
+} // FindURLFromLocalFile
+
+//
+// FindURLFromNativeURL
+//
+// we are looking for a URL and couldn't find it using our internal
+// URL flavor, so look for it using the native URL flavor,
+// CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
+//
+bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen) {
+ MOZ_CLIPBOARD_LOG("%s", __FUNCTION__);
+
+ bool dataFound = false;
+
+ void* tempOutData = nullptr;
+ uint32_t tempDataLen = 0;
+
+ nsresult loadResult = GetNativeDataOffClipboard(
+ inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
+ &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ nsDependentString urlString(static_cast<char16_t*>(tempOutData));
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. Since we don't actually have a title here,
+ // just repeat the URL to fake it.
+ *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ } else {
+ loadResult = GetNativeDataOffClipboard(
+ inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
+ nullptr, &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
+ // CF_TEXT which is by definition ANSI encoded.
+ nsCString urlUnescapedA;
+ bool unescaped =
+ NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
+ esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
+
+ nsString urlString;
+ if (unescaped) {
+ NS_CopyNativeToUnicode(urlUnescapedA, urlString);
+ } else {
+ NS_CopyNativeToUnicode(
+ nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
+ urlString);
+ }
+
+ // the internal mozilla URL format, text/x-moz-url, contains
+ // URL\ntitle. Since we don't actually have a title here,
+ // just repeat the URL to fake it.
+ *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ }
+ }
+
+ return dataFound;
+} // FindURLFromNativeURL
+
+// Other apps can block access to the clipboard. This repeatedly
+// calls `::OleGetClipboard` for a fixed number of times and should be called
+// instead of `::OleGetClipboard`.
+static HRESULT RepeatedlyTryOleGetClipboard(IDataObject** aDataObj) {
+ return RepeatedlyTry(::OleGetClipboard, LogOleGetClipboardResult, aDataObj);
+}
+
+//
+// ResolveShortcut
+//
+void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
+ nsCOMPtr<nsIFileProtocolHandler> fph;
+ nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ uri->GetSpec(outURL);
+} // ResolveShortcut
+
+//
+// IsInternetShortcut
+//
+// A file is an Internet Shortcut if it ends with .URL
+//
+bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
+ return StringEndsWith(inFileName, u".url"_ns,
+ nsCaseInsensitiveStringComparator);
+} // IsInternetShortcut
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ MOZ_CLIPBOARD_LOG("%s aWhichClipboard=%i", __FUNCTION__, aWhichClipboard);
+
+ nsresult res;
+ // This makes sure we can use the OLE functionality for the clipboard
+ IDataObject* dataObj;
+ if (S_OK == RepeatedlyTryOleGetClipboard(&dataObj)) {
+ // Use OLE IDataObject for clipboard operations
+ MOZ_CLIPBOARD_LOG(" use OLE IDataObject:");
+ if (MOZ_CLIPBOARD_LOG_ENABLED()) {
+ IEnumFORMATETC* pEnum = nullptr;
+ if (S_OK == dataObj->EnumFormatEtc(DATADIR_GET, &pEnum)) {
+ FORMATETC fEtc;
+ while (S_OK == pEnum->Next(1, &fEtc, nullptr)) {
+ nsAutoString format;
+ mozilla::widget::WinUtils::GetClipboardFormatAsString(fEtc.cfFormat,
+ format);
+ MOZ_CLIPBOARD_LOG(" FORMAT %s",
+ NS_ConvertUTF16toUTF8(format).get());
+ }
+ }
+ pEnum->Release();
+ }
+
+ res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
+ dataObj->Release();
+ } else {
+ // do it the old manual way
+ res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
+ }
+ return res;
+}
+
+nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+ // Some programs such as ZoneAlarm monitor clipboard usage and then open the
+ // clipboard to scan it. If we i) empty and then ii) set data, then the
+ // 'set data' can sometimes fail with access denied becacuse another program
+ // has the clipboard open. So to avoid this race condition for OpenClipboard
+ // we do not empty the clipboard when we're setting it.
+ RepeatedlyTryOleSetClipboard(nullptr);
+ return NS_OK;
+}
+
+mozilla::Result<int32_t, nsresult>
+nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(kGlobalClipboard == aWhichClipboard);
+ return (int32_t)::GetClipboardSequenceNumber();
+}
+
+//-------------------------------------------------------------------------
+mozilla::Result<bool, nsresult>
+nsClipboard::HasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+ for (const auto& flavor : aFlavorList) {
+ UINT format = GetFormat(flavor.get());
+ if (IsClipboardFormatAvailable(format)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetTempFilePath(const nsAString& aFileName,
+ nsAString& aFilePath) {
+ nsresult result = NS_OK;
+
+ nsCOMPtr<nsIFile> tmpFile;
+ result =
+ GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(result, result);
+
+ result = tmpFile->Append(aFileName);
+ NS_ENSURE_SUCCESS(result, result);
+
+ result = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ NS_ENSURE_SUCCESS(result, result);
+ result = tmpFile->GetPath(aFilePath);
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SaveStorageOrStream(IDataObject* aDataObject, UINT aIndex,
+ const nsAString& aFileName) {
+ NS_ENSURE_ARG_POINTER(aDataObject);
+
+ FORMATETC fe = {0};
+ SET_FORMATETC(fe, RegisterClipboardFormat(CFSTR_FILECONTENTS), 0,
+ DVASPECT_CONTENT, aIndex, TYMED_ISTORAGE | TYMED_ISTREAM);
+
+ STGMEDIUM stm = {0};
+ HRESULT hres = aDataObject->GetData(&fe, &stm);
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto releaseMediumGuard =
+ mozilla::MakeScopeExit([&] { ReleaseStgMedium(&stm); });
+
+ // We do this check because, even though we *asked* for IStorage or IStream,
+ // it seems that IDataObject providers can just hand us back whatever they
+ // feel like. See Bug 1824644 for a fun example of that!
+ if (stm.tymed != TYMED_ISTORAGE && stm.tymed != TYMED_ISTREAM) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (stm.tymed == TYMED_ISTORAGE) {
+ RefPtr<IStorage> file;
+ hres = StgCreateStorageEx(
+ aFileName.Data(), STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
+ STGFMT_STORAGE, 0, NULL, NULL, IID_IStorage, getter_AddRefs(file));
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hres = stm.pstg->CopyTo(0, NULL, NULL, file);
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ file->Commit(STGC_DEFAULT);
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(stm.tymed == TYMED_ISTREAM);
+
+ HANDLE handle = CreateFile(aFileName.Data(), GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto fileCloseGuard = mozilla::MakeScopeExit([&] { CloseHandle(handle); });
+
+ const ULONG bufferSize = 4096;
+ char buffer[bufferSize] = {0};
+ ULONG bytesRead = 0;
+ DWORD bytesWritten = 0;
+ while (true) {
+ HRESULT result = stm.pstm->Read(buffer, bufferSize, &bytesRead);
+ if (FAILED(result)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (bytesRead == 0) {
+ break;
+ }
+ if (!WriteFile(handle, buffer, static_cast<DWORD>(bytesRead), &bytesWritten,
+ NULL)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h
new file mode 100644
index 0000000000..b0afb9ee40
--- /dev/null
+++ b/widget/windows/nsClipboard.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsClipboard_h__
+#define nsClipboard_h__
+
+#include "nsBaseClipboard.h"
+#include "nsIObserver.h"
+#include "nsIURI.h"
+
+#include <ole2.h>
+#include <windows.h>
+
+class nsITransferable;
+class nsIWidget;
+class nsIFile;
+struct IDataObject;
+
+/**
+ * Native Win32 Clipboard wrapper
+ */
+
+class nsClipboard : public nsBaseClipboard, public nsIObserver {
+ virtual ~nsClipboard();
+
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ // Internal Native Routines
+ enum class MightNeedToFlush : bool { No, Yes };
+ static nsresult CreateNativeDataObject(nsITransferable* aTransferable,
+ IDataObject** aDataObj, nsIURI* aUri,
+ MightNeedToFlush* = nullptr);
+ static nsresult SetupNativeDataObject(nsITransferable* aTransferable,
+ IDataObject* aDataObj,
+ MightNeedToFlush* = nullptr);
+ static nsresult GetDataFromDataObject(IDataObject* aDataObject, UINT anIndex,
+ nsIWidget* aWindow,
+ nsITransferable* aTransferable);
+ static nsresult GetNativeDataOffClipboard(nsIWidget* aWindow, UINT aIndex,
+ UINT aFormat, void** aData,
+ uint32_t* aLen);
+ static nsresult GetNativeDataOffClipboard(IDataObject* aDataObject,
+ UINT aIndex, UINT aFormat,
+ const char* aMIMEImageFormat,
+ void** aData, uint32_t* aLen);
+ static nsresult GetGlobalData(HGLOBAL aHGBL, void** aData, uint32_t* aLen);
+
+ // This function returns the internal Windows clipboard format identifier
+ // for a given Mime string. The default is to map kHTMLMime ("text/html")
+ // to the clipboard format CF_HTML ("HTLM Format"), but it can also be
+ // registered as clipboard format "text/html" to support previous versions
+ // of Gecko.
+ static UINT GetFormat(const char* aMimeStr, bool aMapHTMLMime = true);
+
+ static UINT GetClipboardFileDescriptorFormatA();
+ static UINT GetClipboardFileDescriptorFormatW();
+ static UINT GetHtmlClipboardFormat();
+ static UINT GetCustomClipboardFormat();
+
+ protected:
+ // @param aDataObject must be non-nullptr.
+ static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
+ LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed);
+
+ // Implement the native clipboard behavior.
+ NS_IMETHOD SetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) override;
+ NS_IMETHOD GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) override;
+ nsresult EmptyNativeClipboardData(int32_t aWhichClipboard) override;
+ mozilla::Result<int32_t, nsresult> GetNativeClipboardSequenceNumber(
+ int32_t aWhichClipboard) override;
+ mozilla::Result<bool, nsresult> HasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) override;
+
+ static bool IsInternetShortcut(const nsAString& inFileName);
+ static bool FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindUnicodeFromPlainText(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outDataLen);
+ static bool FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
+ void** outData, uint32_t* outStartOfData,
+ uint32_t* outDataLen);
+
+ static void ResolveShortcut(nsIFile* inFileName, nsACString& outURL);
+ static nsresult GetTempFilePath(const nsAString& aFileName,
+ nsAString& aFilePath);
+ static nsresult SaveStorageOrStream(IDataObject* aDataObject, UINT aIndex,
+ const nsAString& aFileName);
+
+ nsIWidget* mWindow;
+};
+
+#define SET_FORMATETC(fe, cf, td, asp, li, med) \
+ { \
+ (fe).cfFormat = cf; \
+ (fe).ptd = td; \
+ (fe).dwAspect = asp; \
+ (fe).lindex = li; \
+ (fe).tymed = med; \
+ }
+
+#endif // nsClipboard_h__
diff --git a/widget/windows/nsColorPicker.cpp b/widget/windows/nsColorPicker.cpp
new file mode 100644
index 0000000000..5074b620f5
--- /dev/null
+++ b/widget/windows/nsColorPicker.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsColorPicker.h"
+
+#include <shlwapi.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla::widget;
+
+namespace {
+static DWORD ColorStringToRGB(const nsAString& aColor) {
+ DWORD result = 0;
+
+ for (uint32_t i = 1; i < aColor.Length(); ++i) {
+ result *= 16;
+
+ char16_t c = aColor[i];
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ result += 10 + (c - 'a');
+ } else {
+ result += 10 + (c - 'A');
+ }
+ }
+
+ DWORD r = result & 0x00FF0000;
+ DWORD g = result & 0x0000FF00;
+ DWORD b = result & 0x000000FF;
+
+ r = r >> 16;
+ b = b << 16;
+
+ result = r | g | b;
+
+ return result;
+}
+
+static nsString ToHexString(BYTE n) {
+ nsString result;
+ if (n <= 0x0F) {
+ result.Append('0');
+ }
+ result.AppendInt(n, 16);
+ return result;
+}
+
+static void BGRIntToRGBString(DWORD color, nsAString& aResult) {
+ BYTE r = GetRValue(color);
+ BYTE g = GetGValue(color);
+ BYTE b = GetBValue(color);
+
+ aResult.Assign('#');
+ aResult.Append(ToHexString(r));
+ aResult.Append(ToHexString(g));
+ aResult.Append(ToHexString(b));
+}
+} // namespace
+
+static AsyncColorChooser* gColorChooser;
+
+AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor,
+ const nsTArray<nsString>& aDefaultColors,
+ nsIWidget* aParentWidget,
+ nsIColorPickerShownCallback* aCallback)
+ : mozilla::Runnable("AsyncColorChooser"),
+ mInitialColor(aInitialColor),
+ mDefaultColors(aDefaultColors.Clone()),
+ mColor(aInitialColor),
+ mParentWidget(aParentWidget),
+ mCallback(aCallback) {}
+
+NS_IMETHODIMP
+AsyncColorChooser::Run() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Color pickers can only be opened from main thread currently");
+
+ // Allow only one color picker to be opened at a time, to workaround bug
+ // 944737
+ if (!gColorChooser) {
+ mozilla::AutoRestore<AsyncColorChooser*> restoreColorChooser(gColorChooser);
+ gColorChooser = this;
+
+ ScopedRtlShimWindow shim(mParentWidget.get());
+
+ COLORREF customColors[16];
+ for (size_t i = 0; i < mozilla::ArrayLength(customColors); i++) {
+ if (i < mDefaultColors.Length()) {
+ customColors[i] = ColorStringToRGB(mDefaultColors[i]);
+ } else {
+ customColors[i] = 0x00FFFFFF;
+ }
+ }
+
+ CHOOSECOLOR options;
+ options.lStructSize = sizeof(options);
+ options.hwndOwner = shim.get();
+ options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
+ options.rgbResult = mInitialColor;
+ options.lpCustColors = customColors;
+ options.lpfnHook = HookProc;
+
+ mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor;
+ } else {
+ NS_WARNING(
+ "Currently, it's not possible to open more than one color "
+ "picker at a time");
+ mColor = mInitialColor;
+ }
+
+ if (mCallback) {
+ nsAutoString colorStr;
+ BGRIntToRGBString(mColor, colorStr);
+ mCallback->Done(colorStr);
+ }
+
+ return NS_OK;
+}
+
+void AsyncColorChooser::Update(COLORREF aColor) {
+ if (mColor != aColor) {
+ mColor = aColor;
+
+ nsAutoString colorStr;
+ BGRIntToRGBString(mColor, colorStr);
+ mCallback->Update(colorStr);
+ }
+}
+
+/* static */ UINT_PTR CALLBACK AsyncColorChooser::HookProc(HWND aDialog,
+ UINT aMsg,
+ WPARAM aWParam,
+ LPARAM aLParam) {
+ if (!gColorChooser) {
+ return 0;
+ }
+
+ if (aMsg == WM_INITDIALOG) {
+ // "The default dialog box procedure processes the WM_INITDIALOG message
+ // before passing it to the hook procedure.
+ // For all other messages, the hook procedure receives the message first."
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpcchookproc
+ // "The dialog box procedure should return TRUE to direct the system to
+ // set the keyboard focus to the control specified by wParam."
+ // https://docs.microsoft.com/en-us/windows/win32/dlgbox/wm-initdialog
+ return 1;
+ }
+
+ if (aMsg == WM_CTLCOLORSTATIC) {
+ // The color picker does not expose a proper way to retrieve the current
+ // color, so we need to obtain it from the static control displaying the
+ // current color instead.
+ const int kCurrentColorBoxID = 709;
+ if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) {
+ gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0));
+ }
+ }
+
+ // Let the default dialog box procedure processes the message.
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIColorPicker
+
+nsColorPicker::nsColorPicker() {}
+
+nsColorPicker::~nsColorPicker() {}
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title,
+ const nsAString& aInitialColor,
+ const nsTArray<nsString>& aDefaultColors) {
+ MOZ_ASSERT(parent,
+ "Null parent passed to colorpicker, no color picker for you!");
+ mParentWidget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
+ mInitialColor = ColorStringToRGB(aInitialColor);
+ mDefaultColors.Assign(aDefaultColors);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) {
+ NS_ENSURE_ARG(aCallback);
+ nsCOMPtr<nsIRunnable> event = new AsyncColorChooser(
+ mInitialColor, mDefaultColors, mParentWidget, aCallback);
+ return NS_DispatchToMainThread(event);
+}
diff --git a/widget/windows/nsColorPicker.h b/widget/windows/nsColorPicker.h
new file mode 100644
index 0000000000..ff34b9bd48
--- /dev/null
+++ b/widget/windows/nsColorPicker.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsColorPicker_h__
+#define nsColorPicker_h__
+
+#include <windows.h>
+#include <commdlg.h>
+
+#include "nsCOMPtr.h"
+#include "nsIColorPicker.h"
+#include "nsThreadUtils.h"
+
+class nsIWidget;
+
+class AsyncColorChooser : public mozilla::Runnable {
+ public:
+ AsyncColorChooser(COLORREF aInitialColor,
+ const nsTArray<nsString>& aDefaultColors,
+ nsIWidget* aParentWidget,
+ nsIColorPickerShownCallback* aCallback);
+ NS_IMETHOD Run() override;
+
+ private:
+ void Update(COLORREF aColor);
+
+ static UINT_PTR CALLBACK HookProc(HWND aDialog, UINT aMsg, WPARAM aWParam,
+ LPARAM aLParam);
+
+ COLORREF mInitialColor;
+ nsTArray<nsString> mDefaultColors;
+ COLORREF mColor;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+class nsColorPicker : public nsIColorPicker {
+ virtual ~nsColorPicker();
+
+ public:
+ nsColorPicker();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOLORPICKER
+
+ private:
+ COLORREF mInitialColor;
+ nsTArray<nsString> mDefaultColors;
+ nsCOMPtr<nsIWidget> mParentWidget;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp
new file mode 100644
index 0000000000..88a2a2ad09
--- /dev/null
+++ b/widget/windows/nsDataObj.cpp
@@ -0,0 +1,2276 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/TextUtils.h"
+
+#include <ole2.h>
+#include <shlobj.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsDataObj.h"
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsReadableUtils.h"
+#include "nsICookieJarSettings.h"
+#include "nsIHttpChannel.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
+#include "IEnumFE.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "nsEscape.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Components.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nscore.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIMIMEService.h"
+#include "imgIEncoder.h"
+#include "imgITools.h"
+#include "WinUtils.h"
+#include "nsLocalFile.h"
+
+#include "mozilla/LazyIdleThread.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::glue;
+using namespace mozilla::widget;
+
+#define BFH_LENGTH 14
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+//-----------------------------------------------------------------------------
+// CStreamBase implementation
+nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStreamBase::~CStreamBase() {}
+
+NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
+
+//-----------------------------------------------------------------------------
+// CStream implementation
+nsDataObj::CStream::CStream() : mChannelRead(false) {}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStream::~CStream() {}
+
+//-----------------------------------------------------------------------------
+// helper - initializes the stream
+nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI,
+ nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ nsIReferrerInfo* aReferrerInfo) {
+ // we can not create a channel without a requestingPrincipal
+ if (!aRequestingPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv;
+ rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
+ aContentPolicyType, aCookieJarSettings,
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_FROM_CACHE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
+ rv = httpChannel->SetReferrerInfo(aReferrerInfo);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ }
+
+ rv = mChannel->AsyncOpen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by
+// IUnknown and nsIStreamListener.
+STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid,
+ void** ppvResult) {
+ *ppvResult = nullptr;
+ if (IID_IUnknown == refiid || refiid == IID_IStream)
+
+ {
+ *ppvResult = this;
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsDataObj::CStream::OnDataAvailable(
+ nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, // offset within the stream
+ uint32_t aCount) // bytes available on this call
+{
+ // If we've been asked to read zero bytes, call `Read` once, just to ensure
+ // any side-effects take place, and return immediately.
+ if (aCount == 0) {
+ char buffer[1] = {0};
+ uint32_t bytesReadByCall = 0;
+ nsresult rv = aInputStream->Read(buffer, 0, &bytesReadByCall);
+ MOZ_ASSERT(bytesReadByCall == 0);
+ return rv;
+ }
+
+ // Extend the write buffer for the incoming data.
+ size_t oldLength = mChannelData.Length();
+ char* buffer =
+ reinterpret_cast<char*>(mChannelData.AppendElements(aCount, fallible));
+ if (!buffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ MOZ_ASSERT(mChannelData.Length() == (aOffset + aCount),
+ "stream length mismatch w/write buffer");
+
+ // Read() may not return aCount on a single call, so loop until we've
+ // accumulated all the data OnDataAvailable has promised.
+ uint32_t bytesRead = 0;
+ while (bytesRead < aCount) {
+ uint32_t bytesReadByCall = 0;
+ nsresult rv = aInputStream->Read(buffer + bytesRead, aCount - bytesRead,
+ &bytesReadByCall);
+ bytesRead += bytesReadByCall;
+
+ if (bytesReadByCall == 0) {
+ // A `bytesReadByCall` of zero indicates EOF without failure... but we
+ // were promised `aCount` elements and haven't gotten them. Return a
+ // generic failure.
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ // Drop any trailing uninitialized elements before erroring out.
+ mChannelData.RemoveElementsAt(oldLength + bytesRead, aCount - bytesRead);
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest* aRequest) {
+ mChannelResult = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) {
+ mChannelRead = true;
+ mChannelResult = aStatusCode;
+ return NS_OK;
+}
+
+// Pumps thread messages while waiting for the async listener operation to
+// complete. Failing this call will fail the stream incall from Windows
+// and cancel the operation.
+nsresult nsDataObj::CStream::WaitForCompletion() {
+ // We are guaranteed OnStopRequest will get called, so this should be ok.
+ SpinEventLoopUntil("widget:nsDataObj::CStream::WaitForCompletion"_ns,
+ [&]() { return mChannelRead; });
+
+ if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE;
+
+ return mChannelResult;
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; }
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream,
+ ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ // Wait for the write into our buffer to complete via the stream listener.
+ // We can't respond to this by saying "call us back later".
+ if (NS_FAILED(WaitForCompletion())) return E_FAIL;
+
+ // Bytes left for Windows to read out of our buffer
+ ULONG bytesLeft = mChannelData.Length() - mStreamRead;
+ // Let Windows know what we will hand back, usually this is the entire buffer
+ *nBytesRead = std::min(bytesLeft, nBytesToRead);
+ // Copy the buffer data over
+ memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead);
+ // Update our bytes read tracking
+ mStreamRead += *nBytesRead;
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; }
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
+ ULARGE_INTEGER* nNewPos) {
+ if (nNewPos == nullptr) return STG_E_INVALIDPOINTER;
+
+ if (nMove.LowPart == 0 && nMove.HighPart == 0 &&
+ (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) {
+ nNewPos->LowPart = 0;
+ nNewPos->HighPart = 0;
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) {
+ if (statstg == nullptr) return STG_E_INVALIDPOINTER;
+
+ if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL;
+
+ memset((void*)statstg, 0, sizeof(STATSTG));
+
+ if (dwFlags != STATFLAG_NONAME) {
+ nsCOMPtr<nsIURI> sourceURI;
+ if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) {
+ return E_FAIL;
+ }
+
+ nsAutoCString strFileName;
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ sourceURL->GetFileName(strFileName);
+
+ if (strFileName.IsEmpty()) return E_FAIL;
+
+ NS_UnescapeURL(strFileName);
+ NS_ConvertUTF8toUTF16 wideFileName(strFileName);
+
+ uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2;
+ void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller
+ if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
+
+ ZeroMemory(retBuf, nMaxNameLength);
+ memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2);
+ statstg->pwcsName = (LPOLESTR)retBuf;
+ }
+
+ SYSTEMTIME st;
+
+ statstg->type = STGTY_STREAM;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
+ statstg->ctime = statstg->atime = statstg->mtime;
+
+ statstg->cbSize.QuadPart = mChannelData.Length();
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer,
+ ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+HRESULT nsDataObj::CreateStream(IStream** outStream) {
+ NS_ENSURE_TRUE(outStream, E_INVALIDARG);
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsAutoString wideFileName;
+ nsCOMPtr<nsIURI> sourceURI;
+ HRESULT res;
+
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) return res;
+
+ nsDataObj::CStream* pStream = new nsDataObj::CStream();
+ NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY);
+
+ pStream->AddRef();
+
+ // query the requestingPrincipal from the transferable and add it to the new
+ // channel
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ mTransferable->GetRequestingPrincipal();
+ MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
+
+ // Note that the cookieJarSettings could be null if the data object is for the
+ // image copy. We will fix this in Bug 1690532.
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ mTransferable->GetCookieJarSettings();
+
+ // The referrer is optional.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mTransferable->GetReferrerInfo();
+
+ nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType();
+ rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal,
+ cookieJarSettings, referrerInfo);
+ if (NS_FAILED(rv)) {
+ pStream->Release();
+ return E_FAIL;
+ }
+ *outStream = pStream;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// AutoCloseEvent implementation
+nsDataObj::AutoCloseEvent::AutoCloseEvent()
+ : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}
+
+bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; }
+
+void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); }
+
+DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const {
+ return ::WaitForSingleObject(mEvent, aMillisec);
+}
+
+//-----------------------------------------------------------------------------
+// AutoSetEvent implementation
+nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent)
+ : mEvent(aEvent) {}
+
+nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); }
+
+void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); }
+
+bool nsDataObj::AutoSetEvent::IsWaiting() const {
+ return mEvent->Wait(0) == WAIT_TIMEOUT;
+}
+
+//-----------------------------------------------------------------------------
+// CMemStream implementation
+Win32SRWLock nsDataObj::CMemStream::mLock;
+
+//-----------------------------------------------------------------------------
+nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength,
+ already_AddRefed<AutoCloseEvent> aEvent)
+ : mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) {
+ ::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler));
+}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CMemStream::~CMemStream() {}
+
+//-----------------------------------------------------------------------------
+// IUnknown
+STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid,
+ void** ppvResult) {
+ *ppvResult = nullptr;
+ if (refiid == IID_IUnknown || refiid == IID_IStream ||
+ refiid == IID_IAgileObject) {
+ *ppvResult = this;
+ } else if (refiid == IID_IMarshal && mMarshaler) {
+ return mMarshaler->QueryInterface(refiid, ppvResult);
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+void nsDataObj::CMemStream::WaitForCompletion() {
+ if (!mEvent) {
+ // We are not waiting for obtaining the icon cache.
+ return;
+ }
+ if (!NS_IsMainThread()) {
+ mEvent->Wait(INFINITE);
+ } else {
+ // We should not block the main thread.
+ mEvent->Signal();
+ }
+ // mEvent will always be in the signaled state here.
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) {
+ // Wait until the event is signaled.
+ WaitForCompletion();
+
+ AutoExclusiveLock lock(mLock);
+ char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get()));
+ if (!contents) {
+ return E_OUTOFMEMORY;
+ }
+
+ // Bytes left for Windows to read out of our buffer
+ ULONG bytesLeft = mTotalLength - mStreamRead;
+ // Let Windows know what we will hand back, usually this is the entire buffer
+ *nBytesRead = std::min(bytesLeft, nBytesToRead);
+ // Copy the buffer data over
+ memcpy(pvBuffer, contents + mStreamRead, *nBytesRead);
+ // Update our bytes read tracking
+ mStreamRead += *nBytesRead;
+
+ GlobalUnlock(mGlobalMem.get());
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) {
+ if (statstg == nullptr) return STG_E_INVALIDPOINTER;
+
+ memset((void*)statstg, 0, sizeof(STATSTG));
+
+ if (dwFlags != STATFLAG_NONAME) {
+ constexpr size_t kMaxNameLength = sizeof(wchar_t);
+ void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller
+ if (!retBuf) return STG_E_INSUFFICIENTMEMORY;
+
+ ZeroMemory(retBuf, kMaxNameLength);
+ statstg->pwcsName = (LPOLESTR)retBuf;
+ }
+
+ SYSTEMTIME st;
+
+ statstg->type = STGTY_STREAM;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime);
+ statstg->ctime = statstg->atime = statstg->mtime;
+
+ statstg->cbSize.QuadPart = mTotalLength;
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+/*
+ * Class nsDataObj
+ */
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+nsDataObj::nsDataObj(nsIURI* uri)
+ : m_cRef(0),
+ mTransferable(nullptr),
+ mIsAsyncMode(FALSE),
+ mIsInOperation(FALSE) {
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "nsDataObj",
+ LazyIdleThread::ManualShutdown);
+ m_enumFE = new CEnumFormatEtc();
+ m_enumFE->AddRef();
+
+ if (uri) {
+ // A URI was obtained, so pass this through to the DataObject
+ // so it can create a SourceURL for CF_HTML flavour
+ uri->GetSpec(mSourceURL);
+ }
+}
+//-----------------------------------------------------
+// destruction
+//-----------------------------------------------------
+nsDataObj::~nsDataObj() {
+ NS_IF_RELEASE(mTransferable);
+
+ mDataFlavors.Clear();
+
+ m_enumFE->Release();
+
+ // Free arbitrary system formats
+ for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
+ CoTaskMemFree(mDataEntryList[idx]->fe.ptd);
+ ReleaseStgMedium(&mDataEntryList[idx]->stgm);
+ CoTaskMemFree(mDataEntryList[idx]);
+ }
+}
+
+//-----------------------------------------------------
+// IUnknown interface methods - see inknown.h for documentation
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
+ *ppv = this;
+ AddRef();
+ return S_OK;
+ } else if (IID_IDataObjectAsyncCapability == riid) {
+ *ppv = static_cast<IDataObjectAsyncCapability*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::AddRef() {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
+
+ // When the first reference is taken, hold our own internal reference.
+ if (m_cRef == 1) {
+ mKeepAlive = this;
+ }
+
+ return m_cRef;
+}
+
+namespace {
+class RemoveTempFileHelper final : public nsIObserver, public nsINamed {
+ public:
+ explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) {
+ MOZ_ASSERT(mTempFile);
+ }
+
+ // The attach method is seperate from the constructor as we may be addref-ing
+ // ourself, and we want to be sure someone has a strong reference to us.
+ void Attach() {
+ // We need to listen to both the xpcom shutdown message and our timer, and
+ // fire when the first of either of these two messages is received.
+ nsresult rv;
+ rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (NS_WARN_IF(!observerService)) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ return;
+ }
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ private:
+ ~RemoveTempFileHelper() {
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ }
+ }
+
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver, nsINamed);
+
+NS_IMETHODIMP
+RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Let's be careful and make sure that we don't die immediately
+ RefPtr<RemoveTempFileHelper> grip = this;
+
+ // Make sure that we aren't called again by destroying references to ourself.
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ // Remove the tempfile
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemoveTempFileHelper::GetName(nsACString& aName) {
+ aName.AssignLiteral("RemoveTempFileHelper");
+ return NS_OK;
+}
+} // namespace
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::Release() {
+ --m_cRef;
+
+ NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
+
+ // If we hold the last reference, submit release of it to the main thread.
+ if (m_cRef == 1 && mKeepAlive) {
+ NS_ReleaseOnMainThread("nsDataObj release", mKeepAlive.forget(), true);
+ }
+
+ if (0 != m_cRef) return m_cRef;
+
+ // We have released our last ref on this object and need to delete the
+ // temp file. External app acting as drop target may still need to open the
+ // temp file. Addref a timer so it can delay deleting file and destroying
+ // this object.
+ if (mCachedTempFile) {
+ RefPtr<RemoveTempFileHelper> helper =
+ new RemoveTempFileHelper(mCachedTempFile);
+ mCachedTempFile = nullptr;
+ helper->Attach();
+ }
+
+ // In case the destructor ever AddRef/Releases, ensure we don't delete twice
+ // or take mKeepAlive as another reference.
+ m_cRef = 1;
+
+ delete this;
+
+ return 0;
+}
+
+//-----------------------------------------------------
+BOOL nsDataObj::FormatsMatch(const FORMATETC& source,
+ const FORMATETC& target) const {
+ if ((source.cfFormat == target.cfFormat) &&
+ (source.dwAspect & target.dwAspect) && (source.tymed & target.tymed)) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+//-----------------------------------------------------
+// IDataObject methods
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
+ if (!mTransferable) return DV_E_FORMATETC;
+
+ // Hold an extra reference in case we end up spinning the event loop.
+ RefPtr<nsDataObj> keepAliveDuringGetData(this);
+
+ uint32_t dfInx = 0;
+
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT uniformResourceLocatorA =
+ ::RegisterClipboardFormat(CFSTR_INETURLA);
+ static CLIPFORMAT uniformResourceLocatorW =
+ ::RegisterClipboardFormat(CFSTR_INETURLW);
+ static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ static CLIPFORMAT PreferredDropEffect =
+ ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
+
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(aFormat, &pde, FALSE)) {
+ return CopyMediumData(pSTM, &pde->stgm, aFormat, FALSE) ? S_OK
+ : E_UNEXPECTED;
+ }
+
+ // Firefox internal formats
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
+ dfInx < mDataFlavors.Length()) {
+ nsCString& df = mDataFlavors.ElementAt(dfInx);
+ if (FormatsMatch(fe, *aFormat)) {
+ pSTM->pUnkForRelease =
+ nullptr; // caller is responsible for deleting this data
+ CLIPFORMAT format = aFormat->cfFormat;
+ switch (format) {
+ // Someone is asking for plain or unicode text
+ case CF_TEXT:
+ case CF_UNICODETEXT:
+ return GetText(df, *aFormat, *pSTM);
+
+ // Some 3rd party apps that receive drag and drop files from the browser
+ // window require support for this.
+ case CF_HDROP:
+ return GetFile(*aFormat, *pSTM);
+
+ // Someone is asking for an image
+ case CF_DIBV5:
+ case CF_DIB:
+ return GetDib(df, *aFormat, *pSTM);
+
+ default:
+ if (format == fileDescriptorFlavorA)
+ return GetFileDescriptor(*aFormat, *pSTM, false);
+ if (format == fileDescriptorFlavorW)
+ return GetFileDescriptor(*aFormat, *pSTM, true);
+ if (format == uniformResourceLocatorA)
+ return GetUniformResourceLocator(*aFormat, *pSTM, false);
+ if (format == uniformResourceLocatorW)
+ return GetUniformResourceLocator(*aFormat, *pSTM, true);
+ if (format == fileFlavor) return GetFileContents(*aFormat, *pSTM);
+ if (format == PreferredDropEffect)
+ return GetPreferredDropEffect(*aFormat, *pSTM);
+ // MOZ_LOG(gWindowsLog, LogLevel::Info,
+ // ("***** nsDataObj::GetData - Unknown format %u\n", format));
+ return GetText(df, *aFormat, *pSTM);
+ } // switch
+ } // if
+ dfInx++;
+ } // while
+
+ return DATA_E_FORMATETC;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+// Other objects querying to see if we support a
+// particular format
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::QueryGetData(LPFORMATETC pFE) {
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(pFE, &pde, FALSE)) return S_OK;
+
+ // Firefox internal formats
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count)) {
+ if (fe.cfFormat == pFE->cfFormat) {
+ return S_OK;
+ }
+ }
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::GetCanonicalFormatEtc(LPFORMATETC pFEIn,
+ LPFORMATETC pFEOut) {
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::SetData(LPFORMATETC aFormat, LPSTGMEDIUM aMedium,
+ BOOL shouldRel) {
+ // Arbitrary system formats are used for image feedback during drag
+ // and drop. We are responsible for storing these internally during
+ // drag operations.
+ LPDATAENTRY pde;
+ if (LookupArbitraryFormat(aFormat, &pde, TRUE)) {
+ // Release the old data the lookup handed us for this format. This
+ // may have been set in CopyMediumData when we originally stored the
+ // data.
+ if (pde->stgm.tymed) {
+ ReleaseStgMedium(&pde->stgm);
+ memset(&pde->stgm, 0, sizeof(STGMEDIUM));
+ }
+
+ bool result = true;
+ if (shouldRel) {
+ // If shouldRel is TRUE, the data object called owns the storage medium
+ // after the call returns. Store the incoming data in our data array for
+ // release when we are destroyed. This is the common case with arbitrary
+ // data from explorer.
+ pde->stgm = *aMedium;
+ } else {
+ // Copy the incoming data into our data array. (AFAICT, this never gets
+ // called with arbitrary formats for drag images.)
+ result = CopyMediumData(&pde->stgm, aMedium, aFormat, TRUE);
+ }
+ pde->fe.tymed = pde->stgm.tymed;
+
+ return result ? S_OK : DV_E_TYMED;
+ }
+
+ if (shouldRel) ReleaseStgMedium(aMedium);
+
+ return S_OK;
+}
+
+bool nsDataObj::LookupArbitraryFormat(FORMATETC* aFormat,
+ LPDATAENTRY* aDataEntry,
+ BOOL aAddorUpdate) {
+ *aDataEntry = nullptr;
+
+ if (aFormat->ptd != nullptr) return false;
+
+ // See if it's already in our list. If so return the data entry.
+ for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) {
+ if (mDataEntryList[idx]->fe.cfFormat == aFormat->cfFormat &&
+ mDataEntryList[idx]->fe.dwAspect == aFormat->dwAspect &&
+ mDataEntryList[idx]->fe.lindex == aFormat->lindex) {
+ if (aAddorUpdate || (mDataEntryList[idx]->fe.tymed & aFormat->tymed)) {
+ // If the caller requests we update, or if the
+ // medium type matches, return the entry.
+ *aDataEntry = mDataEntryList[idx];
+ return true;
+ } else {
+ // Medium does not match, not found.
+ return false;
+ }
+ }
+ }
+
+ if (!aAddorUpdate) return false;
+
+ // Add another entry to mDataEntryList
+ LPDATAENTRY dataEntry = (LPDATAENTRY)CoTaskMemAlloc(sizeof(DATAENTRY));
+ if (!dataEntry) return false;
+
+ dataEntry->fe = *aFormat;
+ *aDataEntry = dataEntry;
+ memset(&dataEntry->stgm, 0, sizeof(STGMEDIUM));
+
+ // Add this to our IEnumFORMATETC impl. so we can return it when
+ // it's requested.
+ m_enumFE->AddFormatEtc(aFormat);
+
+ // Store a copy internally in the arbitrary formats array.
+ mDataEntryList.AppendElement(dataEntry);
+
+ return true;
+}
+
+bool nsDataObj::CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
+ LPFORMATETC aFormat, BOOL aSetData) {
+ STGMEDIUM stgmOut = *aMediumSrc;
+
+ switch (stgmOut.tymed) {
+ case TYMED_ISTREAM:
+ stgmOut.pstm->AddRef();
+ break;
+ case TYMED_ISTORAGE:
+ stgmOut.pstg->AddRef();
+ break;
+ case TYMED_HGLOBAL:
+ if (!aMediumSrc->pUnkForRelease) {
+ if (aSetData) {
+ if (aMediumSrc->tymed != TYMED_HGLOBAL) return false;
+ stgmOut.hGlobal =
+ OleDuplicateData(aMediumSrc->hGlobal, aFormat->cfFormat, 0);
+ if (!stgmOut.hGlobal) return false;
+ } else {
+ // We are returning this data from LookupArbitraryFormat, indicate to
+ // the shell we hold it and will free it.
+ stgmOut.pUnkForRelease = static_cast<IDataObject*>(this);
+ }
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (stgmOut.pUnkForRelease) stgmOut.pUnkForRelease->AddRef();
+
+ *aMediumDst = stgmOut;
+
+ return true;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::EnumFormatEtc(DWORD dwDir, LPENUMFORMATETC* ppEnum) {
+ switch (dwDir) {
+ case DATADIR_GET:
+ m_enumFE->Clone(ppEnum);
+ break;
+ case DATADIR_SET:
+ // fall through
+ default:
+ *ppEnum = nullptr;
+ } // switch
+
+ if (nullptr == *ppEnum) return E_FAIL;
+
+ (*ppEnum)->Reset();
+ // Clone already AddRefed the result so don't addref it again.
+ return NOERROR;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::DAdvise(LPFORMATETC pFE, DWORD dwFlags,
+ LPADVISESINK pIAdviseSink, DWORD* pdwConn) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::DUnadvise(DWORD dwConn) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP nsDataObj::EnumDAdvise(LPENUMSTATDATA* ppEnum) {
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+// IDataObjectAsyncCapability methods
+STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
+ DWORD dwEffects) {
+ mIsInOperation = FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) {
+ *pfIsOpAsync = mIsAsyncMode;
+
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) {
+ *pfInAsyncOp = mIsInOperation;
+
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) {
+ mIsAsyncMode = fDoOpAsync;
+ return S_OK;
+}
+
+STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) {
+ mIsInOperation = TRUE;
+ return S_OK;
+}
+
+//
+// GetDIB
+//
+// Someone is asking for a bitmap. The data in the transferable will be a
+// straight imgIContainer, so just QI it.
+//
+HRESULT
+nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat,
+ STGMEDIUM& aSTG) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ if (NS_FAILED(
+ mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(),
+ getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper);
+ if (!image) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+
+ nsAutoString options(u"bpp=32;"_ns);
+ if (aFormat.cfFormat == CF_DIBV5) {
+ options.AppendLiteral("version=5");
+ } else {
+ options.AppendLiteral("version=3");
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
+ options, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv) || !inputStream) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
+ if (!encoder) {
+ return E_FAIL;
+ }
+
+ uint32_t size = 0;
+ rv = encoder->GetImageBufferUsed(&size);
+ if (NS_FAILED(rv) || size <= BFH_LENGTH) {
+ return E_FAIL;
+ }
+
+ char* src = nullptr;
+ rv = encoder->GetImageBuffer(&src);
+ if (NS_FAILED(rv) || !src) {
+ return E_FAIL;
+ }
+
+ // We don't want the file header.
+ src += BFH_LENGTH;
+ size -= BFH_LENGTH;
+
+ HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
+ if (!glob) {
+ return E_FAIL;
+ }
+
+ char* dst = (char*)::GlobalLock(glob);
+ ::CopyMemory(dst, src, size);
+ ::GlobalUnlock(glob);
+
+ aSTG.hGlobal = glob;
+ aSTG.tymed = TYMED_HGLOBAL;
+ return S_OK;
+}
+
+//
+// GetFileDescriptor
+//
+
+HRESULT
+nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode) {
+ HRESULT res = S_OK;
+
+ // How we handle this depends on if we're dealing with an internet
+ // shortcut, since those are done under the covers.
+ if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) {
+ if (aIsUnicode)
+ return GetFileDescriptor_IStreamW(aFE, aSTG);
+ else
+ return GetFileDescriptor_IStreamA(aFE, aSTG);
+ } else if (IsFlavourPresent(kURLMime)) {
+ if (aIsUnicode)
+ res = GetFileDescriptorInternetShortcutW(aFE, aSTG);
+ else
+ res = GetFileDescriptorInternetShortcutA(aFE, aSTG);
+ } else
+ NS_WARNING("Not yet implemented\n");
+
+ return res;
+} // GetFileDescriptor
+
+//
+HRESULT
+nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT res = S_OK;
+
+ // How we handle this depends on if we're dealing with an internet
+ // shortcut, since those are done under the covers.
+ if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime))
+ return GetFileContents_IStream(aFE, aSTG);
+ else if (IsFlavourPresent(kURLMime))
+ return GetFileContentsInternetShortcut(aFE, aSTG);
+ else
+ NS_WARNING("Not yet implemented\n");
+
+ return res;
+
+} // GetFileContents
+
+// Ensure that the supplied name doesn't have invalid characters.
+static void ValidateFilename(nsString& aFilename, bool isShortcut) {
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (NS_WARN_IF(!mimeService)) {
+ aFilename.Truncate();
+ return;
+ }
+
+ uint32_t flags = nsIMIMEService::VALIDATE_SANITIZE_ONLY;
+ if (isShortcut) {
+ flags |= nsIMIMEService::VALIDATE_ALLOW_INVALID_FILENAMES;
+ }
+
+ nsAutoString outFilename;
+ mimeService->ValidateFileNameForSaving(aFilename, EmptyCString(), flags,
+ outFilename);
+ aFilename = outFilename;
+}
+
+//
+// Given a unicode string, convert it to a valid local charset filename
+// and append the .url extension to be used for a shortcut file.
+// This ensures that we do not cut MBCS characters in the middle.
+//
+// It would seem that this is more functionality suited to being in nsIFile.
+//
+static bool CreateURLFilenameFromTextA(nsAutoString& aText, char* aFilename) {
+ if (aText.IsEmpty()) {
+ return false;
+ }
+ aText.AppendLiteral(".url");
+ ValidateFilename(aText, true);
+ if (aText.IsEmpty()) {
+ return false;
+ }
+
+ // ValidateFilename should already be checking the filename length, but do
+ // an extra check to verify for the local code page that the converted text
+ // doesn't go over MAX_PATH and just return false if it does.
+ char defaultChar = '_';
+ int currLen = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
+ aText.get(), -1, aFilename, MAX_PATH,
+ &defaultChar, nullptr);
+ return currLen != 0;
+}
+
+// Wide character version of CreateURLFilenameFromTextA
+static bool CreateURLFilenameFromTextW(nsAutoString& aText,
+ wchar_t* aFilename) {
+ if (aText.IsEmpty()) {
+ return false;
+ }
+ aText.AppendLiteral(".url");
+ ValidateFilename(aText, true);
+ if (aText.IsEmpty() || aText.Length() >= MAX_PATH) {
+ return false;
+ }
+
+ wcscpy(&aFilename[0], aText.get());
+ return true;
+}
+
+#define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
+
+static bool GetLocalizedString(const char* aName, nsAString& aString) {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::components::StringBundle::Service();
+ if (!stringService) return false;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ nsresult rv = stringService->CreateBundle(PAGEINFO_PROPERTIES,
+ getter_AddRefs(stringBundle));
+ if (NS_FAILED(rv)) return false;
+
+ rv = stringBundle->GetStringFromName(aName, aString);
+ return NS_SUCCEEDED(rv);
+}
+
+//
+// GetFileDescriptorInternetShortcut
+//
+// Create the special format for an internet shortcut and build up the data
+// structures the shell is expecting.
+//
+HRESULT
+nsDataObj ::GetFileDescriptorInternetShortcutA(FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ // get the title of the shortcut
+ nsAutoString title;
+ if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
+
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORA));
+ if (!fileGroupDescHandle) return E_OUTOFMEMORY;
+
+ LPFILEGROUPDESCRIPTORA fileGroupDescA =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORA>(
+ ::GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescA) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ // get a valid filename in the following order: 1) from the page title,
+ // 2) localized string for an untitled page, 3) just use "Untitled.url"
+ if (!CreateURLFilenameFromTextA(title, fileGroupDescA->fgd[0].cFileName)) {
+ nsAutoString untitled;
+ if (!GetLocalizedString("noPageTitle", untitled) ||
+ !CreateURLFilenameFromTextA(untitled,
+ fileGroupDescA->fgd[0].cFileName)) {
+ strcpy(fileGroupDescA->fgd[0].cFileName, "Untitled.url");
+ }
+ }
+
+ // one file in the file block
+ fileGroupDescA->cItems = 1;
+ fileGroupDescA->fgd[0].dwFlags = FD_LINKUI;
+
+ ::GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+} // GetFileDescriptorInternetShortcutA
+
+HRESULT
+nsDataObj ::GetFileDescriptorInternetShortcutW(FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ // get the title of the shortcut
+ nsAutoString title;
+ if (NS_FAILED(ExtractShortcutTitle(title))) return E_OUTOFMEMORY;
+
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ if (!fileGroupDescHandle) return E_OUTOFMEMORY;
+
+ LPFILEGROUPDESCRIPTORW fileGroupDescW =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORW>(
+ ::GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescW) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ // get a valid filename in the following order: 1) from the page title,
+ // 2) localized string for an untitled page, 3) just use "Untitled.url"
+ if (!CreateURLFilenameFromTextW(title, fileGroupDescW->fgd[0].cFileName)) {
+ nsAutoString untitled;
+ if (!GetLocalizedString("noPageTitle", untitled) ||
+ !CreateURLFilenameFromTextW(untitled,
+ fileGroupDescW->fgd[0].cFileName)) {
+ wcscpy(fileGroupDescW->fgd[0].cFileName, L"Untitled.url");
+ }
+ }
+
+ // one file in the file block
+ fileGroupDescW->cItems = 1;
+ fileGroupDescW->fgd[0].dwFlags = FD_LINKUI;
+
+ ::GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+} // GetFileDescriptorInternetShortcutW
+
+//
+// GetFileContentsInternetShortcut
+//
+// Create the special format for an internet shortcut and build up the data
+// structures the shell is expecting.
+//
+HRESULT
+nsDataObj ::GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ static const char* kShellIconPref = "browser.shell.shortcutFavicons";
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ nsCOMPtr<nsIURI> aUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(aUri), url);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ nsAutoCString asciiUrl;
+ rv = aUri->GetAsciiSpec(asciiUrl);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ RefPtr<AutoCloseEvent> event;
+
+ const char* shortcutFormatStr;
+ int totalLen;
+ nsCString asciiPath;
+ if (!Preferences::GetBool(kShellIconPref, true)) {
+ shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n";
+ const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s
+ totalLen = formatLen + asciiUrl.Length(); // don't include null character
+ } else {
+ nsCOMPtr<nsIFile> icoFile;
+
+ nsAutoString aUriHash;
+
+ event = new AutoCloseEvent();
+ if (!event->IsInited()) {
+ return E_FAIL;
+ }
+
+ RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event));
+ mozilla::widget::FaviconHelper::ObtainCachedIconFile(
+ aUri, aUriHash, mIOThread, true,
+ NS_NewRunnableFunction(
+ "FaviconHelper::RefreshDesktop", [e = std::move(e)] {
+ if (e->IsWaiting()) {
+ // Unblock IStream:::Read.
+ e->Signal();
+ } else {
+ // We could not wait until the favicon was available. We have
+ // to refresh to refect the favicon.
+ SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE,
+ SPI_SETNONCLIENTMETRICS, 0);
+ }
+ }));
+
+ rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ nsString path;
+ rv = icoFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+ if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) {
+ LossyCopyUTF16toASCII(path, asciiPath);
+ shortcutFormatStr =
+ "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
+ "IconIndex=0\r\n";
+ } else {
+ int len =
+ WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
+ path.Length(), nullptr, 0, nullptr, nullptr);
+ NS_ENSURE_TRUE(len > 0, E_FAIL);
+ asciiPath.SetLength(len);
+ WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()),
+ path.Length(), asciiPath.BeginWriting(), len, nullptr,
+ nullptr);
+ shortcutFormatStr =
+ "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n"
+ "[InternetShortcut.W]\r\nIconFile=%s\r\n";
+ }
+ const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
+ totalLen = formatLen + asciiUrl.Length() +
+ asciiPath.Length(); // we don't want a null character on the end
+ }
+
+ // create a global memory area and build up the file contents w/in it
+ nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen)));
+ if (!globalMem) return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get()));
+ if (!contents) {
+ return E_OUTOFMEMORY;
+ }
+
+ // NOTE: we intentionally use the Microsoft version of snprintf here because
+ // it does NOT null
+ // terminate strings which reach the maximum size of the buffer. Since we know
+ // that the formatted length here is totalLen, this call to _snprintf will
+ // format the string into the buffer without appending the null character.
+
+ if (!Preferences::GetBool(kShellIconPref, true)) {
+ _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get());
+ } else {
+ _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(),
+ asciiPath.get());
+ }
+
+ ::GlobalUnlock(globalMem.get());
+
+ if (aFE.tymed & TYMED_ISTREAM) {
+ if (!mIsInOperation) {
+ // The drop target didn't initiate an async operation.
+ // We can't block CMemStream::Read.
+ event = nullptr;
+ }
+ RefPtr<IStream> stream =
+ new CMemStream(globalMem.disown(), totalLen, event.forget());
+ stream.forget(&aSTG.pstm);
+ aSTG.tymed = TYMED_ISTREAM;
+ } else {
+ if (event && event->IsInited()) {
+ event->Signal(); // We can't block reading the global memory
+ }
+ aSTG.hGlobal = globalMem.disown();
+ aSTG.tymed = TYMED_HGLOBAL;
+ }
+
+ return S_OK;
+} // GetFileContentsInternetShortcut
+
+// check if specified flavour is present in the transferable
+bool nsDataObj ::IsFlavourPresent(const char* inFlavour) {
+ bool retval = false;
+ NS_ENSURE_TRUE(mTransferable, false);
+
+ // get the list of flavors available in the transferable
+ nsTArray<nsCString> flavors;
+ nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // try to find requested flavour
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ if (flavors[i].Equals(inFlavour)) {
+ retval = true; // found it!
+ break;
+ }
+ } // for each flavor
+
+ return retval;
+}
+
+HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT res = S_OK;
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+ HGLOBAL hGlobalMemory = nullptr;
+ hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
+ if (hGlobalMemory) {
+ DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory);
+ // The PreferredDropEffect clipboard format is only registered if a
+ // drag/drop of an image happens from Mozilla to the desktop. We want its
+ // value to be DROPEFFECT_MOVE in that case so that the file is moved from
+ // the temporary location, not copied. This value should, ideally, be set on
+ // the data object via SetData() but our IDataObject implementation doesn't
+ // implement SetData. It adds data to the data object lazily only when the
+ // drop target asks for it.
+ *pdw = (DWORD)DROPEFFECT_MOVE;
+ GlobalUnlock(hGlobalMemory);
+ } else {
+ res = E_OUTOFMEMORY;
+ }
+ aSTG.hGlobal = hGlobalMemory;
+ return res;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
+ STGMEDIUM& aSTG) {
+ void* data = nullptr;
+
+ const nsPromiseFlatCString& flavorStr = PromiseFlatCString(aDataFlavor);
+
+ // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsresult rv = mTransferable->GetTransferData(
+ flavorStr.get(), getter_AddRefs(genericDataWrapper));
+ if (NS_FAILED(rv) || !genericDataWrapper) {
+ return E_FAIL;
+ }
+
+ uint32_t len;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(flavorStr.get()), genericDataWrapper, &data, &len);
+ if (!data) return E_FAIL;
+
+ HGLOBAL hGlobalMemory = nullptr;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ // We play games under the hood and advertise flavors that we know we
+ // can support, only they require a bit of conversion or munging of the data.
+ // Do that here.
+ //
+ // The transferable gives us data that is null-terminated, but this isn't
+ // reflected in the |len| parameter. Windoze apps expect this null to be there
+ // so bump our data buffer by the appropriate size to account for the null
+ // (one char for CF_TEXT, one char16_t for CF_UNICODETEXT).
+ DWORD allocLen = (DWORD)len;
+ if (aFE.cfFormat == CF_TEXT) {
+ // Someone is asking for text/plain; convert the unicode (assuming it's
+ // present) to text with the correct platform encoding.
+ size_t bufferSize = sizeof(char) * (len + 2);
+ char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
+ int32_t plainTextLen =
+ WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
+ plainTextData, bufferSize, NULL, NULL);
+ // replace the unicode data with our plaintext data. Recall that
+ // |plainTextLen| doesn't include the null in the length.
+ free(data);
+ if (plainTextLen) {
+ data = plainTextData;
+ allocLen = plainTextLen;
+ } else {
+ free(plainTextData);
+ NS_WARNING("Oh no, couldn't convert unicode to plain text");
+ return S_OK;
+ }
+ } else if (aFE.cfFormat == nsClipboard::GetHtmlClipboardFormat()) {
+ // Someone is asking for win32's HTML flavor. Convert our html fragment
+ // from unicode to UTF-8 then put it into a format specified by msft.
+ NS_ConvertUTF16toUTF8 converter(reinterpret_cast<char16_t*>(data));
+ char* utf8HTML = nullptr;
+ nsresult rv =
+ BuildPlatformHTML(converter.get(), &utf8HTML); // null terminates
+
+ free(data);
+ if (NS_SUCCEEDED(rv) && utf8HTML) {
+ // replace the unicode data with our HTML data. Don't forget the null.
+ data = utf8HTML;
+ allocLen = strlen(utf8HTML) + sizeof(char);
+ } else {
+ NS_WARNING("Oh no, couldn't convert to HTML");
+ return S_OK;
+ }
+ } else if (aFE.cfFormat != nsClipboard::GetCustomClipboardFormat()) {
+ // we assume that any data that isn't caught above is unicode. This may
+ // be an erroneous assumption, but is true so far.
+ allocLen += sizeof(char16_t);
+ }
+
+ hGlobalMemory = (HGLOBAL)GlobalAlloc(GMEM_MOVEABLE, allocLen);
+
+ // Copy text to Global Memory Area
+ if (hGlobalMemory) {
+ char* dest = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
+ char* source = reinterpret_cast<char*>(data);
+ memcpy(dest, source, allocLen); // copies the null as well
+ GlobalUnlock(hGlobalMemory);
+ }
+ aSTG.hGlobal = hGlobalMemory;
+
+ // Now, delete the memory that was created by CreateDataFromPrimitive (or our
+ // text/plain data)
+ free(data);
+
+ return S_OK;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ uint32_t dfInx = 0;
+ ULONG count;
+ FORMATETC fe;
+ m_enumFE->Reset();
+ while (NOERROR == m_enumFE->Next(1, &fe, &count) &&
+ dfInx < mDataFlavors.Length()) {
+ if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime))
+ return DropImage(aFE, aSTG);
+ if (mDataFlavors[dfInx].EqualsLiteral(kFileMime))
+ return DropFile(aFE, aSTG);
+ if (mDataFlavors[dfInx].EqualsLiteral(kFilePromiseMime))
+ return DropTempFile(aFE, aSTG);
+ dfInx++;
+ }
+ return E_FAIL;
+}
+
+HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ if (NS_FAILED(mTransferable->GetTransferData(
+ kFileMime, getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper));
+ if (!file) return E_FAIL;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ nsAutoString path;
+ rv = file->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ uint32_t allocLen = path.Length() + 2;
+ HGLOBAL hGlobalMemory = nullptr;
+ char16_t* dest;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure
+ pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure
+ dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t));
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ if (!mCachedTempFile) {
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ if (NS_FAILED(mTransferable->GetTransferData(
+ kNativeImageMime, getter_AddRefs(genericDataWrapper)))) {
+ return E_FAIL;
+ }
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
+ if (!image) return E_FAIL;
+
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP),
+ u"bpp=32;version=3"_ns,
+ getter_AddRefs(inputStream));
+ if (NS_FAILED(rv) || !inputStream) {
+ return E_FAIL;
+ }
+
+ nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream);
+ if (!encoder) {
+ return E_FAIL;
+ }
+
+ uint32_t size = 0;
+ rv = encoder->GetImageBufferUsed(&size);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ char* src = nullptr;
+ rv = encoder->GetImageBuffer(&src);
+ if (NS_FAILED(rv) || !src) {
+ return E_FAIL;
+ }
+
+ // Save the bitmap to a temporary location.
+ nsCOMPtr<nsIFile> dropFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
+ if (!dropFile) {
+ return E_FAIL;
+ }
+
+ // Filename must be random so as not to confuse apps like
+ // Photoshop which handle multiple drags into a single window.
+ char buf[13];
+ nsCString filename;
+ NS_MakeRandomString(buf, 8);
+ memcpy(buf + 8, ".bmp", 5);
+ filename.Append(nsDependentCString(buf, 12));
+ dropFile->AppendNative(filename);
+ rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ // Cache the temp file so we can delete it later and so
+ // it doesn't get recreated over and over on multiple calls
+ // which does occur from windows shell.
+ dropFile->Clone(getter_AddRefs(mCachedTempFile));
+
+ // Write the data to disk.
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
+ if (NS_FAILED(rv)) {
+ return E_FAIL;
+ }
+
+ uint32_t written = 0;
+ rv = outStream->Write(src, size, &written);
+ if (NS_FAILED(rv) || written != size) {
+ return E_FAIL;
+ }
+
+ outStream->Close();
+ }
+
+ // Pass the file name back to the drop target so that it can access the file.
+ nsAutoString path;
+ rv = mCachedTempFile->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ // Two null characters are needed to terminate the file name list.
+ HGLOBAL hGlobalMemory = nullptr;
+
+ uint32_t allocLen = path.Length() + 2;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure.
+ pDropFile->pFiles =
+ sizeof(DROPFILES); // Offset to start of file name char array.
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure.
+ char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(),
+ (allocLen - 1) *
+ sizeof(char16_t)); // Copies the null character in path as well.
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ nsresult rv;
+ if (!mCachedTempFile) {
+ // Tempfile will need a temporary location.
+ nsCOMPtr<nsIFile> dropFile;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile));
+ if (!dropFile) return E_FAIL;
+
+ // Filename must be random
+ nsCString filename;
+ nsAutoString wideFileName;
+ nsCOMPtr<nsIURI> sourceURI;
+ HRESULT res;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) return res;
+ NS_CopyUnicodeToNative(wideFileName, filename);
+
+ dropFile->AppendNative(filename);
+ rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ // Cache the temp file so we can delete it later and so
+ // it doesn't get recreated over and over on multiple calls
+ // which does occur from windows shell.
+ dropFile->Clone(getter_AddRefs(mCachedTempFile));
+
+ // Write the data to disk.
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ IStream* pStream = nullptr;
+ nsDataObj::CreateStream(&pStream);
+ NS_ENSURE_TRUE(pStream, E_FAIL);
+
+ char buffer[512];
+ ULONG readCount = 0;
+ uint32_t writeCount = 0;
+ while (1) {
+ HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount);
+ if (FAILED(hres)) return E_FAIL;
+ if (readCount == 0) break;
+ rv = outStream->Write(buffer, readCount, &writeCount);
+ if (NS_FAILED(rv)) return E_FAIL;
+ }
+ outStream->Close();
+ pStream->Release();
+ }
+
+ // Pass the file name back to the drop target so that it can access the file.
+ nsAutoString path;
+ rv = mCachedTempFile->GetPath(path);
+ if (NS_FAILED(rv)) return E_FAIL;
+
+ uint32_t allocLen = path.Length() + 2;
+
+ // Two null characters are needed to terminate the file name list.
+ HGLOBAL hGlobalMemory = nullptr;
+
+ aSTG.tymed = TYMED_HGLOBAL;
+ aSTG.pUnkForRelease = nullptr;
+
+ hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE,
+ sizeof(DROPFILES) + allocLen * sizeof(char16_t));
+ if (!hGlobalMemory) return E_FAIL;
+
+ DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory);
+
+ // First, populate the drop file structure.
+ pDropFile->pFiles =
+ sizeof(DROPFILES); // Offset to start of file name char array.
+ pDropFile->fNC = 0;
+ pDropFile->pt.x = 0;
+ pDropFile->pt.y = 0;
+ pDropFile->fWide = TRUE;
+
+ // Copy the filename right after the DROPFILES structure.
+ char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles);
+ memcpy(dest, path.get(),
+ (allocLen - 1) *
+ sizeof(char16_t)); // Copies the null character in path as well.
+
+ // Two null characters are needed at the end of the file name.
+ // Lookup the CF_HDROP shell clipboard format for more info.
+ // Add the second null character right after the first one.
+ dest[allocLen - 1] = L'\0';
+
+ GlobalUnlock(hGlobalMemory);
+
+ aSTG.hGlobal = hGlobalMemory;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------
+// Registers the DataFlavor/FE pair.
+//-----------------------------------------------------
+void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) {
+ // These two lists are the mapping to and from data flavors and FEs.
+ // Later, OLE will tell us it needs a certain type of FORMATETC (text,
+ // unicode, etc) unicode, etc), so we will look up the data flavor that
+ // corresponds to the FE and then ask the transferable for that type of data.
+ mDataFlavors.AppendElement(aDataFlavor);
+ m_enumFE->AddFormatEtc(aFE);
+}
+
+//-----------------------------------------------------
+// Sets the transferable object
+//-----------------------------------------------------
+void nsDataObj::SetTransferable(nsITransferable* aTransferable) {
+ NS_IF_RELEASE(mTransferable);
+
+ mTransferable = aTransferable;
+ if (nullptr == mTransferable) {
+ return;
+ }
+
+ NS_ADDREF(mTransferable);
+
+ return;
+}
+
+//
+// ExtractURL
+//
+// Roots around in the transferable for the appropriate flavor that indicates
+// a url and pulls out the url portion of the data. Used mostly for creating
+// internet shortcuts on the desktop. The url flavor is of the format:
+//
+// <url> <linefeed> <page title>
+//
+nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) {
+ NS_ASSERTION(mTransferable, "We don't have a good transferable");
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> genericURL;
+ if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+ outURL = url;
+
+ // find the first linefeed in the data, that's where the url ends. trunc
+ // the result string at that point.
+ int32_t lineIndex = outURL.FindChar('\n');
+ NS_ASSERTION(lineIndex > 0,
+ "Format for url flavor is <url> <linefeed> <page title>");
+ if (lineIndex > 0) {
+ outURL.Truncate(lineIndex);
+ rv = NS_OK;
+ }
+ }
+ } else if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLDataMime, getter_AddRefs(genericURL))) ||
+ NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLPrivateMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+ outURL = url;
+
+ rv = NS_OK;
+ }
+
+ } // if found flavor
+
+ return rv;
+
+} // ExtractShortcutURL
+
+//
+// ExtractShortcutTitle
+//
+// Roots around in the transferable for the appropriate flavor that indicates
+// a url and pulls out the title portion of the data. Used mostly for creating
+// internet shortcuts on the desktop. The url flavor is of the format:
+//
+// <url> <linefeed> <page title>
+//
+nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) {
+ NS_ASSERTION(mTransferable, "We'd don't have a good transferable");
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> genericURL;
+ if (NS_SUCCEEDED(mTransferable->GetTransferData(
+ kURLMime, getter_AddRefs(genericURL)))) {
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+ if (urlObject) {
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ // find the first linefeed in the data, that's where the url ends. we want
+ // everything after that linefeed. FindChar() returns -1 if we can't find
+ int32_t lineIndex = url.FindChar('\n');
+ NS_ASSERTION(lineIndex != -1,
+ "Format for url flavor is <url> <linefeed> <page title>");
+ if (lineIndex != -1) {
+ url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1));
+ rv = NS_OK;
+ }
+ }
+ } // if found flavor
+
+ return rv;
+
+} // ExtractShortcutTitle
+
+//
+// BuildPlatformHTML
+//
+// Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite
+// header information on it. This will null-terminate |outPlatformHTML|. See
+// https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format
+// for details.
+//
+// We assume that |inOurHTML| is already a fragment (ie, doesn't have <HTML>
+// or <BODY> tags). We'll wrap the fragment with them to make other apps
+// happy.
+//
+nsresult nsDataObj ::BuildPlatformHTML(const char* inOurHTML,
+ char** outPlatformHTML) {
+ *outPlatformHTML = nullptr;
+ nsDependentCString inHTMLString(inOurHTML);
+
+ // Do we already have mSourceURL from a drag?
+ if (mSourceURL.IsEmpty()) {
+ nsAutoString url;
+ ExtractShortcutURL(url);
+
+ AppendUTF16toUTF8(url, mSourceURL);
+ }
+
+ constexpr auto kStartHTMLPrefix = "Version:0.9\r\nStartHTML:"_ns;
+ constexpr auto kEndHTMLPrefix = "\r\nEndHTML:"_ns;
+ constexpr auto kStartFragPrefix = "\r\nStartFragment:"_ns;
+ constexpr auto kEndFragPrefix = "\r\nEndFragment:"_ns;
+ constexpr auto kStartSourceURLPrefix = "\r\nSourceURL:"_ns;
+ constexpr auto kEndFragTrailer = "\r\n"_ns;
+
+ // The CF_HTML's size is embedded in the fragment, in such a way that the
+ // number of digits in the size is part of the size itself. While it _is_
+ // technically possible to compute the necessary size of the size-field
+ // precisely -- by trial and error, if nothing else -- it's simpler just to
+ // pick a rough but generous estimate and zero-pad it. (Zero-padding is
+ // explicitly permitted by the format definition.)
+ //
+ // Originally, in 2001, the "rough but generous estimate" was 8 digits. While
+ // a maximum size of (10**9 - 1) bytes probably would have covered all
+ // possible use-cases at the time, it's somewhat more likely to overflow
+ // nowadays. Nonetheless, for the sake of backwards compatibility with any
+ // misbehaving consumers of our existing CF_HTML output, we retain exactly
+ // that padding for (most) fragments where it suffices. (No such misbehaving
+ // consumers are actually known, so this is arguably paranoia.)
+ //
+ // It is now 2022. A padding size of 16 will cover up to about 8.8 petabytes,
+ // which should be enough for at least the next few years or so.
+ const size_t numberLength = inHTMLString.Length() < 9999'0000 ? 8 : 16;
+
+ const size_t sourceURLLength = mSourceURL.Length();
+
+ const size_t fixedHeaderLen =
+ kStartHTMLPrefix.Length() + kEndHTMLPrefix.Length() +
+ kStartFragPrefix.Length() + kEndFragPrefix.Length() +
+ kEndFragTrailer.Length() + (4 * numberLength);
+
+ const size_t totalHeaderLen =
+ fixedHeaderLen + (sourceURLLength > 0
+ ? kStartSourceURLPrefix.Length() + sourceURLLength
+ : 0);
+
+ constexpr auto kHeaderString = "<html><body>\r\n<!--StartFragment-->"_ns;
+ constexpr auto kTrailingString =
+ "<!--EndFragment-->\r\n"
+ "</body>\r\n"
+ "</html>"_ns;
+
+ // calculate the offsets
+ size_t startHTMLOffset = totalHeaderLen;
+ size_t startFragOffset = startHTMLOffset + kHeaderString.Length();
+
+ size_t endFragOffset = startFragOffset + inHTMLString.Length();
+ size_t endHTMLOffset = endFragOffset + kTrailingString.Length();
+
+ // now build the final version
+ nsCString clipboardString;
+ clipboardString.SetCapacity(endHTMLOffset);
+
+ const int numberLengthInt = static_cast<int>(numberLength);
+ clipboardString.Append(kStartHTMLPrefix);
+ clipboardString.AppendPrintf("%0*zu", numberLengthInt, startHTMLOffset);
+
+ clipboardString.Append(kEndHTMLPrefix);
+ clipboardString.AppendPrintf("%0*zu", numberLengthInt, endHTMLOffset);
+
+ clipboardString.Append(kStartFragPrefix);
+ clipboardString.AppendPrintf("%0*zu", numberLengthInt, startFragOffset);
+
+ clipboardString.Append(kEndFragPrefix);
+ clipboardString.AppendPrintf("%0*zu", numberLengthInt, endFragOffset);
+
+ if (sourceURLLength > 0) {
+ clipboardString.Append(kStartSourceURLPrefix);
+ clipboardString.Append(mSourceURL);
+ }
+
+ clipboardString.Append(kEndFragTrailer);
+
+ // Assert that the positional values were correct as we pass by their
+ // corresponding positions.
+ MOZ_ASSERT(clipboardString.Length() == startHTMLOffset);
+ clipboardString.Append(kHeaderString);
+ MOZ_ASSERT(clipboardString.Length() == startFragOffset);
+ clipboardString.Append(inHTMLString);
+ MOZ_ASSERT(clipboardString.Length() == endFragOffset);
+ clipboardString.Append(kTrailingString);
+ MOZ_ASSERT(clipboardString.Length() == endHTMLOffset);
+
+ *outPlatformHTML = ToNewCString(clipboardString, mozilla::fallible);
+ if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+HRESULT
+nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode) {
+ HRESULT res = S_OK;
+ if (IsFlavourPresent(kURLMime)) {
+ if (aIsUnicode)
+ res = ExtractUniformResourceLocatorW(aFE, aSTG);
+ else
+ res = ExtractUniformResourceLocatorA(aFE, aSTG);
+ } else
+ NS_WARNING("Not yet implemented\n");
+ return res;
+}
+
+HRESULT
+nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT result = S_OK;
+
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ NS_LossyConvertUTF16toASCII asciiUrl(url);
+ const int totalLen = asciiUrl.Length() + 1;
+ HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
+ if (!hGlobalMemory) return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory));
+ if (!contents) {
+ GlobalFree(hGlobalMemory);
+ return E_OUTOFMEMORY;
+ }
+
+ strcpy(contents, asciiUrl.get());
+ GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return result;
+}
+
+HRESULT
+nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HRESULT result = S_OK;
+
+ nsAutoString url;
+ if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY;
+
+ const int totalLen = (url.Length() + 1) * sizeof(char16_t);
+ HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen);
+ if (!hGlobalMemory) return E_OUTOFMEMORY;
+
+ wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory));
+ if (!contents) {
+ GlobalFree(hGlobalMemory);
+ return E_OUTOFMEMORY;
+ }
+
+ wcscpy(contents, url.get());
+ GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return result;
+}
+
+// Gets the filename from the kFilePromiseURLMime flavour
+HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI,
+ nsAString& aFilename) {
+ *aSourceURI = nullptr;
+
+ NS_ENSURE_TRUE(mTransferable, E_FAIL);
+
+ // get the URI from the kFilePromiseURLMime flavor
+ nsCOMPtr<nsISupports> urlPrimitive;
+ nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(urlPrimitive));
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
+ NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL);
+
+ nsAutoString srcUri;
+ srcUrlPrimitive->GetData(srcUri);
+ if (srcUri.IsEmpty()) return E_FAIL;
+ nsCOMPtr<nsIURI> sourceURI;
+ NS_NewURI(getter_AddRefs(sourceURI), srcUri);
+
+ nsAutoString srcFileName;
+ nsCOMPtr<nsISupports> fileNamePrimitive;
+ Unused << mTransferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(fileNamePrimitive));
+ nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
+ do_QueryInterface(fileNamePrimitive);
+ if (srcFileNamePrimitive) {
+ srcFileNamePrimitive->GetData(srcFileName);
+ } else {
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ if (!sourceURL) return E_FAIL;
+
+ nsAutoCString urlFileName;
+ sourceURL->GetFileName(urlFileName);
+ NS_UnescapeURL(urlFileName);
+ CopyUTF8toUTF16(urlFileName, srcFileName);
+ }
+
+ // make the name safe for the filesystem
+ ValidateFilename(srcFileName, false);
+ if (srcFileName.IsEmpty()) return E_FAIL;
+
+ sourceURI.swap(*aSourceURI);
+ aFilename = srcFileName;
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
+
+ LPFILEGROUPDESCRIPTORA fileGroupDescA =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORA>(GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescA) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ nsAutoString wideFileName;
+ HRESULT res;
+ nsCOMPtr<nsIURI> sourceURI;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) {
+ ::GlobalFree(fileGroupDescHandle);
+ return res;
+ }
+
+ nsAutoCString nativeFileName;
+ NS_CopyUnicodeToNative(wideFileName, nativeFileName);
+
+ strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), MAX_PATH - 1);
+ fileGroupDescA->fgd[0].cFileName[MAX_PATH - 1] = '\0';
+
+ // one file in the file block
+ fileGroupDescA->cItems = 1;
+ fileGroupDescA->fgd[0].dwFlags = FD_PROGRESSUI;
+
+ GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ HGLOBAL fileGroupDescHandle =
+ ::GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, sizeof(FILEGROUPDESCRIPTORW));
+ NS_ENSURE_TRUE(fileGroupDescHandle, E_OUTOFMEMORY);
+
+ LPFILEGROUPDESCRIPTORW fileGroupDescW =
+ reinterpret_cast<LPFILEGROUPDESCRIPTORW>(GlobalLock(fileGroupDescHandle));
+ if (!fileGroupDescW) {
+ ::GlobalFree(fileGroupDescHandle);
+ return E_OUTOFMEMORY;
+ }
+
+ nsAutoString wideFileName;
+ HRESULT res;
+ nsCOMPtr<nsIURI> sourceURI;
+ res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName);
+ if (FAILED(res)) {
+ ::GlobalFree(fileGroupDescHandle);
+ return res;
+ }
+
+ wcsncpy(fileGroupDescW->fgd[0].cFileName, wideFileName.get(), MAX_PATH - 1);
+ fileGroupDescW->fgd[0].cFileName[MAX_PATH - 1] = '\0';
+ // one file in the file block
+ fileGroupDescW->cItems = 1;
+ fileGroupDescW->fgd[0].dwFlags = FD_PROGRESSUI;
+
+ GlobalUnlock(fileGroupDescHandle);
+ aSTG.hGlobal = fileGroupDescHandle;
+ aSTG.tymed = TYMED_HGLOBAL;
+
+ return S_OK;
+}
+
+HRESULT nsDataObj::GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG) {
+ IStream* pStream = nullptr;
+
+ nsDataObj::CreateStream(&pStream);
+ NS_ENSURE_TRUE(pStream, E_FAIL);
+
+ aSTG.tymed = TYMED_ISTREAM;
+ aSTG.pstm = pStream;
+ aSTG.pUnkForRelease = nullptr;
+
+ return S_OK;
+}
diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h
new file mode 100644
index 0000000000..17683e371a
--- /dev/null
+++ b/widget/windows/nsDataObj.h
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _NSDATAOBJ_H_
+#define _NSDATAOBJ_H_
+
+#include <oleidl.h>
+#include <shldisp.h>
+
+#include "mozilla/glue/WinUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsCOMArray.h"
+#include "nsITimer.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWindowsHelpers.h"
+
+class nsICookieJarSettings;
+class nsIPrincipal;
+class nsIReferrerInfo;
+class nsIThread;
+class nsITransferable;
+class CEnumFormatEtc;
+
+/*
+ * This ole registered class is used to facilitate drag-drop of objects which
+ * can be adapted by an object derived from CfDragDrop. The CfDragDrop is
+ * associated with instances via SetDragDrop().
+ */
+class nsDataObj : public IDataObject, public IDataObjectAsyncCapability {
+ RefPtr<mozilla::LazyIdleThread> mIOThread;
+
+ public: // construction, destruction
+ explicit nsDataObj(nsIURI* uri = nullptr);
+
+ protected:
+ virtual ~nsDataObj();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP QueryInterface(REFIID, void**) override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // support for clipboard
+ virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE);
+ void SetTransferable(nsITransferable* aTransferable);
+
+ public: // IDataObject methods - these are general comments. see CfDragDrop
+ // for overriding behavior
+ // Store data in pSTM according to the format specified by pFE, if the
+ // format is supported (supported formats are specified in CfDragDrop::
+ // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It
+ // is the callers responsibility to free pSTM if NOERROR is returned.
+ STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override;
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData(LPFORMATETC pFE) override;
+
+ // Set pCanonFE to the canonical format of pFE if one exists and return
+ // NOERROR, otherwise return DATA_S_SAMEFORMATETC. A canonical format
+ // implies an identical rendering.
+ STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC pFE,
+ LPFORMATETC pCanonFE) final;
+
+ // Set this objects data according to the format specified by pFE and
+ // the storage medium specified by pSTM and return NOERROR, if the format
+ // is supported. If release is TRUE this object must release the storage
+ // associated with pSTM.
+ STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
+ BOOL release) override;
+
+ // Set ppEnum to an IEnumFORMATETC object which will iterate all of the
+ // data formats that this object supports. direction is either DATADIR_GET
+ // or DATADIR_SET.
+ STDMETHODIMP EnumFormatEtc(DWORD direction, LPENUMFORMATETC* ppEnum) final;
+
+ // Set up an advisory connection to this object based on the format specified
+ // by pFE, flags, and the pAdvise. Set pConn to the established advise
+ // connection.
+ STDMETHODIMP DAdvise(LPFORMATETC pFE, DWORD flags, LPADVISESINK pAdvise,
+ DWORD* pConn) final;
+
+ // Turn off advising of a previous call to DAdvise which set pConn.
+ STDMETHODIMP DUnadvise(DWORD pConn) final;
+
+ // Set ppEnum to an IEnumSTATDATA object which will iterate over the
+ // existing objects which have established advisory connections to this
+ // object.
+ STDMETHODIMP EnumDAdvise(LPENUMSTATDATA* ppEnum) final;
+
+ // IDataObjectAsyncCapability methods
+ STDMETHODIMP EndOperation(HRESULT hResult, IBindCtx* pbcReserved,
+ DWORD dwEffects) final;
+ STDMETHODIMP GetAsyncMode(BOOL* pfIsOpAsync) final;
+ STDMETHODIMP InOperation(BOOL* pfInAsyncOp) final;
+ STDMETHODIMP SetAsyncMode(BOOL fDoOpAsync) final;
+ STDMETHODIMP StartOperation(IBindCtx* pbcReserved) final;
+
+ private: // other methods
+ // Gets the filename from the kFilePromiseURLMime flavour
+ HRESULT GetDownloadDetails(nsIURI** aSourceURI, nsAString& aFilename);
+
+ // help determine the kind of drag
+ bool IsFlavourPresent(const char* inFlavour);
+
+ protected:
+ HRESULT GetFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetText(const nsACString& aDF, FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ private:
+ HRESULT GetDib(const nsACString& inFlavor, FORMATETC&, STGMEDIUM& aSTG);
+
+ HRESULT DropImage(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT DropFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ HRESULT GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG,
+ bool aIsUnicode);
+ HRESULT ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode);
+
+ protected:
+ HRESULT GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ private:
+ HRESULT GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ // Provide the structures needed for an internet shortcut by the shell
+ HRESULT GetFileDescriptorInternetShortcutA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptorInternetShortcutW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ // IStream implementation
+ HRESULT GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG);
+ HRESULT GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG);
+
+ nsresult ExtractShortcutURL(nsString& outURL);
+ nsresult ExtractShortcutTitle(nsString& outTitle);
+
+ // munge our HTML data to win32's CF_HTML spec. Will null terminate
+ nsresult BuildPlatformHTML(const char* inOurHTML, char** outPlatformHTML);
+
+ // Used for the SourceURL part of CF_HTML
+ nsCString mSourceURL;
+
+ protected:
+ BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const;
+
+ ULONG m_cRef; // the reference count
+
+ private:
+ nsTArray<nsCString> mDataFlavors;
+
+ nsITransferable* mTransferable; // nsDataObj owns and ref counts
+ // nsITransferable, the nsITransferable does
+ // know anything about the nsDataObj
+
+ protected:
+ CEnumFormatEtc* m_enumFE; // Ownership Rules:
+ // nsDataObj owns and ref counts CEnumFormatEtc,
+
+ private:
+ nsCOMPtr<nsIFile> mCachedTempFile;
+ RefPtr<nsDataObj> mKeepAlive;
+
+ BOOL mIsAsyncMode;
+ BOOL mIsInOperation;
+ ///////////////////////////////////////////////////////////////////////////////
+ // CStream class implementation
+ // this class is used in Drag and drop with download sample
+ // called from IDataObject::GetData
+ class CStreamBase : public IStream {
+ // IStream
+ STDMETHODIMP Clone(IStream** ppStream) final;
+ STDMETHODIMP Commit(DWORD dwFrags) final;
+ STDMETHODIMP CopyTo(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten) final;
+ STDMETHODIMP LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
+ DWORD dwFlags) final;
+ STDMETHODIMP Revert(void) final;
+ STDMETHODIMP Seek(LARGE_INTEGER nMove, DWORD dwOrigin,
+ ULARGE_INTEGER* nNewPos) final;
+ STDMETHODIMP SetSize(ULARGE_INTEGER nNewSize) final;
+ STDMETHODIMP UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,
+ DWORD dwFlags) final;
+ STDMETHODIMP Write(const void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+
+ protected:
+ uint32_t mStreamRead;
+
+ CStreamBase();
+ virtual ~CStreamBase();
+ };
+
+ class CStream final : public CStreamBase, public nsIStreamListener {
+ nsCOMPtr<nsIChannel> mChannel;
+ FallibleTArray<uint8_t> mChannelData;
+ nsresult mChannelResult;
+ bool mChannelRead;
+
+ virtual ~CStream();
+ nsresult WaitForCompletion();
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;
+
+ // IStream
+ STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+ STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;
+
+ public:
+ CStream();
+ nsresult Init(nsIURI* pSourceURI, nsContentPolicyType aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal,
+ nsICookieJarSettings* aCookieJarSettings,
+ nsIReferrerInfo* aReferrerInfo);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ };
+
+ HRESULT CreateStream(IStream** outStream);
+
+ // This class must be thread-safe.
+ class AutoCloseEvent final {
+ const nsAutoHandle mEvent;
+
+ AutoCloseEvent(const AutoCloseEvent&) = delete;
+ void operator=(const AutoCloseEvent&) = delete;
+ ~AutoCloseEvent() = default;
+
+ public:
+ AutoCloseEvent();
+ bool IsInited() const;
+ void Signal() const;
+ DWORD Wait(DWORD aMillisec) const;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoCloseEvent)
+ };
+
+ // This class must be thread-safe.
+ class AutoSetEvent final {
+ const RefPtr<AutoCloseEvent> mEvent;
+
+ AutoSetEvent(const AutoSetEvent&) = delete;
+ void operator=(const AutoSetEvent&) = delete;
+ ~AutoSetEvent();
+
+ public:
+ explicit AutoSetEvent(mozilla::NotNull<AutoCloseEvent*> aEvent);
+ void Signal() const;
+ bool IsWaiting() const;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoSetEvent)
+ };
+
+ // This class must be thread-safe.
+ class CMemStream final : public CStreamBase {
+ static mozilla::glue::Win32SRWLock mLock;
+ const nsAutoGlobalMem mGlobalMem;
+ const RefPtr<AutoCloseEvent> mEvent;
+ const uint32_t mTotalLength;
+ RefPtr<IUnknown> mMarshaler;
+
+ virtual ~CMemStream();
+ void WaitForCompletion();
+
+ // IStream
+ STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead,
+ ULONG* nBytesRead) final;
+ STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final;
+
+ public:
+ CMemStream(nsHGLOBAL aGlobalMem, uint32_t mTotalLength,
+ already_AddRefed<AutoCloseEvent> aEvent);
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final;
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(CMemStream, final)
+ };
+
+ private:
+ // Drag and drop helper data for implementing drag and drop image support
+ typedef struct {
+ FORMATETC fe;
+ STGMEDIUM stgm;
+ } DATAENTRY, *LPDATAENTRY;
+
+ nsTArray<LPDATAENTRY> mDataEntryList;
+ nsCOMPtr<nsITimer> mTimer;
+
+ bool LookupArbitraryFormat(FORMATETC* aFormat, LPDATAENTRY* aDataEntry,
+ BOOL aAddorUpdate);
+ bool CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc,
+ LPFORMATETC aFormat, BOOL aSetData);
+};
+
+#endif // _NSDATAOBJ_H_
diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp
new file mode 100644
index 0000000000..8750563602
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <shlobj.h>
+
+#include "nsDataObjCollection.h"
+#include "nsClipboard.h"
+#include "IEnumFE.h"
+
+#include <ole2.h>
+
+// {25589C3E-1FAC-47b9-BF43-CAEA89B79533}
+const IID IID_IDataObjCollection = {
+ 0x25589c3e,
+ 0x1fac,
+ 0x47b9,
+ {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}};
+
+/*
+ * Class nsDataObjCollection
+ */
+
+nsDataObjCollection::nsDataObjCollection() {}
+
+nsDataObjCollection::~nsDataObjCollection() { mDataObjects.Clear(); }
+
+// IUnknown interface methods - see iunknown.h for documentation
+STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) {
+ *ppv = static_cast<IDataObject*>(this);
+ AddRef();
+ return NOERROR;
+ }
+
+ if (IID_IDataObjCollection == riid) {
+ *ppv = static_cast<nsIDataObjCollection*>(this);
+ AddRef();
+ return NOERROR;
+ }
+ // offer to operate asynchronously (required by nsDragService)
+ if (IID_IDataObjectAsyncCapability == riid) {
+ *ppv = static_cast<IDataObjectAsyncCapability*>(this);
+ AddRef();
+ return NOERROR;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() { return ++m_cRef; }
+
+STDMETHODIMP_(ULONG) nsDataObjCollection::Release() {
+ if (0 != --m_cRef) return m_cRef;
+
+ delete this;
+
+ return 0;
+}
+
+// IDataObject methods
+STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ static CLIPFORMAT fileDescriptorFlavorA =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ static CLIPFORMAT fileDescriptorFlavorW =
+ ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
+
+ switch (pFE->cfFormat) {
+ case CF_TEXT:
+ case CF_UNICODETEXT:
+ return GetText(pFE, pSTM);
+ case CF_HDROP:
+ return GetFile(pFE, pSTM);
+ default:
+ if (pFE->cfFormat == fileDescriptorFlavorA ||
+ pFE->cfFormat == fileDescriptorFlavorW) {
+ return GetFileDescriptors(pFE, pSTM);
+ }
+ if (pFE->cfFormat == fileFlavor) {
+ return GetFileContents(pFE, pSTM);
+ }
+ }
+ return GetFirstSupporting(pFE, pSTM);
+}
+
+STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ return E_FAIL;
+}
+
+// Other objects querying to see if we support a particular format
+STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) {
+ UINT format = nsClipboard::GetFormat(MULTI_MIME);
+
+ if (format == pFE->cfFormat) {
+ return S_OK;
+ }
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ IDataObject* dataObj = mDataObjects.ElementAt(i);
+ if (S_OK == dataObj->QueryGetData(pFE)) {
+ return S_OK;
+ }
+ }
+
+ return DV_E_FORMATETC;
+}
+
+STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM,
+ BOOL fRelease) {
+ // Set arbitrary data formats on the first object in the collection and let
+ // it handle the heavy lifting
+ if (mDataObjects.Length() == 0) return E_FAIL;
+ return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease);
+}
+
+// Registers a DataFlavor/FE pair
+void nsDataObjCollection::AddDataFlavor(const char* aDataFlavor,
+ LPFORMATETC aFE) {
+ // Add the FormatEtc to our list if it's not already there. We don't care
+ // about the internal aDataFlavor because nsDataObj handles that.
+ IEnumFORMATETC* ifEtc;
+ FORMATETC fEtc;
+ ULONG num;
+ if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) return;
+ while (S_OK == ifEtc->Next(1, &fEtc, &num)) {
+ NS_ASSERTION(
+ 1 == num,
+ "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor");
+ if (FormatsMatch(fEtc, *aFE)) {
+ ifEtc->Release();
+ return;
+ }
+ } // If we didn't find a matching format, add this one
+ ifEtc->Release();
+ m_enumFE->AddFormatEtc(aFE);
+}
+
+// We accept ownership of the nsDataObj which we free on destruction
+void nsDataObjCollection::AddDataObject(IDataObject* aDataObj) {
+ nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj);
+ mDataObjects.AppendElement(dataObj);
+}
+
+// Methods for getting data
+HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ // Make enough space for the header and the trailing null
+ uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t);
+ uint32_t alloclen = 0;
+ char16_t* realbuffer;
+ nsAutoString filename;
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the filename
+ char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ buffer += sizeof(DROPFILES) / sizeof(char16_t);
+ filename = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the filename into our buffer
+ alloclen = (filename.Length() + 1) * sizeof(char16_t);
+ hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!realbuffer) return E_FAIL;
+ realbuffer--; // Overwrite the preceding null
+ memcpy(realbuffer, filename.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ // We get the last null (on the double null terminator) for free since we used
+ // the zero memory flag when we allocated. All we need to do is fill the
+ // DROPFILES structure
+ DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory);
+ if (!df) return E_FAIL;
+ df->pFiles = sizeof(DROPFILES); // Offset to start of file name string
+ df->fNC = 0;
+ df->pt.x = 0;
+ df->pt.y = 0;
+ df->fWide = TRUE; // utf-16 chars
+ GlobalUnlock(hGlobalMemory);
+ // Finally fill out the STGMEDIUM struct
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+}
+
+HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ uint32_t buffersize = 1;
+ uint32_t alloclen = 0;
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ if (pFE->cfFormat == CF_TEXT) {
+ nsAutoCString text;
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the text
+ char* buffer = (char*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ text = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the text into our buffer
+ alloclen = text.Length();
+ hGlobalMemory =
+ ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!buffer) return E_FAIL;
+ buffer--; // Overwrite the preceding null
+ memcpy(buffer, text.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+ }
+ if (pFE->cfFormat == CF_UNICODETEXT) {
+ buffersize = sizeof(char16_t);
+ nsAutoString text;
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the text
+ char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
+ if (buffer == nullptr) return E_FAIL;
+ text = buffer;
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ // Now put the text into our buffer
+ alloclen = text.Length() * sizeof(char16_t);
+ hGlobalMemory =
+ ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize);
+ if (!buffer) return E_FAIL;
+ buffer--; // Overwrite the preceding null
+ memcpy(buffer, text.get(), alloclen);
+ GlobalUnlock(hGlobalMemory);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ STGMEDIUM workingmedium;
+ FORMATETC fe = *pFE;
+ HGLOBAL hGlobalMemory;
+ HRESULT hr;
+ uint32_t buffersize = sizeof(UINT);
+ uint32_t alloclen = sizeof(FILEDESCRIPTOR);
+
+ hGlobalMemory = GlobalAlloc(GHND, buffersize);
+
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ hr = dataObj->GetData(&fe, &workingmedium);
+ if (hr != S_OK) {
+ switch (hr) {
+ case DV_E_FORMATETC:
+ continue;
+ default:
+ return hr;
+ }
+ }
+ // Now we need to pull out the filedescriptor
+ FILEDESCRIPTOR* buffer =
+ (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) +
+ sizeof(UINT));
+ if (buffer == nullptr) return E_FAIL;
+ hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND);
+ if (hGlobalMemory == nullptr) return E_FAIL;
+ FILEGROUPDESCRIPTOR* realbuffer =
+ (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory);
+ if (!realbuffer) return E_FAIL;
+ FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize);
+ memcpy(copyloc, buffer, alloclen);
+ realbuffer->cItems++;
+ GlobalUnlock(hGlobalMemory);
+ GlobalUnlock(workingmedium.hGlobal);
+ ReleaseStgMedium(&workingmedium);
+ buffersize += alloclen;
+ }
+ pSTM->tymed = TYMED_HGLOBAL;
+ pSTM->pUnkForRelease = nullptr; // Caller gets to free the data
+ pSTM->hGlobal = hGlobalMemory;
+ return S_OK;
+}
+
+HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ ULONG num = 0;
+ ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex;
+ FORMATETC fEtc = *pFE;
+ fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone
+
+ // The key for this data type is to figure out which data object the index
+ // corresponds to and then just pass it along
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ nsDataObj* dataObj = mDataObjects.ElementAt(i);
+ if (dataObj->QueryGetData(&fEtc) != S_OK) continue;
+ if (num == numwanted) return dataObj->GetData(pFE, pSTM);
+ num++;
+ }
+ return DV_E_LINDEX;
+}
+
+HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ // There is no way to pass more than one of this, so just find the first data
+ // object that supports it and pass it along
+ for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+ if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK)
+ return mDataObjects.ElementAt(i)->GetData(pFE, pSTM);
+ }
+ return DV_E_FORMATETC;
+}
diff --git a/widget/windows/nsDataObjCollection.h b/widget/windows/nsDataObjCollection.h
new file mode 100644
index 0000000000..02ec7e8916
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _NSDATAOBJCOLLECTION_H_
+#define _NSDATAOBJCOLLECTION_H_
+
+#include <oleidl.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsDataObj.h"
+#include "mozilla/Attributes.h"
+
+#define MULTI_MIME "Mozilla/IDataObjectCollectionFormat"
+
+EXTERN_C const IID IID_IDataObjCollection;
+
+// An interface to make sure we have the right kind of object for D&D
+// this way we can filter out collection objects that aren't ours
+class nsIDataObjCollection : public IUnknown {
+ public:
+};
+
+/*
+ * This ole registered class is used to facilitate drag-drop of objects which
+ * can be adapted by an object derived from CfDragDrop. The CfDragDrop is
+ * associated with instances via SetDragDrop().
+ */
+
+class nsDataObjCollection final : public nsIDataObjCollection,
+ public nsDataObj {
+ public:
+ nsDataObjCollection();
+
+ private:
+ ~nsDataObjCollection() final;
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef() final;
+ STDMETHODIMP QueryInterface(REFIID, void**) final;
+ STDMETHODIMP_(ULONG) Release() final;
+
+ private: // DataGet and DataSet helper methods
+ HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ HRESULT GetFirstSupporting(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ using nsDataObj::GetFile;
+ using nsDataObj::GetFileContents;
+ using nsDataObj::GetText;
+
+ // support for clipboard
+ void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) final;
+
+ public: // from nsPIDataObjCollection
+ void AddDataObject(IDataObject* aDataObj);
+ int32_t GetNumDataObjects() { return mDataObjects.Length(); }
+ nsDataObj* GetDataObjectAt(uint32_t aItem) {
+ return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>());
+ }
+
+ public:
+ // Store data in pSTM according to the format specified by pFE, if the
+ // format is supported (supported formats are specified in CfDragDrop::
+ // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It
+ // is the callers responsibility to free pSTM if NOERROR is returned.
+ STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final;
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final;
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData(LPFORMATETC pFE) final;
+
+ // Set this objects data according to the format specified by pFE and
+ // the storage medium specified by pSTM and return NOERROR, if the format
+ // is supported. If release is TRUE this object must release the storage
+ // associated with pSTM.
+ STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL release) final;
+
+ private:
+ nsTArray<RefPtr<nsDataObj> > mDataObjects;
+};
+
+#endif //
diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp
new file mode 100644
index 0000000000..ac3bf6f6ed
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -0,0 +1,680 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsDeviceContextSpecWin.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/PrintPromise.h"
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetWindows.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "nsAnonymousTemporaryFile.h"
+
+#include <wchar.h>
+#include <windef.h>
+#include <winspool.h>
+
+#include "nsIWidget.h"
+
+#include "nsTArray.h"
+#include "nsIPrintSettingsWin.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsPrinterWin.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+
+#include "gfxWindowsSurface.h"
+
+#include "nsIFileStreams.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/gfx/Logging.h"
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+# include "mozilla/gfx/PrintTargetSkPDF.h"
+# include "mozilla/gfx/PrintTargetEMF.h"
+# include "nsIUUIDGenerator.h"
+# include "nsDirectoryServiceDefs.h"
+# include "nsPrintfCString.h"
+# include "nsThreadUtils.h"
+#endif
+
+extern mozilla::LazyLogModule gPrintingLog;
+#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+using namespace mozilla::widget;
+#endif
+
+static const wchar_t kDriverName[] = L"WINSPOOL";
+
+//----------------------------------------------------------------------------------
+//---------------
+// static members
+//----------------------------------------------------------------------------------
+nsDeviceContextSpecWin::nsDeviceContextSpecWin() = default;
+
+//----------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec)
+
+nsDeviceContextSpecWin::~nsDeviceContextSpecWin() {
+ SetDevMode(nullptr);
+
+ if (mTempFile) {
+ mTempFile->Remove(/* recursive = */ false);
+ }
+
+ if (nsCOMPtr<nsIPrintSettingsWin> ps = do_QueryInterface(mPrintSettings)) {
+ ps->SetDeviceName(u""_ns);
+ ps->SetDriverName(u""_ns);
+ ps->SetDevMode(nullptr);
+ }
+}
+
+static bool GetDefaultPrinterName(nsAString& aDefaultPrinterName) {
+ DWORD length = 0;
+ GetDefaultPrinterW(nullptr, &length);
+
+ if (length) {
+ aDefaultPrinterName.SetLength(length);
+ if (GetDefaultPrinterW((LPWSTR)aDefaultPrinterName.BeginWriting(),
+ &length)) {
+ // `length` includes the terminating null, so we subtract that from our
+ // string length.
+ aDefaultPrinterName.SetLength(length - 1);
+ PR_PL(("DEFAULT PRINTER [%s]\n",
+ NS_ConvertUTF16toUTF8(aDefaultPrinterName).get()));
+ return true;
+ }
+ }
+
+ aDefaultPrinterName.Truncate();
+ PR_PL(("NO DEFAULT PRINTER\n"));
+ return false;
+}
+
+//----------------------------------------------------------------------------------
+NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview) {
+ mPrintSettings = aPrintSettings;
+
+ // Get the Printer Name to be used and output format.
+ nsAutoString printerName;
+ if (mPrintSettings) {
+ mOutputFormat = mPrintSettings->GetOutputFormat();
+ mPrintSettings->GetPrinterName(printerName);
+ }
+
+ // If there is no name then use the default printer
+ if (printerName.IsEmpty()) {
+ GetDefaultPrinterName(printerName);
+ }
+
+ // Gather telemetry on the print target type.
+ //
+ // Unfortunately, if we're not using our own internal save-to-pdf codepaths,
+ // there isn't a good way to determine whether a print is going to be to a
+ // physical printer or to a file or some other non-physical output. We do our
+ // best by checking for what seems to be the most common save-to-PDF virtual
+ // printers.
+ //
+ // We use StringBeginsWith below, since printer names are often followed by a
+ // version number or other product differentiating string. (True for doPDF,
+ // novaPDF, PDF-XChange and Soda PDF, for example.)
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_file"_ns, 1);
+ } else if (StringBeginsWith(printerName, u"Microsoft Print to PDF"_ns) ||
+ StringBeginsWith(printerName, u"Adobe PDF"_ns) ||
+ StringBeginsWith(printerName, u"Bullzip PDF Printer"_ns) ||
+ StringBeginsWith(printerName, u"CutePDF Writer"_ns) ||
+ StringBeginsWith(printerName, u"doPDF"_ns) ||
+ StringBeginsWith(printerName, u"Foxit Reader PDF Printer"_ns) ||
+ StringBeginsWith(printerName, u"Nitro PDF Creator"_ns) ||
+ StringBeginsWith(printerName, u"novaPDF"_ns) ||
+ StringBeginsWith(printerName, u"PDF-XChange"_ns) ||
+ StringBeginsWith(printerName, u"PDF24 PDF"_ns) ||
+ StringBeginsWith(printerName, u"PDFCreator"_ns) ||
+ StringBeginsWith(printerName, u"PrimoPDF"_ns) ||
+ StringBeginsWith(printerName, u"Soda PDF"_ns) ||
+ StringBeginsWith(printerName, u"Solid PDF Creator"_ns) ||
+ StringBeginsWith(printerName,
+ u"Universal Document Converter"_ns)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_file"_ns, 1);
+ } else if (printerName.EqualsLiteral("Microsoft XPS Document Writer")) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"xps_file"_ns, 1);
+ } else {
+ nsAString::const_iterator start, end;
+ printerName.BeginReading(start);
+ printerName.EndReading(end);
+ if (CaseInsensitiveFindInReadable(u"pdf"_ns, start, end)) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"pdf_unknown"_ns, 1);
+ } else {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
+ u"unknown"_ns, 1);
+ }
+ }
+
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ if (aPrintSettings) {
+#ifdef MOZ_ENABLE_SKIA_PDF
+ nsAutoString printViaPdf;
+ Preferences::GetString("print.print_via_pdf_encoder", printViaPdf);
+ if (printViaPdf.EqualsLiteral("skia-pdf")) {
+ mPrintViaSkPDF = true;
+ }
+#endif
+
+ // If we're in the child or we're printing to PDF we only need information
+ // from the print settings.
+ if (XRE_IsContentProcess() ||
+ mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ if (psWin) {
+ nsAutoString deviceName;
+ nsAutoString driverName;
+ psWin->GetDeviceName(deviceName);
+ psWin->GetDriverName(driverName);
+
+ LPDEVMODEW devMode;
+ psWin->GetDevMode(&devMode); // creates new memory (makes a copy)
+
+ if (!deviceName.IsEmpty() && !driverName.IsEmpty() && devMode) {
+ // Scaling is special, it is one of the few
+ // devMode items that we control in layout
+ if (devMode->dmFields & DM_SCALE) {
+ double scale = double(devMode->dmScale) / 100.0f;
+ if (scale != 1.0) {
+ aPrintSettings->SetScaling(scale);
+ devMode->dmScale = 100;
+ }
+ }
+
+ SetDeviceName(deviceName);
+ SetDriverName(driverName);
+ SetDevMode(devMode);
+
+ return NS_OK;
+ } else {
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::Init - "
+ "deviceName/driverName/devMode was NULL!\n"));
+ if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode);
+ }
+ }
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n"));
+ }
+
+ if (printerName.IsEmpty()) {
+ return rv;
+ }
+
+ return GetDataFromPrinter(printerName, mPrintSettings);
+}
+
+//----------------------------------------------------------
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget() {
+ NS_ASSERTION(mDevMode || mOutputFormat == nsIPrintSettings::kOutputFormatPDF,
+ "DevMode can't be NULL here unless we're printing to PDF.");
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ if (mPrintViaSkPDF) {
+ double width, height;
+ mPrintSettings->GetEffectivePageSize(&width, &height);
+ if (width <= 0 || height <= 0) {
+ return nullptr;
+ }
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+ IntSize size = IntSize::Ceil(width, height);
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ nsString filename;
+ // TODO(dshin):
+ // - Does this handle bug 1659470?
+ // - Should this code path be enabled, we should use temporary files and
+ // then move the file in `EndDocument()`.
+ mPrintSettings->GetToFileName(filename);
+
+ nsAutoCString printFile(NS_ConvertUTF16toUTF8(filename).get());
+ auto skStream = MakeUnique<SkFILEWStream>(printFile.get());
+ return PrintTargetSkPDF::CreateOrNull(std::move(skStream), size);
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!");
+ HDC dc =
+ ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode);
+ if (!dc) {
+ gfxCriticalError(gfxCriticalError::DefaultOptions(false))
+ << "Failed to create device context in GetSurfaceForPrinter";
+ return nullptr;
+ }
+ return PrintTargetEMF::CreateOrNull(dc, size);
+ }
+ }
+#endif
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ double width, height;
+ mPrintSettings->GetEffectiveSheetSize(&width, &height);
+ if (width <= 0 || height <= 0) {
+ return nullptr;
+ }
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ auto stream = [&]() -> nsCOMPtr<nsIOutputStream> {
+ if (mPrintSettings->GetOutputDestination() ==
+ nsIPrintSettings::kOutputDestinationStream) {
+ nsCOMPtr<nsIOutputStream> out;
+ mPrintSettings->GetOutputStream(getter_AddRefs(out));
+ return out;
+ }
+
+ // Even if the destination may be a named path, write to a temp file -
+ // this is consistent with behaviour of `PrintTarget` on other platforms.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(mTempFile));
+ file = mTempFile;
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFileOutputStream> stream =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ if (NS_FAILED(stream->Init(file, -1, -1, 0))) {
+ return nullptr;
+ }
+ return stream;
+ }();
+
+ return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height));
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!");
+ HDC dc =
+ ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode);
+ if (!dc) {
+ gfxCriticalError(gfxCriticalError::DefaultOptions(false))
+ << "Failed to create device context in GetSurfaceForPrinter";
+ return nullptr;
+ }
+
+ // The PrintTargetWindows takes over ownership of this DC
+ return PrintTargetWindows::CreateOrNull(dc);
+ }
+
+ return nullptr;
+}
+
+RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecWin::EndDocument() {
+ if (mPrintSettings->GetOutputDestination() !=
+ nsIPrintSettings::kOutputDestinationFile ||
+ mOutputFormat != nsIPrintSettings::kOutputFormatPDF) {
+ return PrintEndDocumentPromise::CreateAndResolve(true, __func__);
+ }
+
+#ifdef MOZ_ENABLE_SKIA_PDF
+ if (mPrintViaSkPDF) {
+ return PrintEndDocumentPromise::CreateAndResolve(true, __func__);
+ }
+#endif
+
+ MOZ_ASSERT(mTempFile, "No handle to temporary PDF file.");
+
+ nsAutoString targetPath;
+ mPrintSettings->GetToFileName(targetPath);
+
+ if (targetPath.IsEmpty()) {
+ return PrintEndDocumentPromise::CreateAndResolve(true, __func__);
+ }
+
+ // We still need to move the file to its actual destination.
+ nsCOMPtr<nsIFile> destFile;
+ auto rv = NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile));
+ if (NS_FAILED(rv)) {
+ return PrintEndDocumentPromise::CreateAndReject(rv, __func__);
+ }
+
+ return nsIDeviceContextSpec::EndDocumentAsync(
+ __func__,
+ [destFile = std::move(destFile),
+ tempFile = std::move(mTempFile)]() -> nsresult {
+ nsAutoString destLeafName;
+ MOZ_TRY(destFile->GetLeafName(destLeafName));
+
+ nsCOMPtr<nsIFile> destDir;
+ MOZ_TRY(destFile->GetParent(getter_AddRefs(destDir)));
+
+ // This should be fine - Windows API calls usually prevent moving
+ // between different volumes (See Win32 API's `MOVEFILE_COPY_ALLOWED`
+ // flag), but we handle that down this call.
+ MOZ_TRY(tempFile->MoveTo(destDir, destLeafName));
+ return NS_OK;
+ });
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDeviceName(const nsAString& aDeviceName) {
+ mDeviceName = aDeviceName;
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDriverName(const nsAString& aDriverName) {
+ mDriverName = aDriverName;
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDevMode(LPDEVMODEW aDevMode) {
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDevMode = aDevMode;
+}
+
+//------------------------------------------------------------------
+void nsDeviceContextSpecWin::GetDevMode(LPDEVMODEW& aDevMode) {
+ aDevMode = mDevMode;
+}
+
+#define DISPLAY_LAST_ERROR
+
+//----------------------------------------------------------------------------------
+// Setup the object's data member with the selected printer's data
+nsresult nsDeviceContextSpecWin::GetDataFromPrinter(const nsAString& aName,
+ nsIPrintSettings* aPS) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsHPRINTER hPrinter = nullptr;
+ const nsString& flat = PromiseFlatString(aName);
+ wchar_t* name =
+ (wchar_t*)flat.get(); // Windows APIs use non-const name argument
+
+ BOOL status = ::OpenPrinterW(name, &hPrinter, nullptr);
+ if (status) {
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ LPDEVMODEW pDevMode;
+
+ // Allocate a buffer of the correct size.
+ LONG needed =
+ ::DocumentPropertiesW(nullptr, hPrinter, name, nullptr, nullptr, 0);
+ if (needed < 0) {
+ PR_PL(
+ ("**** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't get "
+ "size of DEVMODE using DocumentPropertiesW(pDeviceName = \"%s\"). "
+ "GetLastEror() = %08lx\n",
+ NS_ConvertUTF16toUTF8(aName).get(), GetLastError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Some drivers do not return the correct size for their DEVMODE, so we
+ // over-allocate to try and compensate.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ needed *= 2;
+ pDevMode =
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed);
+ if (!pDevMode) return NS_ERROR_FAILURE;
+
+ // Get the default DevMode for the printer and modify it for our needs.
+ LONG ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, nullptr,
+ DM_OUT_BUFFER);
+
+ if (ret == IDOK && aPS) {
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
+ MOZ_ASSERT(psWin);
+ psWin->CopyToNative(pDevMode);
+ // Sets back the changes we made to the DevMode into the Printer Driver
+ ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, pDevMode,
+ DM_IN_BUFFER | DM_OUT_BUFFER);
+
+ // We need to copy the final DEVMODE settings back to our print settings,
+ // because they may have been set from invalid prefs.
+ if (ret == IDOK) {
+ // We need to get information from the device as well.
+ nsAutoHDC printerDC(::CreateICW(kDriverName, name, nullptr, pDevMode));
+ if (NS_WARN_IF(!printerDC)) {
+ ::HeapFree(::GetProcessHeap(), 0, pDevMode);
+ return NS_ERROR_FAILURE;
+ }
+
+ psWin->CopyFromNative(printerDC, pDevMode);
+ }
+ }
+
+ if (ret != IDOK) {
+ ::HeapFree(::GetProcessHeap(), 0, pDevMode);
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::GetDataFromPrinter - "
+ "DocumentProperties call failed code: %ld/0x%lx\n",
+ ret, ret));
+ DISPLAY_LAST_ERROR
+ return NS_ERROR_FAILURE;
+ }
+
+ SetDevMode(
+ pDevMode); // cache the pointer and takes responsibility for the memory
+
+ SetDeviceName(aName);
+
+ SetDriverName(nsDependentString(kDriverName));
+
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND;
+ PR_PL(
+ ("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't open "
+ "printer: [%s]\n",
+ NS_ConvertUTF16toUTF8(aName).get()));
+ DISPLAY_LAST_ERROR
+ }
+ return rv;
+}
+
+//***********************************************************
+// Printer List
+//***********************************************************
+
+nsPrinterListWin::~nsPrinterListWin() = default;
+
+// Helper to get the array of PRINTER_INFO_4 records from the OS into a
+// caller-supplied byte array; returns the number of records present.
+static unsigned GetPrinterInfo4(nsTArray<BYTE>& aBuffer) {
+ const DWORD kLevel = 4;
+ DWORD needed = 0;
+ DWORD count = 0;
+ const DWORD kFlags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
+ BOOL ok = ::EnumPrintersW(kFlags,
+ nullptr, // Name
+ kLevel, // Level
+ nullptr, // pPrinterEnum
+ 0, // cbBuf (buffer size)
+ &needed, // Bytes needed in buffer
+ &count);
+ if (needed > 0) {
+ if (!aBuffer.SetLength(needed, fallible)) {
+ return 0;
+ }
+ ok = ::EnumPrintersW(kFlags, nullptr, kLevel, aBuffer.Elements(),
+ aBuffer.Length(), &needed, &count);
+ }
+ if (!ok) {
+ return 0;
+ }
+ return count;
+}
+
+nsTArray<nsPrinterListBase::PrinterInfo> nsPrinterListWin::Printers() const {
+ PR_PL(("nsPrinterListWin::Printers\n"));
+
+ AutoTArray<BYTE, 1024> buffer;
+ unsigned count = GetPrinterInfo4(buffer);
+
+ if (!count) {
+ PR_PL(("[No printers found]\n"));
+ return {};
+ }
+
+ const auto* printers =
+ reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements());
+ nsTArray<PrinterInfo> list;
+ for (unsigned i = 0; i < count; i++) {
+ // For LOCAL printers, we check whether OpenPrinter succeeds, and omit
+ // them from the list if not. This avoids presenting printers that the
+ // user cannot actually use (e.g. due to Windows permissions).
+ // For NETWORK printers, this check may block for a long time (waiting for
+ // network timeout), so we skip it; if the user tries to access a printer
+ // that isn't available, we'll have to show an error later.
+ // (We always need to be able to handle an error, anyhow, as the printer
+ // could get disconnected after we've created the list, for example.)
+ bool isAvailable = false;
+ if (printers[i].Attributes & PRINTER_ATTRIBUTE_NETWORK) {
+ isAvailable = true;
+ } else if (printers[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) {
+ HANDLE handle;
+ if (::OpenPrinterW(printers[i].pPrinterName, &handle, nullptr)) {
+ ::ClosePrinter(handle);
+ isAvailable = true;
+ }
+ }
+ if (isAvailable) {
+ list.AppendElement(PrinterInfo{nsString(printers[i].pPrinterName)});
+ PR_PL(("Printer Name: %s\n",
+ NS_ConvertUTF16toUTF8(printers[i].pPrinterName).get()));
+ }
+ }
+
+ if (list.IsEmpty()) {
+ PR_PL(("[No usable printers found]\n"));
+ return {};
+ }
+
+ list.Sort([](const PrinterInfo& a, const PrinterInfo& b) {
+ size_t len = std::min(a.mName.Length(), b.mName.Length());
+ int result = CaseInsensitiveCompare(a.mName.BeginReading(),
+ b.mName.BeginReading(), len);
+ return result ? result : int(a.mName.Length()) - int(b.mName.Length());
+ });
+
+ return list;
+}
+
+Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterByName(
+ nsString aName) const {
+ Maybe<PrinterInfo> rv;
+
+ AutoTArray<BYTE, 1024> buffer;
+ unsigned count = GetPrinterInfo4(buffer);
+
+ const auto* printers =
+ reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements());
+ for (unsigned i = 0; i < count; ++i) {
+ if (aName.Equals(nsString(printers[i].pPrinterName))) {
+ rv.emplace(PrinterInfo{aName});
+ break;
+ }
+ }
+
+ return rv;
+}
+
+Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterBySystemName(
+ nsString aName) const {
+ return PrinterByName(std::move(aName));
+}
+
+RefPtr<nsIPrinter> nsPrinterListWin::CreatePrinter(PrinterInfo aInfo) const {
+ return nsPrinterWin::Create(mCommonPaperInfo, std::move(aInfo.mName));
+}
+
+nsresult nsPrinterListWin::SystemDefaultPrinterName(nsAString& aName) const {
+ if (!GetDefaultPrinterName(aName)) {
+ NS_WARNING("Uh oh, GetDefaultPrinterName failed");
+ // Indicate failure by leaving aName untouched, i.e. the empty string.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterListWin::InitPrintSettingsFromPrinter(
+ const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) {
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ if (aPrinterName.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // When printing to PDF on Windows there is no associated printer driver.
+ int16_t outputFormat = aPrintSettings->GetOutputFormat();
+ if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ RefPtr<nsDeviceContextSpecWin> devSpecWin = new nsDeviceContextSpecWin();
+ if (!devSpecWin) return NS_ERROR_OUT_OF_MEMORY;
+
+ // If the settings have already been initialized from prefs then pass these to
+ // GetDataFromPrinter, so that they are saved to the printer.
+ bool initializedFromPrefs;
+ nsresult rv =
+ aPrintSettings->GetIsInitializedFromPrefs(&initializedFromPrefs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (initializedFromPrefs) {
+ // If we pass in print settings to GetDataFromPrinter it already copies
+ // things back to the settings, so we can return here.
+ return devSpecWin->GetDataFromPrinter(aPrinterName, aPrintSettings);
+ }
+
+ devSpecWin->GetDataFromPrinter(aPrinterName);
+
+ LPDEVMODEW devmode;
+ devSpecWin->GetDevMode(devmode);
+ if (NS_WARN_IF(!devmode)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aPrintSettings->SetPrinterName(aPrinterName);
+
+ // We need to get information from the device as well.
+ const nsString& flat = PromiseFlatString(aPrinterName);
+ char16ptr_t printerName = flat.get();
+ HDC dc = ::CreateICW(kDriverName, printerName, nullptr, devmode);
+ if (NS_WARN_IF(!dc)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPrintSettings);
+ MOZ_ASSERT(psWin);
+ psWin->CopyFromNative(dc, devmode);
+ ::DeleteDC(dc);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h
new file mode 100644
index 0000000000..fff6472d62
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsDeviceContextSpecWin_h___
+#define nsDeviceContextSpecWin_h___
+
+#include "nsCOMPtr.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsPrinterListBase.h"
+#include "nsIPrintSettings.h"
+#include <windows.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/PrintPromise.h"
+
+class nsIFile;
+class nsIWidget;
+
+class nsDeviceContextSpecWin : public nsIDeviceContextSpec {
+ public:
+ nsDeviceContextSpecWin();
+
+ NS_DECL_ISUPPORTS
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override {
+ return NS_OK;
+ }
+ RefPtr<mozilla::gfx::PrintEndDocumentPromise> EndDocument() override;
+ NS_IMETHOD BeginPage(const IntSize& aSizeInPoints) override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ NS_IMETHOD Init(nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+
+ void GetDriverName(nsAString& aDriverName) const {
+ aDriverName = mDriverName;
+ }
+ void GetDeviceName(nsAString& aDeviceName) const {
+ aDeviceName = mDeviceName;
+ }
+
+ // The GetDevMode will return a pointer to a DevMode
+ // whether it is from the Global memory handle or just the DevMode
+ // To get the DevMode from the Global memory Handle it must lock it
+ // So this call must be paired with a call to UnlockGlobalHandle
+ void GetDevMode(LPDEVMODEW& aDevMode);
+
+ // helper functions
+ nsresult GetDataFromPrinter(const nsAString& aName,
+ nsIPrintSettings* aPS = nullptr);
+
+ protected:
+ void SetDeviceName(const nsAString& aDeviceName);
+ void SetDriverName(const nsAString& aDriverName);
+ void SetDevMode(LPDEVMODEW aDevMode);
+
+ virtual ~nsDeviceContextSpecWin();
+
+ nsString mDriverName;
+ nsString mDeviceName;
+ LPDEVMODEW mDevMode = nullptr;
+
+ int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative;
+
+ // A temporary file to create an "anonymous" print target. See bug 1664253,
+ // this should ideally not be needed.
+ nsCOMPtr<nsIFile> mTempFile;
+};
+
+//-------------------------------------------------------------------------
+// Printer List
+//-------------------------------------------------------------------------
+class nsPrinterListWin final : public nsPrinterListBase {
+ public:
+ NS_IMETHOD InitPrintSettingsFromPrinter(const nsAString&,
+ nsIPrintSettings*) final;
+
+ nsTArray<PrinterInfo> Printers() const final;
+ RefPtr<nsIPrinter> CreatePrinter(PrinterInfo) const final;
+
+ nsPrinterListWin() = default;
+
+ protected:
+ nsresult SystemDefaultPrinterName(nsAString&) const final;
+
+ mozilla::Maybe<PrinterInfo> PrinterByName(nsString) const final;
+ mozilla::Maybe<PrinterInfo> PrinterBySystemName(
+ nsString aPrinterName) const final;
+
+ private:
+ ~nsPrinterListWin();
+};
+
+#endif
diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp
new file mode 100644
index 0000000000..80f545d9a7
--- /dev/null
+++ b/widget/windows/nsDragService.cpp
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <ole2.h>
+#include <oleidl.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsDragService.h"
+#include "nsITransferable.h"
+#include "nsDataObj.h"
+
+#include "nsWidgetsCID.h"
+#include "nsNativeDragTarget.h"
+#include "nsNativeDragSource.h"
+#include "nsClipboard.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsDataObjCollection.h"
+
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsIScreenManager.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "nsRect.h"
+#include "nsMathUtils.h"
+#include "WinUtils.h"
+#include "KeyboardLayout.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/ScopeExit.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+//-------------------------------------------------------------------------
+//
+// DragService constructor
+//
+//-------------------------------------------------------------------------
+nsDragService::nsDragService()
+ : mDataObject(nullptr), mSentLocalDropEvent(false) {}
+
+//-------------------------------------------------------------------------
+//
+// DragService destructor
+//
+//-------------------------------------------------------------------------
+nsDragService::~nsDragService() { NS_IF_RELEASE(mDataObject); }
+
+bool nsDragService::CreateDragImage(nsINode* aDOMNode,
+ const Maybe<CSSIntRegion>& aRegion,
+ SHDRAGIMAGE* psdi) {
+ if (!psdi) return false;
+
+ memset(psdi, 0, sizeof(SHDRAGIMAGE));
+ if (!aDOMNode) return false;
+
+ // Prepare the drag image
+ LayoutDeviceIntRect dragRect;
+ RefPtr<SourceSurface> surface;
+ nsPresContext* pc;
+ DrawDrag(aDOMNode, aRegion, mScreenPosition, &dragRect, &surface, &pc);
+ if (!surface) return false;
+
+ uint32_t bmWidth = dragRect.Width(), bmHeight = dragRect.Height();
+
+ if (bmWidth == 0 || bmHeight == 0) return false;
+
+ psdi->crColorKey = CLR_NONE;
+
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ IntSize(bmWidth, bmHeight), SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, false);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return false;
+ }
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ dataSurface->Unmap();
+ return false;
+ }
+
+ dt->DrawSurface(
+ surface,
+ Rect(0, 0, dataSurface->GetSize().width, dataSurface->GetSize().height),
+ Rect(0, 0, surface->GetSize().width, surface->GetSize().height),
+ DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ dt->Flush();
+
+ BITMAPV5HEADER bmih;
+ memset((void*)&bmih, 0, sizeof(BITMAPV5HEADER));
+ bmih.bV5Size = sizeof(BITMAPV5HEADER);
+ bmih.bV5Width = bmWidth;
+ bmih.bV5Height = -(int32_t)bmHeight; // flip vertical
+ bmih.bV5Planes = 1;
+ bmih.bV5BitCount = 32;
+ bmih.bV5Compression = BI_BITFIELDS;
+ bmih.bV5RedMask = 0x00FF0000;
+ bmih.bV5GreenMask = 0x0000FF00;
+ bmih.bV5BlueMask = 0x000000FF;
+ bmih.bV5AlphaMask = 0xFF000000;
+
+ HDC hdcSrc = CreateCompatibleDC(nullptr);
+ void* lpBits = nullptr;
+ if (hdcSrc) {
+ psdi->hbmpDragImage =
+ ::CreateDIBSection(hdcSrc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS,
+ (void**)&lpBits, nullptr, 0);
+ if (psdi->hbmpDragImage && lpBits) {
+ CopySurfaceDataToPackedArray(map.mData, static_cast<uint8_t*>(lpBits),
+ dataSurface->GetSize(), map.mStride,
+ BytesPerPixel(dataSurface->GetFormat()));
+ }
+
+ psdi->sizeDragImage.cx = bmWidth;
+ psdi->sizeDragImage.cy = bmHeight;
+
+ const auto screenPoint =
+ LayoutDeviceIntPoint::Round(mScreenPosition * pc->CSSToDevPixelScale());
+ psdi->ptOffset.x = screenPoint.x - dragRect.X();
+ psdi->ptOffset.y = screenPoint.y - dragRect.Y();
+
+ DeleteDC(hdcSrc);
+ }
+
+ dataSurface->Unmap();
+
+ return psdi->hbmpDragImage != nullptr;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsDragService::InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables, const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ // Try and get source URI of the items that are being dragged
+ nsIURI* uri = nullptr;
+
+ RefPtr<dom::Document> doc(mSourceDocument);
+ if (doc) {
+ uri = doc->GetDocumentURI();
+ }
+
+ uint32_t numItemsToDrag = 0;
+ nsresult rv = anArrayTransferables->GetLength(&numItemsToDrag);
+ if (!numItemsToDrag) return NS_ERROR_FAILURE;
+
+ // The clipboard class contains some static utility methods that we
+ // can use to create an IDataObject from the transferable
+
+ // if we're dragging more than one item, we need to create a
+ // "collection" object to fake out the OS. This collection contains
+ // one |IDataObject| for each transferable. If there is just the one
+ // (most cases), only pass around the native |IDataObject|.
+ RefPtr<IDataObject> itemToDrag;
+ if (numItemsToDrag > 1) {
+ nsDataObjCollection* dataObjCollection = new nsDataObjCollection();
+ if (!dataObjCollection) return NS_ERROR_OUT_OF_MEMORY;
+ itemToDrag = dataObjCollection;
+ for (uint32_t i = 0; i < numItemsToDrag; ++i) {
+ nsCOMPtr<nsITransferable> trans =
+ do_QueryElementAt(anArrayTransferables, i);
+ if (trans) {
+ RefPtr<IDataObject> dataObj;
+ rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj),
+ uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Add the flavors to the collection object too
+ rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dataObjCollection->AddDataObject(dataObj);
+ }
+ }
+ } // if dragging multiple items
+ else {
+ nsCOMPtr<nsITransferable> trans =
+ do_QueryElementAt(anArrayTransferables, 0);
+ if (trans) {
+ rv = nsClipboard::CreateNativeDataObject(trans,
+ getter_AddRefs(itemToDrag), uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } // else dragging a single object
+
+ // Create a drag image if support is available
+ IDragSourceHelper* pdsh;
+ if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IDragSourceHelper,
+ (void**)&pdsh))) {
+ SHDRAGIMAGE sdi;
+ if (CreateDragImage(mSourceNode, aRegion, &sdi)) {
+ if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag)))
+ DeleteObject(sdi.hbmpDragImage);
+ }
+ pdsh->Release();
+ }
+
+ // Kick off the native drag session
+ return StartInvokingDragSession(itemToDrag, aActionType);
+}
+
+static HWND GetSourceWindow(dom::Document* aSourceDocument) {
+ if (!aSourceDocument) {
+ return nullptr;
+ }
+
+ auto* pc = aSourceDocument->GetPresContext();
+ if (!pc) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
+ if (!widget) {
+ return nullptr;
+ }
+
+ return (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+}
+
+//-------------------------------------------------------------------------
+nsresult nsDragService::StartInvokingDragSession(IDataObject* aDataObj,
+ uint32_t aActionType) {
+ // To do the drag we need to create an object that
+ // implements the IDataObject interface (for OLE)
+ RefPtr<nsNativeDragSource> nativeDragSrc =
+ new nsNativeDragSource(mDataTransfer);
+
+ // Now figure out what the native drag effect should be
+ DWORD winDropRes;
+ DWORD effects = DROPEFFECT_SCROLL;
+ if (aActionType & DRAGDROP_ACTION_COPY) {
+ effects |= DROPEFFECT_COPY;
+ }
+ if (aActionType & DRAGDROP_ACTION_MOVE) {
+ effects |= DROPEFFECT_MOVE;
+ }
+ if (aActionType & DRAGDROP_ACTION_LINK) {
+ effects |= DROPEFFECT_LINK;
+ }
+
+ // XXX not sure why we bother to cache this, it can change during
+ // the drag
+ mDragAction = aActionType;
+ mSentLocalDropEvent = false;
+
+ // Start dragging
+ StartDragSession();
+ OpenDragPopup();
+
+ RefPtr<IDataObjectAsyncCapability> pAsyncOp;
+ // Offer to do an async drag
+ if (SUCCEEDED(aDataObj->QueryInterface(IID_IDataObjectAsyncCapability,
+ getter_AddRefs(pAsyncOp)))) {
+ pAsyncOp->SetAsyncMode(VARIANT_TRUE);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("When did our data object stop being async");
+ }
+
+ // Call the native D&D method
+ HRESULT res = ::DoDragDrop(aDataObj, nativeDragSrc, effects, &winDropRes);
+
+ // In cases where the drop operation completed outside the application,
+ // update the source node's DataTransfer dropEffect value so it is up to date.
+ if (!mSentLocalDropEvent) {
+ uint32_t dropResult;
+ // Order is important, since multiple flags can be returned.
+ if (winDropRes & DROPEFFECT_COPY)
+ dropResult = DRAGDROP_ACTION_COPY;
+ else if (winDropRes & DROPEFFECT_LINK)
+ dropResult = DRAGDROP_ACTION_LINK;
+ else if (winDropRes & DROPEFFECT_MOVE)
+ dropResult = DRAGDROP_ACTION_MOVE;
+ else
+ dropResult = DRAGDROP_ACTION_NONE;
+
+ if (mDataTransfer) {
+ if (res == DRAGDROP_S_DROP) // Success
+ mDataTransfer->SetDropEffectInt(dropResult);
+ else
+ mDataTransfer->SetDropEffectInt(DRAGDROP_ACTION_NONE);
+ }
+ }
+
+ mUserCancelled = nativeDragSrc->UserCancelled();
+
+ // We're done dragging, get the cursor position and end the drag
+ // Use GetMessagePos to get the position of the mouse at the last message
+ // seen by the event loop. (Bug 489729)
+ // Note that we must convert this from device pixels back to Windows logical
+ // pixels (bug 818927).
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+ if (auto wnd = GetSourceWindow(mSourceDocument)) {
+ // Convert from screen to client coordinates like nsWindow::InitEvent does.
+ ::ScreenToClient(wnd, &cpos);
+ }
+ SetDragEndPoint(LayoutDeviceIntPoint(cpos.x, cpos.y));
+
+ ModifierKeyState modifierKeyState;
+ EndDragSession(true, modifierKeyState.GetModifiers());
+
+ mDoingDrag = false;
+
+ return DRAGDROP_S_DROP == res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//-------------------------------------------------------------------------
+// Make Sure we have the right kind of object
+nsDataObjCollection* nsDragService::GetDataObjCollection(
+ IDataObject* aDataObj) {
+ nsDataObjCollection* dataObjCol = nullptr;
+ if (aDataObj) {
+ nsIDataObjCollection* dataObj;
+ if (aDataObj->QueryInterface(IID_IDataObjCollection, (void**)&dataObj) ==
+ S_OK) {
+ dataObjCol = static_cast<nsDataObjCollection*>(aDataObj);
+ dataObj->Release();
+ }
+ }
+
+ return dataObjCol;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems) {
+ if (!mDataObject) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ if (IsCollectionObject(mDataObject)) {
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ // If the count cannot be determined just return 0.
+ // This can happen if we have collection data of type
+ // MULTI_MIME ("Mozilla/IDataObjectCollectionFormat") on the clipboard
+ // from another process but we can't obtain an IID_IDataObjCollection
+ // from this process.
+ *aNumItems = dataObjCol ? dataObjCol->GetNumDataObjects() : 0;
+ return NS_OK;
+ }
+ // Next check if we have a file drop. Return the number of files in
+ // the file drop as the number of items we have, pretending like we
+ // actually have > 1 drag item.
+ FORMATETC fe2;
+ SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (SUCCEEDED(mDataObject->QueryGetData(&fe2))) {
+ STGMEDIUM stm;
+ if (FAILED(mDataObject->GetData(&fe2, &stm))) {
+ *aNumItems = 1;
+ return NS_OK;
+ }
+ HDROP hdrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
+ MOZ_ASSERT(hdrop != NULL);
+ *aNumItems = ::DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);
+ ::GlobalUnlock(stm.hGlobal);
+ ::ReleaseStgMedium(&stm);
+ // Data may be provided later, so assume we have 1 item
+ if (*aNumItems == 0) {
+ *aNumItems = 1;
+ }
+ return NS_OK;
+ }
+ // Next check if we have a virtual file drop.
+ SET_FORMATETC(fe2, nsClipboard::GetClipboardFileDescriptorFormatW(), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ STGMEDIUM stm;
+
+ if (SUCCEEDED(mDataObject->GetData(&fe2, &stm))) {
+ LPFILEGROUPDESCRIPTOR pDesc =
+ static_cast<LPFILEGROUPDESCRIPTOR>(GlobalLock(stm.hGlobal));
+ if (pDesc) {
+ *aNumItems = pDesc->cItems;
+ }
+ GlobalUnlock(stm.hGlobal);
+ ReleaseStgMedium(&stm);
+ return NS_OK;
+ }
+ *aNumItems = 1;
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t anItem) {
+ // This typcially happens on a drop, the target would be asking
+ // for it's transferable to be filled in
+ // Use a static clipboard utility method for this
+ if (!mDataObject) return NS_ERROR_FAILURE;
+
+ nsresult dataFound = NS_ERROR_FAILURE;
+
+ if (IsCollectionObject(mDataObject)) {
+ // multiple items, use |anItem| as an index into our collection
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ uint32_t cnt = dataObjCol->GetNumDataObjects();
+ if (anItem < cnt) {
+ IDataObject* dataObj = dataObjCol->GetDataObjectAt(anItem);
+ dataFound = nsClipboard::GetDataFromDataObject(dataObj, 0, nullptr,
+ aTransferable);
+ } else
+ NS_WARNING("Index out of range!");
+ } else {
+ // If they are asking for item "0", we can just get it...
+ if (anItem == 0) {
+ dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
+ nullptr, aTransferable);
+ } else {
+ // It better be a file drop, or else non-zero indexes are invalid!
+ FORMATETC fe2;
+ FORMATETC fe3;
+ SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ SET_FORMATETC(fe3, nsClipboard::GetClipboardFileDescriptorFormatW(), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (SUCCEEDED(mDataObject->QueryGetData(&fe2)) ||
+ SUCCEEDED(mDataObject->QueryGetData(&fe3)))
+ dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
+ nullptr, aTransferable);
+ else
+ NS_WARNING(
+ "Reqesting non-zero index, but clipboard data is not a "
+ "collection!");
+ }
+ }
+ return dataFound;
+}
+
+//---------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::SetIDataObject(IDataObject* aDataObj) {
+ // When the native drag starts the DragService gets
+ // the IDataObject that is being dragged
+ NS_IF_RELEASE(mDataObject);
+ mDataObject = aDataObj;
+ NS_IF_ADDREF(mDataObject);
+
+ if (MOZ_DRAGSERVICE_LOG_ENABLED()) {
+ MOZ_DRAGSERVICE_LOG("nsDragService::SetIDataObject (%p)", mDataObject);
+ IEnumFORMATETC* pEnum = nullptr;
+ if (mDataObject &&
+ S_OK == mDataObject->EnumFormatEtc(DATADIR_GET, &pEnum)) {
+ MOZ_DRAGSERVICE_LOG(" formats in DataObject:");
+
+ FORMATETC fEtc;
+ while (S_OK == pEnum->Next(1, &fEtc, nullptr)) {
+ nsAutoString format;
+ WinUtils::GetClipboardFormatAsString(fEtc.cfFormat, format);
+ MOZ_DRAGSERVICE_LOG(" FORMAT %s",
+ NS_ConvertUTF16toUTF8(format).get());
+ }
+ pEnum->Release();
+ }
+ }
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------
+void nsDragService::SetDroppedLocal() {
+ // Sent from the native drag handler, letting us know
+ // a drop occurred within the application vs. outside of it.
+ mSentLocalDropEvent = true;
+ return;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ if (!aDataFlavor || !mDataObject || !_retval) {
+ MOZ_DRAGSERVICE_LOG("%s: error", __PRETTY_FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ *_retval = false;
+ auto logging = MakeScopeExit([&] {
+ MOZ_DRAGSERVICE_LOG("IsDataFlavorSupported: %s is%s found", aDataFlavor,
+ *_retval ? "" : " not");
+ });
+
+ FORMATETC fe;
+ UINT format = 0;
+
+ if (IsCollectionObject(mDataObject)) {
+ // We know we have one of our special collection objects.
+ format = nsClipboard::GetFormat(aDataFlavor);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+
+ // See if any one of the IDataObjects in the collection supports
+ // this data type
+ nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
+ if (dataObjCol) {
+ uint32_t cnt = dataObjCol->GetNumDataObjects();
+ for (uint32_t i = 0; i < cnt; ++i) {
+ IDataObject* dataObj = dataObjCol->GetDataObjectAt(i);
+ if (S_OK == dataObj->QueryGetData(&fe)) {
+ *_retval = true; // found it!
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // Ok, so we have a single object. Check to see if has the correct
+ // data type. Since this can come from an outside app, we also
+ // need to see if we need to perform text->unicode conversion if
+ // the client asked for unicode and it wasn't available.
+ format = nsClipboard::GetFormat(aDataFlavor);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK) {
+ *_retval = true; // found it!
+ return NS_OK;
+ }
+
+ // We haven't found the exact flavor the client asked for, but
+ // maybe we can still find it from something else that's in the
+ // data object.
+ if (strcmp(aDataFlavor, kTextMime) == 0) {
+ // If unicode wasn't there, it might exist as CF_TEXT, client asked
+ // for unicode and it wasn't present, check if we
+ // have CF_TEXT. We'll handle the actual data substitution in
+ // the data object.
+ SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK) {
+ *_retval = true; // found it!
+ }
+ return NS_OK;
+ }
+
+ if (strcmp(aDataFlavor, kURLMime) == 0) {
+ // client asked for a url and it wasn't present, but if we
+ // have a file, then we have a URL to give them (the path, or
+ // the internal URL if an InternetShortcut).
+ format = nsClipboard::GetFormat(kFileMime);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK) {
+ *_retval = true; // found it!
+ }
+ return NS_OK;
+ }
+
+ if (format == CF_HDROP) {
+ // Dragging a link from browsers creates both a URL and a FILE which is a
+ // *.url shortcut in the data object. The file is useful when dropping in
+ // Windows Explorer to create a internet shortcut. But when dropping in the
+ // browser, users do not expect to have this file. So do not try to look up
+ // virtal file if there is a URL in the data object.
+ format = nsClipboard::GetFormat(kURLMime);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (mDataObject->QueryGetData(&fe) == S_OK) {
+ return NS_OK;
+ }
+
+ // If the client wants a file, maybe we find a virtual file.
+ format = nsClipboard::GetClipboardFileDescriptorFormatW();
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (mDataObject->QueryGetData(&fe) == S_OK) {
+ *_retval = true; // found it!
+ }
+
+ // XXX should we fall back to CFSTR_FILEDESCRIPTORA?
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+//
+// IsCollectionObject
+//
+// Determine if this is a single |IDataObject| or one of our private
+// collection objects. We know the difference because our collection
+// object will respond to supporting the private |MULTI_MIME| format.
+//
+bool nsDragService::IsCollectionObject(IDataObject* inDataObj) {
+ bool isCollection = false;
+
+ // setup the format object to ask for the MULTI_MIME format. We only
+ // need to do this once
+ static UINT sFormat = 0;
+ static FORMATETC sFE;
+ if (!sFormat) {
+ sFormat = nsClipboard::GetFormat(MULTI_MIME);
+ SET_FORMATETC(sFE, sFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ }
+
+ // ask the object if it supports it. If yes, we have a collection
+ // object
+ if (inDataObj->QueryGetData(&sFE) == S_OK) isCollection = true;
+
+ return isCollection;
+
+} // IsCollectionObject
+
+//
+// EndDragSession
+//
+// Override the default to make sure that we release the data object
+// when the drag ends. It seems that OLE doesn't like to let apps quit
+// w/out crashing when we're still holding onto their data
+//
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ // Bug 100180: If we've got mouse events captured, make sure we release it -
+ // that way, if we happen to call EndDragSession before diving into a nested
+ // event loop, we can still respond to mouse events.
+ if (::GetCapture()) {
+ ::ReleaseCapture();
+ }
+
+ nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
+ NS_IF_RELEASE(mDataObject);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
+ int32_t aImageY) {
+ if (!mDataObject) {
+ return NS_OK;
+ }
+
+ nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
+
+ IDragSourceHelper* pdsh;
+ if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IDragSourceHelper,
+ (void**)&pdsh))) {
+ SHDRAGIMAGE sdi;
+ if (CreateDragImage(mSourceNode, Nothing(), &sdi)) {
+ nsNativeDragTarget::DragImageChanged();
+ if (FAILED(pdsh->InitializeFromBitmap(&sdi, mDataObject)))
+ DeleteObject(sdi.hbmpDragImage);
+ }
+ pdsh->Release();
+ }
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h
new file mode 100644
index 0000000000..5a86a74f76
--- /dev/null
+++ b/widget/windows/nsDragService.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsDragService_h__
+#define nsDragService_h__
+
+#include "nsBaseDragService.h"
+#include <windows.h>
+#include <shlobj.h>
+
+struct IDataObject;
+class nsDataObjCollection;
+
+/**
+ * Native Win32 DragService wrapper
+ */
+
+class nsDragService : public nsBaseDragService {
+ public:
+ nsDragService();
+ virtual ~nsDragService();
+
+ // nsBaseDragService
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable* aTransferable, uint32_t anItem) override;
+ NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override;
+ NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor,
+ bool* _retval) override;
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag,
+ uint32_t aKeyModifiers) override;
+ NS_IMETHOD UpdateDragImage(nsINode* aImage, int32_t aImageX,
+ int32_t aImageY) override;
+
+ // native impl.
+ NS_IMETHOD SetIDataObject(IDataObject* aDataObj);
+ MOZ_CAN_RUN_SCRIPT nsresult StartInvokingDragSession(IDataObject* aDataObj,
+ uint32_t aActionType);
+
+ // A drop occurred within the application vs. outside of it.
+ void SetDroppedLocal();
+
+ IDataObject* GetDataObject() { return mDataObject; }
+
+ protected:
+ nsDataObjCollection* GetDataObjCollection(IDataObject* aDataObj);
+
+ // determine if we have a single data object or one of our private
+ // collections
+ bool IsCollectionObject(IDataObject* inDataObj);
+
+ // Create a bitmap for drag operations
+ bool CreateDragImage(nsINode* aDOMNode,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ SHDRAGIMAGE* psdi);
+
+ IDataObject* mDataObject;
+ bool mSentLocalDropEvent;
+};
+
+#endif // nsDragService_h__
diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp
new file mode 100644
index 0000000000..310c54bb40
--- /dev/null
+++ b/widget/windows/nsFilePicker.cpp
@@ -0,0 +1,1078 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsFilePicker.h"
+
+#include <cderr.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <sysinfoapi.h>
+#include <winerror.h>
+#include <winuser.h>
+#include <utility>
+
+#include "ContentAnalysis.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsCRT.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIContentAnalysis.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+
+#include "mozilla/glean/GleanMetrics.h"
+
+#include "mozilla/widget/filedialog/WinFileDialogCommands.h"
+#include "mozilla/widget/filedialog/WinFileDialogParent.h"
+
+using mozilla::UniquePtr;
+
+using namespace mozilla::widget;
+
+UniquePtr<char16_t[], nsFilePicker::FreeDeleter>
+ nsFilePicker::sLastUsedUnicodeDirectory;
+
+using mozilla::LogLevel;
+
+#define MAX_EXTENSION_LENGTH 10
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+// Manages matching PickerOpen/PickerClosed calls on the parent widget.
+class AutoWidgetPickerState {
+ static RefPtr<nsWindow> GetWindowForWidget(nsIWidget* aWidget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aWidget) {
+ return nullptr;
+ }
+ HWND hwnd = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
+ return RefPtr(WinUtils::GetNSWindowPtr(hwnd));
+ }
+
+ public:
+ explicit AutoWidgetPickerState(nsIWidget* aWidget)
+ : mWindow(GetWindowForWidget(aWidget)) {
+ MOZ_ASSERT(mWindow);
+ if (mWindow) mWindow->PickerOpen();
+ }
+ ~AutoWidgetPickerState() {
+ // may be null if moved-from
+ if (mWindow) mWindow->PickerClosed();
+ }
+
+ AutoWidgetPickerState(AutoWidgetPickerState const&) = delete;
+ AutoWidgetPickerState(AutoWidgetPickerState&& that) noexcept = default;
+
+ private:
+ RefPtr<nsWindow> mWindow;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker
+
+nsFilePicker::nsFilePicker() : mSelectedType(1) {}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+NS_IMETHODIMP nsFilePicker::Init(
+ mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ nsIFilePicker::Mode aMode,
+ mozilla::dom::BrowsingContext* aBrowsingContext) {
+ // Don't attempt to open a real file-picker in headless mode.
+ if (gfxPlatform::IsHeadless()) {
+ return nsresult::NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent);
+ nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
+ mLoadContext = do_QueryInterface(docShell);
+
+ return nsBaseFilePicker::Init(aParent, aTitle, aMode, aBrowsingContext);
+}
+
+namespace mozilla::detail {
+// Boilerplate for remotely showing a file dialog.
+template <typename ActionType,
+ typename ReturnType = typename decltype(std::declval<ActionType>()(
+ nullptr))::element_type::ResolveValueType>
+static auto ShowRemote(ActionType&& action)
+ -> RefPtr<MozPromise<ReturnType, HRESULT, true>> {
+ using RetPromise = MozPromise<ReturnType, HRESULT, true>;
+
+ constexpr static const auto fail = []() {
+ return RetPromise::CreateAndReject(E_FAIL, __PRETTY_FUNCTION__);
+ };
+
+ auto mgr = mozilla::ipc::UtilityProcessManager::GetSingleton();
+ if (!mgr) {
+ MOZ_ASSERT(false);
+ return fail();
+ }
+
+ auto wfda = mgr->CreateWinFileDialogActor();
+ if (!wfda) {
+ return fail();
+ }
+
+ using mozilla::widget::filedialog::sLogFileDialog;
+
+ return wfda->Then(
+ mozilla::GetMainThreadSerialEventTarget(),
+ "nsFilePicker ShowRemote acquire",
+ [action = std::forward<ActionType>(action)](
+ filedialog::ProcessProxy const& p) -> RefPtr<RetPromise> {
+ MOZ_LOG(sLogFileDialog, LogLevel::Info,
+ ("nsFilePicker ShowRemote first callback: p = [%p]", p.get()));
+
+ // false positive: not actually redundant
+ // NOLINTNEXTLINE(readability-redundant-smartptr-get)
+ return action(p.get())->Then(
+ mozilla::GetMainThreadSerialEventTarget(),
+ "nsFilePicker ShowRemote call",
+ [p](ReturnType ret) {
+ return RetPromise::CreateAndResolve(std::move(ret),
+ __PRETTY_FUNCTION__);
+ },
+ [](mozilla::ipc::ResponseRejectReason error) {
+ MOZ_LOG(sLogFileDialog, LogLevel::Error,
+ ("IPC call rejected: %zu", size_t(error)));
+ return fail();
+ });
+ },
+ [](nsresult error) -> RefPtr<RetPromise> {
+ MOZ_LOG(sLogFileDialog, LogLevel::Error,
+ ("could not acquire WinFileDialog: %zu", size_t(error)));
+ return fail();
+ });
+}
+
+// fd_async
+//
+// Wrapper-namespace for the AsyncExecute() and AsyncAll() functions.
+namespace fd_async {
+
+// Implementation details of, specifically, the AsyncExecute() and AsyncAll()
+// functions.
+namespace details {
+// Helper for generically copying ordinary types and nsTArray (which lacks a
+// copy constructor) in the same breath.
+template <typename T>
+static T Copy(T const& val) {
+ return val;
+}
+template <typename T>
+static nsTArray<T> Copy(nsTArray<T> const& arr) {
+ return arr.Clone();
+}
+
+// The possible execution strategies of AsyncExecute.
+enum Strategy { Local, Remote, RemoteWithFallback };
+
+// Decode the relevant preference to determine the desired execution-
+// strategy.
+static Strategy GetStrategy() {
+ int32_t const pref =
+ mozilla::StaticPrefs::widget_windows_utility_process_file_picker();
+ switch (pref) {
+ case -1:
+ return Local;
+ case 2:
+ return Remote;
+ case 1:
+ return RemoteWithFallback;
+
+ default:
+#ifdef NIGHTLY_BUILD
+ // on Nightly builds, fall back to local on failure
+ return RemoteWithFallback;
+#else
+ // on release and beta, remain local-only for now
+ return Local;
+#endif
+ }
+};
+
+template <typename T>
+class AsyncAllIterator final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(AsyncAllIterator)
+ AsyncAllIterator(
+ nsTArray<T> aItems,
+ std::function<
+ RefPtr<mozilla::MozPromise<bool, nsresult, true>>(const T& item)>
+ aPredicate,
+ RefPtr<mozilla::MozPromise<bool, nsresult, true>::Private> aPromise)
+ : mItems(std::move(aItems)),
+ mNextIndex(0),
+ mPredicate(std::move(aPredicate)),
+ mPromise(std::move(aPromise)) {}
+
+ void StartIterating() { ContinueIterating(); }
+
+ private:
+ ~AsyncAllIterator() = default;
+ void ContinueIterating() {
+ if (mNextIndex >= mItems.Length()) {
+ mPromise->Resolve(true, __func__);
+ return;
+ }
+ mPredicate(mItems.ElementAt(mNextIndex))
+ ->Then(
+ mozilla::GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}](bool aResult) {
+ if (!aResult) {
+ self->mPromise->Resolve(false, __func__);
+ return;
+ }
+ ++self->mNextIndex;
+ self->ContinueIterating();
+ },
+ [self = RefPtr{this}](nsresult aError) {
+ self->mPromise->Reject(aError, __func__);
+ });
+ }
+ nsTArray<T> mItems;
+ uint32_t mNextIndex;
+ std::function<RefPtr<mozilla::MozPromise<bool, nsresult, true>>(
+ const T& item)>
+ mPredicate;
+ RefPtr<mozilla::MozPromise<bool, nsresult, true>::Private> mPromise;
+};
+
+namespace telemetry {
+static uint32_t Delta(uint64_t tb, uint64_t ta) {
+ // FILETIMEs are 100ns intervals; we reduce that to 1ms.
+ // (`u32::max()` milliseconds is roughly 47.91 days.)
+ return uint32_t((tb - ta) / 10'000);
+};
+static nsCString HexString(HRESULT val) {
+ return nsPrintfCString("%08lX", val);
+};
+
+static void RecordSuccess(uint64_t (&&time)[2]) {
+ auto [t0, t1] = time;
+
+ namespace glean_fd = mozilla::glean::file_dialog;
+ glean_fd::FallbackExtra extra{
+ .hresultLocal = Nothing(),
+ .hresultRemote = Nothing(),
+ .succeeded = Some(true),
+ .timeLocal = Nothing(),
+ .timeRemote = Some(Delta(t1, t0)),
+ };
+ glean_fd::fallback.Record(Some(extra));
+}
+
+static void RecordFailure(uint64_t (&&time)[3], HRESULT hrRemote,
+ HRESULT hrLocal) {
+ auto [t0, t1, t2] = time;
+
+ {
+ namespace glean_fd = mozilla::glean::file_dialog;
+ glean_fd::FallbackExtra extra{
+ .hresultLocal = Some(HexString(hrLocal)),
+ .hresultRemote = Some(HexString(hrRemote)),
+ .succeeded = Some(false),
+ .timeLocal = Some(Delta(t2, t1)),
+ .timeRemote = Some(Delta(t1, t0)),
+ };
+ glean_fd::fallback.Record(Some(extra));
+ }
+}
+
+} // namespace telemetry
+} // namespace details
+
+// Invoke either or both of a "do locally" and "do remotely" function with the
+// provided arguments, depending on the relevant preference-value and whether
+// or not the remote version fails.
+//
+// Both functions must be asynchronous, returning a `RefPtr<MozPromise<...>>`.
+// "Failure" is defined as the promise being rejected.
+template <typename Fn1, typename Fn2, typename... Args>
+static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args)
+ -> std::invoke_result_t<Fn1, Args...> {
+ using namespace details;
+
+ static_assert(std::is_same_v<std::invoke_result_t<Fn1, Args...>,
+ std::invoke_result_t<Fn2, Args...>>);
+ using PromiseT = typename std::invoke_result_t<Fn1, Args...>::element_type;
+
+ constexpr static char kFunctionName[] = "LocalAndOrRemote::AsyncExecute";
+
+ switch (GetStrategy()) {
+ case Local:
+ return local(args...);
+
+ case Remote:
+ return remote(args...);
+
+ case RemoteWithFallback:
+ // more complicated; continue below
+ break;
+ }
+
+ // capture time for telemetry
+ constexpr static const auto GetTime = []() -> uint64_t {
+ FILETIME t;
+ ::GetSystemTimeAsFileTime(&t);
+ return (uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime;
+ };
+ uint64_t const t0 = GetTime();
+
+ return remote(args...)->Then(
+ NS_GetCurrentThread(), kFunctionName,
+ [t0](typename PromiseT::ResolveValueType result) -> RefPtr<PromiseT> {
+ // success; stop here
+ auto const t1 = GetTime();
+ // record success
+ telemetry::RecordSuccess({t0, t1});
+ return PromiseT::CreateAndResolve(result, kFunctionName);
+ },
+ // initialized lambda pack captures are C++20 (clang 9, gcc 9);
+ // `make_tuple` is just a C++17 workaround
+ [=, tuple = std::make_tuple(Copy(args)...)](
+ typename PromiseT::RejectValueType err) mutable -> RefPtr<PromiseT> {
+ // failure; record time
+ auto const t1 = GetTime();
+ HRESULT const hrRemote = err;
+
+ // retry locally...
+ auto p0 = std::apply(local, std::move(tuple));
+ // ...then record the telemetry event
+ return p0->Then(
+ NS_GetCurrentThread(), kFunctionName,
+ [t0, t1,
+ hrRemote](typename PromiseT::ResolveOrRejectValue const& val)
+ -> RefPtr<PromiseT> {
+ auto const t2 = GetTime();
+ HRESULT const hrLocal = val.IsReject() ? val.RejectValue() : S_OK;
+ telemetry::RecordFailure({t0, t1, t2}, hrRemote, hrLocal);
+
+ return PromiseT::CreateAndResolveOrReject(val, kFunctionName);
+ });
+ });
+}
+
+// Asynchronously invokes `aPredicate` on each member of `aItems`.
+// Yields `false` (and stops immediately) if any invocation of
+// `predicate` yielded `false`; otherwise yields `true`.
+template <typename T>
+static RefPtr<mozilla::MozPromise<bool, nsresult, true>> AsyncAll(
+ nsTArray<T> aItems,
+ std::function<
+ RefPtr<mozilla::MozPromise<bool, nsresult, true>>(const T& item)>
+ aPredicate) {
+ auto promise =
+ mozilla::MakeRefPtr<mozilla::MozPromise<bool, nsresult, true>::Private>(
+ __func__);
+ auto iterator = mozilla::MakeRefPtr<details::AsyncAllIterator<T>>(
+ std::move(aItems), aPredicate, promise);
+ iterator->StartIterating();
+ return promise;
+}
+} // namespace fd_async
+
+using fd_async::AsyncAll;
+using fd_async::AsyncExecute;
+
+} // namespace mozilla::detail
+
+/* static */
+nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerRemote(
+ HWND parent, filedialog::FileDialogType type,
+ nsTArray<filedialog::Command> const& commands) {
+ using mozilla::widget::filedialog::sLogFileDialog;
+ return mozilla::detail::ShowRemote(
+ [parent, type,
+ commands = commands.Clone()](filedialog::WinFileDialogParent* p) {
+ MOZ_LOG(sLogFileDialog, LogLevel::Info,
+ ("%s: p = [%p]", __PRETTY_FUNCTION__, p));
+ return p->SendShowFileDialog((uintptr_t)parent, type, commands);
+ });
+}
+
+/* static */
+nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerRemote(
+ HWND parent, nsTArray<filedialog::Command> const& commands) {
+ using mozilla::widget::filedialog::sLogFileDialog;
+ return mozilla::detail::ShowRemote([parent, commands = commands.Clone()](
+ filedialog::WinFileDialogParent* p) {
+ MOZ_LOG(sLogFileDialog, LogLevel::Info,
+ ("%s: p = [%p]", __PRETTY_FUNCTION__, p));
+ return p->SendShowFolderDialog((uintptr_t)parent, commands);
+ });
+}
+
+/* static */
+nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerLocal(
+ HWND parent, filedialog::FileDialogType type,
+ nsTArray<filedialog::Command> const& commands) {
+ return filedialog::SpawnFilePicker(parent, type, commands.Clone());
+}
+
+/* static */
+nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerLocal(
+ HWND parent, nsTArray<filedialog::Command> const& commands) {
+ return filedialog::SpawnFolderPicker(parent, commands.Clone());
+}
+
+/*
+ * Folder picker invocation
+ */
+
+/*
+ * Show a folder picker.
+ *
+ * @param aInitialDir The initial directory. The last-used directory will be
+ * used if left blank.
+ * @return A promise which:
+ * - resolves to true if a file was selected successfully (in which
+ * case mUnicodeFile will be updated);
+ * - resolves to false if the dialog was cancelled by the user;
+ * - is rejected with the associated HRESULT if some error occurred.
+ */
+RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFolderPicker(
+ const nsString& aInitialDir) {
+ using Promise = mozilla::MozPromise<bool, HRESULT, true>;
+ constexpr static auto Ok = [](bool val) {
+ return Promise::CreateAndResolve(val, "nsFilePicker::ShowFolderPicker");
+ };
+ constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
+ return Promise::CreateAndReject(val, "nsFilePicker::ShowFolderPicker");
+ };
+
+ namespace fd = ::mozilla::widget::filedialog;
+ nsTArray<fd::Command> commands = {
+ fd::SetOptions(FOS_PICKFOLDERS),
+ fd::SetTitle(mTitle),
+ };
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ commands.AppendElement(fd::SetOkButtonLabel(mOkButtonLabel));
+ }
+
+ if (!aInitialDir.IsEmpty()) {
+ commands.AppendElement(fd::SetFolder(aInitialDir));
+ }
+
+ ScopedRtlShimWindow shim(mParentWidget.get());
+ AutoWidgetPickerState awps(mParentWidget);
+
+ return mozilla::detail::AsyncExecute(&ShowFolderPickerLocal,
+ &ShowFolderPickerRemote, shim.get(),
+ commands)
+ ->Then(
+ NS_GetCurrentThread(), __PRETTY_FUNCTION__,
+ [self = RefPtr(this), shim = std::move(shim),
+ awps = std::move(awps)](Maybe<nsString> val) {
+ if (val) {
+ self->mUnicodeFile = val.extract();
+ return Ok(true);
+ }
+ return Ok(false);
+ },
+ [](HRESULT err) {
+ NS_WARNING("ShowFolderPicker failed");
+ return NotOk(err);
+ });
+}
+
+/*
+ * File open and save picker invocation
+ */
+
+/*
+ * Show a file picker.
+ *
+ * @param aInitialDir The initial directory. The last-used directory will be
+ * used if left blank.
+ * @return A promise which:
+ * - resolves to true if one or more files were selected successfully
+ * (in which case mUnicodeFile and/or mFiles will be updated);
+ * - resolves to false if the dialog was cancelled by the user;
+ * - is rejected with the associated HRESULT if some error occurred.
+ */
+RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker(
+ const nsString& aInitialDir) {
+ AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER);
+
+ using Promise = mozilla::MozPromise<bool, HRESULT, true>;
+ constexpr static auto Ok = [](bool val) {
+ return Promise::CreateAndResolve(val, "nsFilePicker::ShowFilePicker");
+ };
+ constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
+ return Promise::CreateAndReject(val, "nsFilePicker::ShowFilePicker");
+ };
+
+ namespace fd = ::mozilla::widget::filedialog;
+ nsTArray<fd::Command> commands;
+ // options
+ {
+ FILEOPENDIALOGOPTIONS fos = 0;
+ fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM;
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ fos |= FOS_DONTADDTORECENT;
+ }
+
+ // mode specific
+ switch (mMode) {
+ case modeOpen:
+ fos |= FOS_FILEMUSTEXIST;
+ break;
+
+ case modeOpenMultiple:
+ fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
+ break;
+
+ case modeSave:
+ fos |= FOS_NOREADONLYRETURN;
+ // Don't follow shortcuts when saving a shortcut, this can be used
+ // to trick users (bug 271732)
+ if (IsDefaultPathLink()) fos |= FOS_NODEREFERENCELINKS;
+ break;
+
+ case modeGetFolder:
+ MOZ_ASSERT(false, "file-picker opened in directory-picker mode");
+ return NotOk(E_FAIL);
+ }
+
+ commands.AppendElement(fd::SetOptions(fos));
+ }
+
+ // initial strings
+
+ // title
+ commands.AppendElement(fd::SetTitle(mTitle));
+
+ // default filename
+ if (!mDefaultFilename.IsEmpty()) {
+ // Prevent the shell from expanding environment variables by removing
+ // the % characters that are used to delimit them.
+ nsAutoString sanitizedFilename(mDefaultFilename);
+ sanitizedFilename.ReplaceChar('%', '_');
+
+ commands.AppendElement(fd::SetFileName(sanitizedFilename));
+ }
+
+ // default extension to append to new files
+ if (!mDefaultExtension.IsEmpty()) {
+ // We don't want environment variables expanded in the extension either.
+ nsAutoString sanitizedExtension(mDefaultExtension);
+ sanitizedExtension.ReplaceChar('%', '_');
+
+ commands.AppendElement(fd::SetDefaultExtension(sanitizedExtension));
+ } else if (IsDefaultPathHtml()) {
+ commands.AppendElement(fd::SetDefaultExtension(u"html"_ns));
+ }
+
+ // initial location
+ if (!aInitialDir.IsEmpty()) {
+ commands.AppendElement(fd::SetFolder(aInitialDir));
+ }
+
+ // filter types and the default index
+ if (!mFilterList.IsEmpty()) {
+ nsTArray<fd::ComDlgFilterSpec> fileTypes;
+ for (auto const& filter : mFilterList) {
+ fileTypes.EmplaceBack(filter.title, filter.filter);
+ }
+ commands.AppendElement(fd::SetFileTypes(std::move(fileTypes)));
+ commands.AppendElement(fd::SetFileTypeIndex(mSelectedType));
+ }
+
+ ScopedRtlShimWindow shim(mParentWidget.get());
+ AutoWidgetPickerState awps(mParentWidget);
+
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ auto type = mMode == modeSave ? FileDialogType::Save : FileDialogType::Open;
+
+ auto promise = mozilla::detail::AsyncExecute(
+ &ShowFilePickerLocal, &ShowFilePickerRemote, shim.get(), type, commands);
+
+ return promise->Then(
+ mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [self = RefPtr(this), mode = mMode, shim = std::move(shim),
+ awps = std::move(awps)](Maybe<Results> res_opt) {
+ if (!res_opt) {
+ return Ok(false);
+ }
+ auto result = res_opt.extract();
+
+ // Remember what filter type the user selected
+ self->mSelectedType = int32_t(result.selectedFileTypeIndex());
+
+ auto const& paths = result.paths();
+
+ // single selection
+ if (mode != modeOpenMultiple) {
+ if (!paths.IsEmpty()) {
+ MOZ_ASSERT(paths.Length() == 1);
+ self->mUnicodeFile = paths[0];
+ return Ok(true);
+ }
+ return Ok(false);
+ }
+
+ // multiple selection
+ for (auto const& str : paths) {
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(NS_NewLocalFile(str, false, getter_AddRefs(file)))) {
+ self->mFiles.AppendObject(file);
+ }
+ }
+
+ return Ok(true);
+ },
+ [](HRESULT err) {
+ NS_WARNING("ShowFilePicker failed");
+ return NotOk(err);
+ });
+}
+
+void nsFilePicker::ClearFiles() {
+ mUnicodeFile.Truncate();
+ mFiles.Clear();
+}
+
+namespace {
+class GetFilesInDirectoryCallback final
+ : public mozilla::dom::GetFilesCallback {
+ public:
+ explicit GetFilesInDirectoryCallback(
+ RefPtr<mozilla::MozPromise<nsTArray<mozilla::PathString>, nsresult,
+ true>::Private>
+ aPromise)
+ : mPromise(std::move(aPromise)) {}
+ void Callback(
+ nsresult aStatus,
+ const FallibleTArray<RefPtr<mozilla::dom::BlobImpl>>& aBlobImpls) {
+ if (NS_FAILED(aStatus)) {
+ mPromise->Reject(aStatus, __func__);
+ return;
+ }
+ nsTArray<mozilla::PathString> filePaths;
+ filePaths.SetCapacity(aBlobImpls.Length());
+ for (const auto& blob : aBlobImpls) {
+ if (blob->IsFile()) {
+ mozilla::PathString pathString;
+ mozilla::ErrorResult error;
+ blob->GetMozFullPathInternal(pathString, error);
+ nsresult rv = error.StealNSResult();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPromise->Reject(rv, __func__);
+ return;
+ }
+ filePaths.AppendElement(pathString);
+ } else {
+ NS_WARNING("Got a non-file blob, can't do content analysis on it");
+ }
+ }
+ mPromise->Resolve(std::move(filePaths), __func__);
+ }
+
+ private:
+ RefPtr<mozilla::MozPromise<nsTArray<mozilla::PathString>, nsresult,
+ true>::Private>
+ mPromise;
+};
+} // anonymous namespace
+
+RefPtr<nsFilePicker::ContentAnalysisResponse>
+nsFilePicker::CheckContentAnalysisService() {
+ nsresult rv;
+ nsCOMPtr<nsIContentAnalysis> contentAnalysis =
+ mozilla::components::nsIContentAnalysis::Service(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__);
+ }
+ bool contentAnalysisIsActive = false;
+ rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__);
+ }
+ if (!contentAnalysisIsActive) {
+ return nsFilePicker::ContentAnalysisResponse::CreateAndResolve(true,
+ __func__);
+ }
+
+ nsCOMPtr<nsIURI> uri = mBrowsingContext->Canonical()->GetCurrentURI();
+
+ auto processOneItem = [self = RefPtr{this},
+ contentAnalysis = std::move(contentAnalysis),
+ uri =
+ std::move(uri)](const mozilla::PathString& aItem) {
+ nsCString emptyDigestString;
+ auto* windowGlobal =
+ self->mBrowsingContext->Canonical()->GetCurrentWindowGlobal();
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest(
+ new mozilla::contentanalysis::ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eFileAttached, aItem, true,
+ std::move(emptyDigestString), uri,
+ nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
+ windowGlobal));
+
+ auto promise =
+ mozilla::MakeRefPtr<nsFilePicker::ContentAnalysisResponse::Private>(
+ __func__);
+ auto contentAnalysisCallback =
+ mozilla::MakeRefPtr<mozilla::contentanalysis::ContentAnalysisCallback>(
+ [promise](nsIContentAnalysisResponse* aResponse) {
+ promise->Resolve(aResponse->GetShouldAllowContent(), __func__);
+ },
+ [promise](nsresult aError) { promise->Reject(aError, __func__); });
+
+ nsresult rv = contentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest, /* aAutoAcknowledge */ true,
+ contentAnalysisCallback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ }
+ return promise;
+ };
+
+ // Since getting the files to analyze might be asynchronous, use a MozPromise
+ // to unify the logic below.
+ auto getFilesToAnalyzePromise = mozilla::MakeRefPtr<mozilla::MozPromise<
+ nsTArray<mozilla::PathString>, nsresult, true>::Private>(__func__);
+ if (mMode == modeGetFolder) {
+ nsCOMPtr<nsISupports> tmp;
+ nsresult rv = GetDomFileOrDirectory(getter_AddRefs(tmp));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ getFilesToAnalyzePromise->Reject(rv, __func__);
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv,
+ __func__);
+ }
+ auto* directory = static_cast<mozilla::dom::Directory*>(tmp.get());
+ mozilla::dom::OwningFileOrDirectory owningDirectory;
+ owningDirectory.SetAsDirectory() = directory;
+ nsTArray<mozilla::dom::OwningFileOrDirectory> directoryArray{
+ std::move(owningDirectory)};
+
+ mozilla::ErrorResult error;
+ RefPtr<mozilla::dom::GetFilesHelper> helper =
+ mozilla::dom::GetFilesHelper::Create(directoryArray, true, error);
+ rv = error.StealNSResult();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ getFilesToAnalyzePromise->Reject(rv, __func__);
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv,
+ __func__);
+ }
+ auto getFilesCallback = mozilla::MakeRefPtr<GetFilesInDirectoryCallback>(
+ getFilesToAnalyzePromise);
+ helper->AddCallback(getFilesCallback);
+ } else {
+ nsCOMArray<nsIFile> files;
+ if (!mUnicodeFile.IsEmpty()) {
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ getFilesToAnalyzePromise->Reject(rv, __func__);
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv,
+ __func__);
+ }
+ files.AppendElement(file);
+ } else {
+ files.AppendElements(mFiles);
+ }
+ nsTArray<mozilla::PathString> paths(files.Length());
+ std::transform(files.begin(), files.end(), MakeBackInserter(paths),
+ [](auto* entry) { return entry->NativePath(); });
+ getFilesToAnalyzePromise->Resolve(std::move(paths), __func__);
+ }
+
+ return getFilesToAnalyzePromise->Then(
+ mozilla::GetMainThreadSerialEventTarget(), __func__,
+ [processOneItem](nsTArray<mozilla::PathString> aPaths) mutable {
+ return mozilla::detail::AsyncAll<mozilla::PathString>(std::move(aPaths),
+ processOneItem);
+ },
+ [](nsresult aError) {
+ return nsFilePicker::ContentAnalysisResponse::CreateAndReject(aError,
+ __func__);
+ });
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker impl.
+
+nsresult nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ if (MaybeBlockFilePicker(aCallback)) {
+ return NS_OK;
+ }
+
+ // Don't attempt to open a real file-picker in headless mode.
+ if (gfxPlatform::IsHeadless()) {
+ return nsresult::NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoString initialDir;
+ if (mDisplayDirectory) mDisplayDirectory->GetPath(initialDir);
+
+ // If no display directory, re-use the last one.
+ if (initialDir.IsEmpty()) {
+ // Allocate copy of last used dir.
+ initialDir = sLastUsedUnicodeDirectory.get();
+ }
+
+ // Clear previous file selections
+ ClearFiles();
+
+ auto promise = mMode == modeGetFolder ? ShowFolderPicker(initialDir)
+ : ShowFilePicker(initialDir);
+
+ auto p2 = promise->Then(
+ mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [self = RefPtr(this),
+ callback = RefPtr(aCallback)](bool selectionMade) -> void {
+ if (!selectionMade) {
+ callback->Done(ResultCode::returnCancel);
+ return;
+ }
+
+ self->RememberLastUsedDirectory();
+
+ nsIFilePicker::ResultCode retValue = ResultCode::returnOK;
+
+ if (self->mMode == modeSave) {
+ // Windows does not return resultReplace; we must check whether the
+ // file already exists.
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_NewLocalFile(self->mUnicodeFile, false, getter_AddRefs(file));
+
+ bool flag = false;
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(file->Exists(&flag)) && flag) {
+ retValue = ResultCode::returnReplace;
+ }
+ }
+
+ if (self->mBrowsingContext && !self->mBrowsingContext->IsChrome() &&
+ self->mMode != modeSave && retValue != ResultCode::returnCancel) {
+ self->CheckContentAnalysisService()->Then(
+ mozilla::GetMainThreadSerialEventTarget(), __func__,
+ [retValue, callback, self = RefPtr{self}](bool aAllowContent) {
+ if (aAllowContent) {
+ callback->Done(retValue);
+ } else {
+ self->ClearFiles();
+ callback->Done(ResultCode::returnCancel);
+ }
+ },
+ [callback, self = RefPtr{self}](nsresult aError) {
+ self->ClearFiles();
+ callback->Done(ResultCode::returnCancel);
+ });
+ return;
+ }
+
+ callback->Done(retValue);
+ },
+ [callback = RefPtr(aCallback)](HRESULT err) {
+ using mozilla::widget::filedialog::sLogFileDialog;
+ MOZ_LOG(sLogFileDialog, LogLevel::Error,
+ ("nsFilePicker: Show failed with hr=0x%08lX", err));
+ callback->Done(ResultCode::returnCancel);
+ });
+
+ return NS_OK;
+}
+
+nsresult nsFilePicker::Show(nsIFilePicker::ResultCode* aReturnVal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ if (mUnicodeFile.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFileURL(nsIURI** aFileURL) {
+ *aFileURL = nullptr;
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetFile(getter_AddRefs(file));
+ if (!file) return rv;
+
+ return NS_NewFileURI(aFileURL, file);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+ return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
+}
+
+// Get the file + path
+NS_IMETHODIMP
+nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) {
+ mDefaultFilePath = aString;
+
+ // First, make sure the file name is not too long.
+ int32_t nameLength;
+ int32_t nameIndex = mDefaultFilePath.RFind(u"\\");
+ if (nameIndex == kNotFound)
+ nameIndex = 0;
+ else
+ nameIndex++;
+ nameLength = mDefaultFilePath.Length() - nameIndex;
+ mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex));
+
+ if (nameLength > MAX_PATH) {
+ int32_t extIndex = mDefaultFilePath.RFind(u".");
+ if (extIndex == kNotFound) extIndex = mDefaultFilePath.Length();
+
+ // Let's try to shave the needed characters from the name part.
+ int32_t charsToRemove = nameLength - MAX_PATH;
+ if (extIndex - nameIndex >= charsToRemove) {
+ mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove);
+ }
+ }
+
+ // Then, we need to replace illegal characters. At this stage, we cannot
+ // replace the backslash as the string might represent a file path.
+ mDefaultFilePath.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
+ mDefaultFilename.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseWinFilePicker::GetDefaultString(nsAString& aString) {
+ return NS_ERROR_FAILURE;
+}
+
+// The default extension to use for files
+NS_IMETHODIMP
+nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) {
+ aExtension = mDefaultExtension;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) {
+ mDefaultExtension = aExtension;
+ return NS_OK;
+}
+
+// Set the filter index
+NS_IMETHODIMP
+nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ // Windows' filter index is 1-based, we use a 0-based system.
+ *aFilterIndex = mSelectedType - 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ // Windows' filter index is 1-based, we use a 0-based system.
+ mSelectedType = aFilterIndex + 1;
+ return NS_OK;
+}
+
+void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
+ mParentWidget = aParent;
+ mTitle.Assign(aTitle);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ nsString sanitizedFilter(aFilter);
+ sanitizedFilter.ReplaceChar('%', '_');
+
+ if (sanitizedFilter == u"..apps"_ns) {
+ sanitizedFilter = u"*.exe;*.com"_ns;
+ } else {
+ sanitizedFilter.StripWhitespace();
+ if (sanitizedFilter == u"*"_ns) {
+ sanitizedFilter = u"*.*"_ns;
+ }
+ }
+ mFilterList.AppendElement(
+ Filter{.title = nsString(aTitle), .filter = std::move(sanitizedFilter)});
+ return NS_OK;
+}
+
+void nsFilePicker::RememberLastUsedDirectory() {
+ if (IsPrivacyModeEnabled()) {
+ // Don't remember the directory if private browsing was in effect
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_FAILED(NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)))) {
+ NS_WARNING("RememberLastUsedDirectory failed to init file path.");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ nsAutoString newDir;
+ if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
+ !(mDisplayDirectory = dir) ||
+ NS_FAILED(mDisplayDirectory->GetPath(newDir)) || newDir.IsEmpty()) {
+ NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
+ return;
+ }
+
+ sLastUsedUnicodeDirectory.reset(ToNewUnicode(newDir));
+}
+
+bool nsFilePicker::IsPrivacyModeEnabled() {
+ return mLoadContext && mLoadContext->UsePrivateBrowsing();
+}
+
+bool nsFilePicker::IsDefaultPathLink() {
+ NS_ConvertUTF16toUTF8 ext(mDefaultFilePath);
+ ext.Trim(" .", false, true); // watch out for trailing space and dots
+ ToLowerCase(ext);
+ return StringEndsWith(ext, ".lnk"_ns) || StringEndsWith(ext, ".pif"_ns) ||
+ StringEndsWith(ext, ".url"_ns);
+}
+
+bool nsFilePicker::IsDefaultPathHtml() {
+ int32_t extIndex = mDefaultFilePath.RFind(u".");
+ if (extIndex >= 0) {
+ nsAutoString ext;
+ mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex);
+ if (ext.LowerCaseEqualsLiteral(".htm") ||
+ ext.LowerCaseEqualsLiteral(".html") ||
+ ext.LowerCaseEqualsLiteral(".shtml"))
+ return true;
+ }
+ return false;
+}
diff --git a/widget/windows/nsFilePicker.h b/widget/windows/nsFilePicker.h
new file mode 100644
index 0000000000..1938b8bcb6
--- /dev/null
+++ b/widget/windows/nsFilePicker.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsFilePicker_h__
+#define nsFilePicker_h__
+
+#include <windows.h>
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/GetFilesHelper.h"
+#include "nsIContentAnalysis.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsdefs.h"
+#include <commdlg.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+class nsILoadContext;
+
+namespace mozilla::widget::filedialog {
+class Command;
+class Results;
+enum class FileDialogType : uint8_t;
+} // namespace mozilla::widget::filedialog
+
+class nsBaseWinFilePicker : public nsBaseFilePicker {
+ public:
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+
+ protected:
+ nsString mDefaultFilePath;
+ nsString mDefaultFilename;
+ nsString mDefaultExtension;
+};
+
+/**
+ * Native Windows FileSelector wrapper
+ */
+
+class nsFilePicker final : public nsBaseWinFilePicker {
+ virtual ~nsFilePicker() = default;
+
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ template <typename T>
+ using Result = mozilla::Result<T, HRESULT>;
+ template <typename Res>
+ using FPPromise = RefPtr<mozilla::MozPromise<Maybe<Res>, HRESULT, true>>;
+
+ using Command = mozilla::widget::filedialog::Command;
+ using Results = mozilla::widget::filedialog::Results;
+ using FileDialogType = mozilla::widget::filedialog::FileDialogType;
+
+ public:
+ nsFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ nsIFilePicker::Mode aMode,
+ mozilla::dom::BrowsingContext* aBrowsingContext) override;
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker)
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile** aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI** aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+
+ protected:
+ /* method from nsBaseFilePicker */
+ virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) override;
+ nsresult Show(nsIFilePicker::ResultCode* aReturnVal) override;
+ void GetFilterListArray(nsString& aFilterList);
+
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+
+ private:
+ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> ShowFolderPicker(
+ const nsString& aInitialDir);
+ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> ShowFilePicker(
+ const nsString& aInitialDir);
+
+ // Show the dialog out-of-process.
+ static FPPromise<Results> ShowFilePickerRemote(
+ HWND aParent, FileDialogType type, nsTArray<Command> const& commands);
+ static FPPromise<nsString> ShowFolderPickerRemote(
+ HWND aParent, nsTArray<Command> const& commands);
+
+ // Show the dialog in-process.
+ static FPPromise<Results> ShowFilePickerLocal(
+ HWND aParent, FileDialogType type, nsTArray<Command> const& commands);
+ static FPPromise<nsString> ShowFolderPickerLocal(
+ HWND aParent, nsTArray<Command> const& commands);
+
+ void ClearFiles();
+ using ContentAnalysisResponse = mozilla::MozPromise<bool, nsresult, true>;
+ RefPtr<ContentAnalysisResponse> CheckContentAnalysisService();
+
+ protected:
+ void RememberLastUsedDirectory();
+ bool IsPrivacyModeEnabled();
+ bool IsDefaultPathLink();
+ bool IsDefaultPathHtml();
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsString mTitle;
+ nsCString mFile;
+ int32_t mSelectedType;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mUnicodeFile;
+
+ struct FreeDeleter {
+ void operator()(void* aPtr) { ::free(aPtr); }
+ };
+ static mozilla::UniquePtr<char16_t[], FreeDeleter> sLastUsedUnicodeDirectory;
+
+ struct Filter {
+ nsString title;
+ nsString filter;
+ };
+ AutoTArray<Filter, 1> mFilterList;
+};
+
+#endif // nsFilePicker_h__
diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..01b126cd42
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -0,0 +1,916 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsLookAndFeel.h"
+#include <stdint.h>
+#include <windows.h>
+#include <shellapi.h>
+#include "nsStyleConsts.h"
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+#include "WindowsUIUtils.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/widget/WinRegistry.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static Maybe<nscolor> GetColorFromTheme(nsUXThemeClass cls, int32_t aPart,
+ int32_t aState, int32_t aPropId) {
+ COLORREF color;
+ HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState,
+ aPropId, &color);
+ if (hr == S_OK) {
+ return Some(COLOREF_2_NSRGB(color));
+ }
+ return Nothing();
+}
+
+static int32_t GetSystemParam(long flag, int32_t def) {
+ DWORD value;
+ return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def;
+}
+
+static bool SystemWantsDarkTheme() {
+ if (nsUXThemeData::IsHighContrastOn()) {
+ return LookAndFeel::IsDarkColor(
+ LookAndFeel::Color(StyleSystemColor::Window, ColorScheme::Light,
+ LookAndFeel::UseStandins::No));
+ }
+
+ WinRegistry::Key key(
+ HKEY_CURRENT_USER,
+ u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"_ns,
+ WinRegistry::KeyMode::QueryValue);
+ if (NS_WARN_IF(!key)) {
+ return false;
+ }
+ uint32_t light = key.GetValueAsDword(u"AppsUseLightTheme"_ns).valueOr(1);
+ return !light;
+}
+
+uint32_t nsLookAndFeel::SystemColorFilter() {
+ if (NS_WARN_IF(!mColorFilterWatcher)) {
+ return 0;
+ }
+
+ const auto& key = mColorFilterWatcher->GetKey();
+ if (!key.GetValueAsDword(u"Active"_ns).valueOr(0)) {
+ return 0;
+ }
+ return key.GetValueAsDword(u"FilterType"_ns).valueOr(0);
+}
+
+nsLookAndFeel::nsLookAndFeel() {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
+ WinUtils::IsTouchDeviceSupportPresent());
+}
+
+nsLookAndFeel::~nsLookAndFeel() = default;
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+/* virtual */
+void nsLookAndFeel::RefreshImpl() {
+ mInitialized = false; // Fetch system colors next time they're used.
+ nsXPLookAndFeel::RefreshImpl();
+}
+
+static bool UseNonNativeMenuColors(ColorScheme aScheme) {
+ return !LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme) ||
+ aScheme == ColorScheme::Dark;
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
+ nscolor& aColor) {
+ EnsureInit();
+
+ auto IsHighlightColor = [&] {
+ switch (aID) {
+ case ColorID::MozMenuhover:
+ return !UseNonNativeMenuColors(aScheme);
+ case ColorID::Highlight:
+ case ColorID::Selecteditem:
+ // We prefer the generic dark selection color if we don't have an
+ // explicit one.
+ return aScheme != ColorScheme::Dark || mDarkHighlight;
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ auto IsHighlightTextColor = [&] {
+ switch (aID) {
+ case ColorID::MozMenubarhovertext:
+ if (UseNonNativeMenuColors(aScheme)) {
+ return false;
+ }
+ [[fallthrough]];
+ case ColorID::MozMenuhovertext:
+ if (UseNonNativeMenuColors(aScheme)) {
+ return false;
+ }
+ return !mColorMenuHoverText;
+ case ColorID::Highlighttext:
+ case ColorID::Selecteditemtext:
+ // We prefer the generic dark selection color if we don't have an
+ // explicit one.
+ return aScheme != ColorScheme::Dark || mDarkHighlightText;
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ if (IsHighlightColor()) {
+ if (aScheme == ColorScheme::Dark && mDarkHighlight) {
+ aColor = *mDarkHighlight;
+ } else {
+ aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHT);
+ }
+ return NS_OK;
+ }
+
+ if (IsHighlightTextColor()) {
+ if (aScheme == ColorScheme::Dark && mDarkHighlightText) {
+ aColor = *mDarkHighlightText;
+ } else {
+ aColor = GetColorForSysColorIndex(COLOR_HIGHLIGHTTEXT);
+ }
+ return NS_OK;
+ }
+
+ // Titlebar colors are color-scheme aware.
+ switch (aID) {
+ case ColorID::Activecaption:
+ aColor = mTitlebarColors.Get(aScheme, true).mBg;
+ return NS_OK;
+ case ColorID::Captiontext:
+ aColor = mTitlebarColors.Get(aScheme, true).mFg;
+ return NS_OK;
+ case ColorID::Activeborder:
+ aColor = mTitlebarColors.Get(aScheme, true).mBorder;
+ return NS_OK;
+ case ColorID::Inactivecaption:
+ aColor = mTitlebarColors.Get(aScheme, false).mBg;
+ return NS_OK;
+ case ColorID::Inactivecaptiontext:
+ aColor = mTitlebarColors.Get(aScheme, false).mFg;
+ return NS_OK;
+ case ColorID::Inactiveborder:
+ aColor = mTitlebarColors.Get(aScheme, false).mBorder;
+ return NS_OK;
+ default:
+ break;
+ }
+
+ if (aScheme == ColorScheme::Dark) {
+ if (auto color = GenericDarkColor(aID)) {
+ aColor = *color;
+ return NS_OK;
+ }
+ }
+
+ static constexpr auto kNonNativeMenuText = NS_RGB(0x15, 0x14, 0x1a);
+ nsresult res = NS_OK;
+ int idx;
+ switch (aID) {
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+
+ // New CSS 2 Color definitions
+ case ColorID::Appworkspace:
+ idx = COLOR_APPWORKSPACE;
+ break;
+ case ColorID::Background:
+ idx = COLOR_BACKGROUND;
+ break;
+ case ColorID::Buttonface:
+ case ColorID::MozButtonhoverface:
+ case ColorID::MozButtonactiveface:
+ case ColorID::MozButtondisabledface:
+ case ColorID::MozColheader:
+ case ColorID::MozColheaderhover:
+ case ColorID::MozColheaderactive:
+ idx = COLOR_BTNFACE;
+ break;
+ case ColorID::Buttonhighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case ColorID::Buttonshadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case ColorID::Buttontext:
+ case ColorID::MozButtonhovertext:
+ case ColorID::MozButtonactivetext:
+ idx = COLOR_BTNTEXT;
+ break;
+ case ColorID::MozCellhighlighttext:
+ aColor = NS_RGB(0, 0, 0);
+ return NS_OK;
+ case ColorID::MozCellhighlight:
+ aColor = NS_RGB(206, 206, 206);
+ return NS_OK;
+ case ColorID::Graytext:
+ idx = COLOR_GRAYTEXT;
+ break;
+ case ColorID::MozMenubarhovertext:
+ if (UseNonNativeMenuColors(aScheme)) {
+ aColor = kNonNativeMenuText;
+ return NS_OK;
+ }
+ [[fallthrough]];
+ case ColorID::MozMenuhovertext:
+ if (UseNonNativeMenuColors(aScheme)) {
+ aColor = kNonNativeMenuText;
+ return NS_OK;
+ }
+ if (mColorMenuHoverText) {
+ aColor = *mColorMenuHoverText;
+ return NS_OK;
+ }
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case ColorID::MozMenuhover:
+ MOZ_ASSERT(UseNonNativeMenuColors(aScheme));
+ aColor = NS_RGB(0xe0, 0xe0, 0xe6);
+ return NS_OK;
+ case ColorID::MozMenuhoverdisabled:
+ if (UseNonNativeMenuColors(aScheme)) {
+ aColor = NS_RGB(0xf0, 0xf0, 0xf3);
+ return NS_OK;
+ }
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case ColorID::Infobackground:
+ idx = COLOR_INFOBK;
+ break;
+ case ColorID::Infotext:
+ idx = COLOR_INFOTEXT;
+ break;
+ case ColorID::Menu:
+ if (UseNonNativeMenuColors(aScheme)) {
+ aColor = NS_RGB(0xf9, 0xf9, 0xfb);
+ return NS_OK;
+ }
+ idx = COLOR_MENU;
+ break;
+ case ColorID::Menutext:
+ if (UseNonNativeMenuColors(aScheme)) {
+ aColor = kNonNativeMenuText;
+ return NS_OK;
+ }
+ idx = COLOR_MENUTEXT;
+ break;
+ case ColorID::Scrollbar:
+ idx = COLOR_SCROLLBAR;
+ break;
+ case ColorID::Threeddarkshadow:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case ColorID::Threedface:
+ idx = COLOR_3DFACE;
+ break;
+ case ColorID::Threedhighlight:
+ idx = COLOR_3DHIGHLIGHT;
+ break;
+ case ColorID::Threedlightshadow:
+ case ColorID::Buttonborder:
+ case ColorID::MozDisabledfield:
+ case ColorID::MozSidebarborder:
+ idx = COLOR_3DLIGHT;
+ break;
+ case ColorID::Threedshadow:
+ idx = COLOR_3DSHADOW;
+ break;
+ case ColorID::Window:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::Windowframe:
+ idx = COLOR_WINDOWFRAME;
+ break;
+ case ColorID::Windowtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozEventreerow:
+ case ColorID::MozOddtreerow:
+ case ColorID::Field:
+ case ColorID::MozSidebar:
+ case ColorID::MozCombobox:
+ idx = COLOR_WINDOW;
+ break;
+ case ColorID::Fieldtext:
+ case ColorID::MozSidebartext:
+ case ColorID::MozComboboxtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozHeaderbar:
+ case ColorID::MozHeaderbarinactive:
+ case ColorID::MozDialog:
+ idx = COLOR_3DFACE;
+ break;
+ case ColorID::Accentcolor:
+ aColor = mColorAccent;
+ return NS_OK;
+ case ColorID::Accentcolortext:
+ aColor = mColorAccentText;
+ return NS_OK;
+ case ColorID::MozHeaderbartext:
+ case ColorID::MozHeaderbarinactivetext:
+ case ColorID::MozDialogtext:
+ case ColorID::MozColheadertext:
+ case ColorID::MozColheaderhovertext:
+ case ColorID::MozColheaderactivetext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ idx = COLOR_HOTLIGHT;
+ break;
+ case ColorID::Marktext:
+ case ColorID::Mark:
+ case ColorID::SpellCheckerUnderline:
+ aColor = GetStandinForNativeColor(aID, aScheme);
+ return NS_OK;
+ default:
+ idx = COLOR_WINDOW;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ aColor = GetColorForSysColorIndex(idx);
+
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ EnsureInit();
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 3;
+ break;
+ case IntID::CaretBlinkTime:
+ aResult = static_cast<int32_t>(::GetCaretBlinkTime());
+ break;
+ case IntID::CaretBlinkCount: {
+ int32_t timeout = GetSystemParam(SPI_GETCARETTIMEOUT, 5000);
+ auto blinkTime = ::GetCaretBlinkTime();
+ if (timeout <= 0 || blinkTime <= 0) {
+ aResult = -1;
+ break;
+ }
+ // 2 * blinkTime because this integer is a full blink cycle.
+ aResult = std::ceil(float(timeout) / (2.0f * float(blinkTime)));
+ break;
+ }
+
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case IntID::SubmenuDelay:
+ // This will default to the Windows' default
+ // (400ms) on error.
+ aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400);
+ break;
+ case IntID::TooltipDelay:
+ aResult = 500;
+ break;
+ case IntID::MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ // The system metric is the number of pixels at which a drag should
+ // start. Our look and feel metric is the number of pixels you can
+ // move before starting a drag, so subtract 1.
+ aResult = ::GetSystemMetrics(SM_CXDRAG) - 1;
+ break;
+ case IntID::DragThresholdY:
+ aResult = ::GetSystemMetrics(SM_CYDRAG) - 1;
+ break;
+ case IntID::UseAccessibilityTheme:
+ // High contrast is a misnomer under Win32 -- any theme can be used with
+ // it, e.g. normal contrast with large fonts, low contrast, etc. The high
+ // contrast flag really means -- use this theme and don't override it.
+ aResult = nsUXThemeData::IsHighContrastOn();
+ break;
+ case IntID::ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 0;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::WindowsAccentColorInTitlebar: {
+ aResult = mTitlebarColors.mUseAccent;
+ } break;
+ case IntID::AlertNotificationOrigin:
+ aResult = 0;
+ {
+ // Get task bar window handle
+ HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr);
+
+ if (shellWindow != nullptr) {
+ // Determine position
+ APPBARDATA appBarData;
+ appBarData.hWnd = shellWindow;
+ appBarData.cbSize = sizeof(appBarData);
+ if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) {
+ // Set alert origin as a bit field - see LookAndFeel.h
+ // 0 represents bottom right, sliding vertically.
+ switch (appBarData.uEdge) {
+ case ABE_LEFT:
+ aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT;
+ break;
+ case ABE_RIGHT:
+ aResult = NS_ALERT_HORIZONTAL;
+ break;
+ case ABE_TOP:
+ aResult = NS_ALERT_TOP;
+ [[fallthrough]];
+ case ABE_BOTTOM:
+ // If the task bar is right-to-left,
+ // move the origin to the left
+ if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
+ aResult |= NS_ALERT_LEFT;
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::Dashed);
+ break;
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ // Forcibly enable the swipe animation on Windows. It doesn't matter on
+ // platforms where "Drag two fingers to scroll" isn't supported since on
+ // the platforms we will never generate any swipe gesture events.
+ aResult = 1;
+ break;
+ case IntID::UseOverlayScrollbars:
+ aResult = WindowsUIUtils::ComputeOverlayScrollbars();
+ break;
+ case IntID::AllowOverlayScrollbarsOverlap:
+ aResult = 0;
+ break;
+ case IntID::ScrollbarDisplayOnMouseMove:
+ aResult = 1;
+ break;
+ case IntID::ScrollbarFadeBeginDelay:
+ aResult = 2500;
+ break;
+ case IntID::ScrollbarFadeDuration:
+ aResult = 350;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ case IntID::SystemUsesDarkTheme:
+ aResult = SystemWantsDarkTheme();
+ break;
+ case IntID::SystemScrollbarSize:
+ aResult = std::max(WinUtils::GetSystemMetricsForDpi(SM_CXVSCROLL, 96),
+ WinUtils::GetSystemMetricsForDpi(SM_CXHSCROLL, 96));
+ break;
+ case IntID::PrefersReducedMotion: {
+ BOOL enable = TRUE;
+ ::SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &enable, 0);
+ aResult = !enable;
+ break;
+ }
+ case IntID::PrefersReducedTransparency: {
+ // Prefers reduced transparency if the option for "Transparency Effects"
+ // is disabled
+ aResult = !WindowsUIUtils::ComputeTransparencyEffects();
+ break;
+ }
+ case IntID::InvertedColors: {
+ // Color filter values
+ // 1: Inverted
+ // 2: Grayscale inverted
+ aResult = mCurrentColorFilter == 1 || mCurrentColorFilter == 2;
+ break;
+ }
+ case IntID::PrimaryPointerCapabilities: {
+ aResult = static_cast<int32_t>(
+ widget::WinUtils::GetPrimaryPointerCapabilities());
+ break;
+ }
+ case IntID::AllPointerCapabilities: {
+ aResult =
+ static_cast<int32_t>(widget::WinUtils::GetAllPointerCapabilities());
+ break;
+ }
+ case IntID::TouchDeviceSupportPresent:
+ aResult = !!WinUtils::IsTouchDeviceSupportPresent();
+ break;
+ case IntID::PanelAnimations:
+ aResult = 1;
+ break;
+ case IntID::HideCursorWhileTyping: {
+ BOOL enable = TRUE;
+ ::SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &enable, 0);
+ aResult = enable;
+ break;
+ }
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::TextScaleFactor:
+ aResult = WindowsUIUtils::ComputeTextScaleFactor();
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+LookAndFeelFont nsLookAndFeel::GetLookAndFeelFontInternal(
+ const LOGFONTW& aLogFont, bool aUseShellDlg) {
+ LookAndFeelFont result{};
+
+ result.haveFont() = false;
+
+ // Get scaling factor from physical to logical pixels
+ double pixelScale =
+ 1.0 / WinUtils::SystemScaleFactor() / LookAndFeel::GetTextScaleFactor();
+
+ // The lfHeight is in pixels, and it needs to be adjusted for the
+ // device it will be displayed on.
+ // Screens and Printers will differ in DPI
+ //
+ // So this accounts for the difference in the DeviceContexts
+ // The pixelScale will typically be 1.0 for the screen
+ // (though larger for hi-dpi screens where the Windows resolution
+ // scale factor is 125% or 150% or even more), and could be
+ // any value when going to a printer, for example pixelScale is
+ // 6.25 when going to a 600dpi printer.
+ float pixelHeight = -aLogFont.lfHeight;
+ if (pixelHeight < 0) {
+ nsAutoFont hFont(::CreateFontIndirectW(&aLogFont));
+ if (!hFont) {
+ return result;
+ }
+
+ nsAutoHDC dc(::GetDC(nullptr));
+ HGDIOBJ hObject = ::SelectObject(dc, hFont);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(dc, &tm);
+ ::SelectObject(dc, hObject);
+
+ pixelHeight = tm.tmAscent;
+ }
+
+ pixelHeight *= pixelScale;
+
+ // we have problem on Simplified Chinese system because the system
+ // report the default font size is 8 points. but if we use 8, the text
+ // display very ugly. force it to be at 9 points (12 pixels) on that
+ // system (cp936), but leave other sizes alone.
+ if (pixelHeight < 12 && ::GetACP() == 936) {
+ pixelHeight = 12;
+ }
+
+ result.haveFont() = true;
+
+ if (aUseShellDlg) {
+ result.name() = u"MS Shell Dlg 2"_ns;
+ } else {
+ result.name() = aLogFont.lfFaceName;
+ }
+
+ result.size() = pixelHeight;
+ result.italic() = !!aLogFont.lfItalic;
+ // FIXME: Other weights?
+ result.weight() =
+ ((aLogFont.lfWeight == FW_BOLD) ? FontWeight::BOLD : FontWeight::NORMAL)
+ .ToFloat();
+
+ return result;
+}
+
+LookAndFeelFont nsLookAndFeel::GetLookAndFeelFont(LookAndFeel::FontID anID) {
+ LookAndFeelFont result{};
+
+ result.haveFont() = false;
+
+ // FontID::Icon is handled differently than the others
+ if (anID == LookAndFeel::FontID::Icon) {
+ LOGFONTW logFont;
+ if (::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(logFont),
+ (PVOID)&logFont, 0)) {
+ result = GetLookAndFeelFontInternal(logFont, false);
+ }
+ return result;
+ }
+
+ NONCLIENTMETRICSW ncm;
+ ncm.cbSize = sizeof(NONCLIENTMETRICSW);
+ if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm),
+ (PVOID)&ncm, 0)) {
+ return result;
+ }
+
+ switch (anID) {
+ case LookAndFeel::FontID::Menu:
+ case LookAndFeel::FontID::MozPullDownMenu:
+ result = GetLookAndFeelFontInternal(ncm.lfMenuFont, false);
+ break;
+ case LookAndFeel::FontID::Caption:
+ result = GetLookAndFeelFontInternal(ncm.lfCaptionFont, false);
+ break;
+ case LookAndFeel::FontID::SmallCaption:
+ result = GetLookAndFeelFontInternal(ncm.lfSmCaptionFont, false);
+ break;
+ case LookAndFeel::FontID::StatusBar:
+ result = GetLookAndFeelFontInternal(ncm.lfStatusFont, false);
+ break;
+ case LookAndFeel::FontID::MozButton:
+ case LookAndFeel::FontID::MozField:
+ case LookAndFeel::FontID::MozList:
+ // XXX It's not clear to me whether this is exactly the right
+ // set of LookAndFeel values to map to the dialog font; we may
+ // want to add or remove cases here after reviewing the visual
+ // results under various Windows versions.
+ result = GetLookAndFeelFontInternal(ncm.lfMessageFont, true);
+ break;
+ default:
+ result = GetLookAndFeelFontInternal(ncm.lfMessageFont, false);
+ break;
+ }
+
+ return result;
+}
+
+bool nsLookAndFeel::NativeGetFont(LookAndFeel::FontID anID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ LookAndFeelFont font = GetLookAndFeelFont(anID);
+ return LookAndFeelFontToStyle(font, aFontName, aFontStyle);
+}
+
+/* virtual */
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
+ return UNICODE_BLACK_CIRCLE_CHAR;
+}
+
+static nscolor GetAccentColorText(const nscolor aAccentColor) {
+ // We want the color that we return for text that will be drawn over
+ // a background that has the accent color to have good contrast with
+ // the accent color. Windows itself uses either white or black text
+ // depending on how light or dark the accent color is. We do the same
+ // here based on the luminance of the accent color with a threshhold
+ // value. This algorithm should match what Windows does. It comes from:
+ //
+ // https://docs.microsoft.com/en-us/windows/uwp/style/color
+ float luminance = (NS_GET_R(aAccentColor) * 2 + NS_GET_G(aAccentColor) * 5 +
+ NS_GET_B(aAccentColor)) /
+ 8;
+ return luminance <= 128 ? NS_RGB(255, 255, 255) : NS_RGB(0, 0, 0);
+}
+
+static Maybe<nscolor> GetAccentColorText(const Maybe<nscolor>& aAccentColor) {
+ if (!aAccentColor) {
+ return Nothing();
+ }
+ return Some(GetAccentColorText(*aAccentColor));
+}
+
+nscolor nsLookAndFeel::GetColorForSysColorIndex(int index) {
+ MOZ_ASSERT(index >= SYS_COLOR_MIN && index <= SYS_COLOR_MAX);
+ return mSysColorTable[index - SYS_COLOR_MIN];
+}
+
+auto nsLookAndFeel::ComputeTitlebarColors() -> TitlebarColors {
+ TitlebarColors result;
+
+ // Start with the native / non-accent-in-titlebar colors.
+ result.mActiveLight = {GetColorForSysColorIndex(COLOR_ACTIVECAPTION),
+ GetColorForSysColorIndex(COLOR_CAPTIONTEXT),
+ GetColorForSysColorIndex(COLOR_ACTIVEBORDER)};
+
+ result.mInactiveLight = {GetColorForSysColorIndex(COLOR_INACTIVECAPTION),
+ GetColorForSysColorIndex(COLOR_INACTIVECAPTIONTEXT),
+ GetColorForSysColorIndex(COLOR_INACTIVEBORDER)};
+
+ if (!nsUXThemeData::IsHighContrastOn()) {
+ // Use our non-native colors.
+ result.mActiveLight = {
+ GetStandinForNativeColor(ColorID::Activecaption, ColorScheme::Light),
+ GetStandinForNativeColor(ColorID::Captiontext, ColorScheme::Light),
+ GetStandinForNativeColor(ColorID::Activeborder, ColorScheme::Light)};
+ result.mInactiveLight = {
+ GetStandinForNativeColor(ColorID::Inactivecaption, ColorScheme::Light),
+ GetStandinForNativeColor(ColorID::Inactivecaptiontext,
+ ColorScheme::Light),
+ GetStandinForNativeColor(ColorID::Inactiveborder, ColorScheme::Light)};
+ }
+
+ // Our dark colors are always non-native.
+ result.mActiveDark = {*GenericDarkColor(ColorID::Activecaption),
+ *GenericDarkColor(ColorID::Captiontext),
+ *GenericDarkColor(ColorID::Activeborder)};
+ result.mInactiveDark = {*GenericDarkColor(ColorID::Inactivecaption),
+ *GenericDarkColor(ColorID::Inactivecaptiontext),
+ *GenericDarkColor(ColorID::Inactiveborder)};
+
+ // TODO(bug 1825241): Somehow get notified when this changes? Hopefully the
+ // sys color notification is enough.
+ WinRegistry::Key dwmKey(HKEY_CURRENT_USER,
+ u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns,
+ WinRegistry::KeyMode::QueryValue);
+ if (NS_WARN_IF(!dwmKey)) {
+ return result;
+ }
+
+ // The order of the color components in the DWORD stored in the registry
+ // happens to be the same order as we store the components in nscolor
+ // so we can just assign directly here.
+ result.mAccent = dwmKey.GetValueAsDword(u"AccentColor"_ns);
+ result.mAccentText = GetAccentColorText(result.mAccent);
+
+ if (!result.mAccent) {
+ return result;
+ }
+
+ result.mAccentInactive = dwmKey.GetValueAsDword(u"AccentColorInactive"_ns);
+ result.mAccentInactiveText = GetAccentColorText(result.mAccentInactive);
+
+ // The ColorPrevalence value is set to 1 when the "Show color on title bar"
+ // setting in the Color section of Window's Personalization settings is
+ // turned on.
+ result.mUseAccent =
+ dwmKey.GetValueAsDword(u"ColorPrevalence"_ns).valueOr(0) == 1;
+ if (!result.mUseAccent) {
+ return result;
+ }
+
+ // TODO(emilio): Consider reading ColorizationColorBalance to compute a
+ // more correct border color, see [1]. Though for opaque accent colors this
+ // isn't needed.
+ //
+ // [1]:
+ // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/color/win/accent_color_observer.cc;l=42;drc=9d4eb7ed25296abba8fd525a6bdd0fdbf4bcdd9f
+ result.mActiveDark.mBorder = result.mActiveLight.mBorder = *result.mAccent;
+ result.mInactiveDark.mBorder = result.mInactiveLight.mBorder =
+ result.mAccentInactive.valueOr(NS_RGB(57, 57, 57));
+ result.mActiveLight.mBg = result.mActiveDark.mBg = *result.mAccent;
+ result.mActiveLight.mFg = result.mActiveDark.mFg = *result.mAccentText;
+ if (result.mAccentInactive) {
+ result.mInactiveLight.mBg = result.mInactiveDark.mBg =
+ *result.mAccentInactive;
+ result.mInactiveLight.mFg = result.mInactiveDark.mFg =
+ *result.mAccentInactiveText;
+ } else {
+ // This is hand-picked to .8 to change the accent color a bit but not too
+ // much.
+ constexpr uint8_t kBgAlpha = 208;
+ const auto BlendWithAlpha = [](nscolor aBg, nscolor aFg,
+ uint8_t aAlpha) -> nscolor {
+ return NS_ComposeColors(
+ aBg, NS_RGBA(NS_GET_R(aFg), NS_GET_G(aFg), NS_GET_B(aFg), aAlpha));
+ };
+ result.mInactiveLight.mBg =
+ BlendWithAlpha(NS_RGB(255, 255, 255), *result.mAccent, kBgAlpha);
+ result.mInactiveDark.mBg =
+ BlendWithAlpha(NS_RGB(0, 0, 0), *result.mAccent, kBgAlpha);
+ result.mInactiveLight.mFg = result.mInactiveDark.mFg = *result.mAccentText;
+ }
+ return result;
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ mColorMenuHoverText =
+ ::GetColorFromTheme(eUXMenu, MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR);
+
+ // Fill out the sys color table.
+ for (int i = SYS_COLOR_MIN; i <= SYS_COLOR_MAX; ++i) {
+ mSysColorTable[i - SYS_COLOR_MIN] = [&] {
+ if (auto c = WindowsUIUtils::GetSystemColor(ColorScheme::Light, i)) {
+ return *c;
+ }
+ DWORD color = ::GetSysColor(i);
+ return COLOREF_2_NSRGB(color);
+ }();
+ }
+
+ mDarkHighlight =
+ WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHT);
+ mDarkHighlightText =
+ WindowsUIUtils::GetSystemColor(ColorScheme::Dark, COLOR_HIGHLIGHTTEXT);
+
+ mTitlebarColors = ComputeTitlebarColors();
+
+ mColorAccent = [&] {
+ if (auto accent = WindowsUIUtils::GetAccentColor()) {
+ return *accent;
+ }
+ // Try the titlebar accent as a fallback.
+ if (mTitlebarColors.mAccent) {
+ return *mTitlebarColors.mAccent;
+ }
+ // Seems to be the default color (hardcoded because of bug 1065998)
+ return NS_RGB(0, 120, 215);
+ }();
+ mColorAccentText = GetAccentColorText(mColorAccent);
+
+ if (!mColorFilterWatcher) {
+ WinRegistry::Key key(
+ HKEY_CURRENT_USER, u"Software\\Microsoft\\ColorFiltering"_ns,
+ WinRegistry::KeyMode::QueryValue | WinRegistry::KeyMode::Notify);
+ if (key) {
+ mColorFilterWatcher = MakeUnique<WinRegistry::KeyWatcher>(
+ std::move(key), GetCurrentSerialEventTarget(), [this] {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ if (mCurrentColorFilter != SystemColorFilter()) {
+ LookAndFeel::NotifyChangedAllWindows(
+ widget::ThemeChangeKind::MediaQueriesOnly);
+ }
+ });
+ }
+ }
+ mCurrentColorFilter = SystemColorFilter();
+
+ RecordTelemetry();
+}
diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h
new file mode 100644
index 0000000000..d19aa91329
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include <windows.h>
+
+#include "nsXPLookAndFeel.h"
+#include "gfxFont.h"
+
+/*
+ * Gesture System Metrics
+ */
+#ifndef SM_DIGITIZER
+# define SM_DIGITIZER 94
+# define TABLET_CONFIG_NONE 0x00000000
+# define NID_INTEGRATED_TOUCH 0x00000001
+# define NID_EXTERNAL_TOUCH 0x00000002
+# define NID_INTEGRATED_PEN 0x00000004
+# define NID_EXTERNAL_PEN 0x00000008
+# define NID_MULTI_INPUT 0x00000040
+# define NID_READY 0x00000080
+#endif
+
+/*
+ * Tablet mode detection
+ */
+#ifndef SM_SYSTEMDOCKED
+# define SM_CONVERTIBLESLATEMODE 0x00002003
+# define SM_SYSTEMDOCKED 0x00002004
+#endif
+
+/*
+ * Color constant inclusive bounds for GetSysColor
+ */
+#define SYS_COLOR_MIN 0
+#define SYS_COLOR_MAX 30
+#define SYS_COLOR_COUNT (SYS_COLOR_MAX - SYS_COLOR_MIN + 1)
+
+namespace mozilla::widget::WinRegistry {
+class KeyWatcher;
+}
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ void RefreshImpl() override;
+ nsresult NativeGetInt(IntID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID, float& aResult) override;
+ nsresult NativeGetColor(ColorID, ColorScheme, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+ char16_t GetPasswordCharacterImpl() override;
+
+ private:
+ struct TitlebarColors {
+ // NOTE: These are the DWM accent colors, which might not match the
+ // UISettings/UWP accent color in some cases, see bug 1796730.
+ mozilla::Maybe<nscolor> mAccent;
+ mozilla::Maybe<nscolor> mAccentText;
+ mozilla::Maybe<nscolor> mAccentInactive;
+ mozilla::Maybe<nscolor> mAccentInactiveText;
+
+ bool mUseAccent = false;
+
+ struct Set {
+ nscolor mBg = 0;
+ nscolor mFg = 0;
+ nscolor mBorder = 0;
+ };
+
+ Set mActiveLight;
+ Set mActiveDark;
+
+ Set mInactiveLight;
+ Set mInactiveDark;
+
+ const Set& Get(mozilla::ColorScheme aScheme, bool aActive) const {
+ if (aScheme == mozilla::ColorScheme::Dark) {
+ return aActive ? mActiveDark : mInactiveDark;
+ }
+ return aActive ? mActiveLight : mInactiveLight;
+ }
+ };
+
+ TitlebarColors ComputeTitlebarColors();
+
+ nscolor GetColorForSysColorIndex(int index);
+
+ LookAndFeelFont GetLookAndFeelFontInternal(const LOGFONTW& aLogFont,
+ bool aUseShellDlg);
+
+ uint32_t SystemColorFilter();
+
+ LookAndFeelFont GetLookAndFeelFont(LookAndFeel::FontID anID);
+
+ // Cached colors and flags indicating success in their retrieval.
+ mozilla::Maybe<nscolor> mColorMenuHoverText;
+
+ mozilla::Maybe<nscolor> mDarkHighlight;
+ mozilla::Maybe<nscolor> mDarkHighlightText;
+
+ TitlebarColors mTitlebarColors;
+
+ nscolor mColorAccent = 0;
+ nscolor mColorAccentText = 0;
+
+ nscolor mSysColorTable[SYS_COLOR_COUNT];
+
+ mozilla::UniquePtr<mozilla::widget::WinRegistry::KeyWatcher>
+ mColorFilterWatcher;
+ uint32_t mCurrentColorFilter = 0;
+
+ bool mInitialized = false;
+ void EnsureInit();
+};
+
+#endif
diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp
new file mode 100644
index 0000000000..ca5459e9df
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsNativeDragSource.h"
+#include <stdio.h>
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "nsWidgetsCID.h"
+#include "nsIDragService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/DataTransfer.h"
+
+/*
+ * class nsNativeDragSource
+ */
+nsNativeDragSource::nsNativeDragSource(
+ mozilla::dom::DataTransfer* aDataTransfer)
+ : m_cRef(0), m_hCursor(nullptr), mUserCancelled(false) {
+ mDataTransfer = aDataTransfer;
+}
+
+nsNativeDragSource::~nsNativeDragSource() {}
+
+STDMETHODIMP
+nsNativeDragSource::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if (IID_IUnknown == riid || IID_IDropSource == riid) *ppv = this;
+
+ if (nullptr != *ppv) {
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragSource::AddRef(void) {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsNativeDragSource", sizeof(*this));
+ return m_cRef;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragSource::Release(void) {
+ --m_cRef;
+ NS_LOG_RELEASE(this, m_cRef, "nsNativeDragSource");
+ if (0 != m_cRef) return m_cRef;
+
+ delete this;
+ return 0;
+}
+
+STDMETHODIMP
+nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) {
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (dragService) {
+ DWORD pos = ::GetMessagePos();
+ dragService->DragMoved(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
+ }
+
+ if (fEsc) {
+ mUserCancelled = true;
+ return DRAGDROP_S_CANCEL;
+ }
+
+ if (!(grfKeyState & MK_LBUTTON) || (grfKeyState & MK_RBUTTON))
+ return DRAGDROP_S_DROP;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+nsNativeDragSource::GiveFeedback(DWORD dwEffect) {
+ // For drags involving tabs, we do some custom work with cursors.
+ if (mDataTransfer) {
+ nsAutoString cursor;
+ mDataTransfer->GetMozCursor(cursor);
+ if (cursor.EqualsLiteral("default")) {
+ m_hCursor = ::LoadCursor(0, IDC_ARROW);
+ } else {
+ m_hCursor = nullptr;
+ }
+ }
+
+ if (m_hCursor) {
+ ::SetCursor(m_hCursor);
+ return S_OK;
+ }
+
+ // Let the system choose which cursor to apply.
+ return DRAGDROP_S_USEDEFAULTCURSORS;
+}
diff --git a/widget/windows/nsNativeDragSource.h b/widget/windows/nsNativeDragSource.h
new file mode 100644
index 0000000000..a8e37b90a8
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _nsNativeDragSource_h_
+#define _nsNativeDragSource_h_
+
+#include "nscore.h"
+#include <ole2.h>
+#include <oleidl.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DataTransfer;
+} // namespace dom
+} // namespace mozilla
+
+// class nsIDragSource;
+
+/*
+ * nsNativeDragSource implements the IDropSource interface and gets
+ * most of its behavior from the associated adapter (m_dragDrop).
+ */
+class nsNativeDragSource final : public IDropSource {
+ public:
+ // construct an nsNativeDragSource referencing adapter
+ // nsNativeDragSource(nsIDragSource * adapter);
+ explicit nsNativeDragSource(mozilla::dom::DataTransfer* aDataTransfer);
+ ~nsNativeDragSource();
+
+ // IUnknown methods - see iunknown.h for documentation
+
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IDropSource methods - see idropsrc.h for documentation
+
+ // Return DRAGDROP_S_USEDEFAULTCURSORS if this object lets OLE provide
+ // default cursors, otherwise return NOERROR. This method gets called in
+ // response to changes that the target makes to dEffect (DragEnter,
+ // DragOver).
+ STDMETHODIMP GiveFeedback(DWORD dEffect);
+
+ // This method gets called if there is any change in the mouse or key
+ // state. Return DRAGDROP_S_CANCEL to stop the drag, DRAGDROP_S_DROP
+ // to execute the drop, otherwise NOERROR.
+ STDMETHODIMP QueryContinueDrag(BOOL fESC, DWORD grfKeyState);
+
+ bool UserCancelled() { return mUserCancelled; }
+
+ protected:
+ // Reference count
+ ULONG m_cRef;
+
+ // Data object, hold information about cursor state
+ RefPtr<mozilla::dom::DataTransfer> mDataTransfer;
+
+ // Custom drag cursor
+ HCURSOR m_hCursor;
+
+ // true if the user cancelled the drag by pressing escape
+ bool mUserCancelled;
+};
+
+#endif // _nsNativeDragSource_h_
diff --git a/widget/windows/nsNativeDragTarget.cpp b/widget/windows/nsNativeDragTarget.cpp
new file mode 100644
index 0000000000..b615a3e4bc
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.cpp
@@ -0,0 +1,472 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <stdio.h>
+#include "nsIDragService.h"
+#include "nsWidgetsCID.h"
+#include "nsNativeDragTarget.h"
+#include "nsDragService.h"
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+
+#include "nsIWidget.h"
+#include "nsWindow.h"
+#include "nsClipboard.h"
+#include "KeyboardLayout.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+// This is cached for Leave notification
+static POINTL gDragLastPoint;
+
+bool nsNativeDragTarget::gDragImageChanged = false;
+
+/*
+ * class nsNativeDragTarget
+ */
+nsNativeDragTarget::nsNativeDragTarget(nsIWidget* aWidget)
+ : m_cRef(0),
+ mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK),
+ mEffectsPreferred(DROPEFFECT_NONE),
+ mTookOwnRef(false),
+ mWidget(aWidget),
+ mDropTargetHelper(nullptr) {
+ mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ mDragService = do_GetService("@mozilla.org/widget/dragservice;1");
+}
+
+nsNativeDragTarget::~nsNativeDragTarget() {
+ if (mDropTargetHelper) {
+ mDropTargetHelper->Release();
+ mDropTargetHelper = nullptr;
+ }
+}
+
+// IUnknown methods - see iunknown.h for documentation
+STDMETHODIMP
+nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if (IID_IUnknown == riid || IID_IDropTarget == riid) *ppv = this;
+
+ if (nullptr != *ppv) {
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragTarget::AddRef(void) {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this));
+ return m_cRef;
+}
+
+STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) {
+ --m_cRef;
+ NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget");
+ if (0 != m_cRef) return m_cRef;
+
+ delete this;
+ return 0;
+}
+
+void nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState,
+ LPDWORD pdwEffect,
+ uint32_t* aGeckoAction) {
+ // If a window is disabled or a modal window is on top of it
+ // (which implies it is disabled), then we should not allow dropping.
+ if (!mWidget->IsEnabled()) {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ return;
+ }
+
+ // If the user explicitly uses a modifier key, they want the associated action
+ // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY
+ DWORD desiredEffect = DROPEFFECT_NONE;
+ if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) {
+ desiredEffect = DROPEFFECT_LINK;
+ } else if (grfKeyState & MK_SHIFT) {
+ desiredEffect = DROPEFFECT_MOVE;
+ } else if (grfKeyState & MK_CONTROL) {
+ desiredEffect = DROPEFFECT_COPY;
+ }
+
+ // Determine the desired effect from what is allowed and preferred.
+ if (!(desiredEffect &= mEffectsAllowed)) {
+ // No modifier key effect is set which is also allowed, check
+ // the preference of the data.
+ desiredEffect = mEffectsPreferred & mEffectsAllowed;
+ if (!desiredEffect) {
+ // No preference is set, so just fall back to the allowed effect itself
+ desiredEffect = mEffectsAllowed;
+ }
+ }
+
+ // Otherwise we should specify the first available effect
+ // from MOVE, COPY, or LINK.
+ if (desiredEffect & DROPEFFECT_MOVE) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE;
+ } else if (desiredEffect & DROPEFFECT_COPY) {
+ *pdwEffect = DROPEFFECT_COPY;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY;
+ } else if (desiredEffect & DROPEFFECT_LINK) {
+ *pdwEffect = DROPEFFECT_LINK;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ }
+}
+
+inline bool IsKeyDown(char key) { return GetKeyState(key) < 0; }
+
+void nsNativeDragTarget::DispatchDragDropEvent(EventMessage aEventMessage,
+ const POINTL& aPT) {
+ WidgetDragEvent event(true, aEventMessage, mWidget);
+
+ nsWindow* win = static_cast<nsWindow*>(mWidget);
+ win->InitEvent(event);
+ POINT cpos;
+
+ cpos.x = aPT.x;
+ cpos.y = aPT.y;
+
+ if (mHWnd != nullptr) {
+ ::ScreenToClient(mHWnd, &cpos);
+ event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+
+ event.mInputSource =
+ static_cast<nsBaseDragService*>(mDragService.get())->GetInputSource();
+
+ mWidget->DispatchInputEvent(&event);
+}
+
+void nsNativeDragTarget::ProcessDrag(EventMessage aEventMessage,
+ DWORD grfKeyState, POINTL ptl,
+ DWORD* pdwEffect) {
+ // Before dispatching the event make sure we have the correct drop action set
+ uint32_t geckoAction;
+ GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction);
+
+ // Set the current action into the Gecko specific type
+ nsCOMPtr<nsIDragSession> currSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currSession));
+ if (!currSession) {
+ return;
+ }
+
+ currSession->SetDragAction(geckoAction);
+
+ // Dispatch the event into Gecko
+ DispatchDragDropEvent(aEventMessage, ptl);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService*>(mDragService.get());
+ currSession->GetDragAction(&geckoAction);
+
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ geckoAction = childDragAction;
+ }
+
+ if (nsIDragService::DRAGDROP_ACTION_LINK & geckoAction) {
+ *pdwEffect = DROPEFFECT_LINK;
+ } else if (nsIDragService::DRAGDROP_ACTION_COPY & geckoAction) {
+ *pdwEffect = DROPEFFECT_COPY;
+ } else if (nsIDragService::DRAGDROP_ACTION_MOVE & geckoAction) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+
+ if (aEventMessage != eDrop) {
+ // Get the cached drag effect from the drag service, the data member should
+ // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on
+ // drags.
+ bool canDrop;
+ currSession->GetCanDrop(&canDrop);
+ if (!canDrop) {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+ }
+
+ // Clear the cached value
+ currSession->SetCanDrop(false);
+}
+
+// IDropTarget methods
+STDMETHODIMP
+nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
+ POINTL ptl, DWORD* pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pIDataSource);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ // We get a lot of crashes (often uncaught by our handler) later on during
+ // DragOver calls, see bug 1465513. It looks like this might be because
+ // we're not cleaning up previous drags fully and now released resources get
+ // used. Calling IDropTargetHelper::DragLeave before DragEnter seems to fix
+ // this for at least one reproduction of this crash.
+ GetDropTargetHelper()->DragLeave();
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect);
+ }
+
+ // save a ref to this, in case the window is destroyed underneath us
+ NS_ASSERTION(!mTookOwnRef, "own ref already taken!");
+ this->AddRef();
+ mTookOwnRef = true;
+
+ // tell the drag service about this drag (it may have come from an
+ // outside app).
+ mDragService->StartDragSession();
+
+ void* tempOutData = nullptr;
+ uint32_t tempDataLen = 0;
+ nsresult loadResult = nsClipboard::GetNativeDataOffClipboard(
+ pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT),
+ nullptr, &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ mEffectsPreferred = *((DWORD*)tempOutData);
+ free(tempOutData);
+ } else {
+ // We have no preference if we can't obtain it
+ mEffectsPreferred = DROPEFFECT_NONE;
+ }
+
+ // Set the native data object into drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService. It should be a private interface, though.
+ nsDragService* winDragService =
+ static_cast<nsDragService*>(mDragService.get());
+ winDragService->SetIDataObject(pIDataSource);
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragEnter, grfKeyState, ptl, pdwEffect);
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(
+ LPDATAOBJECT aIDataSource) {
+ // If we don't have a link effect, but we can generate one, fix the
+ // drop effect to include it.
+ if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) {
+ if (S_OK == ::OleQueryLinkFromData(aIDataSource)) {
+ mEffectsAllowed |= DROPEFFECT_LINK;
+ }
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragOver(DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ bool dragImageChanged = gDragImageChanged;
+ gDragImageChanged = false;
+
+ // If a LINK effect could be generated previously from a DragEnter(),
+ // then we should include it as an allowed effect.
+ mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // Drag was canceled.
+ }
+
+ // without the AddRef() |this| can get destroyed in an event handler
+ this->AddRef();
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ if (dragImageChanged) {
+ // See comment in nsNativeDragTarget::DragEnter.
+ GetDropTargetHelper()->DragLeave();
+ // The drop helper only updates the image during DragEnter, so emulate
+ // a DragEnter if the image was changed.
+ POINT pt = {ptl.x, ptl.y};
+ nsDragService* dragService =
+ static_cast<nsDragService*>(mDragService.get());
+ GetDropTargetHelper()->DragEnter(mHWnd, dragService->GetDataObject(), &pt,
+ *pdwEffect);
+ }
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
+ }
+
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->FireDragEventAtSource(eDrag, modifierKeyState.GetModifiers());
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragOver, grfKeyState, ptl, pdwEffect);
+
+ this->Release();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragLeave() {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+
+ // dispatch the event into Gecko
+ DispatchDragDropEvent(eDragExit, gDragLastPoint);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ if (currentDragSession) {
+ nsCOMPtr<nsINode> sourceNode;
+ currentDragSession->GetSourceNode(getter_AddRefs(sourceNode));
+
+ if (!sourceNode) {
+ // We're leaving a window while doing a drag that was
+ // initiated in a different app. End the drag session, since
+ // we're done with it for now (until the user drags back into
+ // mozilla).
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ }
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::DragCancel() {
+ // Cancel the drag session if we did DragEnter.
+ if (mTookOwnRef) {
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+ if (mDragService) {
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ this->Release(); // matching the AddRef in DragEnter
+ mTookOwnRef = false;
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::Drop(LPDATAOBJECT pData, DWORD grfKeyState, POINTL aPT,
+ LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pData);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ POINT pt = {aPT.x, aPT.y};
+ GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect);
+ }
+
+ // Set the native data object into the drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService (but it should still be a private interface)
+ nsDragService* winDragService =
+ static_cast<nsDragService*>(mDragService.get());
+ winDragService->SetIDataObject(pData);
+
+ // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects.
+ // We use strong refs to prevent it from destroying these:
+ RefPtr<nsNativeDragTarget> kungFuDeathGrip = this;
+ nsCOMPtr<nsIDragService> serv = mDragService;
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ serv->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // DragCancel() was called.
+ }
+
+ // Let the win drag service know whether this session experienced
+ // a drop event within the application. Drop will not oocur if the
+ // drop landed outside the app. (used in tab tear off, bug 455884)
+ winDragService->SetDroppedLocal();
+
+ // tell the drag service we're done with the session
+ // Use GetMessagePos to get the position of the mouse at the last message
+ // seen by the event loop. (Bug 489729)
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+ winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y));
+ ModifierKeyState modifierKeyState;
+ serv->EndDragSession(true, modifierKeyState.GetModifiers());
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+/**
+ * By lazy loading mDropTargetHelper we save 50-70ms of startup time
+ * which is ~5% of startup time.
+ */
+IDropTargetHelper* nsNativeDragTarget::GetDropTargetHelper() {
+ if (!mDropTargetHelper) {
+ CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper);
+ }
+
+ return mDropTargetHelper;
+}
diff --git a/widget/windows/nsNativeDragTarget.h b/widget/windows/nsNativeDragTarget.h
new file mode 100644
index 0000000000..82edc7aae7
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _nsNativeDragTarget_h_
+#define _nsNativeDragTarget_h_
+
+#include "nsCOMPtr.h"
+#include <ole2.h>
+#include <shlobj.h>
+
+#ifndef IDropTargetHelper
+# include <shobjidl.h> // Vista drag image interfaces
+# undef LogSeverity // SetupAPI.h #defines this as DWORD
+#endif
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+
+class nsIDragService;
+class nsIWidget;
+
+/*
+ * nsNativeDragTarget implements the IDropTarget interface and gets most of its
+ * behavior from the associated adapter (m_dragDrop).
+ */
+
+class nsNativeDragTarget final : public IDropTarget {
+ public:
+ explicit nsNativeDragTarget(nsIWidget* aWidget);
+ ~nsNativeDragTarget();
+
+ // IUnknown members - see iunknown.h for documentation
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IDataTarget members
+
+ // Set pEffect based on whether this object can support a drop based on
+ // the data available from pSource, the key and mouse states specified
+ // in grfKeyState, and the coordinates specified by point. This is
+ // called by OLE when a drag enters this object's window (as registered
+ // by Initialize).
+ STDMETHODIMP DragEnter(LPDATAOBJECT pSource, DWORD grfKeyState, POINTL point,
+ DWORD* pEffect);
+
+ // Similar to DragEnter except it is called frequently while the drag
+ // is over this object's window.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragOver(DWORD grfKeyState,
+ POINTL point,
+ DWORD* pEffect);
+
+ // Release the drag-drop source and put internal state back to the point
+ // before the call to DragEnter. This is called when the drag leaves
+ // without a drop occurring.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragLeave();
+
+ // If point is within our region of interest and pSource's data supports
+ // one of our formats, get the data and set pEffect according to
+ // grfKeyState (DROPEFFECT_MOVE if the control key was not pressed,
+ // DROPEFFECT_COPY if the control key was pressed). Otherwise return
+ // E_FAIL.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP Drop(LPDATAOBJECT pSource,
+ DWORD grfKeyState, POINTL point,
+ DWORD* pEffect);
+ /**
+ * Cancel the current drag session, if any.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DragCancel();
+
+ static void DragImageChanged() { gDragImageChanged = true; }
+
+ protected:
+ void GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect,
+ uint32_t* aGeckoAction);
+ void ProcessDrag(mozilla::EventMessage aEventMessage, DWORD grfKeyState,
+ POINTL pt, DWORD* pdwEffect);
+ void DispatchDragDropEvent(mozilla::EventMessage aEventMessage,
+ const POINTL& aPT);
+ void AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource);
+
+ // Native Stuff
+ ULONG m_cRef; // reference count
+ HWND mHWnd;
+ DWORD mEffectsAllowed;
+ DWORD mEffectsPreferred;
+ bool mTookOwnRef;
+
+ // Gecko Stuff
+ nsIWidget* mWidget;
+ nsCOMPtr<nsIDragService> mDragService;
+ // Drag target helper
+ IDropTargetHelper* GetDropTargetHelper();
+
+ private:
+ // Drag target helper
+ IDropTargetHelper* mDropTargetHelper;
+
+ static bool gDragImageChanged;
+};
+
+#endif // _nsNativeDragTarget_h_
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp
new file mode 100644
index 0000000000..04883a833f
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -0,0 +1,1975 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsNativeThemeWin.h"
+
+#include <algorithm>
+#include <malloc.h>
+
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxWindowsNativeDrawing.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/gfx/Types.h" // for Color::FromABGR
+#include "mozilla/Logging.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "nsColor.h"
+#include "nsComboboxControlFrame.h"
+#include "nsDeviceContext.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsNameSpaceManager.h"
+#include "Theme.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsStyleConsts.h"
+#include "nsTransform2D.h"
+#include "nsWindow.h"
+#include "prinrval.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+using ElementState = dom::ElementState;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+namespace mozilla::widget {
+
+nsNativeThemeWin::nsNativeThemeWin()
+ : Theme(ScrollbarStyle()),
+ mProgressDeterminateTimeStamp(TimeStamp::Now()),
+ mProgressIndeterminateTimeStamp(TimeStamp::Now()),
+ mBorderCacheValid(),
+ mMinimumWidgetSizeCacheValid(),
+ mGutterSizeCacheValid(false) {}
+
+nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
+
+bool nsNativeThemeWin::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
+ aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ aAppearance == StyleAppearance::SpinnerUpbutton ||
+ aAppearance == StyleAppearance::SpinnerDownbutton;
+}
+
+auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance)
+ -> NonNative {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return NonNative::Always;
+ }
+
+ // We only know how to draw light widgets, so we defer to the non-native
+ // theme when appropriate.
+ if (Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance) &&
+ LookAndFeel::ColorSchemeForFrame(aFrame) ==
+ LookAndFeel::ColorScheme::Dark) {
+ return NonNative::BecauseColorMismatch;
+ }
+ return NonNative::No;
+}
+
+static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
+ MARGINS checkboxContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
+ nullptr, &checkboxContent);
+ return checkboxContent;
+}
+
+static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
+ SIZE checkboxSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
+ TS_TRUE, &checkboxSize);
+
+ MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
+
+ int leftMargin = checkboxMargins.cxLeftWidth;
+ int rightMargin = checkboxMargins.cxRightWidth;
+ int topMargin = checkboxMargins.cyTopHeight;
+ int bottomMargin = checkboxMargins.cyBottomHeight;
+
+ int width = leftMargin + checkboxSize.cx + rightMargin;
+ int height = topMargin + checkboxSize.cy + bottomMargin;
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
+ MARGINS checkboxBGSizing = {0};
+ MARGINS checkboxBGContent = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
+
+#define posdx(d) ((d) > 0 ? d : 0)
+
+ int dx =
+ posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
+ posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
+ int dy =
+ posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
+ posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
+
+#undef posdx
+
+ SIZE ret(GetCheckboxBGSize(theme, hdc));
+ ret.cx += dx;
+ ret.cy += dy;
+ return ret;
+}
+
+static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
+ SIZE gutterSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
+ &gutterSize);
+
+ SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
+
+ SIZE itemSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
+ &itemSize);
+
+ // Figure out how big the menuitem's icon will be (if present) at current DPI
+ // Needs the system scale for consistency with Windows Theme API.
+ double scaleFactor = WinUtils::SystemScaleFactor();
+ int iconDevicePixels = NSToIntRound(16 * scaleFactor);
+ SIZE iconSize = {iconDevicePixels, iconDevicePixels};
+ // Not really sure what margins should be used here, but this seems to work in
+ // practice...
+ MARGINS margins = {0};
+ GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
+ TMT_CONTENTMARGINS, nullptr, &margins);
+ iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
+ iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
+
+ int width = std::max(
+ itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
+ int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
+
+ SIZE ret;
+ ret.cx = width;
+ ret.cy = height;
+ return ret;
+}
+
+SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
+ if (mGutterSizeCacheValid) {
+ return mGutterSizeCache;
+ }
+
+ mGutterSizeCache = GetGutterSize(theme, nullptr);
+ mGutterSizeCacheValid = true;
+
+ return mGutterSizeCache;
+}
+
+/*
+ * Notes on progress track and meter part constants:
+ * xp and up:
+ * PP_BAR(_VERT) - base progress track
+ * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
+ * the underlying surface supports alpha. otherwise
+ * theme lib's DrawThemeBackground falls back on
+ * opaque PP_BAR. we currently don't use this.
+ * PP_CHUNK(_VERT) - xp progress meter. this does not draw an xp style
+ * progress w/chunks, it draws fill using the chunk
+ * graphic.
+ * vista and up:
+ * PP_FILL(_VERT) - progress meter. these have four states/colors.
+ * PP_PULSEOVERLAY(_VERT) - white reflection - an overlay, not sure what this
+ * is used for.
+ * PP_MOVEOVERLAY(_VERT) - green pulse - the pulse effect overlay on
+ * determined progress bars. we also use this for
+ * indeterminate chunk.
+ *
+ * Notes on state constants:
+ * PBBS_NORMAL - green progress
+ * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
+ * PBFS_PAUSED - yellow paused progress
+ *
+ * There is no common controls style indeterminate part on vista and up.
+ */
+
+/*
+ * Progress bar related constants. These values are found by experimenting and
+ * comparing against native widgets used by the system. They are very unlikely
+ * exact but try to not be too wrong.
+ */
+// The amount of time we animate progress meters parts across the frame.
+static const double kProgressDeterminateTimeSpan = 3.0;
+static const double kProgressIndeterminateTimeSpan = 5.0;
+// The width of the overlay used to animate the horizontal progress bar (Vista
+// and later).
+static const int32_t kProgressHorizontalOverlaySize = 120;
+// The height of the overlay used to animate the vertical progress bar (Vista
+// and later).
+static const int32_t kProgressVerticalOverlaySize = 45;
+// The height of the overlay used for the vertical indeterminate progress bar
+// (Vista and later).
+static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
+// The width of the overlay used to animate the indeterminate progress bar
+// (Windows Classic).
+static const int32_t kProgressClassicOverlaySize = 40;
+
+/*
+ * GetProgressOverlayStyle - returns the proper overlay part for themed
+ * progress bars based on os and orientation.
+ */
+static int32_t GetProgressOverlayStyle(bool aIsVertical) {
+ return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
+}
+
+/*
+ * GetProgressOverlaySize - returns the minimum width or height for themed
+ * progress bar overlays. This includes the width of indeterminate chunks
+ * and vista pulse overlays.
+ */
+static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
+ if (aIsVertical) {
+ return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
+ : kProgressVerticalOverlaySize;
+ }
+ return kProgressHorizontalOverlaySize;
+}
+
+/*
+ * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
+ * on a comparison of the current value and maximum.
+ */
+static bool IsProgressMeterFilled(nsIFrame* aFrame) {
+ NS_ENSURE_TRUE(aFrame, false);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ NS_ENSURE_TRUE(parentFrame, false);
+ return nsNativeTheme::GetProgressValue(parentFrame) ==
+ nsNativeTheme::GetProgressMaxValue(parentFrame);
+}
+
+/*
+ * CalculateProgressOverlayRect - returns the padded overlay animation rect
+ * used in rendering progress bars. Resulting rects are used in rendering
+ * vista+ pulse overlays and indeterminate progress meters. Graphics should
+ * be rendered at the origin.
+ */
+RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
+ RECT* aWidgetRect,
+ bool aIsVertical,
+ bool aIsIndeterminate,
+ bool aIsClassic) {
+ NS_ASSERTION(aFrame, "bad frame pointer");
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+
+ int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
+ : aWidgetRect->right - aWidgetRect->left;
+
+ // Recycle a set of progress pulse timers - these timers control the position
+ // of all progress overlays and indeterminate chunks that get rendered.
+ double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
+ : kProgressDeterminateTimeSpan;
+ TimeDuration period;
+ if (!aIsIndeterminate) {
+ if (TimeStamp::Now() >
+ (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressDeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
+ } else {
+ if (TimeStamp::Now() >
+ (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
+ mProgressIndeterminateTimeStamp = TimeStamp::Now();
+ }
+ period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
+ }
+
+ double percent = period / TimeDuration::FromSeconds(span);
+
+ if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
+
+ RECT overlayRect = *aWidgetRect;
+ int32_t overlaySize;
+ if (!aIsClassic) {
+ overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
+ } else {
+ overlaySize = kProgressClassicOverlaySize;
+ }
+
+ // Calculate a bounds that is larger than the meters frame such that the
+ // overlay starts and ends completely off the edge of the frame:
+ // [overlay][frame][overlay]
+ // This also yields a nice delay on rotation. Use overlaySize as the minimum
+ // size for [overlay] based on the graphics dims. If [frame] is larger, use
+ // the frame size instead.
+ int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
+ if (!aIsVertical) {
+ int xPos = aWidgetRect->left - trackWidth;
+ xPos += (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.left = xPos;
+ overlayRect.right = xPos + overlaySize;
+ } else {
+ int yPos = aWidgetRect->bottom + trackWidth;
+ yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
+ overlayRect.bottom = yPos;
+ overlayRect.top = yPos - overlaySize;
+ }
+ return overlayRect;
+}
+
+/*
+ * DrawProgressMeter - render an appropriate progress meter based on progress
+ * meter style, orientation, and os. Note, this does not render the underlying
+ * progress track.
+ *
+ * @param aFrame the widget frame
+ * @param aAppearance type of widget
+ * @param aTheme progress theme handle
+ * @param aHdc hdc returned by gfxWindowsNativeDrawing
+ * @param aPart the PP_X progress part
+ * @param aState the theme state
+ * @param aWidgetRect bounding rect for the widget
+ * @param aClipRect dirty rect that needs drawing.
+ * @param aAppUnits app units per device pixel
+ */
+void nsNativeThemeWin::DrawThemedProgressMeter(
+ nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc,
+ int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) {
+ if (!aFrame || !aTheme || !aHdc) return;
+
+ NS_ASSERTION(aWidgetRect, "bad rect pointer");
+ NS_ASSERTION(aClipRect, "bad clip rect pointer");
+
+ RECT adjWidgetRect, adjClipRect;
+ adjWidgetRect = *aWidgetRect;
+ adjClipRect = *aClipRect;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame) {
+ // We have no parent to work with, just bail.
+ NS_WARNING("No parent frame for progress rendering. Can't paint.");
+ return;
+ }
+
+ ElementState elementState = GetContentState(parentFrame, aAppearance);
+ bool vertical = IsVerticalProgress(parentFrame);
+ bool indeterminate = elementState.HasState(ElementState::INDETERMINATE);
+ bool animate = indeterminate;
+
+ // Vista and up progress meter is fill style, rendered here. We render
+ // the pulse overlay in the follow up section below.
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
+ &adjClipRect);
+ if (!IsProgressMeterFilled(aFrame)) {
+ animate = true;
+ }
+
+ if (animate) {
+ // Indeterminate rendering
+ int32_t overlayPart = GetProgressOverlayStyle(vertical);
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &adjWidgetRect, vertical, indeterminate, false);
+ DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
+ &adjClipRect);
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder(
+ HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState) {
+ int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
+ return mBorderCache[cacheIndex];
+ }
+
+ // Get our info.
+ RECT outerRect; // Create a fake outer rect.
+ outerRect.top = outerRect.left = 100;
+ outerRect.right = outerRect.bottom = 200;
+ RECT contentRect(outerRect);
+ HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
+ &outerRect, &contentRect);
+
+ if (FAILED(res)) {
+ return LayoutDeviceIntMargin();
+ }
+
+ // Now compute the delta in each direction and place it in our
+ // nsIntMargin struct.
+ LayoutDeviceIntMargin result;
+ result.top = contentRect.top - outerRect.top;
+ result.bottom = outerRect.bottom - contentRect.bottom;
+ result.left = contentRect.left - outerRect.left;
+ result.right = outerRect.right - contentRect.right;
+
+ mBorderCacheValid[cacheBitIndex] |= cacheBit;
+ mBorderCache[cacheIndex] = result;
+
+ return result;
+}
+
+nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
+ nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
+ StyleAppearance aAppearance, int32_t aPart, int32_t aState,
+ THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) {
+ int32_t cachePart = aPart;
+
+ if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) {
+ // In practice, StyleAppearance::Button is the only widget type which has an
+ // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
+ // stuff that extra bit into the aPart part of the cache, since BP_Count is
+ // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
+ cachePart = BP_Count;
+ }
+
+ MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
+ int32_t cacheIndex =
+ aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
+ int32_t cacheBitIndex = cacheIndex / 8;
+ uint8_t cacheBit = 1u << (cacheIndex % 8);
+
+ if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
+ *aResult = mMinimumWidgetSizeCache[cacheIndex];
+ return NS_OK;
+ }
+
+ HDC hdc = ::GetDC(NULL);
+ if (!hdc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SIZE sz;
+ GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
+ aResult->width = sz.cx;
+ aResult->height = sz.cy;
+
+ ::ReleaseDC(nullptr, hdc);
+
+ mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
+ mMinimumWidgetSizeCache[cacheIndex] = *aResult;
+
+ return NS_OK;
+}
+
+mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ return Some(eUXButton);
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ return Some(eUXEdit);
+ case StyleAppearance::Toolbox:
+ return Some(eUXRebar);
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Separator:
+ return Some(eUXToolbar);
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ return Some(eUXProgress);
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ return Some(eUXTab);
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ return Some(eUXTrackbar);
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ return Some(eUXCombobox);
+ case StyleAppearance::Treeheadercell:
+ return Some(eUXHeader);
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::Treeitem:
+ return Some(eUXListview);
+ default:
+ return Nothing();
+ }
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) {
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ if (themeClass.isNothing()) {
+ return nullptr;
+ }
+ return nsUXThemeData::GetTheme(themeClass.value());
+}
+
+int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ bool wantFocused) {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) {
+ return TS_ACTIVE;
+ }
+ if (elementState.HasState(ElementState::HOVER)) {
+ return TS_HOVER;
+ }
+ if (wantFocused) {
+ if (elementState.HasState(ElementState::FOCUSRING)) {
+ return TS_FOCUSED;
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check ElementState::FOCUS instead of
+ // ElementState::FOCUSRING.
+ if (aAppearance == StyleAppearance::Button &&
+ elementState.HasState(ElementState::FOCUS)) {
+ return TS_FOCUSED;
+ }
+ }
+
+ return TS_NORMAL;
+}
+
+bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsXULElement() &&
+ content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
+ return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
+
+ return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+}
+
+/**
+ * aPart is filled in with the UXTheme part code. On return, values > 0
+ * are the actual UXTheme part code; -1 means the widget will be drawn by
+ * us; 0 means that we should use part code 0, which isn't a real part code
+ * but elicits some kind of default behaviour from UXTheme when drawing
+ * (but isThemeBackgroundPartiallyTransparent may not work).
+ */
+nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ int32_t& aPart,
+ int32_t& aState) {
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ // Check for default dialog buttons. These buttons should always look
+ // focused.
+ if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
+ return NS_OK;
+ }
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea: {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ /* Note: the NOSCROLL type has a rounded corner in each corner. The more
+ * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
+ * edges rendered as straight horizontal lines with sharp corners to
+ * accommodate a scrollbar. However, the scrollbar gets rendered on top
+ * of this for us, so we don't care, and can just use NOSCROLL here.
+ */
+ aPart = TFP_EDITBORDER_NOSCROLL;
+
+ if (!aFrame) {
+ aState = TFS_EDITBORDER_NORMAL;
+ } else if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TFS_EDITBORDER_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ /* no special read-only state */
+ aState = TFS_EDITBORDER_NORMAL;
+ } else if (elementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
+ ElementState::FOCUSRING)) {
+ aState = TFS_EDITBORDER_FOCUSED;
+ } else if (elementState.HasState(ElementState::HOVER)) {
+ aState = TFS_EDITBORDER_HOVER;
+ } else {
+ aState = TFS_EDITBORDER_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::ProgressBar: {
+ bool vertical = IsVerticalProgress(aFrame);
+ aPart = vertical ? PP_BARVERT : PP_BAR;
+ aState = PBBS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (IsVerticalProgress(parentFrame)) {
+ aPart = PP_FILLVERT;
+ } else {
+ aPart = PP_FILL;
+ }
+
+ aState = PBBVS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbarbutton: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ if (elementState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE))
+ aState = TS_ACTIVE;
+ else if (elementState.HasState(ElementState::HOVER)) {
+ if (IsCheckedButton(aFrame))
+ aState = TB_HOVER_CHECKED;
+ else
+ aState = TS_HOVER;
+ } else {
+ if (IsCheckedButton(aFrame))
+ aState = TB_CHECKED;
+ else
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Separator: {
+ aPart = TP_SEPARATOR;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Range: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_TRACK;
+ aState = TRS_NORMAL;
+ } else {
+ aPart = TKP_TRACKVERT;
+ aState = TRVS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_THUMBBOTTOM;
+ } else {
+ aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
+ }
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ } else if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TKP_DISABLED;
+ } else {
+ if (elementState.HasState(
+ ElementState::ACTIVE)) // Hover is not also a requirement for
+ // the thumb, since the drag is not
+ // canceled when you move outside the
+ // thumb.
+ aState = TS_ACTIVE;
+ else if (elementState.HasState(ElementState::FOCUSRING))
+ aState = TKP_FOCUSED;
+ else if (elementState.HasState(ElementState::HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbox: {
+ aState = 0;
+ aPart = RP_BACKGROUND;
+ return NS_OK;
+ }
+ case StyleAppearance::Toolbar: {
+ // Use -1 to indicate we don't wish to have the theme background drawn
+ // for this item. We will pass any nessessary information via aState,
+ // and will render the item using separate code.
+ aPart = -1;
+ aState = 0;
+ if (aFrame) {
+ nsIContent* content = aFrame->GetContent();
+ nsIContent* parent = content->GetParent();
+ // XXXzeniko hiding the first toolbar will result in an unwanted margin
+ if (parent && parent->GetFirstChild() == content) {
+ aState = 1;
+ }
+ }
+ return NS_OK;
+ }
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Listbox: {
+ aPart = TREEVIEW_BODY;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanels: {
+ aPart = TABP_PANELS;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tabpanel: {
+ aPart = TABP_PANEL;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case StyleAppearance::Tab: {
+ aPart = TABP_TAB;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (IsSelectedTab(aFrame)) {
+ aPart = TABP_TAB_SELECTED;
+ aState = TS_ACTIVE; // The selected tab is always "pressed".
+ } else
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::Treeheadercell: {
+ aPart = 1;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aAppearance, true);
+
+ return NS_OK;
+ }
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist: {
+ nsIContent* content = aFrame->GetContent();
+ bool useDropBorder = content && content->IsHTMLElement();
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
+ * content or for editable menulists; this gives us the thin outline,
+ * instead of the gradient-filled background */
+ if (useDropBorder)
+ aPart = CBP_DROPBORDER;
+ else
+ aPart = CBP_DROPFRAME;
+
+ if (elementState.HasState(ElementState::DISABLED)) {
+ aState = TS_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ aState = TS_NORMAL;
+ } else if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ } else if (useDropBorder &&
+ elementState.HasState(ElementState::FOCUSRING)) {
+ aState = TS_ACTIVE;
+ } else if (elementState.HasAllStates(ElementState::HOVER |
+ ElementState::ACTIVE)) {
+ aState = TS_ACTIVE;
+ } else if (elementState.HasState(ElementState::HOVER)) {
+ aState = TS_HOVER;
+ } else {
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ default:
+ aPart = 0;
+ aState = 0;
+ return NS_ERROR_FAILURE;
+ }
+}
+
+static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
+ int32_t aState) {
+ if (!nsUXThemeData::IsHighContrastOn() && aPart == MENU_POPUPITEM &&
+ aState == MBI_NORMAL) {
+ return true;
+ }
+ return false;
+}
+
+// When running with per-monitor DPI (on Win8.1+), and rendering on a display
+// with a different DPI setting from the system's default scaling, we need to
+// apply scaling to native-themed elements as the Windows theme APIs assume
+// the system default resolution.
+static inline double GetThemeDpiScaleFactor(nsPresContext* aPresContext) {
+ if (WinUtils::IsPerMonitorDPIAware() ||
+ StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
+ nsCOMPtr<nsIWidget> rootWidget = aPresContext->GetRootWidget();
+ if (rootWidget) {
+ double systemScale = WinUtils::SystemScaleFactor();
+ return rootWidget->GetDefaultScale().scale / systemScale;
+ }
+ }
+ return 1.0;
+}
+
+static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
+ return GetThemeDpiScaleFactor(aFrame->PresContext());
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect,
+ DrawOverflow aDrawOverflow) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect, aDrawOverflow);
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme)
+ return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect);
+
+ // ^^ without the right sdk, assume xp theming and fall through.
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ gfxContextMatrixAutoSaveRestore save(aContext);
+
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().PreScale(themeScale, themeScale));
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ RECT clipRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / (p2a * themeScale));
+ dr.Scale(1.0 / (p2a * themeScale));
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+ nativeDrawing.TransformToNativeRect(dr, clipRect);
+
+#if 0
+ {
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
+ m._31, m._32));
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
+ tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
+ offset.x, offset.y));
+ }
+#endif
+
+ if (aAppearance == StyleAppearance::Tab) {
+ // For left edge and right edge tabs, we need to adjust the widget
+ // rects and clip rects so that the edges don't get drawn.
+ bool isLeft = IsLeftToSelectedTab(aFrame);
+ bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
+
+ if (isLeft || isRight) {
+ // HACK ALERT: There appears to be no way to really obtain this value, so
+ // we're forced to just use the default value for Luna (which also happens
+ // to be correct for all the other skins I've tried).
+ int32_t edgeSize = 2;
+
+ // Armed with the size of the edge, we now need to either shift to the
+ // left or to the right. The clip rect won't include this extra area, so
+ // we know that we're effectively shifting the edge out of view (such that
+ // it won't be painted).
+ if (isLeft)
+ // The right edge should not be drawn. Extend our rect by the edge
+ // size.
+ widgetRect.right += edgeSize;
+ else
+ // The left edge should not be drawn. Move the widget rect's left coord
+ // back.
+ widgetRect.left -= edgeSize;
+ }
+ }
+
+ // widgetRect is the bounding box for a widget, yet the scale track is only
+ // a small portion of this size, so the edges of the scale need to be
+ // adjusted to the real size of the track.
+ if (aAppearance == StyleAppearance::Range) {
+ RECT contentRect;
+ GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
+ &contentRect);
+
+ SIZE siz;
+ GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
+
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
+ contentRect.bottom = contentRect.top + siz.cy;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.right = contentRect.left + siz.cx;
+ } else {
+ contentRect.right -=
+ (contentRect.right - contentRect.left - siz.cx) / 2;
+ contentRect.left = contentRect.right - siz.cx;
+ }
+ }
+
+ DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
+ } else if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+
+ if (state == TFS_EDITBORDER_DISABLED) {
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
+ }
+ } else if (aAppearance == StyleAppearance::ProgressBar) {
+ // DrawThemeBackground renders each corner with a solid white pixel.
+ // Restore these pixels to the underlying color. Tracks are rendered
+ // using alpha recovery, so this makes the corners transparent.
+ COLORREF color;
+ color = GetPixel(hdc, widgetRect.left, widgetRect.top);
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ SetPixel(hdc, widgetRect.left, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
+ SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
+ SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
+ } else if (aAppearance == StyleAppearance::Progresschunk) {
+ DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state,
+ &widgetRect, &clipRect);
+ }
+ // If part is negative, the element wishes us to not render a themed
+ // background, instead opting to be drawn specially below.
+ else if (part >= 0) {
+ DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
+ }
+
+ // Draw focus rectangles for range elements
+ // XXX it'd be nice to draw these outside of the frame
+ if (aAppearance == StyleAppearance::Range) {
+ ElementState contentState = GetContentState(aFrame, aAppearance);
+
+ if (contentState.HasState(ElementState::FOCUSRING)) {
+ POINT vpOrg;
+ HPEN hPen = nullptr;
+
+ uint8_t id = SaveDC(hdc);
+
+ ::SelectClipRgn(hdc, nullptr);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
+ nullptr);
+ ::SetTextColor(hdc, 0);
+ ::DrawFocusRect(hdc, &widgetRect);
+ ::RestoreDC(hdc, id);
+ if (hPen) {
+ ::DeleteObject(hPen);
+ }
+ }
+ } else if (aAppearance == StyleAppearance::Toolbar && state == 0) {
+ // Draw toolbar separator lines above all toolbars except the first one.
+ // The lines are part of the Rebar theme, which is loaded for
+ // StyleAppearance::Toolbox.
+ theme = GetTheme(StyleAppearance::Toolbox);
+ if (!theme) return NS_ERROR_FAILURE;
+
+ widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
+ DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
+ nullptr);
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return NS_OK;
+}
+
+bool nsNativeThemeWin::CreateWebRenderCommandsForWidget(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::CreateWebRenderCommandsForWidget(
+ aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
+ }
+ return false;
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aMargin->top = NSToIntRound(aMargin->top * themeScale);
+ aMargin->left = NSToIntRound(aMargin->left * themeScale);
+ aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
+ aMargin->right = NSToIntRound(aMargin->right * themeScale);
+ }
+}
+
+static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ aSize->width = NSToIntRound(aSize->width * themeScale);
+ aSize->height = NSToIntRound(aSize->height * themeScale);
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
+ }
+
+ LayoutDeviceIntMargin result;
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ if (!WidgetIsContainer(aAppearance) ||
+ aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Tabpanel)
+ return result; // Don't worry about it.
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) return result;
+
+ if (aAppearance == StyleAppearance::Toolbar) {
+ // make space for the separator line above all toolbars but the first
+ if (state == 0) result.top = TB_SEPARATOR_HEIGHT;
+ return result;
+ }
+
+ result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part,
+ state);
+
+ // Remove the edges for tabs that are before or after the selected tab,
+ if (aAppearance == StyleAppearance::Tab) {
+ if (IsLeftToSelectedTab(aFrame))
+ // Remove the right edge, since we won't be drawing it.
+ result.right = 0;
+ else if (IsRightToSelectedTab(aFrame))
+ // Remove the left edge, since we won't be drawing it.
+ result.left = 0;
+ }
+
+ if (aFrame && (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea)) {
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsHTMLElement()) {
+ // We need to pad textfields by 1 pixel, since the caret will draw
+ // flush against the edge by default if we don't.
+ result.top.value++;
+ result.left.value++;
+ result.bottom.value++;
+ result.right.value++;
+ }
+ }
+
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+}
+
+bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ }
+
+ bool ok = true;
+ HANDLE theme = GetTheme(aAppearance);
+ if (!theme) {
+ ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ /* textfields need extra pixels on all sides, otherwise they wrap their
+ * content too tightly. The actual border is drawn 1px inside the specified
+ * rectangle, so Gecko will end up making the contents look too small.
+ * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
+ * added, see bug 430212)
+ */
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea) {
+ aResult->top = aResult->bottom = 2;
+ aResult->left = aResult->right = 2;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ } else if (IsHTMLContent(aFrame) &&
+ (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton)) {
+ /* For content menulist controls, we need an extra pixel so that we have
+ * room to draw our focus rectangle stuff. Otherwise, the focus rect might
+ * overlap the control's border.
+ */
+ aResult->top = aResult->bottom = 1;
+ aResult->left = aResult->right = 1;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ int32_t right, left, top, bottom;
+ right = left = top = bottom = 0;
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ if (aFrame->GetContent()->IsXULElement()) {
+ top = 2;
+ bottom = 3;
+ }
+ left = right = 5;
+ break;
+ default:
+ return false;
+ }
+
+ if (IsFrameRTL(aFrame)) {
+ aResult->right = left;
+ aResult->left = right;
+ } else {
+ aResult->right = right;
+ aResult->left = left;
+ }
+ aResult->top = top;
+ aResult->bottom = bottom;
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+}
+
+bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
+ aOverflowRect);
+ }
+
+ /* This is disabled for now, because it causes invalidation problems --
+ * see bug 420381. The effect of not updating the overflow area is that
+ * for dropdown buttons in content areas, there is a 1px border on 3 sides
+ * where, if invalidated, the dropdown control probably won't be repainted.
+ * This is fairly minor, as by default there is nothing in that area, and
+ * a border only shows up if the widget is being hovered.
+ *
+ * TODO(jwatt): Figure out what do to about
+ * StyleAppearance::MozMenulistArrowButton too.
+ */
+#if 0
+ /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
+ * and bottom so that they overlap the dropdown's border like they're
+ * supposed to.
+ */
+ if (aAppearance == StyleAppearance::MenulistButton &&
+ IsHTMLContent(aFrame) &&
+ !IsWidgetStyled(aFrame->GetParent()->PresContext(),
+ aFrame->GetParent(),
+ StyleAppearance::Menulist))
+ {
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ /* Note: no overflow on the left */
+ nsMargin m(p2a, p2a, p2a, 0);
+ aOverflowRect->Inflate (m);
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
+ }
+
+ mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance);
+ HTHEME theme = NULL;
+ if (!themeClass.isNothing()) {
+ theme = nsUXThemeData::GetTheme(themeClass.value());
+ }
+ if (!theme) {
+ auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Toolbox:
+ case StyleAppearance::Toolbar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ return {}; // Don't worry about it.
+ default:
+ break;
+ }
+
+ // Call GetSystemMetrics to determine size for WinXP scrollbars
+ // (GetThemeSysSize API returns the optimal size for the theme, but
+ // Windows appears to always use metrics when drawing standard scrollbars)
+ THEMESIZE sizeReq = TS_TRUE; // Best-fit size
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ // Best-fit size for progress meters is too large for most
+ // themes. We want these widgets to be able to really shrink
+ // down, so use the min-size request value (of 0).
+ sizeReq = TS_MIN;
+ break;
+
+ case StyleAppearance::RangeThumb: {
+ LayoutDeviceIntSize result(12, 20);
+ if (!IsRangeHorizontal(aFrame)) {
+ std::swap(result.width, result.height);
+ }
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ case StyleAppearance::Separator: {
+ // that's 2px left margin, 2px right margin and 2px separator
+ // (the margin is drawn as part of the separator, though)
+ LayoutDeviceIntSize result(6, 0);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+ }
+
+ case StyleAppearance::Button:
+ // We should let HTML buttons shrink to their min size.
+ // FIXME bug 403934: We should probably really separate
+ // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
+ // use the one they want.
+ if (aFrame->GetContent()->IsHTMLElement()) {
+ sizeReq = TS_MIN;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ if (NS_FAILED(rv)) {
+ return {};
+ }
+
+ LayoutDeviceIntSize result;
+ rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
+ aAppearance, part, state, sizeReq, &result);
+ ScaleForFrameDPI(&result, aFrame);
+ return result;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Tabpanels ||
+ aAppearance == StyleAppearance::Tabpanel ||
+ aAppearance == StyleAppearance::Separator) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ // We need to repaint the dropdown arrow in vista HTML combobox controls when
+ // the control is closed to get rid of the hover effect.
+ if ((aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) &&
+ nsNativeTheme::IsHTMLContent(aFrame)) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ } else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::ThemeChanged() {
+ nsUXThemeData::Invalidate();
+ memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+ memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
+ mGutterSizeCacheValid = false;
+ return NS_OK;
+}
+
+bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ // XXXdwh We can go even further and call the API to ask if support exists for
+ // specific widgets.
+
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ if (theme || ClassicThemeSupportsWidget(aFrame, aAppearance))
+ // turn off theming for some HTML widgets styled by the page
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ return false;
+}
+
+bool nsNativeThemeWin::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
+ }
+ switch (aAppearance) {
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
+
+nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetTransparency(aFrame, aAppearance);
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Range:
+ return eTransparent;
+ default:
+ break;
+ }
+
+ HANDLE theme = GetTheme(aAppearance);
+ // For the classic theme we don't really have a way of knowing
+ if (!theme) {
+ return eUnknownTransparency;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state);
+ // Fail conservatively
+ NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
+
+ if (part <= 0) {
+ // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
+ // not work, so don't call it.
+ return eUnknownTransparency;
+ }
+
+ if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
+ return eTransparent;
+ return eOpaque;
+}
+
+/* Windows 9x/NT/2000/Classic XP Theme Support */
+
+bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ return true;
+ default:
+ return false;
+ }
+}
+
+LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Tab:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ result.top = result.left = result.bottom = result.right = 2;
+ break;
+ case StyleAppearance::ProgressBar:
+ result.top = result.left = result.bottom = result.right = 1;
+ break;
+ default:
+ result.top = result.bottom = result.left = result.right = 0;
+ break;
+ }
+ return result;
+}
+
+bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ case StyleAppearance::ProgressBar:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
+ 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntSize result;
+ switch (aAppearance) {
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ result.width = 12;
+ result.height = 20;
+ } else {
+ result.width = 20;
+ result.height = 12;
+ }
+ break;
+ }
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Button:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ // no minimum widget size
+ break;
+
+ default:
+ break;
+ }
+ return result;
+}
+
+nsresult nsNativeThemeWin::ClassicGetThemePartAndState(
+ nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart,
+ int32_t& aState, bool& aFocused) {
+ aFocused = false;
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ aPart = DFC_BUTTON;
+ aState = DFCS_BUTTONPUSH;
+ aFocused = false;
+
+ ElementState contentState = GetContentState(aFrame, aAppearance);
+ if (contentState.HasState(ElementState::DISABLED)) {
+ aState |= DFCS_INACTIVE;
+ } else if (IsOpenButton(aFrame)) {
+ aState |= DFCS_PUSHED;
+ } else if (IsCheckedButton(aFrame)) {
+ aState |= DFCS_CHECKED;
+ } else {
+ if (contentState.HasAllStates(ElementState::ACTIVE |
+ ElementState::HOVER)) {
+ aState |= DFCS_PUSHED;
+ // The down state is flat if the button is focusable
+ if (aFrame->StyleUI()->UserFocus() == StyleUserFocus::Normal) {
+ if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
+
+ aFocused = true;
+ }
+ }
+ // On Windows, focused buttons are always drawn as such by the native
+ // theme, that's why we check ElementState::FOCUS instead of
+ // ElementState::FOCUSRING.
+ if (contentState.HasState(ElementState::FOCUS) ||
+ (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
+ aFocused = true;
+ }
+ }
+
+ return NS_OK;
+ }
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Tab:
+ case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ // these don't use DrawFrameControl
+ return NS_OK;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+// Draw classic Windows tab
+// (no system API for this, but DrawEdge can draw all the parts of a tab)
+static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
+ bool aDrawLeft, bool aDrawRight) {
+ int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
+ RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
+ int32_t selectedOffset, lOffset, rOffset;
+
+ selectedOffset = aSelected ? 1 : 0;
+ lOffset = aDrawLeft ? 2 : 0;
+ rOffset = aDrawRight ? 2 : 0;
+
+ // Get info for tab orientation/position (Left, Top, Right, Bottom)
+ switch (aPosition) {
+ case BF_LEFT:
+ leftFlag = BF_TOP;
+ topFlag = BF_LEFT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
+ R.bottom);
+ ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
+ break;
+ case BF_TOP:
+ leftFlag = BF_LEFT;
+ topFlag = BF_TOP;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2, R.right,
+ R.bottom - 1 + selectedOffset);
+ ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
+ ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
+ ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
+ break;
+ case BF_RIGHT:
+ leftFlag = BF_TOP;
+ topFlag = BF_RIGHT;
+ rightFlag = BF_BOTTOM;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
+ ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
+ R.bottom);
+ ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
+ ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ case BF_BOTTOM:
+ leftFlag = BF_LEFT;
+ topFlag = BF_BOTTOM;
+ rightFlag = BF_RIGHT;
+ lightFlag = BF_DIAGONAL_ENDTOPLEFT;
+ shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
+
+ ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
+ ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
+ R.bottom - 2);
+ ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
+ ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
+ ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ // Background
+ ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
+
+ // Tab "Top"
+ ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Bottom"
+ if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
+
+ // Tab "Sides"
+ if (!aDrawLeft) leftFlag = 0;
+ if (!aDrawRight) rightFlag = 0;
+ ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
+
+ // Tab Diagonal Corners
+ if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
+
+ if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
+}
+
+void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
+ int32_t back, HBRUSH defaultBack) {
+ static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
+
+ HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
+ if (patBmp) {
+ HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
+ if (brush) {
+ COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
+ COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
+ POINT vpOrg;
+
+ ::UnrealizeObject(brush);
+ ::GetViewportOrgEx(hdc, &vpOrg);
+ ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
+ HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
+ ::FillRect(hdc, &rc, brush);
+ ::SetTextColor(hdc, oldForeColor);
+ ::SetBkColor(hdc, oldBackColor);
+ ::SelectObject(hdc, oldBrush);
+ ::DeleteObject(brush);
+ } else
+ ::FillRect(hdc, &rc, defaultBack);
+
+ ::DeleteObject(patBmp);
+ }
+}
+
+nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
+ gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aDirtyRect) {
+ int32_t part, state;
+ bool focused;
+ nsresult rv;
+ rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused);
+ if (NS_FAILED(rv)) return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RECT widgetRect;
+ gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
+ dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
+ aDirtyRect.Height());
+
+ tr.Scale(1.0 / p2a);
+ dr.Scale(1.0 / p2a);
+
+ gfxWindowsNativeDrawing nativeDrawing(
+ aContext, dr, GetWidgetNativeDrawingFlags(aAppearance));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc) return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+
+ rv = NS_OK;
+ switch (aAppearance) {
+ // Draw button
+ case StyleAppearance::Button: {
+ if (focused) {
+ // draw dark button focus border first
+ if (HBRUSH brush = ::GetSysColorBrush(COLOR_3DDKSHADOW)) {
+ ::FrameRect(hdc, &widgetRect, brush);
+ }
+ InflateRect(&widgetRect, -1, -1);
+ }
+ // setup DC to make DrawFrameControl draw correctly
+ int32_t oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ ::DrawFrameControl(hdc, &widgetRect, part, state);
+ ::SetTextAlign(hdc, oldTA);
+ break;
+ }
+ // Draw controls with 2px 3D inset border
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ // Fill in background
+
+ if (elementState.HasState(ElementState::DISABLED) ||
+ (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ else
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ case StyleAppearance::Treeview: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ // Fill in window color background
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
+
+ break;
+ }
+ // Draw 3D face background controls
+ case StyleAppearance::ProgressBar:
+ // Draw 3D border
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+ InflateRect(&widgetRect, -1, -1);
+ [[fallthrough]];
+ case StyleAppearance::Tabpanel: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
+ break;
+ }
+ case StyleAppearance::RangeThumb: {
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
+ if (elementState.HasState(ElementState::DISABLED)) {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
+ (HBRUSH)COLOR_3DHILIGHT);
+ }
+
+ break;
+ }
+ // Draw scale track background
+ case StyleAppearance::Range: {
+ const int32_t trackWidth = 4;
+ // When rounding is necessary, we round the position of the track
+ // away from the chevron of the thumb to make it look better.
+ if (IsRangeHorizontal(aFrame)) {
+ widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
+ widgetRect.bottom = widgetRect.top + trackWidth;
+ } else {
+ if (!IsFrameRTL(aFrame)) {
+ widgetRect.left +=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.right = widgetRect.left + trackWidth;
+ } else {
+ widgetRect.right -=
+ (widgetRect.right - widgetRect.left - trackWidth) / 2;
+ widgetRect.left = widgetRect.right - trackWidth;
+ }
+ }
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
+
+ break;
+ }
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ ElementState elementState = GetContentState(stateFrame, aAppearance);
+
+ const bool indeterminate =
+ elementState.HasState(ElementState::INDETERMINATE);
+ bool vertical = IsVerticalProgress(stateFrame);
+
+ nsIContent* content = aFrame->GetContent();
+ if (!indeterminate || !content) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+ break;
+ }
+
+ RECT overlayRect = CalculateProgressOverlayRect(
+ aFrame, &widgetRect, vertical, indeterminate, true);
+
+ ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ break;
+ }
+
+ // Draw Tab
+ case StyleAppearance::Tab: {
+ DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
+ IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
+ !IsLeftToSelectedTab(aFrame));
+
+ break;
+ }
+ case StyleAppearance::Tabpanels:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
+ BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
+
+ break;
+
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return rv;
+}
+
+uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // need to check these others
+ default:
+ return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+ }
+}
+
+} // namespace mozilla::widget
+
+///////////////////////////////////////////
+// Creation Routine
+///////////////////////////////////////////
+
+already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
+ return do_AddRef(new nsNativeThemeWin());
+}
diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h
new file mode 100644
index 0000000000..9937018197
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsNativeThemeWin_h
+#define nsNativeThemeWin_h
+
+#include <windows.h>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "Theme.h"
+#include "nsUXThemeConstants.h"
+#include "nsUXThemeData.h"
+
+namespace mozilla::widget {
+
+class nsNativeThemeWin : public Theme {
+ protected:
+ virtual ~nsNativeThemeWin();
+
+ public:
+ // Whether we draw a non-native widget.
+ //
+ // We always draw scrollbars as non-native so that all of Firefox has
+ // consistent scrollbar styles both in chrome and content (plus, the
+ // non-native scrollbars support scrollbar-width, auto-darkening...).
+ //
+ // We draw other widgets as non-native when their color-scheme is dark. In
+ // that case (`BecauseColorMismatch`) we don't call into the non-native theme
+ // for sizing information (GetWidgetPadding/Border and GetMinimumWidgetSize),
+ // to avoid subtle sizing changes. The non-native theme can basically draw at
+ // any size, so we prefer to have consistent sizing information.
+ enum class NonNative { No, Always, BecauseColorMismatch };
+ static bool IsWidgetAlwaysNonNative(nsIFrame*, StyleAppearance);
+ NonNative IsWidgetNonNative(nsIFrame*, StyleAppearance);
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aDirtyRect,
+ DrawOverflow) override;
+
+ bool CreateWebRenderCommandsForWidget(wr::DisplayListBuilder&,
+ wr::IpcResourceUpdateQueue&,
+ const layers::StackingContextHelper&,
+ layers::RenderRootStateManager*,
+ nsIFrame*, StyleAppearance,
+ const nsRect&) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+
+ LayoutDeviceIntSize GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ virtual Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) override;
+
+ bool ThemeWantsButtonInnerFocusRing() override { return true; }
+
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ nsNativeThemeWin();
+
+ protected:
+ Maybe<nsUXThemeClass> GetThemeClass(StyleAppearance aAppearance);
+ HANDLE GetTheme(StyleAppearance aAppearance);
+ nsresult GetThemePartAndState(nsIFrame* aFrame, StyleAppearance aAppearance,
+ int32_t& aPart, int32_t& aState);
+ nsresult ClassicGetThemePartAndState(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ int32_t& aPart, int32_t& aState,
+ bool& aFocused);
+ nsresult ClassicDrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aClipRect);
+ [[nodiscard]] LayoutDeviceIntMargin ClassicGetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance);
+ bool ClassicGetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult);
+ LayoutDeviceIntSize ClassicGetMinimumWidgetSize(nsIFrame* aFrame,
+ StyleAppearance aAppearance);
+ bool ClassicThemeSupportsWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance);
+ void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back,
+ HBRUSH defaultBack);
+ uint32_t GetWidgetNativeDrawingFlags(StyleAppearance aAppearance);
+ int32_t StandardGetState(nsIFrame* aFrame, StyleAppearance aAppearance,
+ bool wantFocused);
+ bool IsMenuActive(nsIFrame* aFrame, StyleAppearance aAppearance);
+ RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect,
+ bool aIsVertical, bool aIsIndeterminate,
+ bool aIsClassic);
+ void DrawThemedProgressMeter(nsIFrame* aFrame, StyleAppearance aAppearance,
+ HANDLE aTheme, HDC aHdc, int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect);
+
+ [[nodiscard]] LayoutDeviceIntMargin GetCachedWidgetBorder(
+ HANDLE aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState);
+
+ nsresult GetCachedMinimumWidgetSize(nsIFrame* aFrame, HANDLE aTheme,
+ nsUXThemeClass aThemeClass,
+ StyleAppearance aAppearance,
+ int32_t aPart, int32_t aState,
+ THEMESIZE aSizeReq,
+ LayoutDeviceIntSize* aResult);
+
+ SIZE GetCachedGutterSize(HANDLE theme);
+
+ private:
+ TimeStamp mProgressDeterminateTimeStamp;
+ TimeStamp mProgressIndeterminateTimeStamp;
+
+ // eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT is about 800 at the time of
+ // writing this, and nsIntMargin is 16 bytes wide, which makes this cache (1/8
+ // + 16) * 800 bytes, or about ~12KB. We could probably reduce this cache to
+ // 3KB by caching on the aAppearance value instead, but there would be some
+ // uncacheable values, since we derive some theme parts from other arguments.
+ uint8_t
+ mBorderCacheValid[(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) /
+ 8];
+ LayoutDeviceIntMargin
+ mBorderCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
+
+ // See the above not for mBorderCache and friends. However
+ // LayoutDeviceIntSize is half the size of nsIntMargin, making the
+ // cache roughly half as large. In total the caches should come to about 18KB.
+ uint8_t mMinimumWidgetSizeCacheValid
+ [(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / 8];
+ LayoutDeviceIntSize
+ mMinimumWidgetSizeCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT];
+
+ bool mGutterSizeCacheValid;
+ SIZE mGutterSizeCache;
+};
+
+} // namespace mozilla::widget
+
+#endif
diff --git a/widget/windows/nsPrintDialogUtil.cpp b/widget/windows/nsPrintDialogUtil.cpp
new file mode 100644
index 0000000000..43f56e9706
--- /dev/null
+++ b/widget/windows/nsPrintDialogUtil.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* -------------------------------------------------------------------
+To Build This:
+
+ You need to add this to the the makefile.win in mozilla/dom/base:
+
+ .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \
+
+
+ And this to the makefile.win in mozilla/content/build:
+
+WIN_LIBS= \
+ winspool.lib \
+ comctl32.lib \
+ comdlg32.lib
+
+---------------------------------------------------------------------- */
+
+#include <windows.h>
+#include <tchar.h>
+
+#include <unknwn.h>
+#include <commdlg.h>
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Span.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsWin.h"
+#include "nsIPrinterList.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsRect.h"
+
+#include "nsCRT.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include <windows.h>
+#include <winspool.h>
+
+// For Localization
+
+// For NS_CopyUnicodeToNative
+#include "nsNativeCharsetUtils.h"
+
+// This is for extending the dialog
+#include <dlgs.h>
+
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+
+//-----------------------------------------------
+// Global Data
+//-----------------------------------------------
+
+static HWND gParentWnd = nullptr;
+
+//----------------------------------------------------------------------------------
+// Returns a Global Moveable Memory Handle to a DevMode
+// from the Printer by the name of aPrintName
+//
+// NOTE:
+// This function assumes that aPrintName has already been converted from
+// unicode
+//
+static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit(
+ const nsString& aPrintName, nsIPrintSettings* aPS) {
+ nsHPRINTER hPrinter = nullptr;
+ // const cast kludge for silly Win32 api's
+ LPWSTR printName =
+ const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
+ BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
+ if (!status) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Make sure hPrinter is closed on all paths
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ // Get the buffer size
+ LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
+ nullptr, 0);
+ if (needed < 0) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Some drivers do not return the correct size for their DEVMODE, so we
+ // over-allocate to try and compensate.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ needed *= 2;
+ nsAutoDevMode newDevMode(
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed));
+ if (!newDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
+ nsAutoGlobalMem globalDevMode(hDevMode);
+ if (!hDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
+ nullptr, DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Lock memory and copy contents from DEVMODE (current printer)
+ // to Global Memory DEVMODE
+ LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode);
+ if (!devMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ memcpy(devMode, newDevMode.get(), needed);
+ // Initialize values from the PrintSettings
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
+ MOZ_ASSERT(psWin);
+ psWin->CopyToNative(devMode);
+
+ // Sets back the changes we made to the DevMode into the Printer Driver
+ ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
+ DM_IN_BUFFER | DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ ::GlobalUnlock(hDevMode);
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ ::GlobalUnlock(hDevMode);
+
+ return globalDevMode.out();
+}
+
+//------------------------------------------------------------------
+// helper
+static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) {
+ aPrinterName.Truncate();
+ nsCOMPtr<nsIPrinterList> printerList =
+ do_GetService("@mozilla.org/gfx/printerlist;1");
+ if (printerList) {
+ printerList->GetSystemDefaultPrinterName(aPrinterName);
+ }
+}
+
+//------------------------------------------------------------------
+// Displays the native Print Dialog
+nsresult NativeShowPrintDialog(HWND aHWnd, bool aHaveSelection,
+ nsIPrintSettings* aPrintSettings) {
+ // NS_ENSURE_ARG_POINTER(aHWnd);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ // Get the Print Name to be used
+ nsString printerName;
+ aPrintSettings->GetPrinterName(printerName);
+
+ // If there is no name then use the default printer
+ if (printerName.IsEmpty()) {
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ HANDLE hPrinter = nullptr;
+ if (!::OpenPrinterW(const_cast<wchar_t*>(
+ static_cast<const wchar_t*>(printerName.get())),
+ &hPrinter, nullptr)) {
+ // If the last used printer is not found, we should use default printer.
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ ::ClosePrinter(hPrinter);
+ }
+ }
+
+ // Now create a DEVNAMES struct so the the dialog is initialized correctly.
+
+ uint32_t len = printerName.Length();
+ nsHGLOBAL hDevNames =
+ ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES));
+ nsAutoGlobalMem autoDevNames(hDevNames);
+ if (!hDevNames) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
+ if (!pDevNames) {
+ return NS_ERROR_FAILURE;
+ }
+ pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
+ pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
+ pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len;
+ pDevNames->wDefault = 0;
+
+ memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t));
+ ::GlobalUnlock(hDevNames);
+
+ // Create a Moveable Memory Object that holds a new DevMode
+ // from the Printer Name
+ // The PRINTDLG.hDevMode requires that it be a moveable memory object
+ // NOTE: autoDevMode is automatically freed when any error occurred
+ nsAutoGlobalMem autoDevMode(
+ CreateGlobalDevModeAndInit(printerName, aPrintSettings));
+
+ // Prepare to Display the Print Dialog
+ // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
+ // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
+ PRINTDLGEXW prntdlg;
+ memset(&prntdlg, 0, sizeof(prntdlg));
+
+ prntdlg.lStructSize = sizeof(prntdlg);
+ prntdlg.hwndOwner = aHWnd;
+ prntdlg.hDevMode = autoDevMode.get();
+ prntdlg.hDevNames = hDevNames;
+ prntdlg.hDC = nullptr;
+ prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE |
+ PD_COLLATE | PD_NOCURRENTPAGE;
+
+ // If there is a current selection then enable the "Selection" radio button
+ if (!aHaveSelection) {
+ prntdlg.Flags |= PD_NOSELECTION;
+ }
+
+ // 10 seems like a reasonable max number of ranges to support by default if
+ // the user doesn't choose a greater thing in the UI.
+ constexpr size_t kMinSupportedRanges = 10;
+
+ AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges;
+ // Set up the page ranges.
+ {
+ AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
+ aPrintSettings->GetPageRanges(pageRanges);
+ // If there is a specified page range then enable the "Custom" radio button
+ if (!pageRanges.IsEmpty()) {
+ prntdlg.Flags |= PD_PAGENUMS;
+ }
+
+ const size_t specifiedRanges = pageRanges.Length() / 2;
+ const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges);
+
+ prntdlg.nMaxPageRanges = maxRanges;
+ prntdlg.nPageRanges = specifiedRanges;
+
+ winPageRanges.SetCapacity(maxRanges);
+ for (size_t i = 0; i < pageRanges.Length(); i += 2) {
+ PRINTPAGERANGE* range = winPageRanges.AppendElement();
+ range->nFromPage = pageRanges[i];
+ range->nToPage = pageRanges[i + 1];
+ }
+ prntdlg.lpPageRanges = winPageRanges.Elements();
+
+ prntdlg.nMinPage = 1;
+ // TODO(emilio): Could probably get the right page number here from the
+ // new print UI.
+ prntdlg.nMaxPage = 0xFFFF;
+ }
+
+ // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
+ // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
+ prntdlg.nCopies = 1;
+
+ prntdlg.hInstance = nullptr;
+ prntdlg.lpPrintTemplateName = nullptr;
+
+ prntdlg.lpCallback = nullptr;
+ prntdlg.nPropertyPages = 0;
+ prntdlg.lphPropertyPages = nullptr;
+
+ prntdlg.nStartPage = START_PAGE_GENERAL;
+ prntdlg.dwResultAction = 0;
+
+ HRESULT result;
+ {
+ mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ result = ::PrintDlgExW(&prntdlg);
+ }
+
+ auto cancelOnExit = mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd); });
+
+ if (NS_WARN_IF(!SUCCEEDED(result))) {
+#ifdef DEBUG
+ printf_stderr("PrintDlgExW failed with %lx\n", result);
+#endif
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) {
+ return NS_ERROR_ABORT;
+ }
+ // check to make sure we don't have any nullptr pointers
+ NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT);
+ NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT);
+ // Lock the deviceNames and check for nullptr
+ DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames);
+ NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT);
+
+ char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]);
+ char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]);
+
+ // Check to see if the "Print To File" control is checked
+ // then take the name from devNames and set it in the PrintSettings
+ //
+ // NOTE:
+ // As per Microsoft SDK documentation the returned value offset from
+ // devnames->wOutputOffset is either "FILE:" or nullptr
+ // if the "Print To File" checkbox is checked it MUST be "FILE:"
+ // We assert as an extra safety check.
+ if (prntdlg.Flags & PD_PRINTTOFILE) {
+ char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]);
+ NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
+ aPrintSettings->SetOutputDestination(
+ nsIPrintSettings::kOutputDestinationFile);
+ aPrintSettings->SetToFileName(nsDependentString(fileName));
+ } else {
+ // clear "print to file" info
+ aPrintSettings->SetOutputDestination(
+ nsIPrintSettings::kOutputDestinationPrinter);
+ aPrintSettings->SetToFileName(u""_ns);
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ MOZ_RELEASE_ASSERT(psWin);
+
+ // Setup local Data members
+ psWin->SetDeviceName(nsDependentString(device));
+ psWin->SetDriverName(nsDependentString(driver));
+
+ // Fill the print options with the info from the dialog
+ aPrintSettings->SetPrinterName(nsDependentString(device));
+ aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION);
+
+ AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
+ if (prntdlg.Flags & PD_PAGENUMS) {
+ pageRanges.SetCapacity(prntdlg.nPageRanges * 2);
+ for (const auto& range :
+ mozilla::Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) {
+ pageRanges.AppendElement(range.nFromPage);
+ pageRanges.AppendElement(range.nToPage);
+ }
+ }
+ aPrintSettings->SetPageRanges(pageRanges);
+
+ // Unlock DeviceNames
+ ::GlobalUnlock(prntdlg.hDevNames);
+
+ // Transfer the settings from the native data to the PrintSettings
+ LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
+ if (!devMode || !prntdlg.hDC) {
+ return NS_ERROR_FAILURE;
+ }
+ psWin->SetDevMode(devMode); // copies DevMode
+ psWin->CopyFromNative(prntdlg.hDC, devMode);
+ ::GlobalUnlock(prntdlg.hDevMode);
+ ::DeleteDC(prntdlg.hDC);
+
+ cancelOnExit.release();
+ return NS_OK;
+}
diff --git a/widget/windows/nsPrintDialogUtil.h b/widget/windows/nsPrintDialogUtil.h
new file mode 100644
index 0000000000..3ec16e1b1d
--- /dev/null
+++ b/widget/windows/nsPrintDialogUtil.h
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsFlyOwnDialog_h___
+#define nsFlyOwnDialog_h___
+
+nsresult NativeShowPrintDialog(HWND aHWnd, bool aHaveSelection,
+ nsIPrintSettings* aPrintSettings);
+
+#endif /* nsFlyOwnDialog_h___ */
diff --git a/widget/windows/nsPrintDialogWin.cpp b/widget/windows/nsPrintDialogWin.cpp
new file mode 100644
index 0000000000..35ea52b17b
--- /dev/null
+++ b/widget/windows/nsPrintDialogWin.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsPrintDialogWin.h"
+
+#include "nsArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowserChild.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogUtil.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsServiceManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+
+static const char* kPageSetupDialogURL =
+ "chrome://global/content/printPageSetup.xhtml";
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/**
+ * ParamBlock
+ */
+
+class ParamBlock {
+ public:
+ ParamBlock() { mBlock = 0; }
+ ~ParamBlock() { NS_IF_RELEASE(mBlock); }
+ nsresult Init() {
+ return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock);
+ }
+ nsIDialogParamBlock* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return mBlock;
+ }
+ operator nsIDialogParamBlock* const() { return mBlock; }
+
+ private:
+ nsIDialogParamBlock* mBlock;
+};
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceWin, nsIPrintDialogService)
+
+nsPrintDialogServiceWin::nsPrintDialogServiceWin() {}
+
+nsPrintDialogServiceWin::~nsPrintDialogServiceWin() {}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::Init() {
+ nsresult rv;
+ mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::ShowPrintDialog(mozIDOMWindowProxy* aParent,
+ bool aHaveSelection,
+ nsIPrintSettings* aSettings) {
+ NS_ENSURE_ARG(aParent);
+ RefPtr<nsIWidget> parentWidget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(aParent));
+
+ ScopedRtlShimWindow shim(parentWidget.get());
+ NS_ASSERTION(shim.get(), "Couldn't get native window for PRint Dialog!");
+
+ return NativeShowPrintDialog(shim.get(), aHaveSelection, aSettings);
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceWin::ShowPageSetupDialog(mozIDOMWindowProxy* aParent,
+ nsIPrintSettings* aNSSettings) {
+ NS_ENSURE_ARG(aParent);
+ NS_ENSURE_ARG(aNSSettings);
+
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetInt(0, 0);
+ rv = DoDialog(aParent, block, aNSSettings, kPageSetupDialogURL);
+
+ // if aWebBrowserPrint is not null then we are printing
+ // so we want to pass back NS_ERROR_ABORT on cancel
+ if (NS_SUCCEEDED(rv)) {
+ int32_t status;
+ block->GetInt(0, &status);
+ return status == 0 ? NS_ERROR_ABORT : NS_OK;
+ }
+
+ // We don't call nsPrintSettingsService::MaybeSavePrintSettingsToPrefs here
+ // since it's called for us in printPageSetup.js. Maybe we should move that
+ // call here for consistency with the other platforms though?
+
+ return rv;
+}
+
+nsresult nsPrintDialogServiceWin::DoDialog(mozIDOMWindowProxy* aParent,
+ nsIDialogParamBlock* aParamBlock,
+ nsIPrintSettings* aPS,
+ const char* aChromeURL) {
+ NS_ENSURE_ARG(aParamBlock);
+ NS_ENSURE_ARG(aPS);
+ NS_ENSURE_ARG(aChromeURL);
+
+ if (!mWatcher) return NS_ERROR_FAILURE;
+
+ // get a parent, if at all possible
+ // (though we'd rather this didn't fail, it's OK if it does. so there's
+ // no failure or null check.)
+ // retain ownership for method lifetime
+ nsCOMPtr<mozIDOMWindowProxy> activeParent;
+ if (!aParent) {
+ mWatcher->GetActiveWindow(getter_AddRefs(activeParent));
+ aParent = activeParent;
+ }
+
+ // create a nsIMutableArray of the parameters
+ // being passed to the window
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS));
+ NS_ASSERTION(psSupports, "PrintSettings must be a supports");
+ array->AppendElement(psSupports);
+
+ nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock));
+ NS_ASSERTION(blkSupps, "IOBlk must be a supports");
+ array->AppendElement(blkSupps);
+
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ nsresult rv = mWatcher->OpenWindow(
+ aParent, nsDependentCString(aChromeURL), "_blank"_ns,
+ "centerscreen,chrome,modal,titlebar"_ns, array, getter_AddRefs(dialog));
+
+ return rv;
+}
diff --git a/widget/windows/nsPrintDialogWin.h b/widget/windows/nsPrintDialogWin.h
new file mode 100644
index 0000000000..bb8f212eb5
--- /dev/null
+++ b/widget/windows/nsPrintDialogWin.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsPrintDialog_h__
+#define nsPrintDialog_h__
+
+#include "nsIPrintDialogService.h"
+
+#include "nsCOMPtr.h"
+#include "nsIWindowWatcher.h"
+
+#include <windef.h>
+
+class nsIPrintSettings;
+class nsIDialogParamBlock;
+
+class nsPrintDialogServiceWin final : public nsIPrintDialogService {
+ public:
+ nsPrintDialogServiceWin();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTDIALOGSERVICE
+
+ private:
+ virtual ~nsPrintDialogServiceWin();
+
+ nsresult DoDialog(mozIDOMWindowProxy* aParent,
+ nsIDialogParamBlock* aParamBlock, nsIPrintSettings* aPS,
+ const char* aChromeURL);
+
+ nsCOMPtr<nsIWindowWatcher> mWatcher;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintDialogServiceWin,
+ NS_IPRINTDIALOGSERVICE_IID)
+
+#endif
diff --git a/widget/windows/nsPrintSettingsServiceWin.cpp b/widget/windows/nsPrintSettingsServiceWin.cpp
new file mode 100644
index 0000000000..cbc99441ab
--- /dev/null
+++ b/widget/windows/nsPrintSettingsServiceWin.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsPrintSettingsServiceWin.h"
+
+#include "nsCOMPtr.h"
+#include "nsPrintSettingsWin.h"
+#include "nsPrintDialogUtil.h"
+
+#include "nsGfxCIID.h"
+#include "nsIServiceManager.h"
+#include "nsWindowsHelpers.h"
+#include "ipc/IPCMessageUtils.h"
+#include "chrome/common/ipc_channel.h"
+#include "mozilla/embedding/PPrintingTypes.h"
+
+using namespace mozilla::embedding;
+
+NS_IMETHODIMP
+nsPrintSettingsServiceWin::SerializeToPrintData(nsIPrintSettings* aSettings,
+ PrintData* data) {
+ nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings);
+ if (!psWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString deviceName;
+ nsAutoString driverName;
+
+ psWin->GetDeviceName(deviceName);
+ psWin->GetDriverName(driverName);
+
+ data->deviceName().Assign(deviceName);
+ data->driverName().Assign(driverName);
+
+ // When creating the print dialog on Windows, we only need to send certain
+ // print settings information from the parent to the child not vice versa.
+ if (XRE_IsParentProcess()) {
+ // A DEVMODE can actually be of arbitrary size. If it turns out that it'll
+ // make our IPC message larger than the limit, then we'll error out.
+ LPDEVMODEW devModeRaw;
+ psWin->GetDevMode(&devModeRaw); // This actually allocates a copy of the
+ // the nsIPrintSettingsWin DEVMODE, so
+ // we're now responsible for deallocating
+ // it. We'll use an nsAutoDevMode helper
+ // to do this.
+ if (devModeRaw) {
+ nsAutoDevMode devMode(devModeRaw);
+ devModeRaw = nullptr;
+
+ size_t devModeTotalSize = devMode->dmSize + devMode->dmDriverExtra;
+ size_t msgTotalSize = sizeof(PrintData) + devModeTotalSize;
+
+ if (msgTotalSize > IPC::Channel::kMaximumMessageSize / 2) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Instead of reaching in and manually reading each member, we'll just
+ // copy the bits over.
+ const char* devModeData = reinterpret_cast<const char*>(devMode.get());
+ nsTArray<uint8_t> arrayBuf;
+ arrayBuf.AppendElements(devModeData, devModeTotalSize);
+ data->devModeData() = std::move(arrayBuf);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceWin::DeserializeToPrintSettings(
+ const PrintData& data, nsIPrintSettings* settings) {
+ nsresult rv =
+ nsPrintSettingsService::DeserializeToPrintSettings(data, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(settings);
+ if (!settings) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_IsContentProcess()) {
+ psWin->SetDeviceName(data.deviceName());
+ psWin->SetDriverName(data.driverName());
+
+ if (data.devModeData().IsEmpty()) {
+ psWin->SetDevMode(nullptr);
+ } else {
+ // Check minimum length of DEVMODE data.
+ auto devModeDataLength = data.devModeData().Length();
+ if (devModeDataLength < sizeof(DEVMODEW)) {
+ NS_WARNING("DEVMODE data is too short.");
+ return NS_ERROR_FAILURE;
+ }
+
+ DEVMODEW* devMode = reinterpret_cast<DEVMODEW*>(
+ const_cast<uint8_t*>(data.devModeData().Elements()));
+
+ // Check actual length of DEVMODE data.
+ if ((devMode->dmSize + devMode->dmDriverExtra) != devModeDataLength) {
+ NS_WARNING("DEVMODE length is incorrect.");
+ return NS_ERROR_FAILURE;
+ }
+
+ psWin->SetDevMode(devMode); // Copies
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsServiceWin::_CreatePrintSettings(
+ nsIPrintSettings** _retval) {
+ *_retval = nullptr;
+ nsPrintSettingsWin* printSettings =
+ new nsPrintSettingsWin(); // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsPrintSettingsServiceWin.h b/widget/windows/nsPrintSettingsServiceWin.h
new file mode 100644
index 0000000000..e11d307a8b
--- /dev/null
+++ b/widget/windows/nsPrintSettingsServiceWin.h
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsPrintSettingsServiceWin_h
+#define nsPrintSettingsServiceWin_h
+
+#include "nsPrintSettingsService.h"
+
+class nsIPrintSettings;
+
+class nsPrintSettingsServiceWin final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceWin() {}
+
+ NS_IMETHODIMP SerializeToPrintData(
+ nsIPrintSettings* aSettings,
+ mozilla::embedding::PrintData* data) override;
+
+ NS_IMETHODIMP DeserializeToPrintSettings(
+ const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings) override;
+
+ nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceWin_h
diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp
new file mode 100644
index 0000000000..788c4ab6da
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.cpp
@@ -0,0 +1,477 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsPrintSettingsWin.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "nsCRT.h"
+#include "nsDeviceContextSpecWin.h"
+#include "nsPrintSettingsImpl.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+// Using paper sizes from wingdi.h and the units given there, plus a little
+// extra research for the ones it doesn't give. Looks like the list hasn't
+// changed since Windows 2000, so should be fairly stable now.
+const short kPaperSizeUnits[] = {
+ nsIPrintSettings::kPaperSizeMillimeters, // Not Used default to mm as
+ // DEVMODE uses tenths of mm, just
+ // in case
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTERSMALL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEDGER
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_STATEMENT
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_EXECUTIVE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4SMALL
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FOLIO
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_QUARTO
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X14
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_11X17
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_NOTE
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_9
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_10
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_12
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_14
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_CSHEET
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_DSHEET
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ESHEET
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_DL
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C65
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_ITALY
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_MONARCH
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_PERSONAL
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_US
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_STD_GERMAN
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_LGL_GERMAN
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ISO_B4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_9X11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X11
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_15X11
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_INVITE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_48
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_49
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_A4_EXTRA
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_TRANSVERSE
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B_PLUS
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_PLUS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_EXTRA
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA_TRANSVERSE
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4_JIS_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_JIS_ROTATED
+ nsIPrintSettings::
+ kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD_ROTATED
+ nsIPrintSettings::
+ kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS_ROTATED
+ nsIPrintSettings::kPaperSizeInches, // DMPAPER_12X11
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9_ROTATED
+ nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10_ROTATED
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsWin, nsPrintSettings,
+ nsIPrintSettingsWin)
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::nsPrintSettingsWin()
+ : nsPrintSettings(),
+ mDeviceName(nullptr),
+ mDriverName(nullptr),
+ mDevMode(nullptr) {}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::nsPrintSettingsWin(const nsPrintSettingsWin& aPS)
+ : mDevMode(nullptr) {
+ *this = aPS;
+}
+
+/* static */
+void nsPrintSettingsWin::PaperSizeUnitFromDmPaperSize(short aPaperSize,
+ int16_t& aPaperSizeUnit) {
+ if (aPaperSize > 0 && aPaperSize < int32_t(ArrayLength(kPaperSizeUnits))) {
+ aPaperSizeUnit = kPaperSizeUnits[aPaperSize];
+ }
+}
+
+void nsPrintSettingsWin::InitWithInitializer(
+ const PrintSettingsInitializer& aSettings) {
+ nsPrintSettings::InitWithInitializer(aSettings);
+
+ if (aSettings.mDevmodeWStorage.Length() < sizeof(DEVMODEW)) {
+ return;
+ }
+
+ auto* devmode =
+ reinterpret_cast<const DEVMODEW*>(aSettings.mDevmodeWStorage.Elements());
+ if (devmode->dmSize != sizeof(DEVMODEW) ||
+ devmode->dmSize + devmode->dmDriverExtra >
+ aSettings.mDevmodeWStorage.Length()) {
+ return;
+ }
+
+ // SetDevMode copies the DEVMODE.
+ SetDevMode(const_cast<DEVMODEW*>(devmode));
+
+ if (mDevMode->dmFields & DM_SCALE) {
+ // Since we do the scaling, grab the DEVMODE value and reset it back to 100.
+ double scale = double(mDevMode->dmScale) / 100.0f;
+ if (mScaling == 1.0 || scale != 1.0) {
+ SetScaling(scale);
+ }
+ mDevMode->dmScale = 100;
+ }
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const PrintSettingsInitializer& aSettings) {
+ RefPtr<nsPrintSettings> settings = aSettings.mPrintSettings.get();
+ if (!settings) {
+ settings = MakeRefPtr<nsPrintSettingsWin>();
+ }
+ settings->InitWithInitializer(aSettings);
+ return settings.forget();
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::~nsPrintSettingsWin() {
+ if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const nsAString& aDeviceName) {
+ mDeviceName = aDeviceName;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(nsAString& aDeviceName) {
+ aDeviceName = mDeviceName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const nsAString& aDriverName) {
+ mDriverName = aDriverName;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(nsAString& aDriverName) {
+ aDriverName = mDriverName;
+ return NS_OK;
+}
+
+void nsPrintSettingsWin::CopyDevMode(DEVMODEW* aInDevMode,
+ DEVMODEW*& aOutDevMode) {
+ aOutDevMode = nullptr;
+ size_t size = aInDevMode->dmSize + aInDevMode->dmDriverExtra;
+ aOutDevMode =
+ (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, size);
+ if (aOutDevMode) {
+ memcpy(aOutDevMode, aInDevMode, size);
+ }
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::GetDevMode(DEVMODEW** aDevMode) {
+ NS_ENSURE_ARG_POINTER(aDevMode);
+
+ if (mDevMode) {
+ CopyDevMode(mDevMode, *aDevMode);
+ } else {
+ *aDevMode = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDevMode(DEVMODEW* aDevMode) {
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ mDevMode = nullptr;
+ }
+
+ if (aDevMode) {
+ CopyDevMode(aDevMode, mDevMode);
+ }
+ return NS_OK;
+}
+
+void nsPrintSettingsWin::InitUnwriteableMargin(HDC aHdc) {
+ mozilla::gfx::MarginDouble margin =
+ mozilla::widget::WinUtils::GetUnwriteableMarginsForDeviceInInches(aHdc);
+
+ mUnwriteableMargin.SizeTo(NS_INCHES_TO_INT_TWIPS(margin.top),
+ NS_INCHES_TO_INT_TWIPS(margin.right),
+ NS_INCHES_TO_INT_TWIPS(margin.bottom),
+ NS_INCHES_TO_INT_TWIPS(margin.left));
+}
+
+void nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode) {
+ MOZ_ASSERT(aHdc);
+ MOZ_ASSERT(aDevMode);
+
+ mIsInitedFromPrinter = true;
+ if (aDevMode->dmFields & DM_ORIENTATION) {
+ const bool areSheetsOfPaperPortraitMode =
+ (aDevMode->dmOrientation == DMORIENT_PORTRAIT);
+
+ // If our Windows print settings say that we're producing portrait-mode
+ // sheets of paper, then our page format must also be portrait-mode; unless
+ // we've got a pages-per-sheet value with orthogonal pages/sheets, in which
+ // case it's reversed.
+ const bool arePagesPortraitMode =
+ (areSheetsOfPaperPortraitMode != HasOrthogonalPagesPerSheet());
+
+ // Record the orientation of the pages (determined above) in mOrientation:
+ mOrientation = int32_t(arePagesPortraitMode ? kPortraitOrientation
+ : kLandscapeOrientation);
+ }
+
+ if (aDevMode->dmFields & DM_COPIES) {
+ mNumCopies = aDevMode->dmCopies;
+ }
+
+ if (aDevMode->dmFields & DM_DUPLEX) {
+ switch (aDevMode->dmDuplex) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("bad value for dmDuplex field");
+ case DMDUP_SIMPLEX:
+ mDuplex = kDuplexNone;
+ break;
+ case DMDUP_VERTICAL:
+ mDuplex = kDuplexFlipOnLongEdge;
+ break;
+ case DMDUP_HORIZONTAL:
+ mDuplex = kDuplexFlipOnShortEdge;
+ break;
+ }
+ }
+
+ // Since we do the scaling, grab their value and reset back to 100.
+ if (aDevMode->dmFields & DM_SCALE) {
+ double scale = double(aDevMode->dmScale) / 100.0f;
+ if (mScaling == 1.0 || scale != 1.0) {
+ mScaling = scale;
+ }
+ aDevMode->dmScale = 100;
+ }
+
+ if (aDevMode->dmFields & DM_PAPERSIZE) {
+ mPaperId.Truncate(0);
+ mPaperId.AppendInt(aDevMode->dmPaperSize);
+ // If it is not a paper size we know about, the unit will remain unchanged.
+ PaperSizeUnitFromDmPaperSize(aDevMode->dmPaperSize, mPaperSizeUnit);
+ }
+
+ if (aDevMode->dmFields & DM_COLOR) {
+ mPrintInColor = aDevMode->dmColor == DMCOLOR_COLOR;
+ }
+
+ InitUnwriteableMargin(aHdc);
+
+ int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY);
+ int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT);
+ double physicalHeightInch = double(physicalHeight) / pixelsPerInchY;
+ int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX);
+ int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH);
+ double physicalWidthInch = double(physicalWidth) / pixelsPerInchX;
+
+ // Get the paper size from the device context rather than the DEVMODE, because
+ // it is always available.
+ double paperHeightInch = mOrientation == kPortraitOrientation
+ ? physicalHeightInch
+ : physicalWidthInch;
+ mPaperHeight = mPaperSizeUnit == kPaperSizeInches
+ ? paperHeightInch
+ : paperHeightInch * MM_PER_INCH_FLOAT;
+
+ double paperWidthInch = mOrientation == kPortraitOrientation
+ ? physicalWidthInch
+ : physicalHeightInch;
+ mPaperWidth = mPaperSizeUnit == kPaperSizeInches
+ ? paperWidthInch
+ : paperWidthInch * MM_PER_INCH_FLOAT;
+
+ // Using LOGPIXELSY to match existing code for print scaling calculations.
+ mResolution = pixelsPerInchY;
+}
+
+void nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode) {
+ MOZ_ASSERT(aDevMode);
+
+ if (!mPaperId.IsEmpty()) {
+ aDevMode->dmPaperSize = _wtoi((const wchar_t*)mPaperId.BeginReading());
+ aDevMode->dmFields |= DM_PAPERSIZE;
+ } else {
+ aDevMode->dmPaperSize = 0;
+ aDevMode->dmFields &= ~DM_PAPERSIZE;
+ }
+
+ aDevMode->dmFields |= DM_COLOR;
+ aDevMode->dmColor = mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
+
+ // The length and width in DEVMODE are always in tenths of a millimeter.
+ double tenthsOfAmmPerSizeUnit =
+ mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT * 10.0 : 10.0;
+
+ // Note: small page sizes can be required here for sticker, label and slide
+ // printers etc. see bug 1271900.
+ if (mPaperHeight > 0) {
+ aDevMode->dmPaperLength = std::round(mPaperHeight * tenthsOfAmmPerSizeUnit);
+ aDevMode->dmFields |= DM_PAPERLENGTH;
+ } else {
+ aDevMode->dmPaperLength = 0;
+ aDevMode->dmFields &= ~DM_PAPERLENGTH;
+ }
+
+ if (mPaperWidth > 0) {
+ aDevMode->dmPaperWidth = std::round(mPaperWidth * tenthsOfAmmPerSizeUnit);
+ aDevMode->dmFields |= DM_PAPERWIDTH;
+ } else {
+ aDevMode->dmPaperWidth = 0;
+ aDevMode->dmFields &= ~DM_PAPERWIDTH;
+ }
+
+ // Setup Orientation
+ aDevMode->dmOrientation = GetSheetOrientation() == kPortraitOrientation
+ ? DMORIENT_PORTRAIT
+ : DMORIENT_LANDSCAPE;
+ aDevMode->dmFields |= DM_ORIENTATION;
+
+ // Setup Number of Copies
+ aDevMode->dmCopies = mNumCopies;
+ aDevMode->dmFields |= DM_COPIES;
+
+ // Setup Simplex/Duplex mode
+ switch (mDuplex) {
+ case kDuplexNone:
+ aDevMode->dmDuplex = DMDUP_SIMPLEX;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ case kDuplexFlipOnLongEdge:
+ aDevMode->dmDuplex = DMDUP_VERTICAL;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ case kDuplexFlipOnShortEdge:
+ aDevMode->dmDuplex = DMDUP_HORIZONTAL;
+ aDevMode->dmFields |= DM_DUPLEX;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad value for duplex option");
+ break;
+ }
+}
+
+//-------------------------------------------
+nsresult nsPrintSettingsWin::_Clone(nsIPrintSettings** _retval) {
+ RefPtr<nsPrintSettingsWin> printSettings = new nsPrintSettingsWin(*this);
+ printSettings.forget(_retval);
+ return NS_OK;
+}
+
+//-------------------------------------------
+nsPrintSettingsWin& nsPrintSettingsWin::operator=(
+ const nsPrintSettingsWin& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ ((nsPrintSettings&)*this) = rhs;
+
+ // Use free because we used the native malloc to create the memory
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDeviceName = rhs.mDeviceName;
+ mDriverName = rhs.mDriverName;
+
+ if (rhs.mDevMode) {
+ CopyDevMode(rhs.mDevMode, mDevMode);
+ } else {
+ mDevMode = nullptr;
+ }
+
+ return *this;
+}
+
+//-------------------------------------------
+nsresult nsPrintSettingsWin::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettingsWin* psWin = static_cast<nsPrintSettingsWin*>(aPS);
+ *this = *psWin;
+ return NS_OK;
+}
diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h
new file mode 100644
index 0000000000..c127efbeb6
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.h
@@ -0,0 +1,62 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsPrintSettingsWin_h__
+#define nsPrintSettingsWin_h__
+
+#include "nsPrintSettingsImpl.h"
+#include "nsIPrintSettingsWin.h"
+#include <windows.h>
+
+//*****************************************************************************
+//*** nsPrintSettingsWin
+//*****************************************************************************
+class nsPrintSettingsWin : public nsPrintSettings, public nsIPrintSettingsWin {
+ virtual ~nsPrintSettingsWin();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIPRINTSETTINGSWIN
+
+ nsPrintSettingsWin();
+ nsPrintSettingsWin(const nsPrintSettingsWin& aPS);
+
+ /**
+ * @param aPaperSize the Windows dmPaperSize
+ * @param aPaperSizeUnit will be set to the nsIPrintSettings paper size unit
+ * associated with aPaperSize or left unchanged if
+ * aPaperSize is not recognized
+ */
+ static void PaperSizeUnitFromDmPaperSize(short aPaperSize,
+ int16_t& aPaperSizeUnit);
+
+ void InitWithInitializer(const PrintSettingsInitializer& aSettings) final;
+
+ /**
+ * Makes a new copy
+ */
+ virtual nsresult _Clone(nsIPrintSettings** _retval);
+
+ /**
+ * Assigns values
+ */
+ virtual nsresult _Assign(nsIPrintSettings* aPS);
+
+ /**
+ * Assignment
+ */
+ nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs);
+
+ protected:
+ void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW*& aOutDevMode);
+ void InitUnwriteableMargin(HDC aHdc);
+
+ nsString mDeviceName;
+ nsString mDriverName;
+ LPDEVMODEW mDevMode;
+};
+
+#endif /* nsPrintSettingsWin_h__ */
diff --git a/widget/windows/nsPrinterWin.cpp b/widget/windows/nsPrinterWin.cpp
new file mode 100644
index 0000000000..75676cd613
--- /dev/null
+++ b/widget/windows/nsPrinterWin.cpp
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsPrinterWin.h"
+
+#include <algorithm>
+#include <windows.h>
+#include <winspool.h>
+
+#include "mozilla/Array.h"
+#include "mozilla/dom/Promise.h"
+#include "nsPaper.h"
+#include "nsPrintSettingsImpl.h"
+#include "nsPrintSettingsWin.h"
+#include "nsWindowsHelpers.h"
+#include "PrintBackgroundTask.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using mozilla::PrintSettingsInitializer;
+using mozilla::dom::Promise;
+
+static const double kPointsPerTenthMM = 72.0 / 254.0;
+static const double kPointsPerInch = 72.0;
+
+nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray* aArray,
+ const nsAString& aName)
+ : nsPrinterBase(aArray),
+ mName(aName),
+ mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {}
+
+// static
+already_AddRefed<nsPrinterWin> nsPrinterWin::Create(
+ const CommonPaperInfoArray* aArray, const nsAString& aName) {
+ return do_AddRef(new nsPrinterWin(aArray, aName));
+}
+
+template <class T>
+static nsTArray<T> GetDeviceCapabilityArray(const LPWSTR aPrinterName,
+ WORD aCapabilityID,
+ mozilla::Mutex& aDriverMutex,
+ int& aCount) {
+ MOZ_ASSERT(aCount >= 0, "Possibly passed aCount from previous error case.");
+
+ nsTArray<T> caps;
+
+ // We only want to access printer drivers in the parent process.
+ if (!XRE_IsParentProcess()) {
+ return caps;
+ }
+
+ // Both the call to get the size and the call to actually populate the array
+ // are relatively expensive, so as sometimes the lengths of the arrays that we
+ // retrieve depend on each other we allow a count to be passed in to save the
+ // first call. As we allocate double the count anyway this should allay any
+ // safety worries.
+ if (!aCount) {
+ // Passing nullptr as the port here seems to work. Given that we would have
+ // to OpenPrinter with just the name anyway to get the port that makes
+ // sense. Also, the printer set-up seems to stop you from having two
+ // printers with the same name. Note: this (and the call below) are blocking
+ // calls, which could be slow.
+ MutexAutoLock autoLock(aDriverMutex);
+ aCount = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
+ nullptr, nullptr);
+ if (aCount <= 0) {
+ return caps;
+ }
+ }
+
+ // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the
+ // buffer being overflowed, so we over-allocate for safety.
+ caps.SetLength(aCount * 2);
+ MutexAutoLock autoLock(aDriverMutex);
+ int count =
+ ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
+ reinterpret_cast<LPWSTR>(caps.Elements()), nullptr);
+ if (count <= 0) {
+ caps.Clear();
+ return caps;
+ }
+
+ // We know from bug 1673708 that sometimes the final array returned is smaller
+ // than the required array count. Assert here to see if this is reproduced on
+ // test servers.
+ MOZ_ASSERT(count == aCount, "Different array count returned than expected.");
+
+ // Note that TruncateLength will crash if count > caps.Length().
+ caps.TruncateLength(count);
+ return caps;
+}
+
+static void DevmodeToSettingsInitializer(
+ const nsString& aPrinterName, const DEVMODEW* aDevmode,
+ mozilla::Mutex& aDriverMutex,
+ PrintSettingsInitializer& aSettingsInitializer) {
+ aSettingsInitializer.mPrinter.Assign(aPrinterName);
+
+ HDC dc;
+ {
+ MutexAutoLock autoLock(aDriverMutex);
+ dc = ::CreateICW(nullptr, aPrinterName.get(), nullptr, aDevmode);
+ }
+ nsAutoHDC printerDc(dc);
+ MOZ_ASSERT(printerDc, "CreateICW failed");
+ if (!printerDc) {
+ return;
+ }
+
+ if (aDevmode->dmFields & DM_PAPERSIZE) {
+ aSettingsInitializer.mPaperInfo.mId.Truncate();
+ aSettingsInitializer.mPaperInfo.mId.AppendInt(aDevmode->dmPaperSize);
+ // If it is not a paper size we know about, the unit will remain unchanged.
+ nsPrintSettingsWin::PaperSizeUnitFromDmPaperSize(
+ aDevmode->dmPaperSize, aSettingsInitializer.mPaperSizeUnit);
+ }
+
+ int pixelsPerInchY = ::GetDeviceCaps(printerDc, LOGPIXELSY);
+ int physicalHeight = ::GetDeviceCaps(printerDc, PHYSICALHEIGHT);
+ double heightInInches = double(physicalHeight) / pixelsPerInchY;
+ int pixelsPerInchX = ::GetDeviceCaps(printerDc, LOGPIXELSX);
+ int physicalWidth = ::GetDeviceCaps(printerDc, PHYSICALWIDTH);
+ double widthInches = double(physicalWidth) / pixelsPerInchX;
+ if (aDevmode->dmFields & DM_ORIENTATION &&
+ aDevmode->dmOrientation == DMORIENT_LANDSCAPE) {
+ std::swap(widthInches, heightInInches);
+ }
+ aSettingsInitializer.mPaperInfo.mSize.SizeTo(widthInches * kPointsPerInch,
+ heightInInches * kPointsPerInch);
+
+ gfx::MarginDouble margin =
+ WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
+ aSettingsInitializer.mPaperInfo.mUnwriteableMargin = Some(MarginDouble{
+ margin.top * kPointsPerInch, margin.right * kPointsPerInch,
+ margin.bottom * kPointsPerInch, margin.left * kPointsPerInch});
+
+ // Using Y to match existing code for print scaling calculations.
+ aSettingsInitializer.mResolution = pixelsPerInchY;
+
+ if (aDevmode->dmFields & DM_COLOR) {
+ // See comment for PrintSettingsInitializer.mPrintInColor
+ aSettingsInitializer.mPrintInColor =
+ aDevmode->dmColor != DMCOLOR_MONOCHROME;
+ }
+
+ if (aDevmode->dmFields & DM_ORIENTATION) {
+ aSettingsInitializer.mSheetOrientation =
+ int32_t(aDevmode->dmOrientation == DMORIENT_PORTRAIT
+ ? nsPrintSettings::kPortraitOrientation
+ : nsPrintSettings::kLandscapeOrientation);
+ }
+
+ if (aDevmode->dmFields & DM_COPIES) {
+ aSettingsInitializer.mNumCopies = aDevmode->dmCopies;
+ }
+
+ if (aDevmode->dmFields & DM_DUPLEX) {
+ switch (aDevmode->dmDuplex) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("bad value for dmDuplex field");
+ case DMDUP_SIMPLEX:
+ aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexNone;
+ break;
+ case DMDUP_VERTICAL:
+ aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnLongEdge;
+ break;
+ case DMDUP_HORIZONTAL:
+ aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnShortEdge;
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsPrinterWin::GetName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterWin::GetSystemName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+namespace mozilla {
+template <>
+void ResolveOrReject(Promise& aPromise, nsPrinterWin& aPrinter,
+ const PrintSettingsInitializer& aResult) {
+ aPromise.MaybeResolve(
+ RefPtr<nsIPrintSettings>(CreatePlatformPrintSettings(aResult)));
+}
+} // namespace mozilla
+
+NS_IMETHODIMP nsPrinterWin::CopyFromWithValidation(
+ nsIPrintSettings* aSettingsToCopyFrom, JSContext* aCx,
+ Promise** aResultPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aResultPromise);
+
+ PrintSettingsInitializer settingsInitializer =
+ aSettingsToCopyFrom->GetSettingsInitializer();
+ return PrintBackgroundTaskPromise(
+ *this, aCx, aResultPromise, "CopyFromWithValidation"_ns,
+ &nsPrinterWin::GetValidatedSettings, settingsInitializer);
+}
+
+bool nsPrinterWin::SupportsDuplex() const {
+ MutexAutoLock autoLock(mDriverMutex);
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_DUPLEX, nullptr,
+ nullptr) == 1;
+}
+
+bool nsPrinterWin::SupportsColor() const {
+ MutexAutoLock autoLock(mDriverMutex);
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLORDEVICE, nullptr,
+ nullptr) == 1;
+}
+
+bool nsPrinterWin::SupportsMonochrome() const {
+ if (!SupportsColor()) {
+ return true;
+ }
+
+ nsHPRINTER hPrinter = nullptr;
+ if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
+ return false;
+ }
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return false;
+ }
+
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ devmode->dmFields |= DM_COLOR;
+ devmode->dmColor = DMCOLOR_MONOCHROME;
+ // Try to modify the devmode settings and see if the setting sticks.
+ //
+ // This has been the only reliable way to detect it that we've found.
+ MutexAutoLock autoLock(mDriverMutex);
+ LONG ret =
+ ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), devmode,
+ devmode, DM_IN_BUFFER | DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ return false;
+ }
+ return !(devmode->dmFields & DM_COLOR) ||
+ devmode->dmColor == DMCOLOR_MONOCHROME;
+}
+
+bool nsPrinterWin::SupportsCollation() const {
+ MutexAutoLock autoLock(mDriverMutex);
+ return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLLATE, nullptr,
+ nullptr) == 1;
+}
+
+nsPrinterBase::PrinterInfo nsPrinterWin::CreatePrinterInfo() const {
+ return PrinterInfo{PaperList(), DefaultSettings()};
+}
+
+mozilla::gfx::MarginDouble nsPrinterWin::GetMarginsForPaper(
+ nsString aPaperId) const {
+ gfx::MarginDouble margin;
+
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return margin;
+ }
+
+ auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+
+ devmode->dmFields = DM_PAPERSIZE;
+ devmode->dmPaperSize = _wtoi((const wchar_t*)aPaperId.BeginReading());
+ HDC dc;
+ {
+ MutexAutoLock autoLock(mDriverMutex);
+ dc = ::CreateICW(nullptr, mName.get(), nullptr, devmode);
+ }
+ nsAutoHDC printerDc(dc);
+ MOZ_ASSERT(printerDc, "CreateICW failed");
+ if (!printerDc) {
+ return margin;
+ }
+ margin = WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
+ margin.top *= kPointsPerInch;
+ margin.right *= kPointsPerInch;
+ margin.bottom *= kPointsPerInch;
+ margin.left *= kPointsPerInch;
+
+ return margin;
+}
+
+nsTArray<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const {
+ nsTArray<uint8_t> devmodeStorageW;
+
+ auto devmodeStorageWLock = mDefaultDevmodeWStorage.Lock();
+ if (devmodeStorageWLock->IsEmpty()) {
+ nsHPRINTER hPrinter = nullptr;
+ // OpenPrinter could fail if, for example, the printer has been removed
+ // or otherwise become inaccessible since it was selected.
+ if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
+ return devmodeStorageW;
+ }
+ nsAutoPrinter autoPrinter(hPrinter);
+ // Allocate devmode storage of the correct size.
+ MutexAutoLock autoLock(mDriverMutex);
+ LONG bytesNeeded = ::DocumentPropertiesW(nullptr, autoPrinter.get(),
+ mName.get(), nullptr, nullptr, 0);
+ // Note that we must cast the sizeof() to a signed type so that comparison
+ // with the signed, potentially-negative bytesNeeded will work!
+ MOZ_ASSERT(bytesNeeded >= LONG(sizeof(DEVMODEW)),
+ "DocumentPropertiesW failed to get valid size");
+ if (bytesNeeded < LONG(sizeof(DEVMODEW))) {
+ return devmodeStorageW;
+ }
+
+ // Allocate extra space in case of bad drivers that return a too-small
+ // result from DocumentProperties.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
+ if (!devmodeStorageWLock->SetLength(bytesNeeded * 2, fallible)) {
+ return devmodeStorageW;
+ }
+
+ memset(devmodeStorageWLock->Elements(), 0, devmodeStorageWLock->Length());
+ auto* devmode =
+ reinterpret_cast<DEVMODEW*>(devmodeStorageWLock->Elements());
+ LONG ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(),
+ devmode, nullptr, DM_OUT_BUFFER);
+ MOZ_ASSERT(ret == IDOK, "DocumentPropertiesW failed");
+ // Make sure that the lengths in the DEVMODEW make sense.
+ if (ret != IDOK || devmode->dmSize != sizeof(DEVMODEW) ||
+ devmode->dmSize + devmode->dmDriverExtra >
+ devmodeStorageWLock->Length()) {
+ // Clear mDefaultDevmodeWStorage to make sure we try again next time.
+ devmodeStorageWLock->Clear();
+ return devmodeStorageW;
+ }
+ }
+
+ devmodeStorageW.Assign(devmodeStorageWLock.ref());
+ return devmodeStorageW;
+}
+
+nsTArray<mozilla::PaperInfo> nsPrinterWin::PaperList() const {
+ // Paper IDs are returned as WORDs.
+ int requiredArrayCount = 0;
+ auto paperIds = GetDeviceCapabilityArray<WORD>(
+ mName.get(), DC_PAPERS, mDriverMutex, requiredArrayCount);
+ if (!paperIds.Length()) {
+ return {};
+ }
+
+ // Paper names are returned in 64 long character buffers.
+ auto paperNames = GetDeviceCapabilityArray<Array<wchar_t, 64>>(
+ mName.get(), DC_PAPERNAMES, mDriverMutex, requiredArrayCount);
+ // Check that we have the same number of names as IDs.
+ if (paperNames.Length() != paperIds.Length()) {
+ return {};
+ }
+
+ // Paper sizes are returned as POINT structs with a tenth of a millimeter as
+ // the unit.
+ auto paperSizes = GetDeviceCapabilityArray<POINT>(
+ mName.get(), DC_PAPERSIZE, mDriverMutex, requiredArrayCount);
+ // Check that we have the same number of sizes as IDs.
+ if (paperSizes.Length() != paperIds.Length()) {
+ return {};
+ }
+
+ nsTArray<mozilla::PaperInfo> paperList;
+ paperList.SetCapacity(paperNames.Length());
+ for (size_t i = 0; i < paperNames.Length(); ++i) {
+ // Paper names are null terminated unless they are 64 characters long.
+ auto firstNull =
+ std::find(paperNames[i].cbegin(), paperNames[i].cend(), L'\0');
+ auto nameLength = firstNull - paperNames[i].cbegin();
+ double width = paperSizes[i].x * kPointsPerTenthMM;
+ double height = paperSizes[i].y * kPointsPerTenthMM;
+
+ // Skip if no name or invalid size.
+ if (!nameLength || width <= 0 || height <= 0) {
+ continue;
+ }
+
+ // Windows paper IDs are 16-bit integers; we stringify them to store in the
+ // PaperInfo.mId field.
+ nsString paperIdString;
+ paperIdString.AppendInt(paperIds[i]);
+
+ // We don't resolve the margins eagerly because they're really expensive (on
+ // the order of seconds for some drivers).
+ nsDependentSubstring name(paperNames[i].cbegin(), nameLength);
+ paperList.AppendElement(mozilla::PaperInfo(paperIdString, nsString(name),
+ {width, height}, Nothing()));
+ }
+
+ return paperList;
+}
+
+PrintSettingsInitializer nsPrinterWin::DefaultSettings() const {
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return {};
+ }
+
+ const auto* devmode =
+ reinterpret_cast<const DEVMODEW*>(devmodeWStorage.Elements());
+
+ PrintSettingsInitializer settingsInitializer;
+ DevmodeToSettingsInitializer(mName, devmode, mDriverMutex,
+ settingsInitializer);
+ settingsInitializer.mDevmodeWStorage = std::move(devmodeWStorage);
+ return settingsInitializer;
+}
+
+PrintSettingsInitializer nsPrinterWin::GetValidatedSettings(
+ PrintSettingsInitializer aSettingsToValidate) const {
+ // This function validates the settings by relying on the printer driver
+ // rejecting any invalid settings and resetting them to valid values.
+
+ // Create a copy of the default DEVMODE for this printer.
+ nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
+ if (devmodeWStorage.IsEmpty()) {
+ return aSettingsToValidate;
+ }
+
+ nsHPRINTER hPrinter = nullptr;
+ if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
+ return aSettingsToValidate;
+ }
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ // Copy the settings from aSettingsToValidate into our DEVMODE.
+ DEVMODEW* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
+ if (!aSettingsToValidate.mPaperInfo.mId.IsEmpty()) {
+ devmode->dmPaperSize = _wtoi(
+ (const wchar_t*)aSettingsToValidate.mPaperInfo.mId.BeginReading());
+ devmode->dmFields |= DM_PAPERSIZE;
+ } else {
+ devmode->dmPaperSize = 0;
+ devmode->dmFields &= ~DM_PAPERSIZE;
+ }
+
+ devmode->dmFields |= DM_COLOR;
+ devmode->dmColor =
+ aSettingsToValidate.mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
+
+ // Note: small page sizes can be required here for sticker, label and slide
+ // printers etc. see bug 1271900.
+ if (aSettingsToValidate.mPaperInfo.mSize.height > 0) {
+ devmode->dmPaperLength = std::round(
+ aSettingsToValidate.mPaperInfo.mSize.height / kPointsPerTenthMM);
+ devmode->dmFields |= DM_PAPERLENGTH;
+ } else {
+ devmode->dmPaperLength = 0;
+ devmode->dmFields &= ~DM_PAPERLENGTH;
+ }
+
+ if (aSettingsToValidate.mPaperInfo.mSize.width > 0) {
+ devmode->dmPaperWidth = std::round(
+ aSettingsToValidate.mPaperInfo.mSize.width / kPointsPerTenthMM);
+ devmode->dmFields |= DM_PAPERWIDTH;
+ } else {
+ devmode->dmPaperWidth = 0;
+ devmode->dmFields &= ~DM_PAPERWIDTH;
+ }
+
+ // Setup Orientation
+ devmode->dmOrientation = aSettingsToValidate.mSheetOrientation ==
+ nsPrintSettings::kPortraitOrientation
+ ? DMORIENT_PORTRAIT
+ : DMORIENT_LANDSCAPE;
+ devmode->dmFields |= DM_ORIENTATION;
+
+ // Setup Number of Copies
+ devmode->dmCopies = aSettingsToValidate.mNumCopies;
+ devmode->dmFields |= DM_COPIES;
+
+ // Setup Simplex/Duplex mode
+ devmode->dmFields |= DM_DUPLEX;
+ switch (aSettingsToValidate.mDuplex) {
+ case nsPrintSettings::kDuplexNone:
+ devmode->dmDuplex = DMDUP_SIMPLEX;
+ break;
+ case nsPrintSettings::kDuplexFlipOnLongEdge:
+ devmode->dmDuplex = DMDUP_VERTICAL;
+ break;
+ case nsPrintSettings::kDuplexFlipOnShortEdge:
+ devmode->dmDuplex = DMDUP_HORIZONTAL;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad value for duplex option");
+ break;
+ }
+
+ // Apply the settings in the DEVMODE to the printer and retrieve the updated
+ // DEVMODE back into the same structure.
+ LONG ret;
+ {
+ MutexAutoLock autoLock(mDriverMutex);
+ ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(),
+ devmode, devmode, DM_IN_BUFFER | DM_OUT_BUFFER);
+ }
+ if (ret != IDOK) {
+ return aSettingsToValidate;
+ }
+
+ // Copy the settings back from the DEVMODE into aSettingsToValidate and
+ // return.
+ DevmodeToSettingsInitializer(mName, devmode, mDriverMutex,
+ aSettingsToValidate);
+ aSettingsToValidate.mDevmodeWStorage = std::move(devmodeWStorage);
+ return aSettingsToValidate;
+}
diff --git a/widget/windows/nsPrinterWin.h b/widget/windows/nsPrinterWin.h
new file mode 100644
index 0000000000..8afcf14d0e
--- /dev/null
+++ b/widget/windows/nsPrinterWin.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsPrinterWin_h_
+#define nsPrinterWin_h_
+
+#include "nsPrinterBase.h"
+#include "mozilla/DataMutex.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsPrinterWin final : public nsPrinterBase {
+ public:
+ NS_IMETHOD GetName(nsAString& aName) override;
+ NS_IMETHOD GetSystemName(nsAString& aName) override;
+ NS_IMETHOD CopyFromWithValidation(nsIPrintSettings*, JSContext*,
+ Promise**) final;
+ bool SupportsDuplex() const final;
+ bool SupportsColor() const final;
+ bool SupportsMonochrome() const final;
+ bool SupportsCollation() const final;
+ PrinterInfo CreatePrinterInfo() const final;
+ MarginDouble GetMarginsForPaper(nsString aPaperId) const final;
+
+ nsPrinterWin() = delete;
+ static already_AddRefed<nsPrinterWin> Create(
+ const mozilla::CommonPaperInfoArray* aPaperInfoArray,
+ const nsAString& aName);
+
+ private:
+ nsPrinterWin(const mozilla::CommonPaperInfoArray* aPaperInfoArray,
+ const nsAString& aName);
+ ~nsPrinterWin() = default;
+
+ PrintSettingsInitializer GetValidatedSettings(
+ PrintSettingsInitializer aSettingsToValidate) const;
+
+ nsTArray<uint8_t> CopyDefaultDevmodeW() const;
+ nsTArray<mozilla::PaperInfo> PaperList() const;
+ PrintSettingsInitializer DefaultSettings() const;
+
+ const nsString mName;
+ mutable mozilla::DataMutex<nsTArray<uint8_t>> mDefaultDevmodeWStorage;
+ // Even though some documentation seems to suggest that you should be able to
+ // use printer drivers on separate threads if you have separate handles, we
+ // see threading issues with multiple drivers. This Mutex is used to lock
+ // around all calls to DeviceCapabilitiesW, DocumentPropertiesW and
+ // CreateICW/DCW, to hopefully prevent these issues.
+ mutable mozilla::Mutex mDriverMutex MOZ_UNANNOTATED{"nsPrinterWin::Driver"};
+};
+
+#endif // nsPrinterWin_h_
diff --git a/widget/windows/nsSharePicker.cpp b/widget/windows/nsSharePicker.cpp
new file mode 100644
index 0000000000..9cdc792718
--- /dev/null
+++ b/widget/windows/nsSharePicker.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nsSharePicker.h"
+
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "WindowsUIUtils.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Unused.h"
+
+using mozilla::dom::Promise;
+
+///////////////////////////////////////////////////////////////////////////////
+// nsISharePicker
+
+NS_IMPL_ISUPPORTS(nsSharePicker, nsISharePicker)
+
+namespace {
+inline NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_MaybeVoid(
+ const nsACString& aStr) {
+ auto str = NS_ConvertUTF8toUTF16(aStr);
+ str.SetIsVoid(aStr.IsVoid());
+ return str;
+}
+inline nsIGlobalObject* GetGlobalObject() {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+} // namespace
+
+NS_IMETHODIMP
+nsSharePicker::Init(mozIDOMWindowProxy* aOpenerWindow) {
+ if (mInited) {
+ return NS_ERROR_FAILURE;
+ }
+ mOpenerWindow = aOpenerWindow;
+ mInited = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSharePicker::GetOpenerWindow(mozIDOMWindowProxy** aOpenerWindow) {
+ *aOpenerWindow = mOpenerWindow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSharePicker::Share(const nsACString& aTitle, const nsACString& aText,
+ nsIURI* aUrl, Promise** aPromise) {
+ mozilla::ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(GetGlobalObject(), result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+ nsAutoCString urlString;
+ if (aUrl) {
+ nsresult rv = aUrl->GetSpec(urlString);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mozilla::Unused << rv;
+ } else {
+ urlString.SetIsVoid(true);
+ }
+
+ auto mozPromise =
+ WindowsUIUtils::Share(NS_ConvertUTF8toUTF16_MaybeVoid(aTitle),
+ NS_ConvertUTF8toUTF16_MaybeVoid(aText),
+ NS_ConvertUTF8toUTF16_MaybeVoid(urlString));
+ mozPromise->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [promise]() { promise->MaybeResolveWithUndefined(); },
+ [promise]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); });
+
+ promise.forget(aPromise);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsSharePicker.h b/widget/windows/nsSharePicker.h
new file mode 100644
index 0000000000..d9ba9d2a6d
--- /dev/null
+++ b/widget/windows/nsSharePicker.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsSharePicker_h__
+#define nsSharePicker_h__
+
+#include "nsCOMPtr.h"
+#include "nsISharePicker.h"
+#include "nsPIDOMWindow.h"
+#include "nsThreadUtils.h"
+
+class nsSharePicker : public nsISharePicker {
+ virtual ~nsSharePicker() = default;
+
+ public:
+ nsSharePicker() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHAREPICKER
+
+ private:
+ bool mInited = false;
+ mozIDOMWindowProxy* mOpenerWindow;
+};
+
+#endif // nsSharePicker_h__
diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp
new file mode 100644
index 0000000000..1fecf09c3a
--- /dev/null
+++ b/widget/windows/nsSound.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "nscore.h"
+#include <stdio.h>
+#include "nsString.h"
+#include <windows.h>
+
+// mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <mmsystem.h>
+
+#include "HeadlessSound.h"
+#include "nsSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsIObserverService.h"
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "nsNativeCharsetUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "gfxPlatform.h"
+
+using mozilla::LogLevel;
+
+#ifdef DEBUG
+static mozilla::LazyLogModule gWin32SoundLog("nsSound");
+#endif
+
+// Hackaround for bug 1644240
+// When we call PlaySound for the first time in the process, winmm.dll creates
+// a new thread and starts a message loop in winmm!mciwindow. After that,
+// every call of PlaySound communicates with that thread via Window messages.
+// It seems that Warsaw application hooks USER32!GetMessageA, and there is
+// a timing window where they free their trampoline region without reverting
+// the hook on USER32!GetMessageA, resulting in crash when winmm!mciwindow
+// receives a message because it tries to jump to a freed buffer.
+// Based on the crash reports, it happened on all versions of Windows x64, and
+// the possible condition was wslbdhm64.dll was loaded but wslbscrwh64.dll was
+// unloaded. Therefore we suppress playing a sound under such a condition.
+static bool ShouldSuppressPlaySound() {
+#if defined(_M_AMD64)
+ if (::GetModuleHandle(L"wslbdhm64.dll") &&
+ !::GetModuleHandle(L"wslbscrwh64.dll")) {
+ return true;
+ }
+#endif // defined(_M_AMD64)
+ return false;
+}
+
+class nsSoundPlayer : public mozilla::Runnable {
+ public:
+ explicit nsSoundPlayer(const nsAString& aSoundName)
+ : mozilla::Runnable("nsSoundPlayer"),
+ mSoundName(aSoundName),
+ mSoundData(nullptr) {}
+
+ nsSoundPlayer(const uint8_t* aData, size_t aSize)
+ : mozilla::Runnable("nsSoundPlayer"), mSoundName(u""_ns) {
+ MOZ_ASSERT(aSize > 0, "Size should not be zero");
+ MOZ_ASSERT(aData, "Data shoud not be null");
+
+ // We will disptach nsSoundPlayer to playerthread, so keep a data copy
+ mSoundData = new uint8_t[aSize];
+ memcpy(mSoundData, aData, aSize);
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+ protected:
+ ~nsSoundPlayer();
+
+ nsString mSoundName;
+ uint8_t* mSoundData;
+};
+
+NS_IMETHODIMP
+nsSoundPlayer::Run() {
+ if (ShouldSuppressPlaySound()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData,
+ "Sound name or sound data should be specified");
+ DWORD flags = SND_NODEFAULT | SND_ASYNC;
+
+ if (mSoundData) {
+ flags |= SND_MEMORY;
+ ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags);
+ } else {
+ flags |= SND_ALIAS;
+ ::PlaySoundW(mSoundName.get(), nullptr, flags);
+ }
+ return NS_OK;
+}
+
+nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; }
+
+mozilla::StaticRefPtr<nsISound> nsSound::sInstance;
+
+/* static */
+already_AddRefed<nsISound> nsSound::GetInstance() {
+ if (!sInstance) {
+ if (gfxPlatform::IsHeadless()) {
+ sInstance = new mozilla::widget::HeadlessSound();
+ } else {
+ RefPtr<nsSound> sound = new nsSound();
+ nsresult rv = sound->CreatePlayerThread();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ sInstance = sound.forget();
+ }
+ ClearOnShutdown(&sInstance);
+ }
+
+ RefPtr<nsISound> service = sInstance;
+ return service.forget();
+}
+
+#ifndef SND_PURGE
+// Not available on Windows CE, and according to MSDN
+// doesn't do anything on recent windows either.
+# define SND_PURGE 0
+#endif
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver, nsIObserver)
+
+nsSound::nsSound() : mInited(false) {}
+
+nsSound::~nsSound() {}
+
+void nsSound::PurgeLastSound() {
+ // Halt any currently playing sound.
+ if (mSoundPlayer) {
+ if (mPlayerThread) {
+ mPlayerThread->Dispatch(
+ NS_NewRunnableFunction("nsSound::PurgeLastSound",
+ [player = std::move(mSoundPlayer)]() {
+ // Capture move mSoundPlayer to lambda then
+ // PlaySoundW(nullptr, nullptr, SND_PURGE)
+ // will be called before freeing the
+ // nsSoundPlayer.
+ if (ShouldSuppressPlaySound()) {
+ return;
+ }
+ ::PlaySoundW(nullptr, nullptr, SND_PURGE);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+NS_IMETHODIMP nsSound::Beep() {
+ ::MessageBeep(0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context, nsresult aStatus,
+ uint32_t dataLen, const uint8_t* data) {
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ // print a load error on bad status
+ if (NS_FAILED(aStatus)) {
+#ifdef DEBUG
+ if (aLoader) {
+ nsCOMPtr<nsIRequest> request;
+ nsCOMPtr<nsIChannel> channel;
+ aLoader->GetRequest(getter_AddRefs(request));
+ if (request) channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsAutoCString uriSpec;
+ uri->GetSpec(uriSpec);
+ MOZ_LOG(gWin32SoundLog, LogLevel::Info,
+ ("Failed to load %s\n", uriSpec.get()));
+ }
+ }
+ }
+#endif
+ return aStatus;
+ }
+
+ PurgeLastSound();
+
+ if (data && dataLen > 0) {
+ MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
+ mSoundPlayer = new nsSoundPlayer(data, dataLen);
+ MOZ_ASSERT(mSoundPlayer, "Could not create player");
+
+ nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
+ nsresult rv;
+
+#ifdef DEBUG_SOUND
+ char* url;
+ aURL->GetSpec(&url);
+ MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url));
+#endif
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(
+ getter_AddRefs(loader), aURL,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ return rv;
+}
+
+nsresult nsSound::CreatePlayerThread() {
+ if (mPlayerThread) {
+ return NS_OK;
+ }
+ if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
+ getter_AddRefs(mPlayerThread))))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Add an observer for shutdown event to release the thread at that time
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSound::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ PurgeLastSound();
+
+ if (mPlayerThread) {
+ mPlayerThread->Shutdown();
+ mPlayerThread = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Init() {
+ if (mInited) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ // This call halts a sound if it was still playing.
+ // We have to use the sound library for something to make sure
+ // it is initialized.
+ // If we wait until the first sound is played, there will
+ // be a time lag as the library gets loaded.
+ // This should be done in player thread otherwise it will block main thread
+ // at the first time loading sound library.
+ mPlayerThread->Dispatch(
+ NS_NewRunnableFunction("nsSound::Init",
+ []() {
+ if (ShouldSuppressPlaySound()) {
+ return;
+ }
+ ::PlaySoundW(nullptr, nullptr, SND_PURGE);
+ }),
+ NS_DISPATCH_NORMAL);
+
+ mInited = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
+ MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
+ PurgeLastSound();
+
+ const wchar_t* sound = nullptr;
+ switch (aEventId) {
+ case EVENT_NEW_MAIL_RECEIVED:
+ sound = L"MailBeep";
+ break;
+ case EVENT_ALERT_DIALOG_OPEN:
+ sound = L"SystemExclamation";
+ break;
+ case EVENT_CONFIRM_DIALOG_OPEN:
+ sound = L"SystemQuestion";
+ break;
+ case EVENT_MENU_EXECUTE:
+ sound = L"MenuCommand";
+ break;
+ case EVENT_MENU_POPUP:
+ sound = L"MenuPopup";
+ break;
+ case EVENT_EDITOR_MAX_LEN:
+ sound = L".Default";
+ break;
+ default:
+ // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
+ // NS_SYSSOUND_SELECT_DIALOG.
+ return NS_OK;
+ }
+ NS_ASSERTION(sound, "sound is null");
+ MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
+ mSoundPlayer = new nsSoundPlayer(nsDependentString(sound));
+ MOZ_ASSERT(mSoundPlayer, "Could not create player");
+ nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h
new file mode 100644
index 0000000000..d600b0873a
--- /dev/null
+++ b/widget/windows/nsSound.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIObserver.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/StaticPtr.h"
+
+class nsIThread;
+class nsIRunnable;
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver,
+ public nsIObserver
+
+{
+ public:
+ nsSound();
+ static already_AddRefed<nsISound> GetInstance();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIOBSERVER
+
+ private:
+ virtual ~nsSound();
+ void PurgeLastSound();
+
+ private:
+ nsresult CreatePlayerThread();
+
+ nsCOMPtr<nsIThread> mPlayerThread;
+ nsCOMPtr<nsIRunnable> mSoundPlayer;
+ bool mInited;
+
+ static mozilla::StaticRefPtr<nsISound> sInstance;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp
new file mode 100644
index 0000000000..6eea9c958f
--- /dev/null
+++ b/widget/windows/nsToolkit.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsToolkit.h"
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "nsWidgetsCID.h"
+#include "prmon.h"
+#include "prtime.h"
+#include "nsComponentManagerUtils.h"
+#include <objbase.h>
+#include "WinUtils.h"
+
+#include "nsUXThemeData.h"
+
+// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <unknwn.h>
+
+using namespace mozilla::widget;
+
+nsToolkit* nsToolkit::gToolkit = nullptr;
+HINSTANCE nsToolkit::mDllInstance = 0;
+
+//-------------------------------------------------------------------------
+//
+// constructor
+//
+//-------------------------------------------------------------------------
+nsToolkit::nsToolkit() {
+ MOZ_COUNT_CTOR(nsToolkit);
+
+#if defined(MOZ_STATIC_COMPONENT_LIBS)
+ nsToolkit::Startup(GetModuleHandle(nullptr));
+#endif
+}
+
+//-------------------------------------------------------------------------
+//
+// destructor
+//
+//-------------------------------------------------------------------------
+nsToolkit::~nsToolkit() { MOZ_COUNT_DTOR(nsToolkit); }
+
+void nsToolkit::Startup(HMODULE hModule) {
+ nsToolkit::mDllInstance = hModule;
+ WinUtils::Initialize();
+}
+
+void nsToolkit::Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+}
+
+//-------------------------------------------------------------------------
+//
+// Return the nsToolkit for the current thread. If a toolkit does not
+// yet exist, then one will be created...
+//
+//-------------------------------------------------------------------------
+// static
+nsToolkit* nsToolkit::GetToolkit() {
+ if (!gToolkit) {
+ gToolkit = new nsToolkit();
+ }
+
+ return gToolkit;
+}
diff --git a/widget/windows/nsToolkit.h b/widget/windows/nsToolkit.h
new file mode 100644
index 0000000000..4be7bdb80a
--- /dev/null
+++ b/widget/windows/nsToolkit.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsToolkit_h__
+#define nsToolkit_h__
+
+#include "nsdefs.h"
+
+#include "nsCOMPtr.h"
+#include <windows.h>
+
+// Avoid including windowsx.h to prevent macro pollution
+#ifndef GET_X_LPARAM
+# define GET_X_LPARAM(pt) (short(LOWORD(pt)))
+#endif
+#ifndef GET_Y_LPARAM
+# define GET_Y_LPARAM(pt) (short(HIWORD(pt)))
+#endif
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */
+
+class nsToolkit {
+ public:
+ nsToolkit();
+
+ private:
+ ~nsToolkit();
+
+ public:
+ static nsToolkit* GetToolkit();
+
+ static HINSTANCE mDllInstance;
+
+ static void Startup(HMODULE hModule);
+ static void Shutdown();
+
+ protected:
+ static nsToolkit* gToolkit;
+};
+
+#endif // TOOLKIT_H
diff --git a/widget/windows/nsUXThemeConstants.h b/widget/windows/nsUXThemeConstants.h
new file mode 100644
index 0000000000..44ce9ab34e
--- /dev/null
+++ b/widget/windows/nsUXThemeConstants.h
@@ -0,0 +1,256 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 nsUXThemeConstants_h
+#define nsUXThemeConstants_h
+
+/*
+ * The following constants are used to determine how a widget is drawn using
+ * Windows' Theme API. For more information on theme parts and states see
+ * http://msdn.microsoft.com/en-us/library/bb773210(VS.85).aspx
+ */
+
+#include <vssym32.h>
+#include <vsstyle.h>
+
+#define THEME_COLOR 204
+#define THEME_FONT 210
+
+// Generic state constants
+#define TS_NORMAL 1
+#define TS_HOVER 2
+#define TS_ACTIVE 3
+#define TS_DISABLED 4
+#define TS_FOCUSED 5
+
+// These constants are reversed for the trackbar (scale) thumb
+#define TKP_FOCUSED 4
+#define TKP_DISABLED 5
+
+// Toolbarbutton constants
+#define TB_CHECKED 5
+#define TB_HOVER_CHECKED 6
+
+// Button constants
+#define BP_BUTTON 1
+#define BP_RADIO 2
+#define BP_CHECKBOX 3
+#define BP_GROUPBOX 4
+#define BP_Count 5
+
+// Textfield constants
+/* This is the EP_EDITTEXT part */
+#define TFP_TEXTFIELD 1
+#define TFP_EDITBORDER_NOSCROLL 6
+#define TFS_READONLY 6
+
+/* These are the state constants for the EDITBORDER parts */
+#define TFS_EDITBORDER_NORMAL 1
+#define TFS_EDITBORDER_HOVER 2
+#define TFS_EDITBORDER_FOCUSED 3
+#define TFS_EDITBORDER_DISABLED 4
+
+// Treeview/listbox constants
+#define TREEVIEW_BODY 1
+
+// Scrollbar constants
+#define SP_BUTTON 1
+#define SP_THUMBHOR 2
+#define SP_THUMBVERT 3
+#define SP_TRACKSTARTHOR 4
+#define SP_TRACKENDHOR 5
+#define SP_TRACKSTARTVERT 6
+#define SP_TRACKENDVERT 7
+#define SP_GRIPPERHOR 8
+#define SP_GRIPPERVERT 9
+
+// Implicit hover state.
+// BASE + 0 = UP, + 1 = DOWN, etc.
+#define SP_BUTTON_IMPLICIT_HOVER_BASE 17
+
+// Scale constants
+#define TKP_TRACK 1
+#define TKP_TRACKVERT 2
+#define TKP_THUMB 3
+#define TKP_THUMBBOTTOM 4
+#define TKP_THUMBTOP 5
+#define TKP_THUMBVERT 6
+#define TKP_THUMBLEFT 7
+#define TKP_THUMBRIGHT 8
+
+// Track state contstants
+#define TRS_NORMAL 1
+
+// Track vertical state constants
+#define TRVS_NORMAL 1
+
+// Spin constants
+#define SPNP_UP 1
+#define SPNP_DOWN 2
+
+// Tab constants
+#define TABP_TAB 4
+#define TABP_TAB_SELECTED 5
+#define TABP_PANELS 9
+#define TABP_PANEL 10
+
+// Tooltip constants
+#define TTP_STANDARD 1
+
+// Dropdown constants
+#define CBP_DROPMARKER 1
+#define CBP_DROPBORDER 4
+/* This is actually the 'READONLY' style */
+#define CBP_DROPFRAME 5
+#define CBP_DROPMARKER_VISTA 6
+
+// Menu Constants
+#define MENU_BARBACKGROUND 7
+#define MENU_BARITEM 8
+#define MENU_POPUPBACKGROUND 9
+#define MENU_POPUPBORDERS 10
+#define MENU_POPUPCHECK 11
+#define MENU_POPUPCHECKBACKGROUND 12
+#define MENU_POPUPGUTTER 13
+#define MENU_POPUPITEM 14
+#define MENU_POPUPSEPARATOR 15
+#define MENU_POPUPSUBMENU 16
+#define MENU_SYSTEMCLOSE 17
+#define MENU_SYSTEMMAXIMIZE 18
+#define MENU_SYSTEMMINIMIZE 19
+#define MENU_SYSTEMRESTORE 20
+
+#define MB_ACTIVE 1
+#define MB_INACTIVE 2
+
+#define MS_NORMAL 1
+#define MS_SELECTED 2
+#define MS_DEMOTED 3
+
+#define MBI_NORMAL 1
+#define MBI_HOT 2
+#define MBI_PUSHED 3
+#define MBI_DISABLED 4
+#define MBI_DISABLEDHOT 5
+#define MBI_DISABLEDPUSHED 6
+
+#define MC_CHECKMARKNORMAL 1
+#define MC_CHECKMARKDISABLED 2
+#define MC_BULLETNORMAL 3
+#define MC_BULLETDISABLED 4
+
+#define MCB_DISABLED 1
+#define MCB_NORMAL 2
+#define MCB_BITMAP 3
+
+#define MPI_NORMAL 1
+#define MPI_HOT 2
+#define MPI_DISABLED 3
+#define MPI_DISABLEDHOT 4
+
+#define MSM_NORMAL 1
+#define MSM_DISABLED 2
+
+// Rebar constants
+#define RP_BAND 3
+#define RP_BACKGROUND 6
+
+// Constants only found in new (98+, 2K+, XP+, etc.) Windows.
+#ifdef DFCS_HOT
+# undef DFCS_HOT
+#endif
+#define DFCS_HOT 0x00001000
+
+#ifdef COLOR_MENUHILIGHT
+# undef COLOR_MENUHILIGHT
+#endif
+#define COLOR_MENUHILIGHT 29
+
+#ifdef SPI_GETFLATMENU
+# undef SPI_GETFLATMENU
+#endif
+#define SPI_GETFLATMENU 0x1022
+#ifndef SPI_GETMENUSHOWDELAY
+# define SPI_GETMENUSHOWDELAY 106
+#endif // SPI_GETMENUSHOWDELAY
+#ifndef SPI_GETCARETTIMEOUT
+# define SPI_GETCARETTIMEOUT 0x2022
+#endif // SPI_GETCARETTIMEOUT
+#ifndef WS_EX_LAYOUTRTL
+# define WS_EX_LAYOUTRTL 0x00400000L // Right to left mirroring
+#endif
+
+// Our extra constants for passing a little bit more info to the renderer.
+#define DFCS_RTL 0x00010000
+
+// Toolbar separator dimension which can't be gotten from Windows
+#define TB_SEPARATOR_HEIGHT 2
+
+namespace mozilla {
+namespace widget {
+namespace themeconst {
+
+// Pulled from sdk/include/vsstyle.h
+enum {
+ WP_CAPTION = 1,
+ WP_SMALLCAPTION = 2,
+ WP_MINCAPTION = 3,
+ WP_SMALLMINCAPTION = 4,
+ WP_MAXCAPTION = 5,
+ WP_SMALLMAXCAPTION = 6,
+ WP_FRAMELEFT = 7,
+ WP_FRAMERIGHT = 8,
+ WP_FRAMEBOTTOM = 9,
+ WP_SMALLFRAMELEFT = 10,
+ WP_SMALLFRAMERIGHT = 11,
+ WP_SMALLFRAMEBOTTOM = 12,
+ WP_SYSBUTTON = 13,
+ WP_MDISYSBUTTON = 14,
+ WP_MINBUTTON = 15,
+ WP_MDIMINBUTTON = 16,
+ WP_MAXBUTTON = 17,
+ WP_CLOSEBUTTON = 18,
+ WP_SMALLCLOSEBUTTON = 19,
+ WP_MDICLOSEBUTTON = 20,
+ WP_RESTOREBUTTON = 21,
+ WP_MDIRESTOREBUTTON = 22,
+ WP_HELPBUTTON = 23,
+ WP_MDIHELPBUTTON = 24,
+ WP_HORZSCROLL = 25,
+ WP_HORZTHUMB = 26,
+ WP_VERTSCROLL = 27,
+ WP_VERTTHUMB = 28,
+ WP_DIALOG = 29,
+ WP_CAPTIONSIZINGTEMPLATE = 30,
+ WP_SMALLCAPTIONSIZINGTEMPLATE = 31,
+ WP_FRAMELEFTSIZINGTEMPLATE = 32,
+ WP_SMALLFRAMELEFTSIZINGTEMPLATE = 33,
+ WP_FRAMERIGHTSIZINGTEMPLATE = 34,
+ WP_SMALLFRAMERIGHTSIZINGTEMPLATE = 35,
+ WP_FRAMEBOTTOMSIZINGTEMPLATE = 36,
+ WP_SMALLFRAMEBOTTOMSIZINGTEMPLATE = 37,
+ WP_FRAME = 38,
+ WP_Count
+};
+
+enum {
+ BS_NORMAL = 1,
+ BS_HOT = 2,
+ BS_PUSHED = 3,
+ BS_DISABLED = 4,
+ BS_INACTIVE = 5 /* undocumented, inactive caption button */
+};
+
+} // namespace themeconst
+} // namespace widget
+} // namespace mozilla
+
+// If any theme part ends up having a value higher than WP_Count, this will
+// need to change.
+#define THEME_PART_DISTINCT_VALUE_COUNT mozilla::widget::themeconst::WP_Count
+
+#endif
diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp
new file mode 100644
index 0000000000..b3f9e6fce9
--- /dev/null
+++ b/widget/windows/nsUXThemeData.cpp
@@ -0,0 +1,98 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsUXThemeData.h"
+#include "nsDebug.h"
+#include "nsToolkit.h"
+#include "nsUXThemeConstants.h"
+#include "gfxWindowsPlatform.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+nsUXThemeData::ThemeHandle nsUXThemeData::sThemes[eUXNumClasses];
+
+nsUXThemeData::ThemeHandle::~ThemeHandle() { Close(); }
+
+void nsUXThemeData::ThemeHandle::OpenOnce(HWND aWindow, LPCWSTR aClassList) {
+ if (mHandle.isSome()) {
+ return;
+ }
+
+ mHandle = Some(OpenThemeData(aWindow, aClassList));
+}
+
+void nsUXThemeData::ThemeHandle::Close() {
+ if (mHandle.isNothing()) {
+ return;
+ }
+
+ if (HANDLE rawHandle = mHandle.extract()) {
+ CloseThemeData(rawHandle);
+ }
+}
+
+nsUXThemeData::ThemeHandle::operator HANDLE() {
+ return mHandle.valueOr(nullptr);
+}
+
+void nsUXThemeData::Invalidate() {
+ for (auto& theme : sThemes) {
+ theme.Close();
+ }
+}
+
+HANDLE
+nsUXThemeData::GetTheme(nsUXThemeClass cls) {
+ NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!");
+ sThemes[cls].OpenOnce(nullptr, GetClassName(cls));
+ return sThemes[cls];
+}
+
+const wchar_t* nsUXThemeData::GetClassName(nsUXThemeClass cls) {
+ switch (cls) {
+ case eUXButton:
+ return L"Button";
+ case eUXEdit:
+ return L"Edit";
+ case eUXRebar:
+ return L"Rebar";
+ case eUXToolbar:
+ return L"Toolbar";
+ case eUXProgress:
+ return L"Progress";
+ case eUXTab:
+ return L"Tab";
+ case eUXTrackbar:
+ return L"Trackbar";
+ case eUXCombobox:
+ return L"Combobox";
+ case eUXHeader:
+ return L"Header";
+ case eUXListview:
+ return L"Listview";
+ case eUXMenu:
+ return L"Menu";
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown uxtheme class");
+ return L"";
+ }
+}
+
+bool nsUXThemeData::sIsHighContrastOn = false;
+
+// static
+void nsUXThemeData::UpdateNativeThemeInfo() {
+ HIGHCONTRAST highContrastInfo;
+ highContrastInfo.cbSize = sizeof(HIGHCONTRAST);
+ sIsHighContrastOn =
+ SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrastInfo, 0) &&
+ highContrastInfo.dwFlags & HCF_HIGHCONTRASTON;
+}
diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h
new file mode 100644
index 0000000000..38be8b4484
--- /dev/null
+++ b/widget/windows/nsUXThemeData.h
@@ -0,0 +1,69 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 __UXThemeData_h__
+#define __UXThemeData_h__
+#include <windows.h>
+#include <uxtheme.h>
+
+#include "nscore.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "WinUtils.h"
+
+#include "nsWindowDefs.h"
+
+enum nsUXThemeClass {
+ eUXButton = 0,
+ eUXEdit,
+ eUXRebar,
+ eUXToolbar,
+ eUXProgress,
+ eUXTab,
+ eUXTrackbar,
+ eUXCombobox,
+ eUXHeader,
+ eUXListview,
+ eUXMenu,
+ eUXNumClasses
+};
+
+class nsUXThemeData {
+ // This class makes sure we don't attempt to open a theme if the previous
+ // loading attempt has failed because OpenThemeData is a heavy task and
+ // it's less likely that the API returns a different result.
+ class ThemeHandle final {
+ mozilla::Maybe<HANDLE> mHandle;
+
+ public:
+ ThemeHandle() = default;
+ ~ThemeHandle();
+
+ // Disallow copy and move
+ ThemeHandle(const ThemeHandle&) = delete;
+ ThemeHandle(ThemeHandle&&) = delete;
+ ThemeHandle& operator=(const ThemeHandle&) = delete;
+ ThemeHandle& operator=(ThemeHandle&&) = delete;
+
+ operator HANDLE();
+ void OpenOnce(HWND aWindow, LPCWSTR aClassList);
+ void Close();
+ };
+
+ static ThemeHandle sThemes[eUXNumClasses];
+ static const wchar_t* GetClassName(nsUXThemeClass);
+
+ public:
+ static bool sIsHighContrastOn;
+
+ static void Invalidate();
+ static HANDLE GetTheme(nsUXThemeClass cls);
+ static HMODULE GetThemeDLL();
+
+ static void UpdateNativeThemeInfo();
+ static bool IsHighContrastOn() { return sIsHighContrastOn; }
+};
+#endif // __UXThemeData_h__
diff --git a/widget/windows/nsUserIdleServiceWin.cpp b/widget/windows/nsUserIdleServiceWin.cpp
new file mode 100644
index 0000000000..7cf957dffd
--- /dev/null
+++ b/widget/windows/nsUserIdleServiceWin.cpp
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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/. */
+
+#include "nsUserIdleServiceWin.h"
+#include <windows.h>
+
+bool nsUserIdleServiceWin::PollIdleTime(uint32_t* aIdleTime) {
+ LASTINPUTINFO inputInfo;
+ inputInfo.cbSize = sizeof(inputInfo);
+ if (!::GetLastInputInfo(&inputInfo)) return false;
+
+ *aIdleTime =
+ SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime);
+
+ return true;
+}
diff --git a/widget/windows/nsUserIdleServiceWin.h b/widget/windows/nsUserIdleServiceWin.h
new file mode 100644
index 0000000000..f9a47c8df5
--- /dev/null
+++ b/widget/windows/nsUserIdleServiceWin.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 nsUserIdleServiceWin_h__
+#define nsUserIdleServiceWin_h__
+
+#include "nsUserIdleService.h"
+#include "mozilla/AppShutdown.h"
+
+/* NOTE: Compare of GetTickCount() could overflow. This corrects for
+ * overflow situations.
+ ***/
+#ifndef SAFE_COMPARE_EVEN_WITH_WRAPPING
+# define SAFE_COMPARE_EVEN_WITH_WRAPPING(A, B) \
+ (((int)((long)A - (long)B) & 0xFFFFFFFF))
+#endif
+
+class nsUserIdleServiceWin : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceWin, nsUserIdleService)
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceWin> GetInstance() {
+ RefPtr<nsUserIdleServiceWin> idleService =
+ nsUserIdleService::GetInstance().downcast<nsUserIdleServiceWin>();
+ if (!idleService) {
+ // Avoid late instantiation or resurrection during shutdown.
+ if (mozilla::AppShutdown::IsInOrBeyond(
+ mozilla::ShutdownPhase::AppShutdownConfirmed)) {
+ return nullptr;
+ }
+ idleService = new nsUserIdleServiceWin();
+ }
+
+ return idleService.forget();
+ }
+
+ protected:
+ nsUserIdleServiceWin() {}
+ virtual ~nsUserIdleServiceWin() {}
+};
+
+#endif // nsUserIdleServiceWin_h__
diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..47c7d021ed
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWidgetFactory.h"
+
+#include "mozilla/Components.h"
+#include "nsISupports.h"
+#include "nsdefs.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/widget/ScreenManager.h"
+#include "nsLookAndFeel.h"
+#include "WinMouseScrollHandler.h"
+#include "KeyboardLayout.h"
+#include "nsToolkit.h"
+
+// Modules that switch out based on the environment
+#include "nsXULAppAPI.h"
+// Desktop
+#include "nsFilePicker.h" // needs to be included before other shobjidl.h includes
+#include "nsColorPicker.h"
+// Content processes
+#include "nsFilePickerProxy.h"
+
+// Clipboard
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "HeadlessClipboard.h"
+
+#include "WindowsUIUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_COMPONENT_FACTORY(nsIClipboard) {
+ nsCOMPtr<nsIClipboard> inst;
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessClipboard();
+ } else {
+ inst = new nsClipboard();
+ }
+ return inst.forget().downcast<nsISupports>();
+}
+
+nsresult nsWidgetWindowsModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetWindowsModuleDtor() {
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ KeyboardLayout::Shutdown();
+ MouseScrollHandler::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
diff --git a/widget/windows/nsWidgetFactory.h b/widget/windows/nsWidgetFactory.h
new file mode 100644
index 0000000000..41a39220e3
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 widget_windows_nsWidgetFactory_h
+#define widget_windows_nsWidgetFactory_h
+
+#include "nscore.h"
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsAppShellConstructor(const nsIID& iid, void** result);
+
+nsresult nsWidgetWindowsModuleCtor();
+void nsWidgetWindowsModuleDtor();
+
+#endif // defined widget_windows_nsWidgetFactory_h
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp
new file mode 100644
index 0000000000..8fd00b3ff0
--- /dev/null
+++ b/widget/windows/nsWinGesture.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/*
+ * nsWinGesture - Touch input handling for tablet displays.
+ */
+
+#include "nscore.h"
+#include "nsWinGesture.h"
+#include "nsUXThemeData.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/SimpleGestureEventBinding.h"
+#include "mozilla/dom/WheelEventBinding.h"
+
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+static bool gEnableSingleFingerPanEvents = false;
+
+nsWinGesture::nsWinGesture()
+ : mPanActive(false),
+ mFeedbackActive(false),
+ mXAxisFeedback(false),
+ mYAxisFeedback(false),
+ mPanInertiaActive(false) {
+ (void)InitLibrary();
+ mPixelScrollOverflow = 0;
+}
+
+/* Load and shutdown */
+
+bool nsWinGesture::InitLibrary() {
+ // Check to see if we want single finger gesture input. Only do this once
+ // for the app so we don't have to look it up on every window create.
+ gEnableSingleFingerPanEvents =
+ Preferences::GetBool("gestures.enable_single_finger_input", false);
+
+ return true;
+}
+
+#define GCOUNT 5
+
+bool nsWinGesture::SetWinGestureSupport(
+ HWND hWnd, WidgetGestureNotifyEvent::PanDirection aDirection) {
+ GESTURECONFIG config[GCOUNT];
+
+ memset(&config, 0, sizeof(config));
+
+ config[0].dwID = GID_ZOOM;
+ config[0].dwWant = GC_ZOOM;
+ config[0].dwBlock = 0;
+
+ config[1].dwID = GID_ROTATE;
+ config[1].dwWant = GC_ROTATE;
+ config[1].dwBlock = 0;
+
+ config[2].dwID = GID_PAN;
+ config[2].dwWant = GC_PAN | GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER;
+ config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY |
+ GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+
+ if (gEnableSingleFingerPanEvents) {
+ if (aDirection == WidgetGestureNotifyEvent::ePanVertical ||
+ aDirection == WidgetGestureNotifyEvent::ePanBoth) {
+ config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
+ config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
+ }
+
+ if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal ||
+ aDirection == WidgetGestureNotifyEvent::ePanBoth) {
+ config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+ config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
+ }
+ }
+
+ config[3].dwWant = GC_TWOFINGERTAP;
+ config[3].dwID = GID_TWOFINGERTAP;
+ config[3].dwBlock = 0;
+
+ config[4].dwWant = GC_PRESSANDTAP;
+ config[4].dwID = GID_PRESSANDTAP;
+ config[4].dwBlock = 0;
+
+ return SetGestureConfig(hWnd, 0, GCOUNT, (PGESTURECONFIG)&config,
+ sizeof(GESTURECONFIG));
+}
+
+/* Helpers */
+
+bool nsWinGesture::IsPanEvent(LPARAM lParam) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ if (gi.dwID == GID_PAN) return true;
+
+ return false;
+}
+
+/* Gesture event processing */
+
+bool nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam,
+ LPARAM lParam,
+ WidgetSimpleGestureEvent& evt) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ // The coordinates of this event
+ nsPointWin coord;
+ coord = gi.ptsLocation;
+ coord.ScreenToClient(hWnd);
+
+ evt.mRefPoint = LayoutDeviceIntPoint(coord.x, coord.y);
+
+ // Multiple gesture can occur at the same time so gesture state
+ // info can't be shared.
+ switch (gi.dwID) {
+ case GID_BEGIN:
+ case GID_END:
+ // These should always fall through to DefWndProc
+ return false;
+ break;
+
+ case GID_ZOOM: {
+ if (gi.dwFlags & GF_BEGIN) {
+ // Send a zoom start event
+
+ // The low 32 bits are the distance in pixels.
+ mZoomIntermediate = (float)gi.ullArguments;
+
+ evt.mMessage = eMagnifyGestureStart;
+ evt.mDelta = 0.0;
+ } else if (gi.dwFlags & GF_END) {
+ // Send a zoom end event, the delta is the change
+ // in touch points.
+ evt.mMessage = eMagnifyGesture;
+ // (positive for a "zoom in")
+ evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
+ mZoomIntermediate = (float)gi.ullArguments;
+ } else {
+ // Send a zoom intermediate event, the delta is the change
+ // in touch points.
+ evt.mMessage = eMagnifyGestureUpdate;
+ // (positive for a "zoom in")
+ evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
+ mZoomIntermediate = (float)gi.ullArguments;
+ }
+ } break;
+
+ case GID_ROTATE: {
+ // Send a rotate start event
+ double radians = 0.0;
+
+ // On GF_BEGIN, ullArguments contains the absolute rotation at the
+ // start of the gesture. In later events it contains the offset from
+ // the start angle.
+ if (gi.ullArguments != 0)
+ radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments);
+
+ double degrees = -1 * radians * (180 / M_PI);
+
+ if (gi.dwFlags & GF_BEGIN) {
+ // At some point we should pass the initial angle in
+ // along with delta. It's useful.
+ degrees = mRotateIntermediate = 0.0;
+ }
+
+ evt.mDirection = 0;
+ evt.mDelta = degrees - mRotateIntermediate;
+ mRotateIntermediate = degrees;
+
+ if (evt.mDelta > 0) {
+ evt.mDirection =
+ dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE;
+ } else if (evt.mDelta < 0) {
+ evt.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE;
+ }
+
+ if (gi.dwFlags & GF_BEGIN) {
+ evt.mMessage = eRotateGestureStart;
+ } else if (gi.dwFlags & GF_END) {
+ evt.mMessage = eRotateGesture;
+ } else {
+ evt.mMessage = eRotateGestureUpdate;
+ }
+ } break;
+
+ case GID_TWOFINGERTAP:
+ // Normally maps to "restore" from whatever you may have recently changed.
+ // A simple double click.
+ evt.mMessage = eTapGesture;
+ evt.mClickCount = 1;
+ break;
+
+ case GID_PRESSANDTAP:
+ // Two finger right click. Defaults to right click if it falls through.
+ evt.mMessage = ePressTapGesture;
+ evt.mClickCount = 1;
+ break;
+ }
+
+ return true;
+}
+
+bool nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) {
+ GESTUREINFO gi;
+
+ ZeroMemory(&gi, sizeof(GESTUREINFO));
+ gi.cbSize = sizeof(GESTUREINFO);
+
+ BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
+ if (!result) return false;
+
+ // The coordinates of this event
+ nsPointWin coord;
+ coord = mPanRefPoint = gi.ptsLocation;
+ // We want screen coordinates in our local offsets as client coordinates will
+ // change when feedback is taking place. Gui events though require client
+ // coordinates.
+ mPanRefPoint.ScreenToClient(hWnd);
+
+ switch (gi.dwID) {
+ case GID_BEGIN:
+ case GID_END:
+ // These should always fall through to DefWndProc
+ return false;
+ break;
+
+ // Setup pixel scroll events for both axis
+ case GID_PAN: {
+ if (gi.dwFlags & GF_BEGIN) {
+ mPanIntermediate = coord;
+ mPixelScrollDelta = 0;
+ mPanActive = true;
+ mPanInertiaActive = false;
+ } else {
+#ifdef DBG_jimm
+ int32_t deltaX = mPanIntermediate.x - coord.x;
+ int32_t deltaY = mPanIntermediate.y - coord.y;
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x,
+ coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback));
+#endif
+
+ mPixelScrollDelta.x = mPanIntermediate.x - coord.x;
+ mPixelScrollDelta.y = mPanIntermediate.y - coord.y;
+ mPanIntermediate = coord;
+
+ if (gi.dwFlags & GF_INERTIA) mPanInertiaActive = true;
+
+ if (gi.dwFlags & GF_END) {
+ mPanActive = false;
+ mPanInertiaActive = false;
+ PanFeedbackFinalize(hWnd, true);
+ }
+ }
+ } break;
+ }
+ return true;
+}
+
+inline bool TestTransition(int32_t a, int32_t b) {
+ // If a is zero, overflow is zero, implying the cursor has moved back to the
+ // start position. If b is zero, cached overscroll is zero, implying feedback
+ // just begun.
+ if (a == 0 || b == 0) return true;
+ // Test for different signs.
+ return (a < 0) == (b < 0);
+}
+
+void nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow,
+ bool& endFeedback) {
+ // If scroll overflow was returned indicating we panned past the bounds of
+ // the scrollable view port, start feeback.
+ if (scrollOverflow != 0) {
+ if (!mFeedbackActive) {
+ BeginPanningFeedback(hWnd);
+ mFeedbackActive = true;
+ }
+ endFeedback = false;
+ mXAxisFeedback = true;
+ return;
+ }
+
+ if (mXAxisFeedback) {
+ int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x;
+
+ // Detect a reverse transition past the starting drag point. This tells us
+ // the user has panned all the way back so we can stop providing feedback
+ // for this axis.
+ if (!TestTransition(newOverflow, mPixelScrollOverflow.x) ||
+ newOverflow == 0)
+ return;
+
+ // Cache the total over scroll in pixels.
+ mPixelScrollOverflow.x = newOverflow;
+ endFeedback = false;
+ }
+}
+
+void nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow,
+ bool& endFeedback) {
+ // If scroll overflow was returned indicating we panned past the bounds of
+ // the scrollable view port, start feeback.
+ if (scrollOverflow != 0) {
+ if (!mFeedbackActive) {
+ BeginPanningFeedback(hWnd);
+ mFeedbackActive = true;
+ }
+ endFeedback = false;
+ mYAxisFeedback = true;
+ return;
+ }
+
+ if (mYAxisFeedback) {
+ int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y;
+
+ // Detect a reverse transition past the starting drag point. This tells us
+ // the user has panned all the way back so we can stop providing feedback
+ // for this axis.
+ if (!TestTransition(newOverflow, mPixelScrollOverflow.y) ||
+ newOverflow == 0)
+ return;
+
+ // Cache the total over scroll in pixels.
+ mPixelScrollOverflow.y = newOverflow;
+ endFeedback = false;
+ }
+}
+
+void nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) {
+ if (!mFeedbackActive) return;
+
+ if (endFeedback) {
+ mFeedbackActive = false;
+ mXAxisFeedback = false;
+ mYAxisFeedback = false;
+ mPixelScrollOverflow = 0;
+ EndPanningFeedback(hWnd, TRUE);
+ return;
+ }
+
+ UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y,
+ mPanInertiaActive);
+}
+
+bool nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent) {
+ aWheelEvent.mDeltaX = aWheelEvent.mDeltaY = aWheelEvent.mDeltaZ = 0.0;
+ aWheelEvent.mLineOrPageDeltaX = aWheelEvent.mLineOrPageDeltaY = 0;
+
+ aWheelEvent.mRefPoint = LayoutDeviceIntPoint(mPanRefPoint.x, mPanRefPoint.y);
+ aWheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PIXEL;
+ aWheelEvent.mScrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY;
+ aWheelEvent.mIsNoLineOrPageDelta = true;
+
+ aWheelEvent.mOverflowDeltaX = 0.0;
+ aWheelEvent.mOverflowDeltaY = 0.0;
+
+ // Don't scroll the view if we are currently at a bounds, or, if we are
+ // panning back from a max feedback position. This keeps the original drag
+ // point constant.
+ if (!mXAxisFeedback) {
+ aWheelEvent.mDeltaX = mPixelScrollDelta.x;
+ }
+ if (!mYAxisFeedback) {
+ aWheelEvent.mDeltaY = mPixelScrollDelta.y;
+ }
+
+ return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0);
+}
diff --git a/widget/windows/nsWinGesture.h b/widget/windows/nsWinGesture.h
new file mode 100644
index 0000000000..040b460da9
--- /dev/null
+++ b/widget/windows/nsWinGesture.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WinGesture_h__
+#define WinGesture_h__
+
+/*
+ * nsWinGesture - Touch input handling for tablet displays.
+ */
+
+#include "nsdefs.h"
+#include <winuser.h>
+#include <tpcshrd.h>
+#include "nsPoint.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TouchEvents.h"
+
+// WM_TABLET_QUERYSYSTEMGESTURESTATUS return values
+#define TABLET_ROTATE_GESTURE_ENABLE 0x02000000
+
+class nsPointWin : public nsIntPoint {
+ public:
+ nsPointWin& operator=(const POINTS& aPoint) {
+ x = aPoint.x;
+ y = aPoint.y;
+ return *this;
+ }
+ nsPointWin& operator=(const POINT& aPoint) {
+ x = aPoint.x;
+ y = aPoint.y;
+ return *this;
+ }
+ nsPointWin& operator=(int val) {
+ x = y = val;
+ return *this;
+ }
+ void ScreenToClient(HWND hWnd) {
+ POINT tmp;
+ tmp.x = x;
+ tmp.y = y;
+ ::ScreenToClient(hWnd, &tmp);
+ *this = tmp;
+ }
+};
+
+class nsWinGesture {
+ public:
+ nsWinGesture();
+
+ public:
+ bool SetWinGestureSupport(
+ HWND hWnd, mozilla::WidgetGestureNotifyEvent::PanDirection aDirection);
+ bool ShutdownWinGestureSupport();
+
+ // Simple gesture process
+ bool ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam,
+ mozilla::WidgetSimpleGestureEvent& evt);
+
+ // Pan processing
+ bool IsPanEvent(LPARAM lParam);
+ bool ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam);
+ bool PanDeltaToPixelScroll(mozilla::WidgetWheelEvent& aWheelEvent);
+ void UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback);
+ void UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback);
+ void PanFeedbackFinalize(HWND hWnd, bool endFeedback);
+
+ private:
+ // Delay load info
+ bool InitLibrary();
+
+ // Pan and feedback state
+ nsPointWin mPanIntermediate;
+ nsPointWin mPanRefPoint;
+ nsPointWin mPixelScrollDelta;
+ bool mPanActive;
+ bool mFeedbackActive;
+ bool mXAxisFeedback;
+ bool mYAxisFeedback;
+ bool mPanInertiaActive;
+ nsPointWin mPixelScrollOverflow;
+
+ // Zoom state
+ double mZoomIntermediate;
+
+ // Rotate state
+ double mRotateIntermediate;
+};
+
+#endif /* WinGesture_h__ */
diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
new file mode 100644
index 0000000000..1a8646d620
--- /dev/null
+++ b/widget/windows/nsWindow.cpp
@@ -0,0 +1,9000 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* 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/. */
+
+/*
+ * nsWindow - Native window management and event handling.
+ *
+ * nsWindow is organized into a set of major blocks and
+ * block subsections. The layout is as follows:
+ *
+ * Includes
+ * Variables
+ * nsIWidget impl.
+ * nsIWidget methods and utilities
+ * nsSwitchToUIThread impl.
+ * nsSwitchToUIThread methods and utilities
+ * Moz events
+ * Event initialization
+ * Event dispatching
+ * Native events
+ * Wndproc(s)
+ * Event processing
+ * OnEvent event handlers
+ * IME management and accessibility
+ * Transparency
+ * Popup hook handling
+ * Misc. utilities
+ * Child window impl.
+ *
+ * Search for "BLOCK:" to find major blocks.
+ * Search for "SECTION:" to find specific sections.
+ *
+ * Blocks should be split out into separate files if they
+ * become unmanageable.
+ *
+ * Notable related sources:
+ *
+ * nsWindowDefs.h - Definitions, macros, structs, enums
+ * and general setup.
+ * nsWindowDbg.h/.cpp - Debug related code and directives.
+ * nsWindowGfx.h/.cpp - Graphics and painting.
+ *
+ */
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Includes
+ **
+ ** Include headers.
+ **
+ **************************************************************
+ **************************************************************/
+
+#include "gfx2DGlue.h"
+#include "gfxEnv.h"
+#include "gfxPlatform.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PreXULSkeletonUI.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/SwipeTracker.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/TimeStamp.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include <algorithm>
+#include <limits>
+
+#include "mozilla/widget/WinMessages.h"
+#include "nsWindow.h"
+#include "nsWindowTaskbarConcealer.h"
+#include "nsAppRunner.h"
+
+#include <shellapi.h>
+#include <windows.h>
+#include <wtsapi32.h>
+#include <process.h>
+#include <commctrl.h>
+#include <dbt.h>
+#include <unknwn.h>
+#include <psapi.h>
+#include <rpc.h>
+#include <propvarutil.h>
+#include <propkey.h>
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prenv.h"
+
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsContentUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITheme.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "imgIContainer.h"
+#include "nsIFile.h"
+#include "nsIRollupListener.h"
+#include "nsIClipboard.h"
+#include "WinMouseScrollHandler.h"
+#include "nsFontMetrics.h"
+#include "nsIFontEnumerator.h"
+#include "nsFont.h"
+#include "nsRect.h"
+#include "nsThreadUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsWidgetsCID.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsNativeThemeWin.h"
+#include "nsXULPopupManager.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+#include "nsWindowGfx.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxDWriteFonts.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "SystemTimeConverter.h"
+#include "WinTaskbar.h"
+#include "WidgetUtils.h"
+#include "WinWindowOcclusionTracker.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/widget/nsAutoRollup.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "mozilla/widget/Screen.h"
+#include "nsStyleConsts.h"
+#include "nsBidiKeyboard.h"
+#include "nsStyleConsts.h"
+#include "gfxConfig.h"
+#include "InProcessWinCompositorWidget.h"
+#include "InputDeviceUtils.h"
+#include "ScreenHelperWin.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsNativeAppSupportWin.h"
+#include "mozilla/browser/NimbusFeatures.h"
+
+#include "nsIGfxInfo.h"
+#include "nsUXThemeConstants.h"
+#include "KeyboardLayout.h"
+#include "nsNativeDragTarget.h"
+#include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
+#include <zmouse.h>
+#include <richedit.h>
+
+#if defined(ACCESSIBILITY)
+
+# ifdef DEBUG
+# include "mozilla/a11y/Logging.h"
+# endif
+
+# include "oleidl.h"
+# include <winuser.h>
+# include "nsAccessibilityService.h"
+# include "mozilla/a11y/DocAccessible.h"
+# include "mozilla/a11y/LazyInstantiator.h"
+# include "mozilla/a11y/Platform.h"
+# if !defined(WINABLEAPI)
+# include <winable.h>
+# endif // !defined(WINABLEAPI)
+#endif // defined(ACCESSIBILITY)
+
+#include "WindowsUIUtils.h"
+
+#include "nsWindowDefs.h"
+
+#include "nsCrashOnException.h"
+
+#include "nsIContent.h"
+
+#include "mozilla/BackgroundHangMonitor.h"
+#include "WinIMEHandler.h"
+
+#include "npapi.h"
+
+#include <d3d11.h>
+
+// ERROR from wingdi.h (below) gets undefined by some code.
+// #define ERROR 0
+// #define RGN_ERROR ERROR
+#define ERROR 0
+
+#if !defined(SM_CONVERTIBLESLATEMODE)
+# define SM_CONVERTIBLESLATEMODE 0x2003
+#endif
+
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "InputData.h"
+
+#include "mozilla/TaskController.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/webrender/WebRenderAPI.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
+#include "DirectManipulationOwner.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::plugins;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+static const wchar_t kUser32LibName[] = L"user32.dll";
+
+uint32_t nsWindow::sInstanceCount = 0;
+bool nsWindow::sIsOleInitialized = false;
+nsIWidget::Cursor nsWindow::sCurrentCursor = {};
+nsWindow* nsWindow::sCurrentWindow = nullptr;
+bool nsWindow::sJustGotDeactivate = false;
+bool nsWindow::sJustGotActivate = false;
+bool nsWindow::sIsInMouseCapture = false;
+
+// Urgent-message reentrancy depth for the static `WindowProc` callback.
+//
+// Three unfortunate facts collide:
+//
+// 𝛼) Some messages must be processed promptly. If not, Windows will leave the
+// receiving window in an intermediate, and potentially unusable, state until
+// the WindowProc invocation that is handling it returns.
+//
+// 𝛽) Some messages have indefinitely long processing time. These are mostly
+// messages which may cause us to enter a nested modal loop (via
+// `SpinEventLoopUntil` or similar).
+//
+// 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
+// reentrantly reinvoked from the kernel while we're blocking _on_ the
+// kernel, even briefly, during processing of other messages. (Relevant
+// search term: `KeUserModeCallback`.)
+//
+// The nightmare scenario, then, is that during processing of an 𝛼-message, we
+// briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
+// takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
+// bug 1842170.)
+//
+// There is little we can do to prevent the first half of this scenario. 𝛼) and
+// 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
+// need to make blocking calls to process 𝛼-messages. (We may not even be aware
+// that we're making such calls, if they're undocumented implementation details
+// of another API.)
+//
+// In an ideal world, WindowProc would always return promptly (or at least in
+// bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
+// states would instead be implemented in async fashion. In practice, that's far
+// easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
+// al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
+// cutting architectural tasks, each of potentially unbounded scope. For now,
+// and for the foreseeable future, we're stuck with them.
+//
+// We therefore simply punt. More specifically: if a known 𝛽-message jumps the
+// queue to come in while we're in the middle of processing a known 𝛼-message,
+// we:
+// * properly queue the message for processing later;
+// * respond to the 𝛽-message as though we actually had processed it; and
+// * just hope that it can wait until we get around to it.
+//
+// The word "known" requires a bit of justification. There is no canonical set
+// of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
+// can't safely assume that all messages are 𝛼-messages, as that could cause
+// 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
+// event loop is active. We also can't assume all messages are 𝛽-messages,
+// since one 𝛼-message jumping the queue while processing another 𝛼-message is
+// part of normal and required operation for windowed Windows applications.
+//
+// So we simply add messages to those sets as we identify them. (Or, preferably,
+// rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
+//
+// ---
+//
+// The actual value of `sDepth` is the number of active invocations of
+// `WindowProc` that are processing known 𝛼-messages.
+size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
+
+// Hook Data Members for Dropdowns. sProcessHook Tells the
+// hook methods whether they should be processing the hook
+// messages.
+HHOOK nsWindow::sMsgFilterHook = nullptr;
+HHOOK nsWindow::sCallProcHook = nullptr;
+HHOOK nsWindow::sCallMouseHook = nullptr;
+bool nsWindow::sProcessHook = false;
+UINT nsWindow::sRollupMsgId = 0;
+HWND nsWindow::sRollupMsgWnd = nullptr;
+UINT nsWindow::sHookTimerId = 0;
+
+// Used to prevent dispatching mouse events that do not originate from user
+// input.
+POINT nsWindow::sLastMouseMovePoint = {0};
+
+bool nsWindow::sIsRestoringSession = false;
+
+bool nsWindow::sTouchInjectInitialized = false;
+InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
+
+static SystemTimeConverter<DWORD>& TimeConverter() {
+ static SystemTimeConverter<DWORD> timeConverterSingleton;
+ return timeConverterSingleton;
+}
+
+// Global event hook for window cloaking. Never deregistered.
+// - `Nothing` if not yet set.
+// - `Some(nullptr)` if no attempt should be made to set it.
+static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
+static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
+
+namespace mozilla {
+
+class CurrentWindowsTimeGetter {
+ public:
+ explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
+
+ DWORD GetCurrentTime() const { return ::GetTickCount(); }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ DWORD currentTime = GetCurrentTime();
+ if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
+ // There's already one inflight with this timestamp. Don't
+ // send a duplicate.
+ return;
+ }
+ sBackwardsSkewStamp = Some(aNow);
+ sLastPostTime = currentTime;
+ static_assert(sizeof(WPARAM) >= sizeof(DWORD),
+ "Can't fit a DWORD in a WPARAM");
+ ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
+ }
+
+ static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
+ TimeStamp* aOutSkewStamp) {
+ if (aPostTime != sLastPostTime) {
+ // The SKEWFIX message is stale; we've sent a new one since then.
+ // Ignore this one.
+ return false;
+ }
+ MOZ_ASSERT(sBackwardsSkewStamp);
+ *aOutSkewStamp = sBackwardsSkewStamp.value();
+ sBackwardsSkewStamp = Nothing();
+ return true;
+ }
+
+ private:
+ static Maybe<TimeStamp> sBackwardsSkewStamp;
+ static DWORD sLastPostTime;
+ HWND mWnd;
+};
+
+Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
+DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
+
+} // namespace mozilla
+
+/**************************************************************
+ *
+ * SECTION: globals variables
+ *
+ **************************************************************/
+
+static const char* sScreenManagerContractID =
+ "@mozilla.org/gfx/screenmanager;1";
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
+
+// General purpose user32.dll hook object
+static WindowsDllInterceptor sUser32Intercept;
+
+// When the client area is extended out into the default window frame area,
+// this is the minimum amount of space along the edge of resizable windows
+// we will always display a resize cursor in, regardless of the underlying
+// content.
+static const int32_t kResizableBorderMinSize = 3;
+
+// Getting this object from the window server can be expensive. Keep it
+// around, also get it off the main thread. (See bug 1640852)
+StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
+static bool gInitializedVirtualDesktopManager = false;
+
+// We should never really try to accelerate windows bigger than this. In some
+// cases this might lead to no D3D9 acceleration where we could have had it
+// but D3D9 does not reliably report when it supports bigger windows. 8192
+// is as safe as we can get, we know at least D3D10 hardware always supports
+// this, other hardware we expect to report correctly in D3D9.
+#define MAX_ACCELERATED_DIMENSION 8192
+
+// On window open (as well as after), Windows has an unfortunate habit of
+// sending rather a lot of WM_NCHITTEST messages. Because we have to do point
+// to DOM target conversions for these, we cache responses for a given
+// coordinate this many milliseconds:
+#define HITTEST_CACHE_LIFETIME_MS 50
+
+#if defined(ACCESSIBILITY)
+
+namespace mozilla {
+
+/**
+ * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
+ * injecting tiptsf.dll. The touchscreen process then posts registered messages
+ * to our main thread. The tiptsf hook picks up those registered messages and
+ * uses them as commands, some of which call into UIA, which then calls into
+ * MSAA, which then sends WM_GETOBJECT to us.
+ *
+ * We can get ahead of this by installing our own thread-local WH_GETMESSAGE
+ * hook. Since thread-local hooks are called ahead of global hooks, we will
+ * see these registered messages before tiptsf does. At this point we can then
+ * raise a flag that blocks a11y before invoking CallNextHookEx which will then
+ * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
+ * flag by calling TIPMessageHandler::IsA11yBlocked().
+ *
+ * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
+ * function that also calls into UIA.
+ */
+class TIPMessageHandler {
+ public:
+ ~TIPMessageHandler() {
+ if (mHook) {
+ ::UnhookWindowsHookEx(mHook);
+ }
+ }
+
+ static void Initialize() {
+ if (sInstance) {
+ return;
+ }
+
+ sInstance = new TIPMessageHandler();
+ ClearOnShutdown(&sInstance);
+ }
+
+ static bool IsA11yBlocked() {
+ if (!sInstance) {
+ return false;
+ }
+
+ return sInstance->mA11yBlockCount > 0;
+ }
+
+ private:
+ TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Registered messages used by tiptsf
+ mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
+ mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
+ mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
+ mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
+ mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
+ mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
+ mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
+
+ mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
+ ::GetCurrentThreadId());
+ MOZ_ASSERT(mHook);
+
+ if (!sSendMessageTimeoutWStub) {
+ sUser32Intercept.Init("user32.dll");
+ DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
+ sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
+ MOZ_ASSERT(hooked);
+ }
+ }
+
+ class MOZ_RAII A11yInstantiationBlocker {
+ public:
+ A11yInstantiationBlocker() {
+ if (!TIPMessageHandler::sInstance) {
+ return;
+ }
+ ++TIPMessageHandler::sInstance->mA11yBlockCount;
+ }
+
+ ~A11yInstantiationBlocker() {
+ if (!TIPMessageHandler::sInstance) {
+ return;
+ }
+ MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
+ --TIPMessageHandler::sInstance->mA11yBlockCount;
+ }
+ };
+
+ friend class A11yInstantiationBlocker;
+
+ static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
+ if (aCode < 0 || !sInstance) {
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+
+ MSG* msg = reinterpret_cast<MSG*>(aLParam);
+ UINT& msgCode = msg->message;
+
+ for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
+ if (msgCode == sInstance->mMessages[i]) {
+ A11yInstantiationBlocker block;
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+ }
+
+ return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
+ }
+
+ static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
+ WPARAM aWParam, LPARAM aLParam,
+ UINT aFlags, UINT aTimeout,
+ PDWORD_PTR aMsgResult) {
+ // We don't want to handle this unless the message is a WM_GETOBJECT that we
+ // want to block, and the aHwnd is a nsWindow that belongs to the current
+ // (i.e., main) thread.
+ if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
+ static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
+ !WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
+ return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
+ aTimeout, aMsgResult);
+ }
+
+ // In this case we want to fake the result that would happen if we had
+ // decided not to handle WM_GETOBJECT in our WndProc. We hand the message
+ // off to DefWindowProc to accomplish this.
+ *aMsgResult = static_cast<DWORD_PTR>(
+ ::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
+
+ return static_cast<LRESULT>(TRUE);
+ }
+
+ static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
+ sSendMessageTimeoutWStub;
+ static StaticAutoPtr<TIPMessageHandler> sInstance;
+
+ HHOOK mHook;
+ UINT mMessages[7];
+ uint32_t mA11yBlockCount;
+};
+
+WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
+ TIPMessageHandler::sSendMessageTimeoutWStub;
+StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
+
+} // namespace mozilla
+
+#endif // defined(ACCESSIBILITY)
+
+namespace mozilla {
+
+// This task will get the VirtualDesktopManager from the generic thread pool
+// since doing this on the main thread on startup causes performance issues.
+//
+// See bug 1640852.
+//
+// This should be fine and should not require any locking, as when the main
+// thread will access it, if it races with this function it will either find
+// it to be null or to have a valid value.
+class InitializeVirtualDesktopManagerTask : public Task {
+ public:
+ InitializeVirtualDesktopManagerTask()
+ : Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ bool GetName(nsACString& aName) override {
+ aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
+ return true;
+ }
+#endif
+
+ virtual TaskResult Run() override {
+ RefPtr<IVirtualDesktopManager> desktopManager;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
+ __uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
+ if (FAILED(hr)) {
+ return TaskResult::Complete;
+ }
+
+ gVirtualDesktopManager = desktopManager;
+ return TaskResult::Complete;
+ }
+};
+
+// Ground-truth query: does Windows claim the window is cloaked right now?
+static bool IsCloaked(HWND hwnd) {
+ DWORD cloakedState;
+ HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
+ sizeof(cloakedState));
+
+ if (FAILED(hr)) {
+ MOZ_LOG(sCloakingLog, LogLevel::Warning,
+ ("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
+ return false;
+ }
+
+ return cloakedState != 0;
+}
+
+} // namespace mozilla
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsIWidget impl.
+ **
+ ** nsIWidget interface implementation, broken down into
+ ** sections.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow construction and destruction
+ *
+ **************************************************************/
+
+nsWindow::nsWindow(bool aIsChildWindow)
+ : nsBaseWidget(BorderStyle::Default),
+ mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
+ mFrameState(std::in_place, this),
+ mIsChildWindow(aIsChildWindow),
+ mLastPaintEndTime(TimeStamp::Now()),
+ mCachedHitTestTime(TimeStamp::Now()),
+ mSizeConstraintsScale(GetDefaultScale().scale),
+ mDesktopId("DesktopIdMutex") {
+ MOZ_ASSERT(mWindowType == WindowType::Child);
+
+ if (!gInitializedVirtualDesktopManager) {
+ TaskController::Get()->AddTask(
+ MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
+ gInitializedVirtualDesktopManager = true;
+ }
+
+ // Global initialization
+ if (!sInstanceCount) {
+ // Global app registration id for Win7 and up. See
+ // WinTaskbar.cpp for details.
+ // MSIX packages explicitly do not support setting the appid from within
+ // the app, as it is set in the package manifest instead.
+ if (!WinUtils::HasPackageIdentity()) {
+ mozilla::widget::WinTaskbar::RegisterAppUserModelID();
+ }
+ if (!StaticPrefs::ui_key_layout_load_when_first_needed()) {
+ KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
+ }
+#if defined(ACCESSIBILITY)
+ mozilla::TIPMessageHandler::Initialize();
+#endif // defined(ACCESSIBILITY)
+ if (SUCCEEDED(::OleInitialize(nullptr))) {
+ sIsOleInitialized = true;
+ }
+ NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
+ MouseScrollHandler::Initialize();
+ // Init theme data
+ nsUXThemeData::UpdateNativeThemeInfo();
+ RedirectedKeyDownMessageManager::Forget();
+ } // !sInstanceCount
+
+ sInstanceCount++;
+}
+
+nsWindow::~nsWindow() {
+ mInDtor = true;
+
+ // If the widget was released without calling Destroy() then the native window
+ // still exists, and we need to destroy it. Destroy() will early-return if it
+ // was already called. In any case it is important to call it before
+ // destroying mPresentLock (cf. 1156182).
+ Destroy();
+
+ // Free app icon resources. This must happen after `OnDestroy` (see bug
+ // 708033).
+ if (mIconSmall) ::DestroyIcon(mIconSmall);
+
+ if (mIconBig) ::DestroyIcon(mIconBig);
+
+ sInstanceCount--;
+
+ // Global shutdown
+ if (sInstanceCount == 0) {
+ IMEHandler::Terminate();
+ sCurrentCursor = {};
+ if (sIsOleInitialized) {
+ ::OleFlushClipboard();
+ ::OleUninitialize();
+ sIsOleInitialized = false;
+ }
+ }
+
+ NS_IF_RELEASE(mNativeDragTarget);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Create, nsIWidget::Destroy
+ *
+ * Creating and destroying windows for this widget.
+ *
+ **************************************************************/
+
+// Allow Derived classes to modify the height that is passed
+// when the window is created or resized.
+int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
+
+void nsWindow::SendAnAPZEvent(InputData& aEvent) {
+ LRESULT popupHandlingResult;
+ if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
+ // We need to consume the event after using it to roll up the popup(s).
+ return;
+ }
+
+ if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
+ // Give the swipe tracker a first pass at the event. If a new pan gesture
+ // has been started since the beginning of the swipe, the swipe tracker
+ // will know to ignore the event.
+ nsEventStatus status =
+ mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ APZEventResult result;
+ if (mAPZC) {
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
+ }
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
+ aEvent.mInputType == PINCHGESTURE_INPUT);
+
+ if (aEvent.mInputType == PANGESTURE_INPUT) {
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+ WidgetWheelEvent event = panInput.ToWidgetEvent(this);
+ if (!mAPZC) {
+ if (MayStartSwipeForNonAPZ(panInput)) {
+ return;
+ }
+ } else {
+ event = MayStartSwipeForAPZ(panInput, result);
+ }
+
+ ProcessUntransformedAPZEvent(&event, result);
+
+ return;
+ }
+
+ PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
+ WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
+ ProcessUntransformedAPZEvent(&event, result);
+}
+
+void nsWindow::RecreateDirectManipulationIfNeeded() {
+ DestroyDirectManipulation();
+
+ if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
+ return;
+ }
+
+ if (!(StaticPrefs::apz_allow_zooming() ||
+ StaticPrefs::apz_windows_use_direct_manipulation()) ||
+ StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
+ return;
+ }
+
+ mDmOwner = MakeUnique<DirectManipulationOwner>(this);
+
+ LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
+ GetHeight(mBounds.Height()));
+ mDmOwner->Init(bounds);
+}
+
+void nsWindow::ResizeDirectManipulationViewport() {
+ if (mDmOwner) {
+ LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
+ GetHeight(mBounds.Height()));
+ mDmOwner->ResizeViewport(bounds);
+ }
+}
+
+void nsWindow::DestroyDirectManipulation() {
+ if (mDmOwner) {
+ mDmOwner->Destroy();
+ mDmOwner.reset();
+ }
+}
+
+// Create the proper widget
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ widget::InitData* aInitData) {
+ // Historical note: there was once some belief and/or intent that nsWindows
+ // could be created on arbitrary threads, and this may still be reflected in
+ // some comments.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ widget::InitData defaultInitData;
+ if (!aInitData) aInitData = &defaultInitData;
+
+ nsIWidget* baseParent =
+ aInitData->mWindowType == WindowType::Dialog ||
+ aInitData->mWindowType == WindowType::TopLevel ||
+ aInitData->mWindowType == WindowType::Invisible
+ ? nullptr
+ : aParent;
+
+ mIsTopWidgetWindow = (nullptr == baseParent);
+ mBounds = aRect;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ BaseCreate(baseParent, aInitData);
+
+ HWND parent;
+ if (aParent) { // has a nsIWidget parent
+ parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
+ mParent = aParent;
+ } else { // has a nsNative parent
+ parent = (HWND)aNativeParent;
+ mParent =
+ aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
+ }
+
+ mIsRTL = aInitData->mRTL;
+ mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
+ mAlwaysOnTop = aInitData->mAlwaysOnTop;
+ mIsAlert = aInitData->mIsAlert;
+ mResizable = aInitData->mResizable;
+
+ DWORD style = WindowStyle();
+ DWORD extendedStyle = WindowExStyle();
+
+ if (mWindowType == WindowType::Popup) {
+ if (!aParent) {
+ parent = nullptr;
+ }
+ } else if (mWindowType == WindowType::Invisible) {
+ // Make sure CreateWindowEx succeeds at creating a toplevel window
+ style &= ~0x40000000; // WS_CHILDWINDOW
+ } else {
+ // See if the caller wants to explictly set clip children and clip siblings
+ if (aInitData->mClipChildren) {
+ style |= WS_CLIPCHILDREN;
+ } else {
+ style &= ~WS_CLIPCHILDREN;
+ }
+ if (aInitData->mClipSiblings) {
+ style |= WS_CLIPSIBLINGS;
+ }
+ }
+
+ const wchar_t* className = ChooseWindowClass(mWindowType);
+
+ // Take specific actions when creating the first top-level window
+ static bool sFirstTopLevelWindowCreated = false;
+ if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
+ !sFirstTopLevelWindowCreated) {
+ sFirstTopLevelWindowCreated = true;
+ mWnd = ConsumePreXULSkeletonUIHandle();
+ auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
+ if (skeletonUIError) {
+ nsAutoString errorString(
+ GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
+ Telemetry::ScalarSet(
+ Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
+ errorString);
+ }
+ if (mWnd) {
+ MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
+ "The skeleton UI window style should match the expected "
+ "style for the first window created");
+ MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
+ "The skeleton UI window extended style should match the "
+ "expected extended style for the first window created");
+ MOZ_ASSERT(
+ ::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
+ "The skeleton UI window should be created on the same thread as "
+ "other windows");
+ mIsShowingPreXULSkeletonUI = true;
+
+ // If we successfully consumed the pre-XUL skeleton UI, just update
+ // our internal state to match what is currently being displayed.
+ mIsVisible = true;
+ mIsCloaked = mozilla::IsCloaked(mWnd);
+ mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
+
+ // These match the margins set in browser-tabsintitlebar.js with
+ // default prefs on Windows. Bug 1673092 tracks lining this up with
+ // that more correctly instead of hard-coding it.
+ SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
+
+ // Reset the WNDPROC for this window and its whole class, as we had
+ // to use our own WNDPROC when creating the the skeleton UI window.
+ ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(
+ WinUtils::NonClientDpiScalingDefWindowProcW));
+ ::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(
+ WinUtils::NonClientDpiScalingDefWindowProcW));
+ }
+ }
+
+ if (!mWnd) {
+ mWnd =
+ ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
+ aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
+ parent, nullptr, nsToolkit::mDllInstance, nullptr);
+ }
+
+ if (!mWnd) {
+ NS_WARNING("nsWindow CreateWindowEx failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!sWinCloakEventHook) {
+ MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
+
+ // C++03 lambda approximation until P2173R1 is available (-std=c++2b)
+ struct StdcallLambda {
+ static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
+ DWORD event, HWND hwnd,
+ LONG idObject, LONG idChild,
+ DWORD idEventThread,
+ DWORD dwmsEventTime) {
+ const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
+ nsWindow::OnCloakEvent(hwnd, isCloaked);
+ }
+ };
+
+ const HWINEVENTHOOK hook = ::SetWinEventHook(
+ EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
+ &StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
+ ::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
+ sWinCloakEventHook = Some(hook);
+
+ if (!hook) {
+ const DWORD err = ::GetLastError();
+ MOZ_LOG(sCloakingLog, LogLevel::Error,
+ ("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
+ err));
+ }
+ }
+
+ if (aInitData->mIsPrivate) {
+ if (NimbusFeatures::GetBool("majorRelease2022"_ns,
+ "feltPrivacyWindowSeparation"_ns, true) &&
+ // Although permanent Private Browsing mode is indeed Private Browsing,
+ // we choose to make it look like regular Firefox in terms of the icon
+ // it uses (which also means we shouldn't use the Private Browsing
+ // AUMID).
+ !StaticPrefs::browser_privatebrowsing_autostart()) {
+ RefPtr<IPropertyStore> pPropStore;
+ if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
+ getter_AddRefs(pPropStore)))) {
+ PROPVARIANT pv;
+ nsAutoString aumid;
+ // make sure we're using the private browsing AUMID so that taskbar
+ // grouping works properly
+ Unused << NS_WARN_IF(
+ !mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
+ if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
+ if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
+ pPropStore->Commit();
+ }
+
+ PropVariantClear(&pv);
+ }
+ }
+ HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
+ MAKEINTRESOURCEW(IDI_PBMODE));
+ SetBigIcon(icon);
+ SetSmallIcon(icon);
+ }
+ }
+
+ mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
+
+ // If mDefaultScale is set before mWnd has been set, it will have the scale of
+ // the primary monitor, rather than the monitor that the window is actually
+ // on. For non-popup windows this gets corrected by the WM_DPICHANGED message
+ // which resets mDefaultScale, but for popup windows we don't reset
+ // mDefaultScale on that message. In order to ensure that popup windows
+ // spawned on a non-primary monitor end up with the correct scale, we reset
+ // mDefaultScale here so that it gets recomputed using the correct monitor now
+ // that we have a mWnd.
+ mDefaultScale = -1.0;
+
+ if (mIsRTL) {
+ DWORD dwAttribute = TRUE;
+ DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
+ sizeof dwAttribute);
+ }
+
+ UpdateDarkModeToolbar();
+
+ if (mOpeningAnimationSuppressed) {
+ SuppressAnimation(true);
+ }
+
+ if (mAlwaysOnTop) {
+ ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ }
+
+ if (mWindowType != WindowType::Invisible &&
+ MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
+ // Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
+ //
+ // We create two zero-sized windows as descendants of the top-level window,
+ // like so:
+ //
+ // Top-level window (MozillaWindowClass)
+ // FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
+ // FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
+ //
+ // We need to have the middle window, otherwise the Trackpoint driver
+ // will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
+ // sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
+ // window hierarchy until they are handled by nsWindow::WindowProc.
+ // WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
+ // but these do not propagate automatically, so we have the window
+ // procedure pretend that they were dispatched to the top-level window
+ // instead.
+ //
+ // The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
+ // is given below so that it catches the Trackpoint driver's heuristics.
+ HWND scrollContainerWnd = ::CreateWindowW(
+ className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
+ 0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
+ HWND scrollableWnd = ::CreateWindowW(
+ className, L"FAKETRACKPOINTSCROLLABLE",
+ WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
+ scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
+
+ // Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
+ // WindowProcInternal can distinguish it from the top-level window
+ // easily.
+ ::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
+
+ // Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
+ // old window procedure in its "user data".
+ WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
+ scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
+ ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
+ }
+
+ // We will start receiving native events after associating with our native
+ // window. We will also become the output of WinUtils::GetNSWindowPtr for that
+ // window.
+ if (!AssociateWithNativeWindow()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Starting with Windows XP, a process always runs within a terminal services
+ // session. In order to play nicely with RDP, fast user switching, and the
+ // lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
+ // our HWND in order to receive this message.
+ DebugOnly<BOOL> wtsRegistered =
+ ::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
+ NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
+
+ mDefaultIMC.Init(this);
+ IMEHandler::InitInputContext(this, mInputContext);
+
+ static bool a11yPrimed = false;
+ if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
+ a11yPrimed = true;
+ if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
+ ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
+ }
+ }
+
+ RecreateDirectManipulationIfNeeded();
+
+ return NS_OK;
+}
+
+void nsWindow::LocalesChanged() {
+ bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
+ if (mIsRTL != isRTL) {
+ DWORD dwAttribute = isRTL;
+ DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
+ sizeof dwAttribute);
+ mIsRTL = isRTL;
+ }
+}
+
+// Close this nsWindow
+void nsWindow::Destroy() {
+ // WM_DESTROY has already fired, avoid calling it twice
+ if (mOnDestroyCalled) return;
+
+ // Don't destroy windows that have file pickers open, we'll tear these down
+ // later once the picker is closed.
+ mDestroyCalled = true;
+ if (mPickerDisplayCount) return;
+
+ // During the destruction of all of our children, make sure we don't get
+ // deleted.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ DestroyDirectManipulation();
+
+ /**
+ * On windows the LayerManagerOGL destructor wants the widget to be around for
+ * cleanup. It also would like to have the HWND intact, so we nullptr it here.
+ */
+ DestroyLayerManager();
+
+ InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
+ mDeviceNotifyHandle = nullptr;
+
+ // The DestroyWindow function destroys the specified window. The function
+ // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
+ // and remove the keyboard focus from it. The function also destroys the
+ // window's menu, flushes the thread message queue, destroys timers, removes
+ // clipboard ownership, and breaks the clipboard viewer chain (if the window
+ // is at the top of the viewer chain).
+ //
+ // If the specified window is a parent or owner window, DestroyWindow
+ // automatically destroys the associated child or owned windows when it
+ // destroys the parent or owner window. The function first destroys child or
+ // owned windows, and then it destroys the parent or owner window.
+ VERIFY(::DestroyWindow(mWnd));
+
+ // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
+ // OnDestroy() didn't get called, call it now.
+ if (false == mOnDestroyCalled) {
+ MSGResult msgResult;
+ mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
+ OnDestroy();
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Window class utilities
+ *
+ * Utilities for calculating the proper window class name for
+ * Create window.
+ *
+ **************************************************************/
+
+/* static */
+const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle, LPWSTR aIconID) {
+ WNDCLASSW wc;
+ if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
+ // already registered
+ return aClassName;
+ }
+
+ wc.style = CS_DBLCLKS | aExtraStyle;
+ wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hIcon =
+ aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = aClassName;
+
+ if (!::RegisterClassW(&wc)) {
+ // For older versions of Win32 (i.e., not XP), the registration may
+ // fail with aExtraStyle, so we have to re-register without it.
+ wc.style = CS_DBLCLKS;
+ ::RegisterClassW(&wc);
+ }
+ return aClassName;
+}
+
+static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
+
+/* static */
+const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) {
+ switch (aWindowType) {
+ case WindowType::Invisible:
+ return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon);
+ case WindowType::Dialog:
+ return RegisterWindowClass(kClassNameDialog, 0, nullptr);
+ case WindowType::Popup:
+ return RegisterWindowClass(kClassNameDropShadow, 0,
+ gStockApplicationIcon);
+ default:
+ return RegisterWindowClass(GetMainWindowClass(), 0,
+ gStockApplicationIcon);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Window styles utilities
+ *
+ * Return the proper windows styles and extended styles.
+ *
+ **************************************************************/
+
+// Return nsWindow styles
+DWORD nsWindow::WindowStyle() {
+ DWORD style;
+
+ switch (mWindowType) {
+ case WindowType::Child:
+ style = WS_OVERLAPPED;
+ break;
+
+ case WindowType::Dialog:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
+ DS_MODALFRAME | WS_CLIPCHILDREN;
+ if (mBorderStyle != BorderStyle::Default)
+ style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
+ break;
+
+ case WindowType::Popup:
+ style = WS_POPUP | WS_OVERLAPPED;
+ break;
+
+ default:
+ NS_ERROR("unknown border style");
+ [[fallthrough]];
+
+ case WindowType::TopLevel:
+ case WindowType::Invisible:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
+ WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
+ break;
+ }
+
+ if (mBorderStyle != BorderStyle::Default &&
+ mBorderStyle != BorderStyle::All) {
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::Border))
+ style &= ~WS_BORDER;
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::Title)) {
+ style &= ~WS_DLGFRAME;
+ }
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::Close))
+ style &= ~0;
+ // XXX The close box can only be removed by changing the window class,
+ // as far as I know --- roc+moz@cs.cmu.edu
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close)))
+ style &= ~WS_SYSMENU;
+ // Looks like getting rid of the system menu also does away with the
+ // close box. So, we only get rid of the system menu if you want neither it
+ // nor the close box. How does the Windows "Dialog" window class get just
+ // closebox and no sysmenu? Who knows.
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::ResizeH))
+ style &= ~WS_THICKFRAME;
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::Minimize))
+ style &= ~WS_MINIMIZEBOX;
+
+ if (mBorderStyle == BorderStyle::None ||
+ !(mBorderStyle & BorderStyle::Maximize))
+ style &= ~WS_MAXIMIZEBOX;
+
+ if (IsPopupWithTitleBar()) {
+ style |= WS_CAPTION;
+ if (mBorderStyle & BorderStyle::Close) {
+ style |= WS_SYSMENU;
+ }
+ }
+ }
+
+ if (mIsChildWindow) {
+ style |= WS_CLIPCHILDREN;
+ if (!(style & WS_POPUP)) {
+ style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
+ }
+ }
+
+ VERIFY_WINDOW_STYLE(style);
+ return style;
+}
+
+// Return nsWindow extended styles
+DWORD nsWindow::WindowExStyle() {
+ MOZ_ASSERT_IF(mIsAlert, mWindowType == WindowType::Dialog);
+ switch (mWindowType) {
+ case WindowType::Child:
+ return 0;
+ case WindowType::Popup: {
+ DWORD extendedStyle = WS_EX_TOOLWINDOW;
+ if (mPopupLevel == PopupLevel::Top) {
+ extendedStyle |= WS_EX_TOPMOST;
+ }
+ return extendedStyle;
+ }
+ case WindowType::Dialog: {
+ if (mIsAlert) {
+ return WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
+ }
+ return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME;
+ }
+ case WindowType::Sheet:
+ MOZ_FALLTHROUGH_ASSERT("Sheets are macOS specific");
+ case WindowType::TopLevel:
+ case WindowType::Invisible:
+ break;
+ }
+ return WS_EX_WINDOWEDGE;
+}
+
+/**************************************************************
+ *
+ * SECTION: Native window association utilities
+ *
+ * Used in Create and Destroy. A nsWindow can associate with its
+ * underlying native window mWnd. Once a native window is
+ * associated with a nsWindow, its native events will be handled
+ * by the static member function nsWindow::WindowProc. Moreover,
+ * the association will be registered in the WinUtils association
+ * list, that is, calling WinUtils::GetNSWindowPtr on the native
+ * window will return the associated nsWindow. This is used in
+ * nsWindow::WindowProc to correctly dispatch native events to
+ * the handler methods defined in nsWindow, even though it is a
+ * static member function.
+ *
+ * After dissociation, the native events of the native window will
+ * no longer be handled by nsWindow::WindowProc, and will thus not
+ * be dispatched to the nsWindow native event handler methods.
+ * Moreover, the association will no longer be registered in the
+ * WinUtils association list, so calling WinUtils::GetNSWindowPtr
+ * on the native window will return nullptr.
+ *
+ **************************************************************/
+
+bool nsWindow::AssociateWithNativeWindow() {
+ if (!mWnd || !IsWindow(mWnd)) {
+ NS_ERROR("Invalid window handle");
+ return false;
+ }
+
+ // Connect the this pointer to the native window handle.
+ // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
+ // uses WinUtils::GetNSWindowPtr internally.
+ WinUtils::SetNSWindowPtr(mWnd, this);
+
+ ::SetLastError(ERROR_SUCCESS);
+ const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
+ mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
+ if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
+ NS_ERROR("Failure in SetWindowLongPtrW");
+ WinUtils::SetNSWindowPtr(mWnd, nullptr);
+ return false;
+ }
+
+ mPrevWndProc.emplace(prevWndProc);
+ return true;
+}
+
+void nsWindow::DissociateFromNativeWindow() {
+ if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
+ return;
+ }
+
+ DebugOnly<WNDPROC> wndProcBeforeDissociate =
+ reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
+ mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
+ NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
+ "Unstacked an unexpected native window procedure");
+
+ WinUtils::SetNSWindowPtr(mWnd, nullptr);
+ mPrevWndProc.reset();
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetParent, nsIWidget::GetParent
+ *
+ * Set or clear the parent widgets using window properties, and
+ * handles calculating native parent handles.
+ *
+ **************************************************************/
+
+// Get and set parent widgets
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+ nsIWidget* parent = GetParent();
+ if (parent) {
+ parent->RemoveChild(this);
+ }
+
+ mParent = aNewParent;
+
+ if (aNewParent) {
+ ReparentNativeWidget(aNewParent);
+ aNewParent->AddChild(this);
+ return;
+ }
+ if (mWnd) {
+ // If we have no parent, SetParent should return the desktop.
+ VERIFY(::SetParent(mWnd, nullptr));
+ RecreateDirectManipulationIfNeeded();
+ }
+}
+
+void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
+ MOZ_ASSERT(aNewParent, "null widget");
+
+ mParent = aNewParent;
+ if (mWindowType == WindowType::Popup) {
+ return;
+ }
+ HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
+ NS_ASSERTION(newParent, "Parent widget has a null native window handle");
+ if (newParent && mWnd) {
+ ::SetParent(mWnd, newParent);
+ RecreateDirectManipulationIfNeeded();
+ }
+}
+
+nsIWidget* nsWindow::GetParent(void) {
+ if (mIsTopWidgetWindow) {
+ return nullptr;
+ }
+ if (mInDtor || mOnDestroyCalled) {
+ return nullptr;
+ }
+ return mParent;
+}
+
+static int32_t RoundDown(double aDouble) {
+ return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
+ : static_cast<int32_t>(ceil(aDouble));
+}
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ if (mDefaultScale <= 0.0) {
+ mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
+ }
+ return mDefaultScale;
+}
+
+int32_t nsWindow::LogToPhys(double aValue) {
+ return WinUtils::LogToPhys(
+ ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
+}
+
+nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
+ return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
+}
+
+nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
+ if (mIsTopWidgetWindow) {
+ // Must use a flag instead of mWindowType to tell if the window is the
+ // owned by the topmost widget, because a child window can be embedded
+ // inside a HWND which is not associated with a nsIWidget.
+ return nullptr;
+ }
+
+ // If this widget has already been destroyed, pretend we have no parent.
+ // This corresponds to code in Destroy which removes the destroyed
+ // widget from its parent's child list.
+ if (mInDtor || mOnDestroyCalled) return nullptr;
+
+ // aIncludeOwner set to true implies walking the parent chain to retrieve the
+ // root owner. aIncludeOwner set to false implies the search will stop at the
+ // true parent (default).
+ nsWindow* widget = nullptr;
+ if (mWnd) {
+ HWND parent = nullptr;
+ if (aIncludeOwner)
+ parent = ::GetParent(mWnd);
+ else
+ parent = ::GetAncestor(mWnd, GA_PARENT);
+
+ if (parent) {
+ widget = WinUtils::GetNSWindowPtr(parent);
+ if (widget) {
+ // If the widget is in the process of being destroyed then
+ // do NOT return it
+ if (widget->mInDtor) {
+ widget = nullptr;
+ }
+ }
+ }
+ }
+
+ return widget;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Show
+ *
+ * Hide or show this component.
+ *
+ **************************************************************/
+
+void nsWindow::Show(bool bState) {
+ if (bState && mIsShowingPreXULSkeletonUI) {
+ // The first time we decide to actually show the window is when we decide
+ // that we've taken over the window from the skeleton UI, and we should
+ // no longer treat resizes / moves specially.
+ mIsShowingPreXULSkeletonUI = false;
+#if defined(ACCESSIBILITY)
+ // If our HWND has focus and the a11y engine hasn't started yet, fire a
+ // focus win event. Windows already did this when the skeleton UI appeared,
+ // but a11y wouldn't have been able to start at that point even if a client
+ // responded. Firing this now gives clients the chance to respond with
+ // WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
+ // this if the a11y engine has already started because it has probably
+ // already fired focus on a descendant.
+ if (::GetFocus() == mWnd && !GetAccService()) {
+ ::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
+ }
+#endif // defined(ACCESSIBILITY)
+ }
+
+ if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(ChooseWindowClass(mWindowType) == kClassNameDropShadow);
+ // WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
+ // some popup menus to become invisible.
+ LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
+ if (exStyle & WS_EX_LAYERED) {
+ ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
+ }
+ }
+
+ bool syncInvalidate = false;
+
+ bool wasVisible = mIsVisible;
+ // Set the status now so that anyone asking during ShowWindow or
+ // SetWindowPos would get the correct answer.
+ mIsVisible = bState;
+
+ // We may have cached an out of date visible state. This can happen
+ // when session restore sets the full screen mode.
+ if (mIsVisible)
+ mOldStyle |= WS_VISIBLE;
+ else
+ mOldStyle &= ~WS_VISIBLE;
+
+ if (mWnd) {
+ if (bState) {
+ if (!wasVisible && mWindowType == WindowType::TopLevel) {
+ // speed up the initial paint after show for
+ // top level windows:
+ syncInvalidate = true;
+
+ // Set the cursor before showing the window to avoid the default wait
+ // cursor.
+ SetCursor(Cursor{eCursor_standard});
+
+ switch (mFrameState->GetSizeMode()) {
+ case nsSizeMode_Fullscreen:
+ ::ShowWindow(mWnd, SW_SHOW);
+ break;
+ case nsSizeMode_Maximized:
+ ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
+ break;
+ case nsSizeMode_Minimized:
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ break;
+ default:
+ if (CanTakeFocus() && !mAlwaysOnTop) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ // Don't flicker the window if we're restoring session
+ if (!sIsRestoringSession) {
+ Unused << GetAttention(2);
+ }
+ }
+ break;
+ }
+ } else {
+ DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
+ if (wasVisible) {
+ flags |= SWP_NOZORDER;
+ }
+ if (mAlwaysOnTop || mIsAlert) {
+ flags |= SWP_NOACTIVATE;
+ }
+
+ if (mWindowType == WindowType::Popup) {
+ // ensure popups are the topmost of the TOPMOST
+ // layer. Remember not to set the SWP_NOZORDER
+ // flag as that might allow the taskbar to overlap
+ // the popup.
+ flags |= SWP_NOACTIVATE;
+ HWND owner = ::GetWindow(mWnd, GW_OWNER);
+ if (owner) {
+ // PopupLevel::Top popups should be above all else. All other
+ // types should be placed in front of their owner, without
+ // changing the owner's z-level relative to other windows.
+ if (mPopupLevel != PopupLevel::Top) {
+ ::SetWindowPos(mWnd, owner, 0, 0, 0, 0, flags);
+ ::SetWindowPos(owner, mWnd, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ } else {
+ ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
+ }
+ } else {
+ ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
+ }
+ } else {
+ if (mWindowType == WindowType::Dialog && !CanTakeFocus())
+ flags |= SWP_NOACTIVATE;
+
+ ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
+ }
+ }
+ } else {
+ // Clear contents to avoid ghosting of old content if we display
+ // this window again.
+ if (wasVisible && mTransparencyMode == TransparencyMode::Transparent) {
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->ClearTransparentWindow();
+ }
+ }
+ if (mWindowType != WindowType::Dialog) {
+ ::ShowWindow(mWnd, SW_HIDE);
+ } else {
+ ::SetWindowPos(mWnd, 0, 0, 0, 0, 0,
+ SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER |
+ SWP_NOACTIVATE);
+ }
+ }
+ }
+
+ if (!wasVisible && bState) {
+ Invalidate();
+ if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
+ ::UpdateWindow(mWnd);
+ }
+ }
+
+ if (mOpeningAnimationSuppressed) {
+ SuppressAnimation(false);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::IsVisible
+ *
+ * Returns the visibility state.
+ *
+ **************************************************************/
+
+// Return true if the component is visible, false otherwise.
+//
+// This does not take cloaking into account.
+bool nsWindow::IsVisible() const { return mIsVisible; }
+
+/**************************************************************
+ *
+ * SECTION: Touch and APZ-related functions
+ *
+ **************************************************************/
+
+void nsWindow::RegisterTouchWindow() {
+ mTouchWindow = true;
+ ::RegisterTouchWindow(mWnd, TWF_WANTPALM);
+ ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
+}
+
+BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
+ if (win) {
+ ::RegisterTouchWindow(aWnd, TWF_WANTPALM);
+ }
+ return TRUE;
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (aShouldLock) {
+ mAspectRatio = (float)mBounds.Width() / (float)mBounds.Height();
+ } else {
+ mAspectRatio = 0.0;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetInputRegion
+ *
+ * Sets whether the window should ignore mouse events.
+ *
+ **************************************************************/
+void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
+ mInputRegion = aInputRegion;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Move, nsIWidget::Resize, nsIWidget::Size
+ *
+ * Repositioning and sizing a window.
+ *
+ **************************************************************/
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ SizeConstraints c = aConstraints;
+
+ if (mWindowType != WindowType::Popup && mResizable) {
+ c.mMinSize.width =
+ std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
+ c.mMinSize.height =
+ std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
+ }
+
+ if (mMaxTextureSize > 0) {
+ // We can't make ThebesLayers bigger than this anyway.. no point it letting
+ // a window grow bigger as we won't be able to draw content there in
+ // general.
+ c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
+ }
+
+ mSizeConstraintsScale = GetDefaultScale().scale;
+
+ nsBaseWidget::SetSizeConstraints(c);
+}
+
+const SizeConstraints nsWindow::GetSizeConstraints() {
+ double scale = GetDefaultScale().scale;
+ if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) {
+ return mSizeConstraints;
+ }
+ scale /= mSizeConstraintsScale;
+ SizeConstraints c = mSizeConstraints;
+ if (c.mMinSize.width != NS_MAXSIZE) {
+ c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale);
+ }
+ if (c.mMinSize.height != NS_MAXSIZE) {
+ c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale);
+ }
+ if (c.mMaxSize.width != NS_MAXSIZE) {
+ c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale);
+ }
+ if (c.mMaxSize.height != NS_MAXSIZE) {
+ c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale);
+ }
+ return c;
+}
+
+// Move this component
+void nsWindow::Move(double aX, double aY) {
+ if (mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ // Check to see if window needs to be moved first
+ // to avoid a costly call to SetWindowPos. This check
+ // can not be moved to the calling code in nsView, because
+ // some platforms do not position child windows correctly
+
+ // Only perform this check for non-popup windows, since the positioning can
+ // in fact change even when the x/y do not. We always need to perform the
+ // check. See bug #97805 for details.
+ if (mWindowType != WindowType::Popup && mBounds.IsEqualXY(x, y)) {
+ // Nothing to do, since it is already positioned correctly.
+ return;
+ }
+
+ mBounds.MoveTo(x, y);
+
+ if (mWnd) {
+#ifdef DEBUG
+ // complain if a window is moved offscreen (legal, but potentially
+ // worrisome)
+ if (mIsTopWidgetWindow) { // only a problem for top-level windows
+ // Make sure this window is actually on the screen before we move it
+ // XXX: Needs multiple monitor support
+ HDC dc = ::GetDC(mWnd);
+ if (dc) {
+ if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) {
+ RECT workArea;
+ ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
+ // no annoying assertions. just mention the issue.
+ if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("window moved to offscreen position\n"));
+ }
+ }
+ ::ReleaseDC(mWnd, dc);
+ }
+ }
+#endif
+
+ // Normally, when the skeleton UI is disabled, we resize+move the window
+ // before showing it in order to ensure that it restores to the correct
+ // position when the user un-maximizes it. However, when we are using the
+ // skeleton UI, this results in the skeleton UI window being moved around
+ // undesirably before being locked back into the maximized position. To
+ // avoid this, we simply set the placement to restore to via
+ // SetWindowPlacement. It's a little bit more of a dance, though, since we
+ // need to convert the workspace coords that SetWindowPlacement uses to the
+ // screen space coordinates we normally use with SetWindowPos.
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (NS_WARN_IF(!monitor)) {
+ return;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ int32_t deltaX =
+ x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
+ int32_t deltaY =
+ y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
+ pl.rcNormalPosition.left += deltaX;
+ pl.rcNormalPosition.right += deltaX;
+ pl.rcNormalPosition.top += deltaY;
+ pl.rcNormalPosition.bottom += deltaY;
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ } else {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
+ double oldScale = mDefaultScale;
+ mResizeState = IN_SIZEMOVE;
+ VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ }
+
+ ResizeDirectManipulationViewport();
+ }
+}
+
+// Resize this component
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+
+ NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
+ NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
+ if (width < 0 || height < 0) {
+ gfxCriticalNoteOnce << "Negative passed to Resize(" << width << ", "
+ << height << ") repaint: " << aRepaint;
+ }
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.IsEqualSize(width, height)) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return;
+ }
+
+ // Set cached value for lightweight and printing
+ bool wasLocking = mAspectRatio != 0.0;
+ mBounds.SizeTo(width, height);
+ if (wasLocking) {
+ LockAspectRatio(true); // This causes us to refresh the mAspectRatio value
+ }
+
+ if (mWnd) {
+ // Refer to the comment above a similar check in nsWindow::Move
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+ pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
+ pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
+ mResizeState = RESIZING;
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ mResizeState = NOT_RESIZING;
+ } else {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
+
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ double oldScale = mDefaultScale;
+ mResizeState = RESIZING;
+ VERIFY(
+ ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags));
+
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ }
+
+ ResizeDirectManipulationViewport();
+ }
+
+ if (aRepaint) Invalidate();
+}
+
+// Resize this component
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ // for top-level windows only, convert coordinates from desktop pixels
+ // (the "parent" coordinate space) to the window's device pixel space
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+
+ NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize");
+ NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize");
+ if (width < 0 || height < 0) {
+ gfxCriticalNoteOnce << "Negative passed to Resize(" << x << " ," << y
+ << ", " << width << ", " << height
+ << ") repaint: " << aRepaint;
+ }
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.IsEqualRect(x, y, width, height)) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.SetRect(x, y, width, height);
+
+ if (mWnd) {
+ // Refer to the comment above a similar check in nsWindow::Move
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (NS_WARN_IF(!monitor)) {
+ return;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ int32_t deltaX =
+ x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left;
+ int32_t deltaY =
+ y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top;
+ pl.rcNormalPosition.left += deltaX;
+ pl.rcNormalPosition.right = pl.rcNormalPosition.left + width;
+ pl.rcNormalPosition.top += deltaY;
+ pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height);
+ VERIFY(::SetWindowPlacement(mWnd, &pl));
+ } else {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ double oldScale = mDefaultScale;
+ mResizeState = RESIZING;
+ VERIFY(
+ ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags));
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+
+ if (mTransitionWnd) {
+ // If we have a fullscreen transition window, we need to make
+ // it topmost again, otherwise the taskbar may be raised by
+ // the system unexpectedly when we leave fullscreen state.
+ ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
+ }
+ }
+
+ ResizeDirectManipulationViewport();
+ }
+
+ if (aRepaint) Invalidate();
+}
+
+mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() {
+ if (mResizeState == RESIZING) {
+ return Some(true);
+ }
+ return Some(false);
+}
+
+/**************************************************************
+ *
+ * SECTION: Window Z-order and state.
+ *
+ * nsIWidget::PlaceBehind, nsIWidget::SetSizeMode,
+ * nsIWidget::ConstrainPosition
+ *
+ * Z-order, positioning, restore, minimize, and maximize.
+ *
+ **************************************************************/
+
+// Position the window behind the given window
+void nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
+ nsIWidget* aWidget, bool aActivate) {
+ HWND behind = HWND_TOP;
+ if (aPlacement == eZPlacementBottom)
+ behind = HWND_BOTTOM;
+ else if (aPlacement == eZPlacementBelow && aWidget)
+ behind = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
+ UINT flags = SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE;
+ if (!aActivate) flags |= SWP_NOACTIVATE;
+
+ if (!CanTakeFocus() && behind == HWND_TOP) {
+ // Can't place the window to top so place it behind the foreground window
+ // (as long as it is not topmost)
+ HWND wndAfter = ::GetForegroundWindow();
+ if (!wndAfter)
+ behind = HWND_BOTTOM;
+ else if (!(GetWindowLongPtrW(wndAfter, GWL_EXSTYLE) & WS_EX_TOPMOST))
+ behind = wndAfter;
+ flags |= SWP_NOACTIVATE;
+ }
+
+ ::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
+}
+
+static UINT GetCurrentShowCmd(HWND aWnd) {
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(aWnd, &pl);
+ return pl.showCmd;
+}
+
+// Maximize, minimize or restore the window.
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ // If we are still displaying a maximized pre-XUL skeleton UI, ignore the
+ // noise of sizemode changes. Once we have "shown" the window for the first
+ // time (called nsWindow::Show(true), even though the window is already
+ // technically displayed), we will again accept sizemode changes.
+ if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) {
+ return;
+ }
+
+ mFrameState->EnsureSizeMode(aMode);
+}
+
+nsSizeMode nsWindow::SizeMode() { return mFrameState->GetSizeMode(); }
+
+void DoGetWorkspaceID(HWND aWnd, nsAString* aWorkspaceID) {
+ RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
+ if (!desktopManager || !aWnd) {
+ return;
+ }
+
+ GUID desktop;
+ HRESULT hr = desktopManager->GetWindowDesktopId(aWnd, &desktop);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RPC_WSTR workspaceIDStr = nullptr;
+ if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) {
+ aWorkspaceID->Assign((wchar_t*)workspaceIDStr);
+ RpcStringFreeW(&workspaceIDStr);
+ }
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ // If we have a value cached, use that, but also make sure it is
+ // scheduled to be updated. If we don't yet have a value, get
+ // one synchronously.
+ auto desktop = mDesktopId.Lock();
+ if (desktop->mID.IsEmpty()) {
+ DoGetWorkspaceID(mWnd, &desktop->mID);
+ desktop->mUpdateIsQueued = false;
+ } else {
+ AsyncUpdateWorkspaceID(*desktop);
+ }
+
+ workspaceID = desktop->mID;
+}
+
+void nsWindow::AsyncUpdateWorkspaceID(Desktop& aDesktop) {
+ struct UpdateWorkspaceIdTask : public Task {
+ explicit UpdateWorkspaceIdTask(nsWindow* aSelf)
+ : Task(Kind::OffMainThreadOnly, EventQueuePriority::Normal),
+ mSelf(aSelf) {}
+
+ TaskResult Run() override {
+ auto desktop = mSelf->mDesktopId.Lock();
+ if (desktop->mUpdateIsQueued) {
+ DoGetWorkspaceID(mSelf->mWnd, &desktop->mID);
+ desktop->mUpdateIsQueued = false;
+ }
+ return TaskResult::Complete;
+ }
+
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ bool GetName(nsACString& aName) override {
+ aName.AssignLiteral("UpdateWorkspaceIdTask");
+ return true;
+ }
+#endif
+
+ RefPtr<nsWindow> mSelf;
+ };
+
+ if (aDesktop.mUpdateIsQueued) {
+ return;
+ }
+
+ aDesktop.mUpdateIsQueued = true;
+ TaskController::Get()->AddTask(MakeAndAddRef<UpdateWorkspaceIdTask>(this));
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceID) {
+ RefPtr<IVirtualDesktopManager> desktopManager = gVirtualDesktopManager;
+ if (!desktopManager) {
+ return;
+ }
+
+ GUID desktop;
+ const nsString flat = PromiseFlatString(workspaceID);
+ RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get());
+ if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) {
+ if (SUCCEEDED(desktopManager->MoveWindowToDesktop(mWnd, desktop))) {
+ auto desktop = mDesktopId.Lock();
+ desktop->mID = workspaceID;
+ }
+ }
+}
+
+void nsWindow::SuppressAnimation(bool aSuppress) {
+ DWORD dwAttribute = aSuppress ? TRUE : FALSE;
+ DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute,
+ sizeof dwAttribute);
+}
+
+// Constrain a potential move to fit onscreen
+// Position (aX, aY) is specified in Windows screen (logical) pixels,
+// except when using per-monitor DPI, in which case it's device pixels.
+void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
+ if (!mIsTopWidgetWindow) // only a problem for top-level windows
+ return;
+
+ double dpiScale = GetDesktopToDeviceScale().scale;
+
+ // We need to use the window size in the kind of pixels used for window-
+ // manipulation APIs.
+ int32_t logWidth =
+ std::max<int32_t>(NSToIntRound(mBounds.Width() / dpiScale), 1);
+ int32_t logHeight =
+ std::max<int32_t>(NSToIntRound(mBounds.Height() / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ RECT screenRect;
+
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService(sScreenManagerContractID);
+ if (!screenmgr) {
+ return;
+ }
+ nsCOMPtr<nsIScreen> screen;
+ int32_t left, top, width, height;
+
+ screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
+ getter_AddRefs(screen));
+ if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
+ // For normalized windows, use the desktop work area.
+ nsresult rv = screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ } else {
+ // For full screen windows, use the desktop.
+ nsresult rv = screen->GetRectDisplayPix(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+ screenRect.left = left;
+ screenRect.right = left + width;
+ screenRect.top = top;
+ screenRect.bottom = top + height;
+
+ if (aPoint.x < screenRect.left)
+ aPoint.x = screenRect.left;
+ else if (aPoint.x >= screenRect.right - logWidth)
+ aPoint.x = screenRect.right - logWidth;
+
+ if (aPoint.y < screenRect.top)
+ aPoint.y = screenRect.top;
+ else if (aPoint.y >= screenRect.bottom - logHeight)
+ aPoint.y = screenRect.bottom - logHeight;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
+ *
+ * Enabling and disabling the widget.
+ *
+ **************************************************************/
+
+// Enable/disable this component
+void nsWindow::Enable(bool bState) {
+ if (mWnd) {
+ ::EnableWindow(mWnd, bState);
+ }
+}
+
+// Return the current enable state
+bool nsWindow::IsEnabled() const {
+ return !mWnd || (::IsWindowEnabled(mWnd) &&
+ ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT)));
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetFocus
+ *
+ * Give the focus to this widget.
+ *
+ **************************************************************/
+
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ if (mWnd) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes));
+ }
+#endif
+ // Uniconify, if necessary
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
+ if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) {
+ ::ShowWindow(toplevelWnd, SW_RESTORE);
+ }
+ ::SetFocus(mWnd);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Bounds
+ *
+ * GetBounds, GetClientBounds, GetScreenBounds,
+ * GetRestoredBounds, GetClientOffset, SetNonClientMargins
+ *
+ * Bound calculations.
+ *
+ **************************************************************/
+
+// Return the window's full dimensions in screen coordinates.
+// If the window has a parent, converts the origin to an offset
+// of the parent's screen origin.
+LayoutDeviceIntRect nsWindow::GetBounds() {
+ if (!mWnd) {
+ return mBounds;
+ }
+
+ RECT r;
+ VERIFY(::GetWindowRect(mWnd, &r));
+
+ LayoutDeviceIntRect rect;
+
+ // assign size
+ rect.SizeTo(r.right - r.left, r.bottom - r.top);
+
+ // popup window bounds' are in screen coordinates, not relative to parent
+ // window
+ if (mWindowType == WindowType::Popup) {
+ rect.MoveTo(r.left, r.top);
+ return rect;
+ }
+
+ // chrome on parent:
+ // ___ 5,5 (chrome start)
+ // | ____ 10,10 (client start)
+ // | | ____ 20,20 (child start)
+ // | | |
+ // 20,20 - 5,5 = 15,15 (??)
+ // minus GetClientOffset:
+ // 15,15 - 5,5 = 10,10
+ //
+ // no chrome on parent:
+ // ______ 10,10 (win start)
+ // | ____ 20,20 (child start)
+ // | |
+ // 20,20 - 10,10 = 10,10
+ //
+ // walking the chain:
+ // ___ 5,5 (chrome start)
+ // | ___ 10,10 (client start)
+ // | | ___ 20,20 (child start)
+ // | | | __ 30,30 (child start)
+ // | | | |
+ // 30,30 - 20,20 = 10,10 (offset from second child to first)
+ // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??)
+ // minus GetClientOffset:
+ // 25,25 - 5,5 = 20,20 (offset from second child to parent client)
+
+ // convert coordinates if parent exists
+ HWND parent = ::GetParent(mWnd);
+ if (parent) {
+ RECT pr;
+ VERIFY(::GetWindowRect(parent, &pr));
+ r.left -= pr.left;
+ r.top -= pr.top;
+ // adjust for chrome
+ nsWindow* pWidget = static_cast<nsWindow*>(GetParent());
+ if (pWidget && pWidget->IsTopLevelWidget()) {
+ LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset();
+ r.left -= clientOffset.x;
+ r.top -= clientOffset.y;
+ }
+ }
+ rect.MoveTo(r.left, r.top);
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(rect.width, rect.height)) {
+ gfxCriticalNoteOnce << "Invalid size" << rect << " size mode "
+ << mFrameState->GetSizeMode();
+ }
+
+ return rect;
+}
+
+// Get this component dimension
+LayoutDeviceIntRect nsWindow::GetClientBounds() {
+ if (!mWnd) {
+ return LayoutDeviceIntRect(0, 0, 0, 0);
+ }
+
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
+ return mBounds;
+ }
+
+ LayoutDeviceIntRect bounds = GetBounds();
+ LayoutDeviceIntRect rect;
+ rect.MoveTo(bounds.TopLeft() + GetClientOffset());
+ rect.SizeTo(r.right - r.left, r.bottom - r.top);
+ return rect;
+}
+
+// Like GetBounds, but don't offset by the parent
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ if (!mWnd) {
+ return mBounds;
+ }
+
+ RECT r;
+ VERIFY(::GetWindowRect(mWnd, &r));
+
+ return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
+}
+
+nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) {
+ if (SizeMode() == nsSizeMode_Normal) {
+ aRect = GetScreenBounds();
+ return NS_OK;
+ }
+ if (!mWnd) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)};
+ VERIFY(::GetWindowPlacement(mWnd, &pl));
+ const RECT& r = pl.rcNormalPosition;
+
+ HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ return NS_ERROR_FAILURE;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ VERIFY(::GetMonitorInfo(monitor, &mi));
+
+ aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top);
+ aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left,
+ mi.rcWork.top - mi.rcMonitor.top);
+ return NS_OK;
+}
+
+// Return the x,y offset of the client area from the origin of the window. If
+// the window is borderless returns (0,0).
+LayoutDeviceIntPoint nsWindow::GetClientOffset() {
+ if (!mWnd) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ RECT r1;
+ GetWindowRect(mWnd, &r1);
+ LayoutDeviceIntPoint pt = WidgetToScreenOffset();
+ return LayoutDeviceIntPoint(pt.x - LayoutDeviceIntCoord(r1.left),
+ pt.y - LayoutDeviceIntCoord(r1.top));
+}
+
+void nsWindow::ResetLayout() {
+ // This will trigger a frame changed event, triggering
+ // nc calc size and a sizemode gecko event.
+ SetWindowPos(mWnd, 0, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
+
+ // If hidden, just send the frame changed event for now.
+ if (!mIsVisible) {
+ return;
+ }
+
+ // Send a gecko size event to trigger reflow.
+ RECT clientRc = {0};
+ GetClientRect(mWnd, &clientRc);
+ OnResize(WinUtils::ToIntRect(clientRc).Size());
+
+ // Invalidate and update
+ Invalidate();
+}
+
+// Internally track the caption status via a window property. Required
+// due to our internal handling of WM_NCACTIVATE when custom client
+// margins are set.
+static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty";
+typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi);
+static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr>
+ sGetWindowInfoPtrStub;
+
+BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) {
+ if (!sGetWindowInfoPtrStub) {
+ NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!");
+ return FALSE;
+ }
+ int windowStatus =
+ reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty));
+ // No property set, return the default data.
+ if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi);
+ // Call GetWindowInfo and update dwWindowStatus with our
+ // internally tracked value.
+ BOOL result = sGetWindowInfoPtrStub(hWnd, pwi);
+ if (result && pwi)
+ pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION);
+ return result;
+}
+
+void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) {
+ if (!mWnd) return;
+
+ sUser32Intercept.Init("user32.dll");
+ sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo",
+ &GetWindowInfoHook);
+ if (!sGetWindowInfoPtrStub) {
+ return;
+ }
+
+ // Update our internally tracked caption status
+ SetPropW(mWnd, kManageWindowInfoProperty,
+ reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
+}
+
+#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
+#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
+
+void nsWindow::UpdateDarkModeToolbar() {
+ PreferenceSheet::EnsureInitialized();
+ BOOL dark = PreferenceSheet::ColorSchemeForChrome() == ColorScheme::Dark;
+ DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &dark,
+ sizeof dark);
+ DwmSetWindowAttribute(mWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
+ sizeof dark);
+}
+
+LayoutDeviceIntMargin nsWindow::NormalWindowNonClientOffset() const {
+ LayoutDeviceIntMargin nonClientOffset;
+
+ // We're dealing with a "normal" window (not maximized, minimized, or
+ // fullscreen), so process `mNonClientMargins` and set `mNonClientOffset`
+ // accordingly.
+ //
+ // Setting `mNonClientOffset` to 0 has the effect of leaving the default
+ // frame intact. Setting it to a value greater than 0 reduces the frame
+ // size by that amount.
+
+ if (mNonClientMargins.top > 0) {
+ nonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
+ } else if (mNonClientMargins.top == 0) {
+ nonClientOffset.top = mCaptionHeight;
+ } else {
+ nonClientOffset.top = 0;
+ }
+
+ if (mNonClientMargins.bottom > 0) {
+ nonClientOffset.bottom =
+ std::min(mVertResizeMargin, mNonClientMargins.bottom);
+ } else if (mNonClientMargins.bottom == 0) {
+ nonClientOffset.bottom = mVertResizeMargin;
+ } else {
+ nonClientOffset.bottom = 0;
+ }
+
+ if (mNonClientMargins.left > 0) {
+ nonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
+ } else if (mNonClientMargins.left == 0) {
+ nonClientOffset.left = mHorResizeMargin;
+ } else {
+ nonClientOffset.left = 0;
+ }
+
+ if (mNonClientMargins.right > 0) {
+ nonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
+ } else if (mNonClientMargins.right == 0) {
+ nonClientOffset.right = mHorResizeMargin;
+ } else {
+ nonClientOffset.right = 0;
+ }
+ return nonClientOffset;
+}
+
+/**
+ * Called when the window layout changes: full screen mode transitions,
+ * theme changes, and composition changes. Calculates the new non-client
+ * margins and fires off a frame changed event, which triggers an nc calc
+ * size windows event, kicking the changes in.
+ *
+ * The offsets calculated here are based on the value of `mNonClientMargins`
+ * which is specified in the "chromemargins" attribute of the window. For
+ * each margin, the value specified has the following meaning:
+ * -1 - leave the default frame in place
+ * 0 - remove the frame
+ * >0 - frame size equals min(0, (default frame size - margin value))
+ *
+ * This function calculates and populates `mNonClientOffset`.
+ * In our processing of `WM_NCCALCSIZE`, the frame size will be calculated
+ * as (default frame size - offset). For example, if the left frame should
+ * be 1 pixel narrower than the default frame size, `mNonClientOffset.left`
+ * will equal 1.
+ *
+ * For maximized, fullscreen, and minimized windows, the values stored in
+ * `mNonClientMargins` are ignored, and special processing takes place.
+ *
+ * For non-glass windows, we only allow frames to be their default size
+ * or removed entirely.
+ */
+bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) {
+ if (!mCustomNonClient) {
+ return false;
+ }
+
+ const nsSizeMode sizeMode = mFrameState->GetSizeMode();
+
+ bool hasCaption =
+ bool(mBorderStyle & (BorderStyle::All | BorderStyle::Title |
+ BorderStyle::Menu | BorderStyle::Default));
+
+ float dpi = GetDPI();
+
+ // mCaptionHeight is the default size of the NC area at
+ // the top of the window. If the window has a caption,
+ // the size is calculated as the sum of:
+ // SM_CYFRAME - The thickness of the sizing border
+ // around a resizable window
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows
+ // SM_CYCAPTION - The height of the caption area
+ //
+ // If the window does not have a caption, mCaptionHeight will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
+ mCaptionHeight =
+ WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) +
+ WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+ if (!mUseResizeMarginOverrides) {
+ // mHorResizeMargin is the size of the default NC areas on the
+ // left and right sides of our window. It is calculated as
+ // the sum of:
+ // SM_CXFRAME - The thickness of the sizing border
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows
+ //
+ // If the window does not have a caption, mHorResizeMargin will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)`
+ mHorResizeMargin =
+ WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+
+ // mVertResizeMargin is the size of the default NC area at the
+ // bottom of the window. It is calculated as the sum of:
+ // SM_CYFRAME - The thickness of the sizing border
+ // SM_CXPADDEDBORDER - The amount of border padding
+ // for captioned windows.
+ //
+ // If the window does not have a caption, mVertResizeMargin will be equal to
+ // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)`
+ mVertResizeMargin =
+ WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+ }
+
+ if (sizeMode == nsSizeMode_Minimized) {
+ // Use default frame size for minimized windows
+ mNonClientOffset.top = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+ mNonClientOffset.bottom = 0;
+ } else if (sizeMode == nsSizeMode_Fullscreen) {
+ // Remove the default frame from the top of our fullscreen window. This
+ // makes the whole caption part of our client area, allowing us to draw
+ // in the whole caption area. Additionally remove the default frame from
+ // the left, right, and bottom.
+ mNonClientOffset.top = mCaptionHeight;
+ mNonClientOffset.bottom = mVertResizeMargin;
+ mNonClientOffset.left = mHorResizeMargin;
+ mNonClientOffset.right = mHorResizeMargin;
+ } else if (sizeMode == nsSizeMode_Maximized) {
+ // We make the entire frame part of the client area. We leave the default
+ // frame sizes for left, right and bottom since Windows will automagically
+ // position the edges "offscreen" for maximized windows.
+ int verticalResize =
+ WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) +
+ (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
+ : 0);
+
+ mNonClientOffset.top = mCaptionHeight - verticalResize;
+ mNonClientOffset.bottom = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+
+ mozilla::Maybe<UINT> maybeEdge = GetHiddenTaskbarEdge();
+ if (maybeEdge) {
+ auto edge = maybeEdge.value();
+ if (ABE_LEFT == edge) {
+ mNonClientOffset.left -= kHiddenTaskbarSize;
+ } else if (ABE_RIGHT == edge) {
+ mNonClientOffset.right -= kHiddenTaskbarSize;
+ } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
+ mNonClientOffset.bottom -= kHiddenTaskbarSize;
+ }
+
+ // When we are drawing the non-client region, we need
+ // to clear the portion of the NC region that is exposed by the
+ // hidden taskbar. As above, we clear the bottom of the NC region
+ // when the taskbar is at the top of the screen.
+ UINT clearEdge = (edge == ABE_TOP) ? ABE_BOTTOM : edge;
+ mClearNCEdge = Some(clearEdge);
+ }
+ } else {
+ mNonClientOffset = NormalWindowNonClientOffset();
+ }
+
+ if (aReflowWindow) {
+ // Force a reflow of content based on the new client
+ // dimensions.
+ ResetLayout();
+ }
+
+ return true;
+}
+
+nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) {
+ if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mHideChrome) {
+ mFutureMarginsOnceChromeShows = margins;
+ mFutureMarginsToUse = true;
+ return NS_OK;
+ }
+ mFutureMarginsToUse = false;
+
+ // Request for a reset
+ if (margins.top == -1 && margins.left == -1 && margins.right == -1 &&
+ margins.bottom == -1) {
+ mCustomNonClient = false;
+ mNonClientMargins = margins;
+ // Force a reflow of content based on the new client
+ // dimensions.
+ ResetLayout();
+
+ int windowStatus =
+ reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty));
+ if (windowStatus) {
+ ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0);
+ }
+
+ return NS_OK;
+ }
+
+ if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 ||
+ margins.right < -1)
+ return NS_ERROR_INVALID_ARG;
+
+ mNonClientMargins = margins;
+ mCustomNonClient = true;
+ if (!UpdateNonClientMargins()) {
+ NS_WARNING("UpdateNonClientMargins failed!");
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) {
+ mUseResizeMarginOverrides = true;
+ mHorResizeMargin = aResizeMargin;
+ mVertResizeMargin = aResizeMargin;
+ UpdateNonClientMargins();
+}
+
+void nsWindow::InvalidateNonClientRegion() {
+ // +-+-----------------------+-+
+ // | | app non-client chrome | |
+ // | +-----------------------+ |
+ // | | app client chrome | | }
+ // | +-----------------------+ | }
+ // | | app content | | } area we don't want to invalidate
+ // | +-----------------------+ | }
+ // | | app client chrome | | }
+ // | +-----------------------+ |
+ // +---------------------------+ <
+ // ^ ^ windows non-client chrome
+ // client area = app *
+ RECT rect;
+ GetWindowRect(mWnd, &rect);
+ MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ HRGN winRgn = CreateRectRgnIndirect(&rect);
+
+ // Subtract app client chrome and app content leaving
+ // windows non-client chrome and app non-client chrome
+ // in winRgn.
+ GetWindowRect(mWnd, &rect);
+ rect.top += mCaptionHeight;
+ rect.right -= mHorResizeMargin;
+ rect.bottom -= mVertResizeMargin;
+ rect.left += mHorResizeMargin;
+ MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ HRGN clientRgn = CreateRectRgnIndirect(&rect);
+ CombineRgn(winRgn, winRgn, clientRgn, RGN_DIFF);
+ DeleteObject(clientRgn);
+
+ // triggers ncpaint and paint events for the two areas
+ RedrawWindow(mWnd, nullptr, winRgn, RDW_FRAME | RDW_INVALIDATE);
+ DeleteObject(winRgn);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetBackgroundColor
+ *
+ * Sets the window background paint color.
+ *
+ **************************************************************/
+
+void nsWindow::SetBackgroundColor(const nscolor& aColor) {
+ if (mBrush) ::DeleteObject(mBrush);
+
+ mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(aColor));
+ if (mWnd != nullptr) {
+ ::SetClassLongPtrW(mWnd, GCLP_HBRBACKGROUND, (LONG_PTR)mBrush);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetCursor
+ *
+ * SetCursor and related utilities for manging cursor state.
+ *
+ **************************************************************/
+
+// Set this component cursor
+static HCURSOR CursorFor(nsCursor aCursor) {
+ switch (aCursor) {
+ case eCursor_select:
+ return ::LoadCursor(nullptr, IDC_IBEAM);
+ case eCursor_wait:
+ return ::LoadCursor(nullptr, IDC_WAIT);
+ case eCursor_hyperlink:
+ return ::LoadCursor(nullptr, IDC_HAND);
+ case eCursor_standard:
+ case eCursor_context_menu: // XXX See bug 258960.
+ return ::LoadCursor(nullptr, IDC_ARROW);
+
+ case eCursor_n_resize:
+ case eCursor_s_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENS);
+
+ case eCursor_w_resize:
+ case eCursor_e_resize:
+ return ::LoadCursor(nullptr, IDC_SIZEWE);
+
+ case eCursor_nw_resize:
+ case eCursor_se_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENWSE);
+
+ case eCursor_ne_resize:
+ case eCursor_sw_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENESW);
+
+ case eCursor_crosshair:
+ return ::LoadCursor(nullptr, IDC_CROSS);
+
+ case eCursor_move:
+ return ::LoadCursor(nullptr, IDC_SIZEALL);
+
+ case eCursor_help:
+ return ::LoadCursor(nullptr, IDC_HELP);
+
+ case eCursor_copy: // CSS3
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
+
+ case eCursor_alias:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
+
+ case eCursor_cell:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
+ case eCursor_grab:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
+
+ case eCursor_grabbing:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_GRABBING));
+
+ case eCursor_spinning:
+ return ::LoadCursor(nullptr, IDC_APPSTARTING);
+
+ case eCursor_zoom_in:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
+
+ case eCursor_zoom_out:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_ZOOMOUT));
+
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ return ::LoadCursor(nullptr, IDC_NO);
+
+ case eCursor_col_resize:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_COLRESIZE));
+
+ case eCursor_row_resize:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_ROWRESIZE));
+
+ case eCursor_vertical_text:
+ return ::LoadCursor(nsToolkit::mDllInstance,
+ MAKEINTRESOURCE(IDC_VERTICALTEXT));
+
+ case eCursor_all_scroll:
+ // XXX not 100% appropriate perhaps
+ return ::LoadCursor(nullptr, IDC_SIZEALL);
+
+ case eCursor_nesw_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENESW);
+
+ case eCursor_nwse_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENWSE);
+
+ case eCursor_ns_resize:
+ return ::LoadCursor(nullptr, IDC_SIZENS);
+
+ case eCursor_ew_resize:
+ return ::LoadCursor(nullptr, IDC_SIZEWE);
+
+ case eCursor_none:
+ return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
+
+ default:
+ NS_ERROR("Invalid cursor type");
+ return nullptr;
+ }
+}
+
+static HCURSOR CursorForImage(const nsIWidget::Cursor& aCursor,
+ CSSToLayoutDeviceScale aScale) {
+ if (!aCursor.IsCustom()) {
+ return nullptr;
+ }
+
+ nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
+
+ // Reject cursors greater than 128 pixels in either direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ if (size.width > 128 || size.height > 128) {
+ return nullptr;
+ }
+
+ LayoutDeviceIntSize layoutSize =
+ RoundedToInt(CSSIntSize(size.width, size.height) * aScale);
+ LayoutDeviceIntPoint hotspot =
+ RoundedToInt(CSSIntPoint(aCursor.mHotspotX, aCursor.mHotspotY) * aScale);
+ HCURSOR cursor;
+ nsresult rv = nsWindowGfx::CreateIcon(aCursor.mContainer, true, hotspot,
+ layoutSize, &cursor);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return cursor;
+}
+
+void nsWindow::SetCursor(const Cursor& aCursor) {
+ static HCURSOR sCurrentHCursor = nullptr;
+ static bool sCurrentHCursorIsCustom = false;
+
+ mCursor = aCursor;
+
+ if (sCurrentCursor == aCursor && sCurrentHCursor && !mUpdateCursor) {
+ // Cursors in windows are global, so even if our mUpdateCursor flag is
+ // false we always need to make sure the Windows cursor is up-to-date,
+ // since stuff like native drag and drop / resizers code can mutate it
+ // outside of this method.
+ ::SetCursor(sCurrentHCursor);
+ return;
+ }
+
+ mUpdateCursor = false;
+
+ if (sCurrentHCursorIsCustom) {
+ ::DestroyIcon(sCurrentHCursor);
+ }
+ sCurrentHCursor = nullptr;
+ sCurrentHCursorIsCustom = false;
+ sCurrentCursor = aCursor;
+
+ HCURSOR cursor = nullptr;
+ if (mCustomCursorAllowed) {
+ cursor = CursorForImage(aCursor, GetDefaultScale());
+ }
+ bool custom = false;
+ if (cursor) {
+ custom = true;
+ } else {
+ cursor = CursorFor(aCursor.mDefaultCursor);
+ }
+
+ if (!cursor) {
+ return;
+ }
+
+ sCurrentHCursor = cursor;
+ sCurrentHCursorIsCustom = custom;
+ ::SetCursor(cursor);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Get/SetTransparencyMode
+ *
+ * Manage the transparency mode of the window containing this
+ * widget. Only works for popup and dialog windows when the
+ * Desktop Window Manager compositor is not enabled.
+ *
+ **************************************************************/
+
+TransparencyMode nsWindow::GetTransparencyMode() {
+ return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
+}
+
+void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
+ nsWindow* window = GetTopLevelWindow(true);
+ MOZ_ASSERT(window);
+
+ if (!window || window->DestroyCalled()) {
+ return;
+ }
+
+ window->SetWindowTranslucencyInner(aMode);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::UpdateWindowDraggingRegion
+ *
+ * For setting the draggable titlebar region from CSS
+ * with -moz-window-dragging: drag.
+ *
+ **************************************************************/
+
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HideWindowChrome
+ *
+ * Show or hide window chrome.
+ *
+ **************************************************************/
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ if (!WinUtils::GetNSWindowPtr(hwnd)) {
+ NS_WARNING("Trying to hide window decorations in an embedded context");
+ return;
+ }
+
+ if (mHideChrome == aShouldHide) return;
+
+ DWORD_PTR style, exStyle;
+ mHideChrome = aShouldHide;
+ if (aShouldHide) {
+ DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
+ DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
+
+ style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME);
+ exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
+ WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
+
+ mOldStyle = tempStyle;
+ mOldExStyle = tempExStyle;
+ } else {
+ if (!mOldStyle || !mOldExStyle) {
+ mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
+ mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
+ }
+
+ style = mOldStyle;
+ exStyle = mOldExStyle;
+ if (mFutureMarginsToUse) {
+ SetNonClientMargins(mFutureMarginsOnceChromeShows);
+ }
+ }
+
+ VERIFY_WINDOW_STYLE(style);
+ ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
+ ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsWindow::Invalidate
+ *
+ * Invalidate an area of the client for painting.
+ *
+ **************************************************************/
+
+// Invalidate this component visible area
+void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea,
+ bool aIncludeChildren) {
+ if (!mWnd) {
+ return;
+ }
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ DWORD flags = RDW_INVALIDATE;
+ if (aEraseBackground) {
+ flags |= RDW_ERASE;
+ }
+ if (aUpdateNCArea) {
+ flags |= RDW_FRAME;
+ }
+ if (aIncludeChildren) {
+ flags |= RDW_ALLCHILDREN;
+ }
+
+ VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags));
+}
+
+// Invalidate this component visible area
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (mWnd) {
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ RECT rect;
+
+ rect.left = aRect.X();
+ rect.top = aRect.Y();
+ rect.right = aRect.XMost();
+ rect.bottom = aRect.YMost();
+
+ VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
+ }
+}
+
+static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg,
+ WPARAM wParam,
+ LPARAM lParam) {
+ switch (uMsg) {
+ case WM_FULLSCREEN_TRANSITION_BEFORE:
+ case WM_FULLSCREEN_TRANSITION_AFTER: {
+ DWORD duration = (DWORD)lParam;
+ DWORD flags = AW_BLEND;
+ if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) {
+ flags |= AW_HIDE;
+ }
+ ::AnimateWindow(hWnd, duration, flags);
+ // The message sender should have added ref for us.
+ NS_DispatchToMainThread(
+ already_AddRefed<nsIRunnable>((nsIRunnable*)wParam));
+ break;
+ }
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ break;
+ default:
+ return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
+ return 0;
+}
+
+struct FullscreenTransitionInitData {
+ LayoutDeviceIntRect mBounds;
+ HANDLE mSemaphore;
+ HANDLE mThread;
+ HWND mWnd;
+
+ FullscreenTransitionInitData()
+ : mSemaphore(nullptr), mThread(nullptr), mWnd(nullptr) {}
+
+ ~FullscreenTransitionInitData() {
+ if (mSemaphore) {
+ ::CloseHandle(mSemaphore);
+ }
+ if (mThread) {
+ ::CloseHandle(mThread);
+ }
+ }
+};
+
+static DWORD WINAPI FullscreenTransitionThreadProc(LPVOID lpParam) {
+ // Initialize window class
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ WNDCLASSW wc = {};
+ wc.lpfnWndProc = ::FullscreenTransitionWindowProc;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hbrBackground = ::CreateSolidBrush(RGB(0, 0, 0));
+ wc.lpszClassName = kClassNameTransition;
+ ::RegisterClassW(&wc);
+ sInitialized = true;
+ }
+
+ auto data = static_cast<FullscreenTransitionInitData*>(lpParam);
+ HWND wnd = ::CreateWindowW(kClassNameTransition, L"", 0, 0, 0, 0, 0, nullptr,
+ nullptr, nsToolkit::mDllInstance, nullptr);
+ if (!wnd) {
+ ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
+ return 0;
+ }
+
+ // Since AnimateWindow blocks the thread of the transition window,
+ // we need to hide the cursor for that window, otherwise the system
+ // would show the busy pointer to the user.
+ ::ShowCursor(false);
+ ::SetWindowLongW(wnd, GWL_STYLE, 0);
+ ::SetWindowLongW(
+ wnd, GWL_EXSTYLE,
+ WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE);
+ ::SetWindowPos(wnd, HWND_TOPMOST, data->mBounds.X(), data->mBounds.Y(),
+ data->mBounds.Width(), data->mBounds.Height(), 0);
+ data->mWnd = wnd;
+ ::ReleaseSemaphore(data->mSemaphore, 1, nullptr);
+ // The initialization data may no longer be valid
+ // after we release the semaphore.
+ data = nullptr;
+
+ MSG msg;
+ while (::GetMessageW(&msg, nullptr, 0, 0)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+ ::ShowCursor(true);
+ ::DestroyWindow(wnd);
+ return 0;
+}
+
+class FullscreenTransitionData final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionData(HWND aWnd) : mWnd(aWnd) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "FullscreenTransitionData "
+ "should be constructed in the main thread");
+ }
+
+ const HWND mWnd;
+
+ private:
+ ~FullscreenTransitionData() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "FullscreenTransitionData "
+ "should be deconstructed in the main thread");
+ ::PostMessageW(mWnd, WM_DESTROY, 0, 0);
+ }
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
+
+/* virtual */
+bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ FullscreenTransitionInitData initData;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ const DesktopIntRect rect = screen->GetRectDisplayPix();
+ MOZ_ASSERT(BoundsUseDesktopPixels(),
+ "Should only be called on top-level window");
+ initData.mBounds =
+ LayoutDeviceIntRect::Round(rect * GetDesktopToDeviceScale());
+
+ // Create a semaphore for synchronizing the window handle which will
+ // be created by the transition thread and used by the main thread for
+ // posting the transition messages.
+ initData.mSemaphore = ::CreateSemaphore(nullptr, 0, 1, nullptr);
+ if (initData.mSemaphore) {
+ initData.mThread = ::CreateThread(
+ nullptr, 0, FullscreenTransitionThreadProc, &initData, 0, nullptr);
+ if (initData.mThread) {
+ ::WaitForSingleObject(initData.mSemaphore, INFINITE);
+ }
+ }
+ if (!initData.mWnd) {
+ return false;
+ }
+
+ mTransitionWnd = initData.mWnd;
+
+ auto data = new FullscreenTransitionData(initData.mWnd);
+ *aData = data;
+ NS_ADDREF(data);
+ return true;
+}
+
+/* virtual */
+void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ nsCOMPtr<nsIRunnable> callback = aCallback;
+ UINT msg = aStage == eBeforeFullscreenToggle ? WM_FULLSCREEN_TRANSITION_BEFORE
+ : WM_FULLSCREEN_TRANSITION_AFTER;
+ WPARAM wparam = (WPARAM)callback.forget().take();
+ ::PostMessage(data->mWnd, msg, wparam, (LPARAM)aDuration);
+}
+
+/* virtual */
+void nsWindow::CleanupFullscreenTransition() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CleanupFullscreenTransition "
+ "should only run on the main thread");
+
+ mTransitionWnd = nullptr;
+}
+
+void nsWindow::TryDwmResizeHack() {
+ // The "DWM resize hack", aka the "fullscreen resize hack", is a workaround
+ // for DWM's occasional and not-entirely-predictable failure to update its
+ // internal state when the client area of a window changes without changing
+ // the window size. The effect of this is that DWM will clip the content of
+ // the window to its former client area.
+ //
+ // It is not known under what circumstances the bug will trigger. Windows 11
+ // is known to be required, but many Windows 11 machines do not exhibit the
+ // issue. Even machines that _do_ exhibit it will sometimes not do so when
+ // apparently-irrelevant changes are made to the configuration. (See bug
+ // 1763981.)
+ //
+ // The bug is triggered by Firefox when a maximized window (which has window
+ // decorations) becomes fullscreen (which doesn't). To work around this, if we
+ // think it may occur, we "flicker-resize" the relevant window -- that is, we
+ // reduce its height by 1px, then restore it. This causes DWM to acquire the
+ // new client-area metrics.
+ //
+ // Note that, in particular, this bug will not occur when using a separate
+ // compositor window, as our compositor windows never have any nonclient area.
+ //
+ // This is admittedly a sledgehammer where a screwdriver should suffice.
+
+ // ---------------------------------------------------------------------------
+
+ // Regardless of preferences or heuristics, only apply the hack if this is the
+ // first time we've entered fullscreen across the entire Firefox session.
+ // (Subsequent transitions to fullscreen, even with different windows, don't
+ // appear to induce the bug.)
+ {
+ // (main thread only; `atomic` not needed)
+ static bool sIsFirstFullscreenEntry = true;
+ bool isFirstFullscreenEntry = sIsFirstFullscreenEntry;
+ sIsFirstFullscreenEntry = false;
+ if (MOZ_LIKELY(!isFirstFullscreenEntry)) {
+ return;
+ }
+ MOZ_LOG(gWindowsLog, LogLevel::Verbose,
+ ("%s: first fullscreen entry", __PRETTY_FUNCTION__));
+ }
+
+ // Check whether to try to apply the DWM resize hack, based on the override
+ // pref and/or some internal heuristics.
+ {
+ const auto hackApplicationHeuristics = [&]() -> bool {
+ // The bug has only been seen under Windows 11. (At time of writing, this
+ // is the latest version of Windows.)
+ if (!IsWin11OrLater()) {
+ return false;
+ }
+
+ KnowsCompositor const* const kc = mWindowRenderer->AsKnowsCompositor();
+ // This should never happen...
+ MOZ_ASSERT(kc);
+ // ... so if it does, we are in uncharted territory: don't apply the hack.
+ if (!kc) {
+ return false;
+ }
+
+ // The bug doesn't occur when we're using a separate compositor window
+ // (since the compositor window always comprises exactly its client area,
+ // with no non-client border).
+ if (kc->GetUseCompositorWnd()) {
+ return false;
+ }
+
+ // Otherwise, apply the hack.
+ return true;
+ };
+
+ // Figure out whether or not we should perform the hack, and -- arguably
+ // more importantly -- log that decision.
+ bool const shouldApplyHack = [&]() {
+ enum Reason : bool { Pref, Heuristics };
+ auto const msg = [&](bool decision, Reason reason) -> bool {
+ MOZ_LOG(gWindowsLog, LogLevel::Verbose,
+ ("%s %s per %s", decision ? "applying" : "skipping",
+ "DWM resize hack", reason == Pref ? "pref" : "heuristics"));
+ return decision;
+ };
+ switch (StaticPrefs::widget_windows_apply_dwm_resize_hack()) {
+ case 0:
+ return msg(false, Pref);
+ case 1:
+ return msg(true, Pref);
+ default: // treat all other values as `auto`
+ return msg(hackApplicationHeuristics(), Heuristics);
+ }
+ }();
+
+ if (!shouldApplyHack) {
+ return;
+ }
+ }
+
+ // The DWM bug is believed to involve a race condition: some users have
+ // reported that setting a custom theme or adding unused command-line
+ // parameters sometimes causes the bug to vanish.
+ //
+ // Out of an abundance of caution, we therefore apply the hack in a later
+ // event, rather than inline.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsWindow::TryFullscreenResizeHack", [self = RefPtr(this)]() {
+ HWND const hwnd = self->GetWindowHandle();
+
+ if (self->mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) {
+ MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info,
+ ("DWM resize hack: window no longer fullscreen; aborting"));
+ return;
+ }
+
+ RECT origRect;
+ if (!::GetWindowRect(hwnd, &origRect)) {
+ MOZ_LOG(gWindowsLog, mozilla::LogLevel::Error,
+ ("DWM resize hack: could not get window size?!"));
+ return;
+ }
+ LONG const x = origRect.left;
+ LONG const y = origRect.top;
+ LONG const width = origRect.right - origRect.left;
+ LONG const height = origRect.bottom - origRect.top;
+
+ MOZ_DIAGNOSTIC_ASSERT(!self->mIsPerformingDwmFlushHack);
+ auto const onExit =
+ MakeScopeExit([&, oldVal = self->mIsPerformingDwmFlushHack]() {
+ self->mIsPerformingDwmFlushHack = oldVal;
+ });
+ self->mIsPerformingDwmFlushHack = true;
+
+ MOZ_LOG(gWindowsLog, LogLevel::Debug,
+ ("beginning DWM resize hack for HWND %08" PRIXPTR,
+ uintptr_t(hwnd)));
+ ::MoveWindow(hwnd, x, y, width, height - 1, FALSE);
+ ::MoveWindow(hwnd, x, y, width, height, TRUE);
+ MOZ_LOG(gWindowsLog, LogLevel::Debug,
+ ("concluded DWM resize hack for HWND %08" PRIXPTR,
+ uintptr_t(hwnd)));
+ }));
+}
+
+void nsWindow::OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen) {
+ MOZ_ASSERT((aOldSizeMode != nsSizeMode_Fullscreen) == aFullScreen);
+
+ // HACK: Potentially flicker-resize the window, to force DWM to get the right
+ // client-area information.
+ if (aFullScreen) {
+ TryDwmResizeHack();
+ }
+
+ // Hide chrome and reposition window. Note this will also cache dimensions for
+ // restoration, so it should only be called once per fullscreen request.
+ //
+ // Don't do this when minimized, since our bounds make no sense then, nor when
+ // coming back from that state.
+ const bool toOrFromMinimized =
+ mFrameState->GetSizeMode() == nsSizeMode_Minimized ||
+ aOldSizeMode == nsSizeMode_Minimized;
+ if (!toOrFromMinimized) {
+ InfallibleMakeFullScreen(aFullScreen);
+ }
+
+ // Possibly notify the taskbar that we have changed our fullscreen mode.
+ TaskbarConcealer::OnFullscreenChanged(this, aFullScreen);
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
+ mFrameState->EnsureFullscreenMode(aFullScreen);
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: Native data storage
+ *
+ * nsIWidget::GetNativeData
+ * nsIWidget::FreeNativeData
+ *
+ * Set or clear native data based on a constant.
+ *
+ **************************************************************/
+
+// Return some native data according to aDataType
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+ return (void*)mWnd;
+ case NS_NATIVE_GRAPHIC:
+ MOZ_ASSERT_UNREACHABLE("Not supported on Windows:");
+ return nullptr;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ [[fallthrough]];
+ }
+ case NS_NATIVE_TSF_THREAD_MGR:
+ case NS_NATIVE_TSF_CATEGORY_MGR:
+ case NS_NATIVE_TSF_DISPLAY_ATTR_MGR:
+ return IMEHandler::GetNativeData(this, aDataType);
+
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+// Free some native data according to aDataType
+void nsWindow::FreeNativeData(void* data, uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_GRAPHIC:
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_WINDOW:
+ break;
+ default:
+ break;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetTitle
+ *
+ * Set the main windows title text.
+ *
+ **************************************************************/
+
+nsresult nsWindow::SetTitle(const nsAString& aTitle) {
+ const nsString& strTitle = PromiseFlatString(aTitle);
+ AutoRestore<bool> sendingText(mSendingSetText);
+ mSendingSetText = true;
+ ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get());
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetIcon
+ *
+ * Set the main windows icon.
+ *
+ **************************************************************/
+
+void nsWindow::SetBigIcon(HICON aIcon) {
+ HICON icon =
+ (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon);
+ if (icon) {
+ ::DestroyIcon(icon);
+ }
+
+ mIconBig = aIcon;
+}
+
+void nsWindow::SetSmallIcon(HICON aIcon) {
+ HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL,
+ (LPARAM)aIcon);
+ if (icon) {
+ ::DestroyIcon(icon);
+ }
+
+ mIconSmall = aIcon;
+}
+
+void nsWindow::SetIcon(const nsAString& aIconSpec) {
+ // Assume the given string is a local identifier for an icon file.
+
+ nsCOMPtr<nsIFile> iconFile;
+ ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile));
+ if (!iconFile) return;
+
+ nsAutoString iconPath;
+ iconFile->GetPath(iconPath);
+
+ // XXX this should use MZLU (see bug 239279)
+
+ ::SetLastError(0);
+
+ HICON bigIcon =
+ (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
+ ::GetSystemMetrics(SM_CXICON),
+ ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE);
+ HICON smallIcon =
+ (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON,
+ ::GetSystemMetrics(SM_CXSMICON),
+ ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE);
+
+ if (bigIcon) {
+ SetBigIcon(bigIcon);
+ }
+#ifdef DEBUG_SetIcon
+ else {
+ NS_LossyConvertUTF16toASCII cPath(iconPath);
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
+ ::GetLastError()));
+ }
+#endif
+ if (smallIcon) {
+ SetSmallIcon(smallIcon);
+ }
+#ifdef DEBUG_SetIcon
+ else {
+ NS_LossyConvertUTF16toASCII cPath(iconPath);
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(),
+ ::GetLastError()));
+ }
+#endif
+}
+
+void nsWindow::SetBigIconNoData() {
+ HICON bigIcon =
+ ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
+ SetBigIcon(bigIcon);
+}
+
+void nsWindow::SetSmallIconNoData() {
+ HICON smallIcon =
+ ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
+ SetSmallIcon(smallIcon);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::WidgetToScreenOffset
+ *
+ * Return this widget's origin in screen coordinates.
+ *
+ **************************************************************/
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ POINT point;
+ point.x = 0;
+ point.y = 0;
+ ::ClientToScreen(mWnd, &point);
+ return LayoutDeviceIntPoint(point.x, point.y);
+}
+
+LayoutDeviceIntMargin nsWindow::ClientToWindowMargin() {
+ if (mWindowType == WindowType::Popup && !IsPopupWithTitleBar()) {
+ return {};
+ }
+
+ if (mCustomNonClient) {
+ return NonClientSizeMargin(NormalWindowNonClientOffset());
+ }
+
+ // Just use a dummy 200x200 at (200, 200) client rect as the rect.
+ RECT clientRect;
+ clientRect.left = 200;
+ clientRect.top = 200;
+ clientRect.right = 400;
+ clientRect.bottom = 400;
+
+ auto ToRect = [](const RECT& aRect) -> LayoutDeviceIntRect {
+ return {aRect.left, aRect.top, aRect.right - aRect.left,
+ aRect.bottom - aRect.top};
+ };
+
+ RECT windowRect = clientRect;
+ ::AdjustWindowRectEx(&windowRect, WindowStyle(), false, WindowExStyle());
+
+ return ToRect(windowRect) - ToRect(clientRect);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::EnableDragDrop
+ *
+ * Enables/Disables drag and drop of files on this widget.
+ *
+ **************************************************************/
+
+void nsWindow::EnableDragDrop(bool aEnable) {
+ if (!mWnd) {
+ // Return early if the window already closed
+ return;
+ }
+
+ if (aEnable) {
+ if (!mNativeDragTarget) {
+ mNativeDragTarget = new nsNativeDragTarget(this);
+ mNativeDragTarget->AddRef();
+ ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
+ }
+ } else {
+ if (mWnd && mNativeDragTarget) {
+ ::RevokeDragDrop(mWnd);
+ mNativeDragTarget->DragCancel();
+ NS_RELEASE(mNativeDragTarget);
+ }
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::CaptureMouse
+ *
+ * Enables/Disables system mouse capture.
+ *
+ **************************************************************/
+
+void nsWindow::CaptureMouse(bool aCapture) {
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ if (aCapture) {
+ mTrack.dwFlags = TME_CANCEL | TME_LEAVE;
+ ::SetCapture(mWnd);
+ } else {
+ mTrack.dwFlags = TME_LEAVE;
+ ::ReleaseCapture();
+ }
+ sIsInMouseCapture = aCapture;
+ TrackMouseEvent(&mTrack);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::CaptureRollupEvents
+ *
+ * Dealing with event rollup on destroy for popups. Enables &
+ * Disables system capture of any and all events that would
+ * cause a dropdown to be rolled up.
+ *
+ **************************************************************/
+
+void nsWindow::CaptureRollupEvents(bool aDoCapture) {
+ if (aDoCapture) {
+ if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
+ RegisterSpecialDropdownHooks();
+ }
+ sProcessHook = true;
+ } else {
+ sProcessHook = false;
+ UnregisterSpecialDropdownHooks();
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::GetAttention
+ *
+ * Bring this window to the user's attention.
+ *
+ **************************************************************/
+
+// Draw user's attention to this window until it comes to foreground.
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ // Got window?
+ if (!mWnd) return NS_ERROR_NOT_INITIALIZED;
+
+ HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false);
+ HWND fgWnd = ::GetForegroundWindow();
+ // Don't flash if the flash count is 0 or if the foreground window is our
+ // window handle or that of our owned-most window.
+ if (aCycleCount == 0 || flashWnd == fgWnd ||
+ flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) {
+ return NS_OK;
+ }
+
+ DWORD defaultCycleCount = 0;
+ ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0);
+
+ FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL,
+ aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0};
+ ::FlashWindowEx(&flashInfo);
+
+ return NS_OK;
+}
+
+void nsWindow::StopFlashing() {
+ HWND flashWnd = mWnd;
+ while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) {
+ flashWnd = ownerWnd;
+ }
+
+ FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0};
+ ::FlashWindowEx(&flashInfo);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HasPendingInputEvent
+ *
+ * Ask whether there user input events pending. All input events are
+ * included, including those not targeted at this nsIwidget instance.
+ *
+ **************************************************************/
+
+bool nsWindow::HasPendingInputEvent() {
+ // If there is pending input or the user is currently
+ // moving the window then return true.
+ // Note: When the user is moving the window WIN32 spins
+ // a separate event loop and input events are not
+ // reported to the application.
+ if (HIWORD(GetQueueStatus(QS_INPUT))) return true;
+ GUITHREADINFO guiInfo;
+ guiInfo.cbSize = sizeof(GUITHREADINFO);
+ if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false;
+ return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::GetWindowRenderer
+ *
+ * Get the window renderer associated with this widget.
+ *
+ **************************************************************/
+
+WindowRenderer* nsWindow::GetWindowRenderer() {
+ if (mWindowRenderer) {
+ return mWindowRenderer;
+ }
+
+ if (!mLocalesChangedObserver) {
+ mLocalesChangedObserver = new LocalesChangedObserver(this);
+ }
+
+ // Try OMTC first.
+ if (!mWindowRenderer && ShouldUseOffMainThreadCompositing()) {
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+ CreateCompositor();
+ }
+
+ if (!mWindowRenderer) {
+ MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild);
+ MOZ_ASSERT(!mCompositorWidgetDelegate);
+
+ // Ensure we have a widget proxy even if we're not using the compositor,
+ // since all our transparent window handling lives there.
+ WinCompositorWidgetInitData initData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode, mFrameState->GetSizeMode());
+ // If we're not using the compositor, the options don't actually matter.
+ CompositorOptions options(false, false);
+ mBasicLayersSurface =
+ new InProcessWinCompositorWidget(initData, options, this);
+ mCompositorWidgetDelegate = mBasicLayersSurface;
+ mWindowRenderer = CreateFallbackRenderer();
+ }
+
+ NS_ASSERTION(mWindowRenderer, "Couldn't provide a valid window renderer.");
+
+ if (mWindowRenderer) {
+ // Update the size constraints now that the layer manager has been
+ // created.
+ KnowsCompositor* knowsCompositor = mWindowRenderer->AsKnowsCompositor();
+ if (knowsCompositor) {
+ SizeConstraints c = mSizeConstraints;
+ mMaxTextureSize = knowsCompositor->GetMaxTextureSize();
+ c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize);
+ nsBaseWidget::SetSizeConstraints(c);
+ }
+ }
+
+ return mWindowRenderer;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsBaseWidget::SetCompositorWidgetDelegate
+ *
+ * Called to connect the nsWindow to the delegate providing
+ * platform compositing API access.
+ *
+ **************************************************************/
+
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::OnDefaultButtonLoaded
+ *
+ * Called after the dialog is loaded and it has a default button.
+ *
+ **************************************************************/
+
+nsresult nsWindow::OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) {
+ if (aButtonRect.IsEmpty()) return NS_OK;
+
+ // Don't snap when we are not active.
+ HWND activeWnd = ::GetActiveWindow();
+ if (activeWnd != ::GetForegroundWindow() ||
+ WinUtils::GetTopLevelHWND(mWnd, true) !=
+ WinUtils::GetTopLevelHWND(activeWnd, true)) {
+ return NS_OK;
+ }
+
+ bool isAlwaysSnapCursor =
+ Preferences::GetBool("ui.cursor_snapping.always_enabled", false);
+
+ if (!isAlwaysSnapCursor) {
+ BOOL snapDefaultButton;
+ if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton,
+ 0) ||
+ !snapDefaultButton)
+ return NS_OK;
+ }
+
+ LayoutDeviceIntRect widgetRect = GetScreenBounds();
+ LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
+
+ LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2,
+ buttonRect.Y() + buttonRect.Height() / 2);
+ // The center of the button can be outside of the widget.
+ // E.g., it could be hidden by scrolling.
+ if (!widgetRect.Contains(centerOfButton)) {
+ return NS_OK;
+ }
+
+ if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
+ NS_ERROR("SetCursorPos failed");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+uint32_t nsWindow::GetMaxTouchPoints() const {
+ return WinUtils::GetMaxTouchPoints();
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType,
+ const nsAString& xulWinClass,
+ const nsAString& xulWinName) {
+ mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank");
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Moz Events
+ **
+ ** Moz GUI event management.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: Mozilla event initialization
+ *
+ * Helpers for initializing moz events.
+ *
+ **************************************************************/
+
+// Event initialization
+void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) {
+ if (nullptr == aPoint) { // use the point from the event
+ // get the message position in client coordinates
+ if (mWnd != nullptr) {
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+
+ ::ScreenToClient(mWnd, &cpos);
+ event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+ } else {
+ // use the point override if provided
+ event.mRefPoint = *aPoint;
+ }
+
+ event.AssignEventTime(CurrentMessageWidgetEventTime());
+}
+
+WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const {
+ LONG messageTime = ::GetMessageTime();
+ return WidgetEventTime(GetMessageTimeStamp(messageTime));
+}
+
+/**************************************************************
+ *
+ * SECTION: Moz event dispatch helpers
+ *
+ * Helpers for dispatching different types of moz events.
+ *
+ **************************************************************/
+
+// Main event dispatch. Invokes callback and ProcessEvent method on
+// Event Listener object. Part of nsIWidget.
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus) {
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ aStatus = nsEventStatus_eIgnore;
+
+ // Top level windows can have a view attached which requires events be sent
+ // to the underlying base window and the view. Added when we combined the
+ // base chrome window with the main content child for nc client area (title
+ // bar) rendering.
+ if (mAttachedWidgetListener) {
+ aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
+ } else if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
+ }
+
+ // the window can be destroyed during processing of seemingly innocuous events
+ // like, say, mousedowns due to the magic of scripting. mousedowns will return
+ // nsEventStatus_eIgnore, which causes problems with the deleted window.
+ // therefore:
+ if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault;
+ return NS_OK;
+}
+
+bool nsWindow::DispatchStandardEvent(EventMessage aMsg) {
+ WidgetGUIEvent event(true, aMsg, this);
+ InitEvent(event);
+
+ bool result = DispatchWindowEvent(event);
+ return result;
+}
+
+bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) {
+ nsEventStatus status = DispatchInputEvent(event).mContentStatus;
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchContentCommandEvent(WidgetContentCommandEvent* aEvent) {
+ nsEventStatus status;
+ DispatchEvent(aEvent, status);
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWheelEvent(WidgetWheelEvent* aEvent) {
+ nsEventStatus status =
+ DispatchInputEvent(aEvent->AsInputEvent()).mContentStatus;
+ return ConvertStatus(status);
+}
+
+// Recursively dispatch synchronous paints for nsIWidget
+// descendants with invalidated rectangles.
+BOOL CALLBACK nsWindow::DispatchStarvedPaints(HWND aWnd, LPARAM aMsg) {
+ LONG_PTR proc = ::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
+ if (proc == (LONG_PTR)&nsWindow::WindowProc) {
+ // its one of our windows so check to see if it has a
+ // invalidated rect. If it does. Dispatch a synchronous
+ // paint.
+ if (GetUpdateRect(aWnd, nullptr, FALSE)) VERIFY(::UpdateWindow(aWnd));
+ }
+ return TRUE;
+}
+
+// Check for pending paints and dispatch any pending paint
+// messages for any nsIWidget which is a descendant of the
+// top-level window that *this* window is embedded within.
+//
+// Note: We do not dispatch pending paint messages for non
+// nsIWidget managed windows.
+void nsWindow::DispatchPendingEvents() {
+ // We need to ensure that reflow events do not get starved.
+ // At the same time, we don't want to recurse through here
+ // as that would prevent us from dispatching starved paints.
+ static int recursionBlocker = 0;
+ if (recursionBlocker++ == 0) {
+ NS_ProcessPendingEvents(nullptr, PR_MillisecondsToInterval(100));
+ --recursionBlocker;
+ }
+
+ // Quickly check to see if there are any paint events pending,
+ // but only dispatch them if it has been long enough since the
+ // last paint completed.
+ if (::GetQueueStatus(QS_PAINT) &&
+ ((TimeStamp::Now() - mLastPaintEndTime).ToMilliseconds() >= 50)) {
+ // Find the top level window.
+ HWND topWnd = WinUtils::GetTopLevelHWND(mWnd);
+
+ // Dispatch pending paints for topWnd and all its descendant windows.
+ // Note: EnumChildWindows enumerates all descendant windows not just
+ // the children (but not the window itself).
+ nsWindow::DispatchStarvedPaints(topWnd, 0);
+ ::EnumChildWindows(topWnd, nsWindow::DispatchStarvedPaints, 0);
+ }
+}
+
+void nsWindow::DispatchCustomEvent(const nsString& eventName) {
+ if (Document* doc = GetDocument()) {
+ if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
+ win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage,
+ LayoutDeviceIntPoint aEventPoint) {
+ // Allow users to start dragging by double-tapping.
+ if (aEventMessage == eMouseDoubleClick) {
+ return true;
+ }
+
+ // In chrome UI, allow touchdownstartsdrag attributes
+ // to cause any touchdown event to trigger a drag.
+ if (aEventMessage == eMouseDown) {
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEventPoint;
+ hittest.mIgnoreRootScrollFrame = true;
+ hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ DispatchInputEvent(&hittest);
+
+ if (EventTarget* target = hittest.GetDOMEventTarget()) {
+ if (nsIContent* content = nsIContent::FromEventTarget(target)) {
+ // Check if the element or any parent element has the
+ // attribute we're looking for.
+ for (Element* element = content->GetAsElementOrParentElement(); element;
+ element = element->GetParentElement()) {
+ nsAutoString startDrag;
+ element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag);
+ if (!startDrag.IsEmpty()) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+// Deal with all sort of mouse event
+bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
+ LPARAM lParam, bool aIsContextMenuKey,
+ int16_t aButton, uint16_t aInputSource,
+ WinPointerInfo* aPointerInfo,
+ bool aIgnoreAPZ) {
+ ContextMenuPreventer contextMenuPreventer(this);
+ bool result = false;
+
+ UserActivity();
+
+ if (!mWidgetListener) {
+ return result;
+ }
+
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
+
+ // Suppress mouse moves caused by widget creation. Make sure to do this early
+ // so that we update sLastMouseMovePoint even for touch-induced mousemove
+ // events.
+ if (aEventMessage == eMouseMove) {
+ if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
+ (sLastMouseMovePoint.y == mpScreen.y.value)) {
+ return result;
+ }
+ sLastMouseMovePoint.x = mpScreen.x;
+ sLastMouseMovePoint.y = mpScreen.y;
+ }
+
+ if (!aIgnoreAPZ && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
+ if (mTouchWindow) {
+ // If mTouchWindow is true, then we must have APZ enabled and be
+ // feeding it raw touch events. In that case we only want to
+ // send touch-generated mouse events to content if they should
+ // start a touch-based drag-and-drop gesture, such as on
+ // double-tapping or when tapping elements marked with the
+ // touchdownstartsdrag attribute in chrome UI.
+ MOZ_ASSERT(mAPZC);
+ if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) {
+ aEventMessage = eMouseTouchDrag;
+ } else {
+ return result;
+ }
+ }
+ }
+
+ uint32_t pointerId =
+ aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID();
+
+ switch (aEventMessage) {
+ case eMouseDown:
+ CaptureMouse(true);
+ break;
+
+ // eMouseMove and eMouseExitFromWidget are here because we need to make
+ // sure capture flag isn't left on after a drag where we wouldn't see a
+ // button up message (see bug 324131).
+ case eMouseUp:
+ case eMouseMove:
+ case eMouseExitFromWidget:
+ if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) &&
+ sIsInMouseCapture)
+ CaptureMouse(false);
+ break;
+
+ default:
+ break;
+
+ } // switch
+
+ WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal,
+ aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey
+ : WidgetMouseEvent::eNormal);
+ if (aEventMessage == eContextMenu && aIsContextMenuKey) {
+ LayoutDeviceIntPoint zero(0, 0);
+ InitEvent(event, &zero);
+ } else {
+ InitEvent(event, &eventPoint);
+ }
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+
+ // eContextMenu with Shift state is special. It won't fire "contextmenu"
+ // event in the web content for blocking web content to prevent its default.
+ // However, Shift+F10 is a standard shortcut key on Windows. Therefore,
+ // this should not block web page to prevent its default. I.e., it should
+ // behave same as ContextMenu key without Shift key.
+ // XXX Should we allow to block web page to prevent its default with
+ // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
+ if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() &&
+ NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN &&
+ NativeKey::LastKeyOrCharMSG().wParam == VK_F10) {
+ event.mModifiers &= ~MODIFIER_SHIFT;
+ }
+
+ event.mButton = aButton;
+ event.mInputSource = aInputSource;
+ if (aPointerInfo) {
+ // Mouse events from Windows WM_POINTER*. Fill more information in
+ // WidgetMouseEvent.
+ event.AssignPointerHelperData(*aPointerInfo);
+ event.mPressure = aPointerInfo->mPressure;
+ event.mButtons = aPointerInfo->mButtons;
+ } else {
+ // If we get here the mouse events must be from non-touch sources, so
+ // convert it to pointer events as well
+ event.convertToPointer = true;
+ event.pointerId = pointerId;
+ }
+
+ // Static variables used to distinguish simple-, double- and triple-clicks.
+ static POINT sLastMousePoint = {0};
+ static LONG sLastMouseDownTime = 0L;
+ static LONG sLastClickCount = 0L;
+ static BYTE sLastMouseButton = 0;
+
+ bool insideMovementThreshold =
+ (DeprecatedAbs(sLastMousePoint.x - eventPoint.x.value) <
+ (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
+ (DeprecatedAbs(sLastMousePoint.y - eventPoint.y.value) <
+ (short)::GetSystemMetrics(SM_CYDOUBLECLK));
+
+ BYTE eventButton;
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ eventButton = VK_LBUTTON;
+ break;
+ case MouseButton::eMiddle:
+ eventButton = VK_MBUTTON;
+ break;
+ case MouseButton::eSecondary:
+ eventButton = VK_RBUTTON;
+ break;
+ default:
+ eventButton = 0;
+ break;
+ }
+
+ // Doubleclicks are used to set the click count, then changed to mousedowns
+ // We're going to time double-clicks from mouse *up* to next mouse *down*
+ LONG curMsgTime = ::GetMessageTime();
+
+ switch (aEventMessage) {
+ case eMouseDoubleClick:
+ event.mMessage = eMouseDown;
+ event.mButton = aButton;
+ sLastClickCount = 2;
+ sLastMouseDownTime = curMsgTime;
+ break;
+ case eMouseUp:
+ // remember when this happened for the next mouse down
+ sLastMousePoint.x = eventPoint.x;
+ sLastMousePoint.y = eventPoint.y;
+ sLastMouseButton = eventButton;
+ break;
+ case eMouseDown:
+ // now look to see if we want to convert this to a double- or triple-click
+ if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) &&
+ insideMovementThreshold && eventButton == sLastMouseButton) {
+ sLastClickCount++;
+ } else {
+ // reset the click count, to count *this* click
+ sLastClickCount = 1;
+ }
+ // Set last Click time on MouseDown only
+ sLastMouseDownTime = curMsgTime;
+ break;
+ case eMouseMove:
+ if (!insideMovementThreshold) {
+ sLastClickCount = 0;
+ }
+ break;
+ case eMouseExitFromWidget:
+ event.mExitFrom =
+ Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+ break;
+ default:
+ break;
+ }
+ event.mClickCount = sLastClickCount;
+
+#ifdef NS_DEBUG_XX
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount));
+#endif
+
+ // call the event callback
+ if (mWidgetListener) {
+ if (aEventMessage == eMouseMove) {
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveTo(0, 0);
+
+ if (rect.Contains(event.mRefPoint)) {
+ if (sCurrentWindow == nullptr || sCurrentWindow != this) {
+ if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(
+ eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary,
+ aInputSource, aPointerInfo);
+ }
+ sCurrentWindow = this;
+ if (!mInDtor) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(
+ eMouseEnterIntoWidget, wParam, pos, false,
+ MouseButton::ePrimary, aInputSource, aPointerInfo);
+ }
+ }
+ }
+ } else if (aEventMessage == eMouseExitFromWidget) {
+ if (sCurrentWindow == this) {
+ sCurrentWindow = nullptr;
+ }
+ }
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus =
+ DispatchInputEvent(&event);
+ contextMenuPreventer.Update(event, eventStatus);
+ return ConvertStatus(eventStatus.mContentStatus);
+ }
+
+ return result;
+}
+
+HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) {
+ // retrieve the toplevel window or dialogue
+ HWND toplevelWnd = nullptr;
+ while (aCurWnd) {
+ toplevelWnd = aCurWnd;
+ nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd);
+ if (win) {
+ if (win->mWindowType == WindowType::TopLevel ||
+ win->mWindowType == WindowType::Dialog) {
+ break;
+ }
+ }
+
+ aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent)
+ }
+ return toplevelWnd;
+}
+
+void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) {
+ if (aIsActivate) {
+ sJustGotActivate = false;
+ }
+ sJustGotDeactivate = false;
+ mLastKillFocusWindow = nullptr;
+
+ HWND toplevelWnd = GetTopLevelForFocus(mWnd);
+
+ if (toplevelWnd) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd);
+ if (win && win->mWidgetListener) {
+ if (aIsActivate) {
+ win->mWidgetListener->WindowActivated();
+ } else {
+ win->mWidgetListener->WindowDeactivated();
+ }
+ }
+ }
+}
+
+HWND nsWindow::WindowAtMouse() {
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ return ::WindowFromPoint(mp);
+}
+
+bool nsWindow::IsTopLevelMouseExit(HWND aWnd) {
+ HWND mouseWnd = WindowAtMouse();
+
+ // WinUtils::GetTopLevelHWND() will return a HWND for the window frame
+ // (which includes the non-client area). If the mouse has moved into
+ // the non-client area, we should treat it as a top-level exit.
+ HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd);
+ if (mouseWnd == mouseTopLevel) return true;
+
+ return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel;
+}
+
+/**************************************************************
+ *
+ * SECTION: IPC
+ *
+ * IPC related helpers.
+ *
+ **************************************************************/
+
+// static
+bool nsWindow::IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult) {
+ switch (aMsg) {
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ case WM_ENABLE:
+ case WM_WINDOWPOSCHANGING:
+ case WM_WINDOWPOSCHANGED:
+ case WM_PARENTNOTIFY:
+ case WM_ACTIVATEAPP:
+ case WM_NCACTIVATE:
+ case WM_ACTIVATE:
+ case WM_CHILDACTIVATE:
+ case WM_IME_SETCONTEXT:
+ case WM_IME_NOTIFY:
+ case WM_SHOWWINDOW:
+ case WM_CANCELMODE:
+ case WM_MOUSEACTIVATE:
+ case WM_CONTEXTMENU:
+ aResult = 0;
+ return true;
+
+ case WM_SETTINGCHANGE:
+ case WM_SETCURSOR:
+ return false;
+ }
+
+#ifdef DEBUG
+ char szBuf[200];
+ sprintf(szBuf,
+ "An unhandled ISMEX_SEND message was received during spin loop! (%X)",
+ aMsg);
+ NS_WARNING(szBuf);
+#endif
+
+ return false;
+}
+
+void nsWindow::IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam) {
+ MOZ_ASSERT_IF(
+ msg != WM_GETOBJECT,
+ !mozilla::ipc::MessageChannel::IsPumpingMessages() ||
+ mozilla::ipc::SuppressedNeuteringRegion::IsNeuteringSuppressed());
+
+ // Modal UI being displayed in windowless plugins.
+ if (mozilla::ipc::MessageChannel::IsSpinLoopActive() &&
+ (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
+ LRESULT res;
+ if (IsAsyncResponseEvent(msg, res)) {
+ ReplyMessage(res);
+ }
+ return;
+ }
+
+ // Handle certain sync plugin events sent to the parent which
+ // trigger ipc calls that result in deadlocks.
+
+ DWORD dwResult = 0;
+ bool handled = false;
+
+ switch (msg) {
+ // Windowless flash sending WM_ACTIVATE events to the main window
+ // via calls to ShowWindow.
+ case WM_ACTIVATE:
+ if (lParam != 0 && LOWORD(wParam) == WA_ACTIVE &&
+ IsWindow((HWND)lParam)) {
+ // Check for Adobe Reader X sync activate message from their
+ // helper window and ignore. Fixes an annoying focus problem.
+ if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) ==
+ ISMEX_SEND) {
+ wchar_t szClass[10];
+ HWND focusWnd = (HWND)lParam;
+ if (IsWindowVisible(focusWnd) &&
+ GetClassNameW(focusWnd, szClass,
+ sizeof(szClass) / sizeof(char16_t)) &&
+ !wcscmp(szClass, L"Edit") &&
+ !WinUtils::IsOurProcessWindow(focusWnd)) {
+ break;
+ }
+ }
+ handled = true;
+ }
+ break;
+ // Plugins taking or losing focus triggering focus app messages.
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ // Windowed plugins that pass sys key events to defwndproc generate
+ // WM_SYSCOMMAND events to the main window.
+ case WM_SYSCOMMAND:
+ // Windowed plugins that fire context menu selection events to parent
+ // windows.
+ case WM_CONTEXTMENU:
+ // IME events fired as a result of synchronous focus changes
+ case WM_IME_SETCONTEXT:
+ handled = true;
+ break;
+ }
+
+ if (handled &&
+ (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) {
+ ReplyMessage(dwResult);
+ }
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Native events
+ **
+ ** Main Windows message handlers and OnXXX handlers for
+ ** Windows event handling.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: Wind proc.
+ *
+ * The main Windows event procedures and associated
+ * message processing methods.
+ *
+ **************************************************************/
+
+static bool DisplaySystemMenu(HWND hWnd, nsSizeMode sizeMode, bool isRtl,
+ int32_t x, int32_t y) {
+ HMENU hMenu = GetSystemMenu(hWnd, FALSE);
+ if (hMenu) {
+ MENUITEMINFO mii;
+ mii.cbSize = sizeof(MENUITEMINFO);
+ mii.fMask = MIIM_STATE;
+ mii.fType = 0;
+
+ // update the options
+ mii.fState = MF_ENABLED;
+ SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
+
+ mii.fState = MF_GRAYED;
+ switch (sizeMode) {
+ case nsSizeMode_Fullscreen:
+ // intentional fall through
+ case nsSizeMode_Maximized:
+ SetMenuItemInfo(hMenu, SC_SIZE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MOVE, FALSE, &mii);
+ SetMenuItemInfo(hMenu, SC_MAXIMIZE, FALSE, &mii);
+ break;
+ case nsSizeMode_Minimized:
+ SetMenuItemInfo(hMenu, SC_MINIMIZE, FALSE, &mii);
+ break;
+ case nsSizeMode_Normal:
+ SetMenuItemInfo(hMenu, SC_RESTORE, FALSE, &mii);
+ break;
+ case nsSizeMode_Invalid:
+ NS_ASSERTION(false, "Did the argument come from invalid IPC?");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected");
+ break;
+ }
+ LPARAM cmd = TrackPopupMenu(
+ hMenu,
+ (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN |
+ (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
+ x, y, 0, hWnd, nullptr);
+ if (cmd) {
+ PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0);
+ return true;
+ }
+ }
+ return false;
+}
+
+// The WndProc procedure for all nsWindows in this toolkit. This merely catches
+// SEH exceptions and passes the real work to WindowProcInternal. See bug 587406
+// and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx
+LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ mozilla::ipc::CancelCPOWs();
+
+ BackgroundHangMonitor().NotifyActivity();
+
+ return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg,
+ wParam, lParam);
+}
+
+LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam) {
+ if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) {
+ // This message was sent to the FAKETRACKPOINTSCROLLABLE.
+ if (msg == WM_HSCROLL) {
+ // Route WM_HSCROLL messages to the main window.
+ hWnd = ::GetParent(::GetParent(hWnd));
+ } else {
+ // Handle all other messages with its original window procedure.
+ WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam);
+ }
+ }
+
+ if (msg == MOZ_WM_TRACE) {
+ // This is a tracer event for measuring event loop latency.
+ // See WidgetTraceEvent.cpp for more details.
+ mozilla::SignalTracerThread();
+ return 0;
+ }
+
+ // Get the window which caused the event and ask it to process the message
+ nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd);
+ NS_ASSERTION(targetWindow, "nsWindow* is null!");
+ if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+
+ // Hold the window for the life of this method, in case it gets
+ // destroyed during processing, unless we're in the dtor already.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip;
+ if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow;
+
+ targetWindow->IPCWindowProcHandler(msg, wParam, lParam);
+
+ // Create this here so that we store the last rolled up popup until after
+ // the event has been processed.
+ nsAutoRollup autoRollup;
+
+ LRESULT popupHandlingResult;
+ if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult))
+ return popupHandlingResult;
+
+ // Call ProcessMessage
+ LRESULT retValue;
+ if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) {
+ return retValue;
+ }
+
+ LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg,
+ wParam, lParam);
+
+ return res;
+}
+
+const char16_t* GetQuitType() {
+ if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) {
+ DWORD cchCmdLine = 0;
+ HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr,
+ &cchCmdLine, nullptr);
+ if (rc == S_OK) {
+ return u"os-restart";
+ }
+ }
+ return nullptr;
+}
+
+bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam,
+ MSGResult& aResult) {
+ if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) {
+ return true;
+ }
+
+ if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) {
+ return true;
+ }
+
+ if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam,
+ aResult)) {
+ return true;
+ }
+
+ return false;
+}
+
+// The main windows message processing method. Wraps ProcessMessageInternal so
+// we can log aRetValue.
+bool nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue) {
+ // For some events we might change the parameter values, so log
+ // before and after we process them.
+ NativeEventLogger eventLogger("nsWindow", mWnd, msg, wParam, lParam);
+ bool result = ProcessMessageInternal(msg, wParam, lParam, aRetValue);
+ eventLogger.SetResult(*aRetValue, result);
+
+ return result;
+}
+
+// The main windows message processing method. Called by ProcessMessage.
+bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue) {
+ MSGResult msgResult(aRetValue);
+ if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
+ return (msgResult.mConsumed || !mWnd);
+ }
+
+ bool result = false; // call the default nsWindow proc
+ *aRetValue = 0;
+
+ // The DWM resize hack (see bug 1763981) causes us to process a number of
+ // messages, notably including some WM_WINDOWPOSCHANG{ING,ED} messages which
+ // would ordinarily result in a whole lot of internal state being updated.
+ //
+ // Since we're supposed to end in the same state we started in (and since the
+ // content shouldn't know about any of this nonsense), just discard any
+ // messages synchronously dispatched from within the hack.
+ if (MOZ_UNLIKELY(mIsPerformingDwmFlushHack)) {
+ return true;
+ }
+
+ // Glass hit testing w/custom transparent margins.
+ //
+ // FIXME(emilio): is this needed? We deal with titlebar buttons non-natively
+ // now.
+ LRESULT dwmHitResult;
+ if (mCustomNonClient &&
+ DwmDefWindowProc(mWnd, msg, wParam, lParam, &dwmHitResult)) {
+ *aRetValue = dwmHitResult;
+ return true;
+ }
+
+ // The preference whether to use a different keyboard layout for each
+ // window is cached, and updating it will not take effect until the
+ // next restart. We read the preference here and not upon WM_ACTIVATE to make
+ // sure that this behavior is consistent. Otherwise, if the user changed the
+ // preference before having ever lowered the window, the preference would take
+ // effect immediately.
+ static const bool sSwitchKeyboardLayout =
+ Preferences::GetBool("intl.keyboard.per_window_layout", false);
+ AppShutdownReason shutdownReason = AppShutdownReason::Unknown;
+
+ // (Large blocks of code should be broken out into OnEvent handlers.)
+ switch (msg) {
+ // WM_QUERYENDSESSION must be handled by all windows.
+ // Otherwise Windows thinks the window can just be killed at will.
+ case WM_QUERYENDSESSION: {
+ // Ask around if it's ok to quit.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsISupportsPRBool> cancelQuitWrapper =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+ cancelQuitWrapper->SetData(false);
+
+ const char16_t* quitType = GetQuitType();
+ obsServ->NotifyObservers(cancelQuitWrapper, "quit-application-requested",
+ quitType);
+
+ bool shouldCancelQuit;
+ cancelQuitWrapper->GetData(&shouldCancelQuit);
+ *aRetValue = !shouldCancelQuit;
+ result = true;
+ } break;
+
+ case MOZ_WM_STARTA11Y:
+#if defined(ACCESSIBILITY)
+ Unused << GetAccessible();
+ result = true;
+#else
+ result = false;
+#endif
+ break;
+
+ case WM_ENDSESSION: {
+ // For WM_ENDSESSION, wParam indicates whether we need to shutdown
+ // (TRUE) or not (FALSE).
+ if (!wParam) {
+ result = true;
+ break;
+ }
+ // According to WM_ENDSESSION lParam documentation:
+ // 0 -> OS shutdown or restart (no way to distinguish)
+ // ENDSESSION_LOGOFF -> User is logging off
+ // ENDSESSION_CLOSEAPP -> Application must shutdown
+ // ENDSESSION_CRITICAL -> Application is forced to shutdown
+ // The difference of the last two is not very clear.
+ if (lParam == 0) {
+ shutdownReason = AppShutdownReason::OSShutdown;
+ } else if (lParam & ENDSESSION_LOGOFF) {
+ shutdownReason = AppShutdownReason::OSSessionEnd;
+ } else if (lParam & (ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL)) {
+ shutdownReason = AppShutdownReason::OSForceClose;
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Received WM_ENDSESSION with unknown flags.");
+ shutdownReason = AppShutdownReason::OSForceClose;
+ }
+ }
+ [[fallthrough]];
+ case MOZ_WM_APP_QUIT: {
+ if (shutdownReason == AppShutdownReason::Unknown) {
+ // TODO: We do not expect that these days anybody sends us
+ // MOZ_WM_APP_QUIT, see bug 1827807.
+ shutdownReason = AppShutdownReason::WinUnexpectedMozQuit;
+ }
+ // Let's fake a shutdown sequence without actually closing windows etc.
+ // to avoid Windows killing us in the middle. A proper shutdown would
+ // require having a chance to pump some messages. Unfortunately
+ // Windows won't let us do that. Bug 212316.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ const char16_t* syncShutdown = u"syncShutdown";
+ const char16_t* quitType = GetQuitType();
+
+ AppShutdown::Init(AppShutdownMode::Normal, 0, shutdownReason);
+
+ obsServ->NotifyObservers(nullptr, "quit-application-granted",
+ syncShutdown);
+ obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
+
+ AppShutdown::OnShutdownConfirmed();
+
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownConfirmed,
+ quitType);
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown,
+ nullptr);
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown,
+ nullptr);
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown, nullptr);
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM, nullptr);
+ AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry,
+ nullptr);
+
+ AppShutdown::DoImmediateExit();
+ MOZ_ASSERT_UNREACHABLE("Our process was supposed to exit.");
+ } break;
+
+ case WM_SYSCOLORCHANGE:
+ // No need to invalidate layout for system color changes, but we need to
+ // invalidate style.
+ NotifyThemeChanged(widget::ThemeChangeKind::Style);
+ break;
+
+ case WM_THEMECHANGED: {
+ // Update non-client margin offsets
+ UpdateNonClientMargins();
+ nsUXThemeData::UpdateNativeThemeInfo();
+
+ // We assume pretty much everything could've changed here.
+ NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout);
+
+ UpdateDarkModeToolbar();
+
+ // Invalidate the window so that the repaint will
+ // pick up the new theme.
+ Invalidate(true, true, true);
+ } break;
+
+ case WM_WTSSESSION_CHANGE: {
+ switch (wParam) {
+ case WTS_CONSOLE_CONNECT:
+ case WTS_REMOTE_CONNECT:
+ case WTS_SESSION_UNLOCK:
+ // When a session becomes visible, we should invalidate.
+ Invalidate(true, true, true);
+ break;
+ default:
+ break;
+ }
+ } break;
+
+ case WM_FONTCHANGE: {
+ // We only handle this message for the hidden window,
+ // as we only need to update the (global) font list once
+ // for any given change, not once per window!
+ if (mWindowType != WindowType::Invisible) {
+ break;
+ }
+
+ // update the global font list
+ gfxPlatform::GetPlatform()->UpdateFontList();
+ } break;
+
+ case WM_SETTINGCHANGE: {
+ if (wParam == SPI_SETCLIENTAREAANIMATION ||
+ wParam == SPI_SETKEYBOARDDELAY || wParam == SPI_SETMOUSEVANISH) {
+ // These need to update LookAndFeel cached values.
+ // They affect reduced motion settings / caret blink count / show
+ // pointer while typing, so no need to invalidate style / layout.
+ NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
+ break;
+ }
+ if (wParam == SPI_SETFONTSMOOTHING ||
+ wParam == SPI_SETFONTSMOOTHINGTYPE) {
+ gfxDWriteFont::UpdateSystemTextVars();
+ break;
+ }
+ if (wParam == SPI_SETWORKAREA) {
+ // NB: We also refresh screens on WM_DISPLAYCHANGE but the rcWork
+ // values are sometimes wrong at that point. This message then
+ // arrives soon afterward, when we can get the right rcWork values.
+ ScreenHelperWin::RefreshScreens();
+ break;
+ }
+ if (auto lParamString = reinterpret_cast<const wchar_t*>(lParam)) {
+ if (!wcscmp(lParamString, L"ImmersiveColorSet")) {
+ // This affects system colors (-moz-win-accentcolor), so gotta pass
+ // the style flag.
+ NotifyThemeChanged(widget::ThemeChangeKind::Style);
+ break;
+ }
+
+ // UserInteractionMode, ConvertibleSlateMode, SystemDockMode may cause
+ // @media(pointer) queries to change, which layout needs to know about
+ //
+ // (WM_SETTINGCHANGE will be sent to all top-level windows, so we
+ // only respond to the hidden top-level window to avoid hammering
+ // layout with a bunch of NotifyThemeChanged() calls)
+ //
+ if (mWindowType == WindowType::Invisible) {
+ if (!wcscmp(lParamString, L"UserInteractionMode") ||
+ !wcscmp(lParamString, L"ConvertibleSlateMode") ||
+ !wcscmp(lParamString, L"SystemDockMode")) {
+ NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
+ WindowsUIUtils::UpdateInTabletMode();
+ }
+ }
+ }
+ } break;
+
+ case WM_DEVICECHANGE: {
+ if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
+ DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
+ // Check dbch_devicetype explicitly since we will get other device types
+ // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify
+ // DBT_DEVTYP_DEVICEINTERFACE in the filter for
+ // RegisterDeviceNotification.
+ if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
+ // This can only change media queries (any-hover/any-pointer).
+ NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly);
+ }
+ }
+ } break;
+
+ case WM_NCCALCSIZE: {
+ // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and
+ // will need to be kept in sync.
+ if (mCustomNonClient) {
+ // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
+ // the proposed window rectangle for our window. During our
+ // processing of the `WM_NCCALCSIZE` message, we are expected to
+ // modify the `RECT` that `lParam` points to, so that its value upon
+ // our return is the new client area. We must return 0 if `wParam`
+ // is `FALSE`.
+ //
+ // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
+ // struct. This struct contains an array of 3 `RECT`s, the first of
+ // which has the exact same meaning as the `RECT` that is pointed to
+ // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
+ // conjunction with our return value, can
+ // be used to specify portions of the source and destination window
+ // rectangles that are valid and should be preserved. We opt not to
+ // implement an elaborate client-area preservation technique, and
+ // simply return 0, which means "preserve the entire old client area
+ // and align it with the upper-left corner of our new client area".
+ RECT* clientRect =
+ wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
+ : (reinterpret_cast<RECT*>(lParam));
+ auto margin = NonClientSizeMargin();
+ clientRect->top += margin.top;
+ clientRect->left += margin.left;
+ clientRect->right -= margin.right;
+ clientRect->bottom -= margin.bottom;
+ // Make client rect's width and height more than 0 to
+ // avoid problems of webrender and angle.
+ clientRect->right = std::max(clientRect->right, clientRect->left + 1);
+ clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1);
+
+ result = true;
+ *aRetValue = 0;
+ }
+ break;
+ }
+
+ case WM_NCHITTEST: {
+ if (mInputRegion.mFullyTransparent) {
+ // Treat this window as transparent.
+ *aRetValue = HTTRANSPARENT;
+ result = true;
+ break;
+ }
+
+ if (mInputRegion.mMargin) {
+ const LayoutDeviceIntPoint screenPoint(GET_X_LPARAM(lParam),
+ GET_Y_LPARAM(lParam));
+ LayoutDeviceIntRect screenRect = GetScreenBounds();
+ screenRect.Deflate(mInputRegion.mMargin);
+ if (!screenRect.Contains(screenPoint)) {
+ *aRetValue = HTTRANSPARENT;
+ result = true;
+ break;
+ }
+ }
+
+ /*
+ * If an nc client area margin has been moved, we are responsible
+ * for calculating where the resize margins are and returning the
+ * appropriate set of hit test constants. DwmDefWindowProc (above)
+ * will handle hit testing on it's command buttons if we are on a
+ * composited desktop.
+ */
+
+ if (!mCustomNonClient) {
+ break;
+ }
+
+ *aRetValue =
+ ClientMarginHitTestPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ result = true;
+ break;
+ }
+
+ case WM_SETTEXT:
+ /*
+ * WM_SETTEXT paints the titlebar area. Avoid this if we have a
+ * custom titlebar we paint ourselves, or if we're the ones
+ * sending the message with an updated title
+ */
+
+ if (mSendingSetText || !mCustomNonClient || mNonClientMargins.top == -1)
+ break;
+
+ {
+ // From msdn, the way around this is to disable the visible state
+ // temporarily. We need the text to be set but we don't want the
+ // redraw to occur. However, we need to make sure that we don't
+ // do this at the same time that a Present is happening.
+ //
+ // To do this we take mPresentLock in nsWindow::PreRender and
+ // if that lock is taken we wait before doing WM_SETTEXT
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->EnterPresentLock();
+ }
+ DWORD style = GetWindowLong(mWnd, GWL_STYLE);
+ SetWindowLong(mWnd, GWL_STYLE, style & ~WS_VISIBLE);
+ *aRetValue =
+ CallWindowProcW(GetPrevWindowProc(), mWnd, msg, wParam, lParam);
+ SetWindowLong(mWnd, GWL_STYLE, style);
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->LeavePresentLock();
+ }
+
+ return true;
+ }
+
+ case WM_NCACTIVATE: {
+ /*
+ * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting
+ * through WM_NCPAINT via InvalidateNonClientRegion.
+ */
+ UpdateGetWindowInfoCaptionStatus(FALSE != wParam);
+
+ if (!mCustomNonClient) {
+ break;
+ }
+
+ // There is a case that rendered result is not kept. Bug 1237617
+ if (wParam == TRUE && !gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
+ NS_DispatchToMainThread(NewRunnableMethod(
+ "nsWindow::ForcePresent", this, &nsWindow::ForcePresent));
+ }
+
+ // let the dwm handle nc painting on glass
+ // Never allow native painting if we are on fullscreen
+ if (mFrameState->GetSizeMode() != nsSizeMode_Fullscreen) break;
+
+ if (wParam == TRUE) {
+ // going active
+ *aRetValue = FALSE; // ignored
+ result = true;
+ // invalidate to trigger a paint
+ InvalidateNonClientRegion();
+ break;
+ } else {
+ // going inactive
+ *aRetValue = TRUE; // go ahead and deactive
+ result = true;
+ // invalidate to trigger a paint
+ InvalidateNonClientRegion();
+ break;
+ }
+ }
+
+ case WM_NCPAINT: {
+ /*
+ * ClearType changes often don't send a WM_SETTINGCHANGE message. But they
+ * do seem to always send a WM_NCPAINT message, so let's update on that.
+ */
+ gfxDWriteFont::UpdateSystemTextVars();
+ } break;
+
+ case WM_POWERBROADCAST:
+ switch (wParam) {
+ case PBT_APMSUSPEND:
+ PostSleepWakeNotification(true);
+ break;
+ case PBT_APMRESUMEAUTOMATIC:
+ case PBT_APMRESUMECRITICAL:
+ case PBT_APMRESUMESUSPEND:
+ PostSleepWakeNotification(false);
+ break;
+ }
+ break;
+
+ case WM_CLOSE: // close request
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+ result = true; // abort window closure
+ break;
+
+ case WM_DESTROY:
+ // clean up.
+ DestroyLayerManager();
+ OnDestroy();
+ result = true;
+ break;
+
+ case WM_PAINT:
+ *aRetValue = (int)OnPaint(0);
+ result = true;
+ break;
+
+ case WM_HOTKEY:
+ result = OnHotKey(wParam, lParam);
+ break;
+
+ case WM_SYSCHAR:
+ case WM_CHAR: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = ProcessCharMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ case WM_SYSKEYUP:
+ case WM_KEYUP: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ nativeMsg.time = ::GetMessageTime();
+ result = ProcessKeyUpMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ case WM_SYSKEYDOWN:
+ case WM_KEYDOWN: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = ProcessKeyDownMessage(nativeMsg, nullptr);
+ DispatchPendingEvents();
+ } break;
+
+ // Say we've dealt with erasing the background. (This is actually handled in
+ // WM_PAINT, where necessary.)
+ case WM_ERASEBKGND: {
+ *aRetValue = 1;
+ result = true;
+ } break;
+
+ case WM_MOUSEMOVE: {
+ LPARAM lParamScreen = lParamToScreen(lParam);
+ mSimulatedClientArea = IsSimulatedClientArea(GET_X_LPARAM(lParamScreen),
+ GET_Y_LPARAM(lParamScreen));
+
+ if (!mMousePresent && !sIsInMouseCapture) {
+ // First MOUSEMOVE over the client area. Ask for MOUSELEAVE
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ }
+ mMousePresent = true;
+
+ // Suppress dispatch of pending events
+ // when mouse moves are generated by widget
+ // creation instead of user input.
+ POINT mp;
+ mp.x = GET_X_LPARAM(lParamScreen);
+ mp.y = GET_Y_LPARAM(lParamScreen);
+ bool userMovedMouse = false;
+ if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
+ userMovedMouse = true;
+ }
+
+ if (userMovedMouse) {
+ result = DispatchMouseEvent(
+ eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
+ MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ }
+ } break;
+
+ case WM_NCMOUSEMOVE: {
+ LPARAM lParamClient = lParamToClient(lParam);
+ if (IsSimulatedClientArea(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) {
+ if (!sIsInMouseCapture) {
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ }
+ // If we noticed the mouse moving in our draggable region, forward the
+ // message as a normal WM_MOUSEMOVE.
+ SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient);
+ } else {
+ // We've transitioned from a draggable area to somewhere else within
+ // the non-client area - perhaps one of the edges of the window for
+ // resizing.
+ mSimulatedClientArea = false;
+ }
+
+ if (mMousePresent && !sIsInMouseCapture && !mSimulatedClientArea) {
+ SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
+ }
+ } break;
+
+ case WM_LBUTTONDOWN: {
+ result =
+ DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ } break;
+
+ case WM_LBUTTONUP: {
+ result =
+ DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ } break;
+
+ case WM_NCMOUSELEAVE: {
+ mSimulatedClientArea = false;
+
+ if (EventIsInsideWindow(this)) {
+ // If we're handling WM_NCMOUSELEAVE and the mouse is still over the
+ // window, then by process of elimination, the mouse has moved from the
+ // non-client to client area, so no need to fall-through to the
+ // WM_MOUSELEAVE handler. We also need to re-register for the
+ // WM_MOUSELEAVE message, since according to the documentation at [1],
+ // all tracking requested via TrackMouseEvent is cleared once
+ // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires.
+ // [1]:
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent
+ TRACKMOUSEEVENT mTrack;
+ mTrack.cbSize = sizeof(TRACKMOUSEEVENT);
+ mTrack.dwFlags = TME_LEAVE;
+ mTrack.dwHoverTime = 0;
+ mTrack.hwndTrack = mWnd;
+ TrackMouseEvent(&mTrack);
+ break;
+ }
+ // We've transitioned from non-client to outside of the window, so
+ // fall-through to the WM_MOUSELEAVE handler.
+ [[fallthrough]];
+ }
+ case WM_MOUSELEAVE: {
+ if (!mMousePresent) break;
+ if (mSimulatedClientArea) break;
+ mMousePresent = false;
+
+ // Check if the mouse is over the fullscreen transition window, if so
+ // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
+ // transition window disappears will not be ignored, even if the mouse
+ // hasn't moved.
+ if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
+ sLastMouseMovePoint = {0};
+ }
+
+ // We need to check mouse button states and put them in for
+ // wParam.
+ WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) |
+ (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) |
+ (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0);
+ // Synthesize an event position because we don't get one from
+ // WM_MOUSELEAVE.
+ LPARAM pos = lParamToClient(::GetMessagePos());
+ DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ } break;
+
+ case WM_CONTEXTMENU: {
+ // If the context menu is brought up by a touch long-press, then
+ // the APZ code is responsible for dealing with this, so we don't
+ // need to do anything.
+ if (mTouchWindow &&
+ MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
+ result = true;
+ break;
+ }
+
+ // If this WM_CONTEXTMENU is triggered by a mouse's secondary button up
+ // event in overscroll gutter, we shouldn't open context menu.
+ if (MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
+ mNeedsToPreventContextMenu) {
+ result = true;
+ break;
+ }
+
+ // if the context menu is brought up from the keyboard, |lParam|
+ // will be -1.
+ LPARAM pos;
+ bool contextMenukey = false;
+ if (lParam == -1) {
+ contextMenukey = true;
+ pos = lParamToClient(GetMessagePos());
+ } else {
+ pos = lParamToClient(lParam);
+ }
+
+ result = DispatchMouseEvent(
+ eContextMenu, wParam, pos, contextMenukey,
+ contextMenukey ? MouseButton::ePrimary : MouseButton::eSecondary,
+ MOUSE_INPUT_SOURCE());
+ if (lParam != -1 && !result && mCustomNonClient &&
+ mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) {
+ // Blank area hit, throw up the system menu.
+ DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
+ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ result = true;
+ }
+ } break;
+
+ case WM_POINTERLEAVE:
+ case WM_POINTERDOWN:
+ case WM_POINTERUP:
+ case WM_POINTERUPDATE:
+ result = OnPointerEvents(msg, wParam, lParam);
+ if (result) {
+ DispatchPendingEvents();
+ }
+ break;
+
+ case DM_POINTERHITTEST:
+ if (mDmOwner) {
+ UINT contactId = GET_POINTERID_WPARAM(wParam);
+ POINTER_INPUT_TYPE pointerType;
+ if (mPointerEvents.GetPointerType(contactId, &pointerType) &&
+ pointerType == PT_TOUCHPAD) {
+ mDmOwner->SetContact(contactId);
+ }
+ }
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDBLCLK:
+ result =
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDOWN:
+ result =
+ DispatchMouseEvent(eMouseDown, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONUP:
+ result =
+ DispatchMouseEvent(eMouseUp, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDBLCLK:
+ result =
+ DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDOWN:
+ result =
+ DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONUP:
+ result =
+ DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::eSecondary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, MouseButton::eSecondary,
+ MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ // Windows doesn't provide to customize the behavior of 4th nor 5th button
+ // of mouse. If 5-button mouse works with standard mouse deriver of
+ // Windows, users cannot disable 4th button (browser back) nor 5th button
+ // (browser forward). We should allow to do it with our prefs since we can
+ // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP
+ // messages are not sent to DefWindowProc.
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONUP:
+ case WM_NCXBUTTONDOWN:
+ case WM_NCXBUTTONUP:
+ *aRetValue = TRUE;
+ switch (GET_XBUTTON_WPARAM(wParam)) {
+ case XBUTTON1:
+ result = !Preferences::GetBool("mousebutton.4th.enabled", true);
+ break;
+ case XBUTTON2:
+ result = !Preferences::GetBool("mousebutton.5th.enabled", true);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case WM_SIZING: {
+ if (mAspectRatio > 0) {
+ LPRECT rect = (LPRECT)lParam;
+ int32_t newWidth, newHeight;
+
+ // The following conditions and switch statement borrow heavily from the
+ // Chromium source code from
+ // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45
+ if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT ||
+ wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) {
+ newWidth = rect->right - rect->left;
+ newHeight = newWidth / mAspectRatio;
+ if (newHeight < mSizeConstraints.mMinSize.height) {
+ newHeight = mSizeConstraints.mMinSize.height;
+ newWidth = newHeight * mAspectRatio;
+ } else if (newHeight > mSizeConstraints.mMaxSize.height) {
+ newHeight = mSizeConstraints.mMaxSize.height;
+ newWidth = newHeight * mAspectRatio;
+ }
+ } else {
+ newHeight = rect->bottom - rect->top;
+ newWidth = newHeight * mAspectRatio;
+ if (newWidth < mSizeConstraints.mMinSize.width) {
+ newWidth = mSizeConstraints.mMinSize.width;
+ newHeight = newWidth / mAspectRatio;
+ } else if (newWidth > mSizeConstraints.mMaxSize.width) {
+ newWidth = mSizeConstraints.mMaxSize.width;
+ newHeight = newWidth / mAspectRatio;
+ }
+ }
+
+ switch (wParam) {
+ case WMSZ_RIGHT:
+ case WMSZ_BOTTOM:
+ rect->right = newWidth + rect->left;
+ rect->bottom = rect->top + newHeight;
+ break;
+ case WMSZ_TOP:
+ rect->right = newWidth + rect->left;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_LEFT:
+ case WMSZ_TOPLEFT:
+ rect->left = rect->right - newWidth;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_TOPRIGHT:
+ rect->right = rect->left + newWidth;
+ rect->top = rect->bottom - newHeight;
+ break;
+ case WMSZ_BOTTOMLEFT:
+ rect->left = rect->right - newWidth;
+ rect->bottom = rect->top + newHeight;
+ break;
+ case WMSZ_BOTTOMRIGHT:
+ rect->right = rect->left + newWidth;
+ rect->bottom = rect->top + newHeight;
+ break;
+ }
+ }
+
+ // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live
+ // resize or move event. Instead we wait for first VM_SIZING message
+ // within a ENTERSIZEMOVE to consider this a live resize event.
+ if (mResizeState == IN_SIZEMOVE) {
+ mResizeState = RESIZING;
+ NotifyLiveResizeStarted();
+ }
+ break;
+ }
+
+ case WM_MOVING:
+ FinishLiveResizing(MOVING);
+ if (WinUtils::IsPerMonitorDPIAware()) {
+ // Sometimes, we appear to miss a WM_DPICHANGED message while moving
+ // a window around. Therefore, call ChangedDPI and ResetLayout here
+ // if it appears that the window's scaling is not what we expect.
+ // This causes the prescontext and appshell window management code to
+ // check the appUnitsPerDevPixel value and current widget size, and
+ // refresh them if necessary. If nothing has changed, these calls will
+ // return without actually triggering any extra reflow or painting.
+ if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) {
+ ChangedDPI();
+ ResetLayout();
+ if (mWidgetListener) {
+ mWidgetListener->UIResolutionChanged();
+ }
+ }
+ }
+ break;
+
+ case WM_ENTERSIZEMOVE: {
+ if (mResizeState == NOT_RESIZING) {
+ mResizeState = IN_SIZEMOVE;
+ }
+ break;
+ }
+
+ case WM_EXITSIZEMOVE: {
+ FinishLiveResizing(NOT_RESIZING);
+
+ if (!sIsInMouseCapture) {
+ NotifySizeMoveDone();
+ }
+
+ // Windows spins a separate hidden event loop when moving a window so we
+ // don't hear mouse events during this time and WM_EXITSIZEMOVE is fired
+ // when the hidden event loop exits. We set mDraggingWindowWithMouse to
+ // true in WM_NCLBUTTONDOWN when we started moving the window with the
+ // mouse so we know that if mDraggingWindowWithMouse is true, we can send
+ // a mouse up event.
+ if (mDraggingWindowWithMouse) {
+ mDraggingWindowWithMouse = false;
+ result = DispatchMouseEvent(
+ eMouseUp, wParam, lParam, false, MouseButton::ePrimary,
+ MOUSE_INPUT_SOURCE(),
+ mPointerEvents.GetCachedPointerInfo(msg, wParam));
+ }
+
+ break;
+ }
+
+ case WM_DISPLAYCHANGE: {
+ ScreenHelperWin::RefreshScreens();
+ if (mWidgetListener) {
+ mWidgetListener->UIResolutionChanged();
+ }
+ break;
+ }
+
+ case WM_NCLBUTTONDBLCLK:
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false,
+ MouseButton::ePrimary, MOUSE_INPUT_SOURCE());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCLBUTTONDOWN: {
+ // Dispatch a custom event when this happens in the draggable region, so
+ // that non-popup-based panels can react to it. This doesn't send an
+ // actual mousedown event because that would break dragging or interfere
+ // with other mousedown handling in the caption area.
+ if (ClientMarginHitTestPoint(GET_X_LPARAM(lParam),
+ GET_Y_LPARAM(lParam)) == HTCAPTION) {
+ DispatchCustomEvent(u"draggableregionleftmousedown"_ns);
+ mDraggingWindowWithMouse = true;
+ }
+
+ if (IsWindowButton(wParam) && mCustomNonClient) {
+ DispatchMouseEvent(eMouseDown, wParamFromGlobalMouseState(),
+ lParamToClient(lParam), false, MouseButton::ePrimary,
+ MOUSE_INPUT_SOURCE(), nullptr, true);
+ DispatchPendingEvents();
+ result = true;
+ }
+ break;
+ }
+
+ case WM_APPCOMMAND: {
+ MSG nativeMsg = WinUtils::InitMSG(msg, wParam, lParam, mWnd);
+ result = HandleAppCommandMsg(nativeMsg, aRetValue);
+ break;
+ }
+
+ // The WM_ACTIVATE event is fired when a window is raised or lowered,
+ // and the loword of wParam specifies which. But we don't want to tell
+ // the focus system about this until the WM_SETFOCUS or WM_KILLFOCUS
+ // events are fired. Instead, set either the sJustGotActivate or
+ // gJustGotDeactivate flags and activate/deactivate once the focus
+ // events arrive.
+ case WM_ACTIVATE: {
+ int32_t fActive = LOWORD(wParam);
+ if (mWidgetListener) {
+ if (WA_INACTIVE == fActive) {
+ // when minimizing a window, the deactivation and focus events will
+ // be fired in the reverse order. Instead, just deactivate right away.
+ // This can also happen when a modal system dialog is opened, so check
+ // if the last window to receive the WM_KILLFOCUS message was this one
+ // or a child of this one.
+ if (HIWORD(wParam) ||
+ (mLastKillFocusWindow &&
+ (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) {
+ DispatchFocusToTopLevelWindow(false);
+ } else {
+ sJustGotDeactivate = true;
+ }
+ if (mIsTopWidgetWindow) {
+ mLastKeyboardLayout = KeyboardLayout::GetLayout();
+ }
+ } else {
+ StopFlashing();
+
+ sJustGotActivate = true;
+ WidgetMouseEvent event(true, eMouseActivate, this,
+ WidgetMouseEvent::eReal);
+ InitEvent(event);
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+ DispatchInputEvent(&event);
+ if (sSwitchKeyboardLayout && mLastKeyboardLayout)
+ ActivateKeyboardLayout(mLastKeyboardLayout, 0);
+
+#ifdef ACCESSIBILITY
+ a11y::LazyInstantiator::ResetUiaDetectionCache();
+#endif
+ }
+ }
+ } break;
+
+ case WM_ACTIVATEAPP: {
+ // Bug 1851991: Sometimes this can be called before gfxPlatform::Init
+ // when a window is created very early. In that case we just forego
+ // setting this and accept the GPU process might briefly run at a lower
+ // priority.
+ if (GPUProcessManager::Get()) {
+ GPUProcessManager::Get()->SetAppInForeground(wParam);
+ }
+ } break;
+
+ case WM_MOUSEACTIVATE:
+ // A popup with a parent owner should not be activated when clicked but
+ // should still allow the mouse event to be fired, so the return value
+ // is set to MA_NOACTIVATE. But if the owner isn't the frontmost window,
+ // just use default processing so that the window is activated.
+ if (IsPopup() && IsOwnerForegroundWindow()) {
+ *aRetValue = MA_NOACTIVATE;
+ result = true;
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGING: {
+ LPWINDOWPOS info = (LPWINDOWPOS)lParam;
+ OnWindowPosChanging(info);
+ result = true;
+ } break;
+
+ // Workaround for race condition in explorer.exe.
+ case MOZ_WM_FULLSCREEN_STATE_UPDATE: {
+ TaskbarConcealer::OnAsyncStateUpdateRequest(mWnd);
+ result = true;
+ } break;
+
+ case WM_GETMINMAXINFO: {
+ MINMAXINFO* mmi = (MINMAXINFO*)lParam;
+ // Set the constraints. The minimum size should also be constrained to the
+ // default window maximum size so that it fits on screen.
+ mmi->ptMinTrackSize.x =
+ std::min((int32_t)mmi->ptMaxTrackSize.x,
+ std::max((int32_t)mmi->ptMinTrackSize.x,
+ mSizeConstraints.mMinSize.width));
+ mmi->ptMinTrackSize.y =
+ std::min((int32_t)mmi->ptMaxTrackSize.y,
+ std::max((int32_t)mmi->ptMinTrackSize.y,
+ mSizeConstraints.mMinSize.height));
+ mmi->ptMaxTrackSize.x = std::min((int32_t)mmi->ptMaxTrackSize.x,
+ mSizeConstraints.mMaxSize.width);
+ mmi->ptMaxTrackSize.y = std::min((int32_t)mmi->ptMaxTrackSize.y,
+ mSizeConstraints.mMaxSize.height);
+ } break;
+
+ case WM_SETFOCUS: {
+ WndProcUrgentInvocation::Marker _marker;
+
+ // If previous focused window isn't ours, it must have received the
+ // redirected message. So, we should forget it.
+ if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
+ RedirectedKeyDownMessageManager::Forget();
+ }
+ if (sJustGotActivate) {
+ DispatchFocusToTopLevelWindow(true);
+ }
+ TaskbarConcealer::OnFocusAcquired(this);
+ } break;
+
+ case WM_KILLFOCUS:
+ if (sJustGotDeactivate) {
+ DispatchFocusToTopLevelWindow(false);
+ } else {
+ mLastKillFocusWindow = mWnd;
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED: {
+ WINDOWPOS* wp = (LPWINDOWPOS)lParam;
+ OnWindowPosChanged(wp);
+ TaskbarConcealer::OnWindowPosChanged(this);
+ result = true;
+ } break;
+
+ case WM_INPUTLANGCHANGEREQUEST:
+ *aRetValue = TRUE;
+ result = false;
+ break;
+
+ case WM_INPUTLANGCHANGE:
+ KeyboardLayout::GetInstance()->OnLayoutChange(
+ reinterpret_cast<HKL>(lParam));
+ nsBidiKeyboard::OnLayoutChange();
+ result = false; // always pass to child window
+ break;
+
+ case WM_DESTROYCLIPBOARD: {
+ nsIClipboard* clipboard;
+ nsresult rv = CallGetService(kCClipboardCID, &clipboard);
+ if (NS_SUCCEEDED(rv)) {
+ clipboard->EmptyClipboard(nsIClipboard::kGlobalClipboard);
+ NS_RELEASE(clipboard);
+ }
+ } break;
+
+#ifdef ACCESSIBILITY
+ case WM_GETOBJECT: {
+ *aRetValue = 0;
+ // Do explicit casting to make it working on 64bit systems (see bug 649236
+ // for details).
+ int32_t objId = static_cast<DWORD>(lParam);
+ if (objId == OBJID_CLIENT) { // oleacc.dll will be loaded dynamically
+ RefPtr<IAccessible> root(
+ a11y::LazyInstantiator::GetRootAccessible(mWnd));
+ if (root) {
+ *aRetValue = LresultFromObject(IID_IAccessible, wParam, root);
+ a11y::LazyInstantiator::EnableBlindAggregation(mWnd);
+ result = true;
+ }
+ }
+ } break;
+#endif
+
+ case WM_SYSCOMMAND: {
+ WPARAM const filteredWParam = (wParam & 0xFFF0);
+
+ // SC_CLOSE may trigger a synchronous confirmation prompt. If we're in the
+ // middle of something important, put off responding to it.
+ if (filteredWParam == SC_CLOSE && WndProcUrgentInvocation::IsActive()) {
+ ::PostMessageW(mWnd, msg, wParam, lParam);
+ result = true;
+ break;
+ }
+
+ if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
+ filteredWParam == SC_RESTORE &&
+ GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
+ mFrameState->EnsureFullscreenMode(false);
+ result = true;
+ }
+
+ // Handle the system menu manually when we're in full screen mode
+ // so we can set the appropriate options.
+ if (filteredWParam == SC_KEYMENU && lParam == VK_SPACE &&
+ mFrameState->GetSizeMode() == nsSizeMode_Fullscreen) {
+ DisplaySystemMenu(mWnd, mFrameState->GetSizeMode(), mIsRTL,
+ MOZ_SYSCONTEXT_X_POS, MOZ_SYSCONTEXT_Y_POS);
+ result = true;
+ }
+ } break;
+
+ case WM_DPICHANGED: {
+ LPRECT rect = (LPRECT)lParam;
+ OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
+ rect->bottom - rect->top);
+ break;
+ }
+
+ /* Gesture support events */
+ case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
+ // According to MS samples, this must be handled to enable
+ // rotational support in multi-touch drivers.
+ result = true;
+ *aRetValue = TABLET_ROTATE_GESTURE_ENABLE;
+ break;
+
+ case WM_TOUCH:
+ result = OnTouch(wParam, lParam);
+ if (result) {
+ *aRetValue = 0;
+ }
+ break;
+
+ case WM_GESTURE:
+ result = OnGesture(wParam, lParam);
+ break;
+
+ case WM_GESTURENOTIFY: {
+ if (mWindowType != WindowType::Invisible) {
+ // A GestureNotify event is dispatched to decide which single-finger
+ // panning direction should be active (including none) and if pan
+ // feedback should be displayed. Java and plugin windows can make their
+ // own calls.
+
+ GESTURENOTIFYSTRUCT* gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
+ nsPointWin touchPoint;
+ touchPoint = gestureinfo->ptsLocation;
+ touchPoint.ScreenToClient(mWnd);
+ WidgetGestureNotifyEvent gestureNotifyEvent(true, eGestureNotify, this);
+ gestureNotifyEvent.mRefPoint =
+ LayoutDeviceIntPoint::FromUnknownPoint(touchPoint);
+ nsEventStatus status;
+ DispatchEvent(&gestureNotifyEvent, status);
+ mDisplayPanFeedback = gestureNotifyEvent.mDisplayPanFeedback;
+ if (!mTouchWindow)
+ mGesture.SetWinGestureSupport(mWnd, gestureNotifyEvent.mPanDirection);
+ }
+ result = false; // should always bubble to DefWindowProc
+ } break;
+
+ case WM_CLEAR: {
+ WidgetContentCommandEvent command(true, eContentCommandDelete, this);
+ DispatchWindowEvent(command);
+ result = true;
+ } break;
+
+ case WM_CUT: {
+ WidgetContentCommandEvent command(true, eContentCommandCut, this);
+ DispatchWindowEvent(command);
+ result = true;
+ } break;
+
+ case WM_COPY: {
+ WidgetContentCommandEvent command(true, eContentCommandCopy, this);
+ DispatchWindowEvent(command);
+ result = true;
+ } break;
+
+ case WM_PASTE: {
+ WidgetContentCommandEvent command(true, eContentCommandPaste, this);
+ DispatchWindowEvent(command);
+ result = true;
+ } break;
+
+ case EM_UNDO: {
+ WidgetContentCommandEvent command(true, eContentCommandUndo, this);
+ DispatchWindowEvent(command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_REDO: {
+ WidgetContentCommandEvent command(true, eContentCommandRedo, this);
+ DispatchWindowEvent(command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_CANPASTE: {
+ // Support EM_CANPASTE message only when wParam isn't specified or
+ // is plain text format.
+ if (wParam == 0 || wParam == CF_TEXT || wParam == CF_UNICODETEXT) {
+ WidgetContentCommandEvent command(true, eContentCommandPaste, this,
+ true);
+ DispatchWindowEvent(command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ }
+ } break;
+
+ case EM_CANUNDO: {
+ WidgetContentCommandEvent command(true, eContentCommandUndo, this, true);
+ DispatchWindowEvent(command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case EM_CANREDO: {
+ WidgetContentCommandEvent command(true, eContentCommandRedo, this, true);
+ DispatchWindowEvent(command);
+ *aRetValue = (LRESULT)(command.mSucceeded && command.mIsEnabled);
+ result = true;
+ } break;
+
+ case MOZ_WM_SKEWFIX: {
+ TimeStamp skewStamp;
+ if (CurrentWindowsTimeGetter::GetAndClearBackwardsSkewStamp(wParam,
+ &skewStamp)) {
+ TimeConverter().CompensateForBackwardsSkew(::GetMessageTime(),
+ skewStamp);
+ }
+ } break;
+
+ default: {
+ if (msg == nsAppShell::GetTaskbarButtonCreatedMessage()) {
+ SetHasTaskbarIconBeenCreated();
+ }
+ } break;
+ }
+
+ //*aRetValue = result;
+ if (mWnd) {
+ return result;
+ } else {
+ // Events which caused mWnd destruction and aren't consumed
+ // will crash during the Windows default processing.
+ return true;
+ }
+}
+
+void nsWindow::FinishLiveResizing(ResizeState aNewState) {
+ if (mResizeState == RESIZING) {
+ NotifyLiveResizeStopped();
+ }
+ mResizeState = aNewState;
+ ForcePresent();
+}
+
+/**************************************************************
+ *
+ * SECTION: Event processing helpers
+ *
+ * Special processing for certain event types and
+ * synthesized events.
+ *
+ **************************************************************/
+
+LayoutDeviceIntMargin nsWindow::NonClientSizeMargin(
+ const LayoutDeviceIntMargin& aNonClientOffset) const {
+ return LayoutDeviceIntMargin(mCaptionHeight - aNonClientOffset.top,
+ mHorResizeMargin - aNonClientOffset.right,
+ mVertResizeMargin - aNonClientOffset.bottom,
+ mHorResizeMargin - aNonClientOffset.left);
+}
+
+int32_t nsWindow::ClientMarginHitTestPoint(int32_t aX, int32_t aY) {
+ const nsSizeMode sizeMode = mFrameState->GetSizeMode();
+ if (sizeMode == nsSizeMode_Minimized || sizeMode == nsSizeMode_Fullscreen) {
+ return HTCLIENT;
+ }
+
+ // Calculations are done in screen coords
+ const LayoutDeviceIntRect winRect = GetScreenBounds();
+ const LayoutDeviceIntPoint point(aX, aY);
+
+ // hit return constants:
+ // HTBORDER - non-resizable border
+ // HTBOTTOM, HTLEFT, HTRIGHT, HTTOP - resizable border
+ // HTBOTTOMLEFT, HTBOTTOMRIGHT - resizable corner
+ // HTTOPLEFT, HTTOPRIGHT - resizable corner
+ // HTCAPTION - general title bar area
+ // HTCLIENT - area considered the client
+ // HTCLOSE - hovering over the close button
+ // HTMAXBUTTON - maximize button
+ // HTMINBUTTON - minimize button
+
+ int32_t testResult = HTCLIENT;
+ const bool isResizable =
+ sizeMode != nsSizeMode_Maximized &&
+ (mBorderStyle &
+ (BorderStyle::All | BorderStyle::ResizeH | BorderStyle::Default));
+
+ LayoutDeviceIntMargin nonClientSizeMargin = NonClientSizeMargin();
+
+ // Ensure being accessible to borders of window. Even if contents are in
+ // this area, the area must behave as border.
+ nonClientSizeMargin.EnsureAtLeast(
+ LayoutDeviceIntMargin(kResizableBorderMinSize, kResizableBorderMinSize,
+ kResizableBorderMinSize, kResizableBorderMinSize));
+
+ LayoutDeviceIntRect clientRect = winRect;
+ clientRect.Deflate(nonClientSizeMargin);
+
+ const bool allowContentOverride =
+ sizeMode == nsSizeMode_Maximized || clientRect.Contains(point);
+
+ // The border size. If there is no content under mouse cursor, the border
+ // size should be larger than the values in system settings. Otherwise,
+ // contents under the mouse cursor should be able to override the behavior.
+ // E.g., user must expect that Firefox button always opens the popup menu
+ // even when the user clicks on the above edge of it.
+ LayoutDeviceIntMargin borderSize = nonClientSizeMargin;
+ borderSize.EnsureAtLeast(
+ LayoutDeviceIntMargin(mVertResizeMargin, mHorResizeMargin,
+ mVertResizeMargin, mHorResizeMargin));
+
+ bool top = false;
+ bool bottom = false;
+ bool left = false;
+ bool right = false;
+
+ if (point.y >= winRect.y && point.y < winRect.y + borderSize.top) {
+ top = true;
+ } else if (point.y <= winRect.YMost() &&
+ point.y > winRect.YMost() - borderSize.bottom) {
+ bottom = true;
+ }
+
+ // (the 2x case here doubles the resize area for corners)
+ int multiplier = (top || bottom) ? 2 : 1;
+ if (point.x >= winRect.x &&
+ point.x < winRect.x + (multiplier * borderSize.left)) {
+ left = true;
+ } else if (point.x <= winRect.XMost() &&
+ point.x > winRect.XMost() - (multiplier * borderSize.right)) {
+ right = true;
+ }
+
+ bool inResizeRegion = false;
+ if (isResizable) {
+ if (top) {
+ testResult = HTTOP;
+ if (left) {
+ testResult = HTTOPLEFT;
+ } else if (right) {
+ testResult = HTTOPRIGHT;
+ }
+ } else if (bottom) {
+ testResult = HTBOTTOM;
+ if (left) {
+ testResult = HTBOTTOMLEFT;
+ } else if (right) {
+ testResult = HTBOTTOMRIGHT;
+ }
+ } else {
+ if (left) {
+ testResult = HTLEFT;
+ }
+ if (right) {
+ testResult = HTRIGHT;
+ }
+ }
+ inResizeRegion = (testResult != HTCLIENT);
+ } else {
+ if (top) {
+ testResult = HTCAPTION;
+ } else if (bottom || left || right) {
+ testResult = HTBORDER;
+ }
+ }
+
+ if (!sIsInMouseCapture && allowContentOverride) {
+ {
+ POINT pt = {aX, aY};
+ ::ScreenToClient(mWnd, &pt);
+
+ if (pt.x == mCachedHitTestPoint.x.value &&
+ pt.y == mCachedHitTestPoint.y.value &&
+ TimeStamp::Now() - mCachedHitTestTime <
+ TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
+ return mCachedHitTestResult;
+ }
+
+ mCachedHitTestPoint = {pt.x, pt.y};
+ mCachedHitTestTime = TimeStamp::Now();
+ }
+
+ auto pt = mCachedHitTestPoint;
+
+ if (mWindowBtnRect[WindowButtonType::Minimize].Contains(pt)) {
+ testResult = HTMINBUTTON;
+ } else if (mWindowBtnRect[WindowButtonType::Maximize].Contains(pt)) {
+ testResult = HTMAXBUTTON;
+ } else if (mWindowBtnRect[WindowButtonType::Close].Contains(pt)) {
+ testResult = HTCLOSE;
+ } else if (!inResizeRegion) {
+ // If we're in the resize region, avoid overriding that with either a
+ // drag or a client result; resize takes priority over either (but not
+ // over the window controls, which is why we check this after those).
+ if (mDraggableRegion.Contains(pt)) {
+ testResult = HTCAPTION;
+ } else {
+ testResult = HTCLIENT;
+ }
+ }
+
+ mCachedHitTestResult = testResult;
+ }
+
+ return testResult;
+}
+
+bool nsWindow::IsSimulatedClientArea(int32_t screenX, int32_t screenY) {
+ int32_t testResult = ClientMarginHitTestPoint(screenX, screenY);
+ return testResult == HTCAPTION || IsWindowButton(testResult);
+}
+
+bool nsWindow::IsWindowButton(int32_t hitTestResult) {
+ return hitTestResult == HTMINBUTTON || hitTestResult == HTMAXBUTTON ||
+ hitTestResult == HTCLOSE;
+}
+
+TimeStamp nsWindow::GetMessageTimeStamp(LONG aEventTime) const {
+ CurrentWindowsTimeGetter getCurrentTime(mWnd);
+ return TimeConverter().GetTimeStampFromSystemTime(aEventTime, getCurrentTime);
+}
+
+void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode) {
+ // Retain the previous mode that was notified to observers
+ static bool sWasSleepMode = false;
+
+ // Only notify observers if mode changed
+ if (aIsSleepMode == sWasSleepMode) return;
+
+ sWasSleepMode = aIsSleepMode;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(nullptr,
+ aIsSleepMode
+ ? NS_WIDGET_SLEEP_OBSERVER_TOPIC
+ : NS_WIDGET_WAKE_OBSERVER_TOPIC,
+ nullptr);
+}
+
+LRESULT nsWindow::ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched) {
+ if (IMEHandler::IsComposingOn(this)) {
+ IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
+ }
+ // These must be checked here too as a lone WM_CHAR could be received
+ // if a child window didn't handle it (for example Alt+Space in a content
+ // window)
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
+}
+
+LRESULT nsWindow::ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched) {
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ bool result = nativeKey.HandleKeyUpMessage(aEventDispatched);
+ if (aMsg.wParam == VK_F10) {
+ // Bug 1382199: Windows default behavior will trigger the System menu bar
+ // when F10 is released. Among other things, this causes the System menu bar
+ // to appear when a web page overrides the contextmenu event. We *never*
+ // want this default behavior, so eat this key (never pass it to Windows).
+ return true;
+ }
+ return result;
+}
+
+LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg,
+ bool* aEventDispatched) {
+ // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method
+ // must clean up the redirected message information itself. For more
+ // information, see above comment of
+ // RedirectedKeyDownMessageManager::AutoFlusher class definition in
+ // KeyboardLayout.h.
+ RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg);
+
+ ModifierKeyState modKeyState;
+
+ NativeKey nativeKey(this, aMsg, modKeyState);
+ LRESULT result =
+ static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched));
+ // HandleKeyDownMessage cleaned up the redirected message information
+ // itself, so, we should do nothing.
+ redirectedMsgFlusher.Cancel();
+
+ if (aMsg.wParam == VK_MENU ||
+ (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) {
+ // We need to let Windows handle this keypress,
+ // by returning false, if there's a native menu
+ // bar somewhere in our containing window hierarchy.
+ // Otherwise we handle the keypress and don't pass
+ // it on to Windows, by returning true.
+ bool hasNativeMenu = false;
+ HWND hWnd = mWnd;
+ while (hWnd) {
+ if (::GetMenu(hWnd)) {
+ hasNativeMenu = true;
+ break;
+ }
+ hWnd = ::GetParent(hWnd);
+ }
+ result = !hasNativeMenu;
+ }
+
+ return result;
+}
+
+nsresult nsWindow::SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode,
+ uint32_t aModifierFlags, const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "keyevent");
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ return keyboardLayout->SynthesizeNativeKeyEvent(
+ this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters,
+ aUnmodifiedCharacters);
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(
+ LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
+ MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ INPUT input;
+ memset(&input, 0, sizeof(input));
+
+ // TODO (bug 1693240):
+ // Now, we synthesize native mouse events asynchronously since we want to
+ // synthesize the event on the front window at the point. However, Windows
+ // does not provide a way to set modifier only while a mouse message is
+ // being handled, and MOUSEEVENTF_MOVE may be coalesced by Windows. So, we
+ // need a trick for handling it.
+
+ switch (aNativeMessage) {
+ case NativeMouseMessage::Move:
+ input.mi.dwFlags = MOUSEEVENTF_MOVE;
+ // Reset sLastMouseMovePoint so that even if we're moving the mouse
+ // to the position it's already at, we still dispatch a mousemove
+ // event, because the callers of this function expect that.
+ sLastMouseMovePoint = {0};
+ break;
+ case NativeMouseMessage::ButtonDown:
+ case NativeMouseMessage::ButtonUp: {
+ const bool isDown = aNativeMessage == NativeMouseMessage::ButtonDown;
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ input.mi.dwFlags = isDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
+ break;
+ case MouseButton::eMiddle:
+ input.mi.dwFlags =
+ isDown ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
+ break;
+ case MouseButton::eSecondary:
+ input.mi.dwFlags =
+ isDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
+ break;
+ case MouseButton::eX1:
+ input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
+ input.mi.mouseData = XBUTTON1;
+ break;
+ case MouseButton::eX2:
+ input.mi.dwFlags = isDown ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP;
+ input.mi.mouseData = XBUTTON2;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ break;
+ }
+ case NativeMouseMessage::EnterWindow:
+ case NativeMouseMessage::LeaveWindow:
+ MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Windows");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ input.type = INPUT_MOUSE;
+ ::SetCursorPos(aPoint.x, aPoint.y);
+ ::SendInput(1, &input, sizeof(INPUT));
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+ return MouseScrollHandler::SynthesizeNativeMouseScrollEvent(
+ this, aPoint, aNativeMessage,
+ (aNativeMessage == WM_MOUSEWHEEL || aNativeMessage == WM_VSCROLL)
+ ? static_cast<int32_t>(aDeltaY)
+ : static_cast<int32_t>(aDeltaX),
+ aModifierFlags, aAdditionalFlags);
+}
+
+nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
+ DirectManipulationOwner::SynthesizeNativeTouchpadPan(
+ this, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags);
+ return NS_OK;
+}
+
+static void MaybeLogPosChanged(HWND aWnd, WINDOWPOS* wp) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (aWnd == WinUtils::GetTopLevelHWND(aWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [ top] "));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** OnWindowPosChanged: [child] "));
+ }
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("WINDOWPOS flags:"));
+ if (wp->flags & SWP_FRAMECHANGED) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_FRAMECHANGED "));
+ }
+ if (wp->flags & SWP_SHOWWINDOW) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_SHOWWINDOW "));
+ }
+ if (wp->flags & SWP_NOSIZE) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOSIZE "));
+ }
+ if (wp->flags & SWP_HIDEWINDOW) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_HIDEWINDOW "));
+ }
+ if (wp->flags & SWP_NOZORDER) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOZORDER "));
+ }
+ if (wp->flags & SWP_NOACTIVATE) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("SWP_NOACTIVATE "));
+ }
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("\n"));
+#endif
+}
+
+/**************************************************************
+ *
+ * SECTION: OnXXX message handlers
+ *
+ * For message handlers that need to be broken out or
+ * implemented in specific platform code.
+ *
+ **************************************************************/
+
+void nsWindow::OnWindowPosChanged(WINDOWPOS* wp) {
+ if (!wp) {
+ return;
+ }
+
+ MaybeLogPosChanged(mWnd, wp);
+
+ // Handle window size mode changes
+ if (wp->flags & SWP_FRAMECHANGED) {
+ // Bug 566135 - Windows theme code calls show window on SW_SHOWMINIMIZED
+ // windows when fullscreen games disable desktop composition. If we're
+ // minimized and not being activated, ignore the event and let windows
+ // handle it.
+ if (mFrameState->GetSizeMode() == nsSizeMode_Minimized &&
+ (wp->flags & SWP_NOACTIVATE)) {
+ return;
+ }
+
+ mFrameState->OnFrameChanged();
+
+ if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
+ // Skip window size change events below on minimization.
+ return;
+ }
+ }
+
+ // Notify visibility change when window is activated.
+ if (!(wp->flags & SWP_NOACTIVATE) && NeedsToTrackWindowOcclusionState()) {
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ this, mFrameState->GetSizeMode() != nsSizeMode_Minimized);
+ }
+
+ // Handle window position changes
+ if (!(wp->flags & SWP_NOMOVE)) {
+ mBounds.MoveTo(wp->x, wp->y);
+ NotifyWindowMoved(wp->x, wp->y);
+ }
+
+ // Handle window size changes
+ if (!(wp->flags & SWP_NOSIZE)) {
+ RECT r;
+ int32_t newWidth, newHeight;
+
+ ::GetWindowRect(mWnd, &r);
+
+ newWidth = r.right - r.left;
+ newHeight = r.bottom - r.top;
+
+ if (newWidth > mLastSize.width) {
+ RECT drect;
+
+ // getting wider
+ drect.left = wp->x + mLastSize.width;
+ drect.top = wp->y;
+ drect.right = drect.left + (newWidth - mLastSize.width);
+ drect.bottom = drect.top + newHeight;
+
+ ::RedrawWindow(mWnd, &drect, nullptr,
+ RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
+ RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+ if (newHeight > mLastSize.height) {
+ RECT drect;
+
+ // getting taller
+ drect.left = wp->x;
+ drect.top = wp->y + mLastSize.height;
+ drect.right = drect.left + newWidth;
+ drect.bottom = drect.top + (newHeight - mLastSize.height);
+
+ ::RedrawWindow(mWnd, &drect, nullptr,
+ RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT |
+ RDW_ERASENOW | RDW_ALLCHILDREN);
+ }
+
+ mBounds.SizeTo(newWidth, newHeight);
+ mLastSize.width = newWidth;
+ mLastSize.height = newHeight;
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth,
+ newHeight));
+#endif
+
+ if (mAspectRatio > 0) {
+ // It's possible (via Windows Aero Snap) that the size of the window
+ // has changed such that it violates the aspect ratio constraint. If so,
+ // queue up an event to enforce the aspect ratio constraint and repaint.
+ // When resized with Windows Aero Snap, we are in the NOT_RESIZING state.
+ float newAspectRatio = (float)newWidth / newHeight;
+ if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) {
+ // Hold a reference to self alive and pass it into the lambda to make
+ // sure this nsIWidget stays alive long enough to run this function.
+ nsCOMPtr<nsIWidget> self(this);
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "EnforceAspectRatio", [self, this, newWidth]() -> void {
+ if (mWnd) {
+ Resize(newWidth, newWidth / mAspectRatio, true);
+ }
+ }));
+ }
+ }
+
+ // If a maximized window is resized, recalculate the non-client margins.
+ if (mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
+ if (UpdateNonClientMargins(true)) {
+ // gecko resize event already sent by UpdateNonClientMargins.
+ return;
+ }
+ }
+ }
+
+ // Notify the widget listener for size change of client area for gecko
+ // events. This needs to be done when either window size is changed,
+ // or window frame is changed. They may not happen together.
+ // However, we don't invoke that for popup when window frame changes,
+ // because popups may trigger frame change before size change via
+ // {Set,Clear}ThemeRegion they invoke in Resize. That would make the
+ // code below call OnResize with a wrong client size first, which can
+ // lead to flickerling for some popups.
+ if (!(wp->flags & SWP_NOSIZE) ||
+ ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) {
+ RECT r;
+ LayoutDeviceIntSize clientSize;
+ if (::GetClientRect(mWnd, &r)) {
+ clientSize = WinUtils::ToIntRect(r).Size();
+ } else {
+ clientSize = mBounds.Size();
+ }
+ // Send a gecko resize event
+ OnResize(clientSize);
+ }
+}
+
+void nsWindow::OnWindowPosChanging(WINDOWPOS* info) {
+ // Update non-client margins if the frame size is changing, and let the
+ // browser know we are changing size modes, so alternative css can kick in.
+ // If we're going into fullscreen mode, ignore this, since it'll reset
+ // margins to normal mode.
+ if (info->flags & SWP_FRAMECHANGED && !(info->flags & SWP_NOSIZE)) {
+ mFrameState->OnFrameChanging();
+ }
+
+ // Force fullscreen. This works around a bug in Windows 10 1809 where
+ // using fullscreen when a window is "snapped" causes a spurious resize
+ // smaller than the full screen, see bug 1482920.
+ if (mFrameState->GetSizeMode() == nsSizeMode_Fullscreen &&
+ !(info->flags & SWP_NOMOVE) && !(info->flags & SWP_NOSIZE)) {
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService(sScreenManagerContractID);
+ if (screenmgr) {
+ LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy);
+ DesktopIntRect deskBounds =
+ RoundedToInt(bounds / GetDesktopToDeviceScale());
+ nsCOMPtr<nsIScreen> screen;
+ screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(),
+ deskBounds.Width(), deskBounds.Height(),
+ getter_AddRefs(screen));
+
+ if (screen) {
+ auto rect = screen->GetRect();
+ info->x = rect.x;
+ info->y = rect.y;
+ info->cx = rect.width;
+ info->cy = rect.height;
+ }
+ }
+ }
+
+ // enforce local z-order rules
+ if (!(info->flags & SWP_NOZORDER)) {
+ HWND hwndAfter = info->hwndInsertAfter;
+
+ nsWindow* aboveWindow = 0;
+ nsWindowZ placement;
+
+ if (hwndAfter == HWND_BOTTOM)
+ placement = nsWindowZBottom;
+ else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST ||
+ hwndAfter == HWND_NOTOPMOST)
+ placement = nsWindowZTop;
+ else {
+ placement = nsWindowZRelative;
+ aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter);
+ }
+
+ if (mWidgetListener) {
+ nsCOMPtr<nsIWidget> actualBelow = nullptr;
+ if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow,
+ getter_AddRefs(actualBelow))) {
+ if (placement == nsWindowZBottom)
+ info->hwndInsertAfter = HWND_BOTTOM;
+ else if (placement == nsWindowZTop)
+ info->hwndInsertAfter = HWND_TOP;
+ else {
+ info->hwndInsertAfter =
+ (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW);
+ }
+ }
+ }
+ }
+ // prevent rude external programs from making hidden window visible
+ if (mWindowType == WindowType::Invisible) info->flags &= ~SWP_SHOWWINDOW;
+
+ // When waking from sleep or switching out of tablet mode, Windows 10
+ // Version 1809 will reopen popup windows that should be hidden. Detect
+ // this case and refuse to show the window.
+ static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater();
+ if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) &&
+ mWindowType == WindowType::Popup && mWidgetListener &&
+ mWidgetListener->ShouldNotBeVisible()) {
+ info->flags &= ~SWP_SHOWWINDOW;
+ }
+}
+
+void nsWindow::UserActivity() {
+ // Check if we have the idle service, if not we try to get it.
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
+ }
+
+ // Check that we now have the idle service.
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+// Helper function for TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT,
+// uint32_t).
+static bool TouchDeviceNeedsPanGestureConversion(HANDLE aSource) {
+ std::string deviceName;
+ UINT dataSize = 0;
+ // The first call just queries how long the name string will be.
+ GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, nullptr, &dataSize);
+ if (!dataSize || dataSize > 0x10000) {
+ return false;
+ }
+ deviceName.resize(dataSize);
+ // The second call actually populates the string.
+ UINT result = GetRawInputDeviceInfoA(aSource, RIDI_DEVICENAME, &deviceName[0],
+ &dataSize);
+ if (result == UINT_MAX) {
+ return false;
+ }
+ // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash
+ // needs to be escaped with another one.
+ std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER";
+ // For some reason, the dataSize returned by the first call is double the
+ // actual length of the device name (as if it were returning the size of a
+ // wide-character string in bytes) even though we are using the narrow
+ // version of the API. For the comparison against the expected device name
+ // to pass, we truncate the buffer to be no longer tha the expected device
+ // name.
+ if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) {
+ return false;
+ }
+
+ RID_DEVICE_INFO deviceInfo;
+ deviceInfo.cbSize = sizeof(deviceInfo);
+ dataSize = sizeof(deviceInfo);
+ result =
+ GetRawInputDeviceInfoA(aSource, RIDI_DEVICEINFO, &deviceInfo, &dataSize);
+ if (result == UINT_MAX) {
+ return false;
+ }
+ // The device identifiers that we check for here come from bug 1355162
+ // comment 1 (see also bug 1511901 comment 35).
+ return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 &&
+ deviceInfo.hid.dwProductId == 0 &&
+ deviceInfo.hid.dwVersionNumber == 1 &&
+ deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4;
+}
+
+// Determine if the touch device that originated |aOSEvent| needs to have
+// touch events representing a two-finger gesture converted to pan
+// gesture events.
+// We only do this for touch devices with a specific name and identifiers.
+static bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent,
+ uint32_t aTouchCount) {
+ if (!StaticPrefs::apz_windows_check_for_pan_gesture_conversion()) {
+ return false;
+ }
+ if (aTouchCount == 0) {
+ return false;
+ }
+ HANDLE source = aOSEvent[0].hSource;
+
+ // Cache the result of this computation for each touch device.
+ // Touch devices are identified by the HANDLE stored in the hSource
+ // field of TOUCHINPUT.
+ static std::map<HANDLE, bool> sResultCache;
+ auto [iter, inserted] = sResultCache.emplace(source, false);
+ if (inserted) {
+ iter->second = TouchDeviceNeedsPanGestureConversion(source);
+ }
+ return iter->second;
+}
+
+Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture(
+ const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) {
+ // Checks if the touch device that originated the touch event is one
+ // for which we want to convert the touch events to pang gesture events.
+ bool shouldConvert = TouchDeviceNeedsPanGestureConversion(
+ aOSEvent, aTouchInput.mTouches.Length());
+ if (!shouldConvert) {
+ return Nothing();
+ }
+
+ // Only two-finger gestures need conversion.
+ if (aTouchInput.mTouches.Length() != 2) {
+ return Nothing();
+ }
+
+ PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
+ if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) {
+ eventType = PanGestureInput::PANGESTURE_CANCELLED;
+ }
+
+ // Use the midpoint of the two touches as the start point of the pan gesture.
+ ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint +
+ aTouchInput.mTouches[1].mScreenPoint) /
+ 2;
+ // To compute the displacement of the pan gesture, we keep track of the
+ // location of the previous event.
+ ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START)
+ ? ScreenPoint(0, 0)
+ : (focusPoint - mLastPanGestureFocus);
+ mLastPanGestureFocus = focusPoint;
+
+ // We need to negate the displacement because for a touch event, moving the
+ // fingers down results in scrolling up, but for a touchpad gesture, we want
+ // moving the fingers down to result in scrolling down.
+ PanGestureInput result(eventType, aTouchInput.mTimeStamp, focusPoint,
+ -displacement, aTouchInput.modifiers);
+ result.mSimulateMomentum = true;
+
+ return Some(result);
+}
+
+// Dispatch an event that originated as an OS touch event.
+// Usually, we want to dispatch it as a touch event, but some touchpads
+// produce touch events for two-finger scrolling, which need to be converted
+// to pan gesture events for correct behaviour.
+void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput,
+ PTOUCHINPUT aOSEvent) {
+ if (Maybe<PanGestureInput> panInput =
+ ConvertTouchToPanGesture(aTouchInput, aOSEvent)) {
+ DispatchPanGestureInput(*panInput);
+ return;
+ }
+
+ DispatchTouchInput(aTouchInput);
+}
+
+bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) {
+ uint32_t cInputs = LOWORD(wParam);
+ PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
+
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs,
+ sizeof(TOUCHINPUT))) {
+ MultiTouchInput touchInput, touchEndInput;
+
+ // Walk across the touch point array processing each contact point.
+ for (uint32_t i = 0; i < cInputs; i++) {
+ bool addToEvent = false, addToEndEvent = false;
+
+ // N.B.: According with MS documentation
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx
+ // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or
+ // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and
+ // TOUCHEVENTF_UP can be combined together.
+
+ if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) {
+ if (touchInput.mTimeStamp.IsNull()) {
+ // Initialize a touch event to send.
+ touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE;
+ touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
+ ModifierKeyState modifierKeyState;
+ touchInput.modifiers = modifierKeyState.GetModifiers();
+ }
+ // Pres shell expects this event to be a eTouchStart
+ // if any new contact points have been added since the last event sent.
+ if (pInputs[i].dwFlags & TOUCHEVENTF_DOWN) {
+ touchInput.mType = MultiTouchInput::MULTITOUCH_START;
+ }
+ addToEvent = true;
+ }
+ if (pInputs[i].dwFlags & TOUCHEVENTF_UP) {
+ // Pres shell expects removed contacts points to be delivered in a
+ // separate eTouchEnd event containing only the contact points that were
+ // removed.
+ if (touchEndInput.mTimeStamp.IsNull()) {
+ // Initialize a touch event to send.
+ touchEndInput.mType = MultiTouchInput::MULTITOUCH_END;
+ touchEndInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
+ ModifierKeyState modifierKeyState;
+ touchEndInput.modifiers = modifierKeyState.GetModifiers();
+ }
+ addToEndEvent = true;
+ }
+ if (!addToEvent && !addToEndEvent) {
+ // Filter out spurious Windows events we don't understand, like palm
+ // contact.
+ continue;
+ }
+
+ // Setup the touch point we'll append to the touch event array.
+ nsPointWin touchPoint;
+ touchPoint.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x);
+ touchPoint.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y);
+ touchPoint.ScreenToClient(mWnd);
+
+ // Initialize the touch data.
+ SingleTouchData touchData(
+ pInputs[i].dwID, // aIdentifier
+ ScreenIntPoint::FromUnknownPoint(touchPoint), // aScreenPoint
+ // The contact area info cannot be trusted even when
+ // TOUCHINPUTMASKF_CONTACTAREA is set when the input source is pen,
+ // which somehow violates the API docs. (bug 1710509) Ultimately the
+ // dwFlags check will become redundant since we want to migrate to
+ // WM_POINTER for pens. (bug 1707075)
+ (pInputs[i].dwMask & TOUCHINPUTMASKF_CONTACTAREA) &&
+ !(pInputs[i].dwFlags & TOUCHEVENTF_PEN)
+ ? ScreenSize(TOUCH_COORD_TO_PIXEL(pInputs[i].cxContact) / 2,
+ TOUCH_COORD_TO_PIXEL(pInputs[i].cyContact) / 2)
+ : ScreenSize(1, 1), // aRadius
+ 0.0f, // aRotationAngle
+ 0.0f); // aForce
+
+ // Append touch data to the appropriate event.
+ if (addToEvent) {
+ touchInput.mTouches.AppendElement(touchData);
+ }
+ if (addToEndEvent) {
+ touchEndInput.mTouches.AppendElement(touchData);
+ }
+ }
+
+ // Dispatch touch start and touch move event if we have one.
+ if (!touchInput.mTimeStamp.IsNull()) {
+ DispatchTouchOrPanGestureInput(touchInput, pInputs);
+ }
+ // Dispatch touch end event if we have one.
+ if (!touchEndInput.mTimeStamp.IsNull()) {
+ DispatchTouchOrPanGestureInput(touchEndInput, pInputs);
+ }
+ }
+
+ delete[] pInputs;
+ CloseTouchInputHandle((HTOUCHINPUT)lParam);
+ return true;
+}
+
+// Gesture event processing. Handles WM_GESTURE events.
+bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) {
+ // Treatment for pan events which translate into scroll events:
+ if (mGesture.IsPanEvent(lParam)) {
+ if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam))
+ return false; // ignore
+
+ nsEventStatus status;
+
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(wheelEvent);
+
+ wheelEvent.mButton = 0;
+ wheelEvent.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
+ wheelEvent.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+
+ bool endFeedback = true;
+
+ if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
+ DispatchEvent(&wheelEvent, status);
+ }
+
+ if (mDisplayPanFeedback) {
+ mGesture.UpdatePanFeedbackX(
+ mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaX)),
+ endFeedback);
+ mGesture.UpdatePanFeedbackY(
+ mWnd, DeprecatedAbs(RoundDown(wheelEvent.mOverflowDeltaY)),
+ endFeedback);
+ mGesture.PanFeedbackFinalize(mWnd, endFeedback);
+ }
+
+ CloseGestureInfoHandle((HGESTUREINFO)lParam);
+
+ return true;
+ }
+
+ // Other gestures translate into simple gesture events:
+ WidgetSimpleGestureEvent event(true, eVoidEvent, this);
+ if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) {
+ return false; // fall through to DefWndProc
+ }
+
+ // Polish up and send off the new event
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+ event.mButton = 0;
+ event.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
+ event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ if (status == nsEventStatus_eIgnore) {
+ return false; // Ignored, fall through
+ }
+
+ // Only close this if we process and return true.
+ CloseGestureInfoHandle((HGESTUREINFO)lParam);
+
+ return true; // Handled
+}
+
+// WM_DESTROY event handler
+void nsWindow::OnDestroy() {
+ mOnDestroyCalled = true;
+
+ // If this is a toplevel window, notify the taskbar concealer to clean up any
+ // relevant state.
+ if (!mParent) {
+ TaskbarConcealer::OnWindowDestroyed(mWnd);
+ }
+
+ // Make sure we don't get destroyed in the process of tearing down.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ // Dispatch the destroy notification.
+ if (!mInDtor) NotifyWindowDestroyed();
+
+ // Prevent the widget from sending additional events.
+ mWidgetListener = nullptr;
+ mAttachedWidgetListener = nullptr;
+
+ DestroyDirectManipulation();
+
+ if (mWnd == mLastKillFocusWindow) {
+ mLastKillFocusWindow = nullptr;
+ }
+ // Unregister notifications from terminal services
+ ::WTSUnRegisterSessionNotification(mWnd);
+
+ // We will stop receiving native events after dissociating from our native
+ // window. We will also disappear from the output of WinUtils::GetNSWindowPtr
+ // for that window.
+ DissociateFromNativeWindow();
+
+ // Once mWidgetListener is cleared and the subclass is reset, sCurrentWindow
+ // can be cleared. (It's used in tracking windows for mouse events.)
+ if (sCurrentWindow == this) sCurrentWindow = nullptr;
+
+ // Disconnects us from our parent, will call our GetParent().
+ nsBaseWidget::Destroy();
+
+ // Release references to children, device context, toolkit, and app shell.
+ nsBaseWidget::OnDestroy();
+
+ // Clear our native parent handle.
+ // XXX Windows will take care of this in the proper order, and
+ // SetParent(nullptr)'s remove child on the parent already took place in
+ // nsBaseWidget's Destroy call above.
+ // SetParent(nullptr);
+ mParent = nullptr;
+
+ // We have to destroy the native drag target before we null out our window
+ // pointer.
+ EnableDragDrop(false);
+
+ // If we're going away and for some reason we're still the rollup widget,
+ // rollup and turn off capture.
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (this == rollupWidget) {
+ rollupListener->Rollup({});
+ CaptureRollupEvents(false);
+ }
+
+ IMEHandler::OnDestroyWindow(this);
+
+ // Free GDI window class objects
+ if (mBrush) {
+ VERIFY(::DeleteObject(mBrush));
+ mBrush = nullptr;
+ }
+
+ // Destroy any custom cursor resources.
+ if (mCursor.IsCustom()) {
+ SetCursor(Cursor{eCursor_standard});
+ }
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->OnDestroyWindow();
+ }
+ mBasicLayersSurface = nullptr;
+
+ // Finalize panning feedback to possibly restore window displacement
+ mGesture.PanFeedbackFinalize(mWnd, true);
+
+ // Clear the main HWND.
+ mWnd = nullptr;
+}
+
+// Send a resize message to the listener
+bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) {
+ if (mCompositorWidgetDelegate &&
+ !mCompositorWidgetDelegate->OnWindowResize(aSize)) {
+ return false;
+ }
+
+ bool result = false;
+ if (mWidgetListener) {
+ result = mWidgetListener->WindowResized(this, aSize.width, aSize.height);
+ }
+
+ // If there is an attached view, inform it as well as the normal widget
+ // listener.
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->WindowResized(this, aSize.width,
+ aSize.height);
+ }
+
+ return result;
+}
+
+void nsWindow::OnSizeModeChange() {
+ const nsSizeMode mode = mFrameState->GetSizeMode();
+
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("nsWindow::OnSizeModeChange() sizeMode %d", mode));
+
+ if (NeedsToTrackWindowOcclusionState()) {
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ this, mode != nsSizeMode_Minimized);
+
+ wr::DebugFlags flags{0};
+ flags._0 = gfx::gfxVars::WebRenderDebugFlags();
+ bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
+ if (debugEnabled && mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyVisibilityUpdated(mode,
+ mIsFullyOccluded);
+ }
+ }
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->OnWindowModeChange(mode);
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mode);
+ }
+}
+
+bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam) { return true; }
+
+bool nsWindow::IsPopup() { return mWindowType == WindowType::Popup; }
+
+bool nsWindow::ShouldUseOffMainThreadCompositing() {
+ if (mWindowType == WindowType::Popup && mPopupType == PopupType::Tooltip) {
+ return false;
+ }
+
+ // Content rendering of popup is always done by child window.
+ // See nsDocumentViewer::ShouldAttachToTopLevel().
+ if (mWindowType == WindowType::Popup && !mIsChildWindow) {
+ MOZ_ASSERT(!mParent);
+ return false;
+ }
+
+ return nsBaseWidget::ShouldUseOffMainThreadCompositing();
+}
+
+void nsWindow::WindowUsesOMTC() {
+ ULONG_PTR style = ::GetClassLongPtr(mWnd, GCL_STYLE);
+ if (!style) {
+ NS_WARNING("Could not get window class style");
+ return;
+ }
+ style |= CS_HREDRAW | CS_VREDRAW;
+ DebugOnly<ULONG_PTR> result = ::SetClassLongPtr(mWnd, GCL_STYLE, style);
+ NS_WARNING_ASSERTION(result, "Could not reset window class style");
+}
+
+void nsWindow::OnDPIChanged(int32_t x, int32_t y, int32_t width,
+ int32_t height) {
+ // Don't try to handle WM_DPICHANGED for popup windows (see bug 1239353);
+ // they remain tied to their original parent's resolution.
+ if (mWindowType == WindowType::Popup) {
+ return;
+ }
+ if (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) {
+ return;
+ }
+ mDefaultScale = -1.0; // force recomputation of scale factor
+
+ if (mResizeState != RESIZING &&
+ mFrameState->GetSizeMode() == nsSizeMode_Normal) {
+ // Limit the position (if not in the middle of a drag-move) & size,
+ // if it would overflow the destination screen
+ nsCOMPtr<nsIScreenManager> sm = do_GetService(sScreenManagerContractID);
+ if (sm) {
+ nsCOMPtr<nsIScreen> screen;
+ sm->ScreenForRect(x, y, width, height, getter_AddRefs(screen));
+ if (screen) {
+ int32_t availLeft, availTop, availWidth, availHeight;
+ screen->GetAvailRect(&availLeft, &availTop, &availWidth, &availHeight);
+ if (mResizeState != MOVING) {
+ x = std::max(x, availLeft);
+ y = std::max(y, availTop);
+ }
+ width = std::min(width, availWidth);
+ height = std::min(height, availHeight);
+ }
+ }
+
+ Resize(x, y, width, height, true);
+ }
+ UpdateNonClientMargins();
+ ChangedDPI();
+ ResetLayout();
+}
+
+// Callback to generate OnCloakChanged pseudo-events.
+/* static */
+void nsWindow::OnCloakEvent(HWND aWnd, bool aCloaked) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const char* const kEventName = aCloaked ? "CLOAKED" : "UNCLOAKED";
+ nsWindow* pWin = WinUtils::GetNSWindowPtr(aWnd);
+ if (!pWin) {
+ MOZ_LOG(
+ sCloakingLog, LogLevel::Debug,
+ ("Received %s event for HWND %p (not an nsWindow)", kEventName, aWnd));
+ return;
+ }
+
+ const char* const kWasCloakedStr = pWin->mIsCloaked ? "cloaked" : "uncloaked";
+ if (mozilla::IsCloaked(aWnd) == pWin->mIsCloaked) {
+ MOZ_LOG(sCloakingLog, LogLevel::Debug,
+ ("Received redundant %s event for %s HWND %p; discarding",
+ kEventName, kWasCloakedStr, aWnd));
+ return;
+ }
+
+ MOZ_LOG(
+ sCloakingLog, LogLevel::Info,
+ ("Received %s event for %s HWND %p", kEventName, kWasCloakedStr, aWnd));
+
+ // Cloaking events like the one we've just received are sent asynchronously.
+ // Rather than process them one-by-one, we jump the gun a bit and perform
+ // updates on all newly cloaked/uncloaked nsWindows at once. This also lets us
+ // batch operations that consider more than one window's state.
+ struct Item {
+ nsWindow* win;
+ bool nowCloaked;
+ };
+ nsTArray<Item> changedWindows;
+
+ mozilla::EnumerateThreadWindows([&](HWND hwnd) {
+ nsWindow* pWin = WinUtils::GetNSWindowPtr(hwnd);
+ if (!pWin) {
+ return;
+ }
+
+ const bool isCloaked = mozilla::IsCloaked(hwnd);
+ if (isCloaked != pWin->mIsCloaked) {
+ changedWindows.AppendElement(Item{pWin, isCloaked});
+ }
+ });
+
+ if (changedWindows.IsEmpty()) {
+ return;
+ }
+
+ for (const Item& item : changedWindows) {
+ item.win->OnCloakChanged(item.nowCloaked);
+ }
+
+ nsWindow::TaskbarConcealer::OnCloakChanged();
+}
+
+void nsWindow::OnCloakChanged(bool aCloaked) {
+ MOZ_LOG(sCloakingLog, LogLevel::Info,
+ ("Calling OnCloakChanged(): HWND %p, aCloaked %s", mWnd,
+ aCloaked ? "true" : "false"));
+ mIsCloaked = aCloaked;
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: IME management and accessibility
+ **
+ ** Handles managing IME input and accessibility.
+ **
+ **************************************************************
+ **************************************************************/
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ InputContext newInputContext = aContext;
+ IMEHandler::SetInputContext(this, newInputContext, aAction);
+ mInputContext = newInputContext;
+}
+
+InputContext nsWindow::GetInputContext() {
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) {
+ mInputContext.mIMEState.mOpen = IMEState::OPEN;
+ } else {
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ }
+ return mInputContext;
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ return IMEHandler::GetNativeTextEventDispatcherListener();
+}
+
+#ifdef ACCESSIBILITY
+# ifdef DEBUG
+# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \
+ if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \
+ printf( \
+ "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \
+ "%p,\n", \
+ aHwnd, ::GetParent(aHwnd), aWnd); \
+ printf(" acc: %p", aAcc); \
+ if (aAcc) { \
+ nsAutoString name; \
+ aAcc->Name(name); \
+ printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \
+ } \
+ printf("\n }\n"); \
+ }
+
+# else
+# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc)
+# endif
+
+a11y::LocalAccessible* nsWindow::GetAccessible() {
+ // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
+ if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
+ return nullptr;
+
+ if (mInDtor || mOnDestroyCalled || mWindowType == WindowType::Invisible) {
+ return nullptr;
+ }
+
+ // In case of popup window return a popup accessible.
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsIFrame* frame = view->GetFrame();
+ if (frame && nsLayoutUtils::IsPopup(frame)) {
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (accService) {
+ a11y::DocAccessible* docAcc =
+ GetAccService()->GetDocAccessible(frame->PresShell());
+ if (docAcc) {
+ NS_LOG_WMGETOBJECT(
+ this, mWnd,
+ docAcc->GetAccessibleOrDescendant(frame->GetContent()));
+ return docAcc->GetAccessibleOrDescendant(frame->GetContent());
+ }
+ }
+ }
+ }
+
+ // otherwise root document accessible.
+ NS_LOG_WMGETOBJECT(this, mWnd, GetRootAccessible());
+ return GetRootAccessible();
+}
+#endif
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Transparency
+ **
+ ** Window transparency helpers.
+ **
+ **************************************************************
+ **************************************************************/
+
+void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) {
+ if (aMode == mTransparencyMode) return;
+
+ // stop on dialogs and popups!
+ HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+ nsWindow* parent = WinUtils::GetNSWindowPtr(hWnd);
+
+ if (!parent) {
+ NS_WARNING("Trying to use transparent chrome in an embedded context");
+ return;
+ }
+
+ if (parent != this) {
+ NS_WARNING(
+ "Setting SetWindowTranslucencyInner on a parent this is not us!");
+ }
+
+ if (aMode == TransparencyMode::Transparent) {
+ // If we're switching to the use of a transparent window, hide the chrome
+ // on our parent.
+ HideWindowChrome(true);
+ } else if (mHideChrome &&
+ mTransparencyMode == TransparencyMode::Transparent) {
+ // if we're switching out of transparent, re-enable our parent's chrome.
+ HideWindowChrome(false);
+ }
+
+ LONG_PTR style = ::GetWindowLongPtrW(hWnd, GWL_STYLE),
+ exStyle = ::GetWindowLongPtr(hWnd, GWL_EXSTYLE);
+
+ if (parent->mIsVisible) {
+ style |= WS_VISIBLE;
+ if (parent->mFrameState->GetSizeMode() == nsSizeMode_Maximized) {
+ style |= WS_MAXIMIZE;
+ } else if (parent->mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
+ style |= WS_MINIMIZE;
+ }
+ }
+
+ if (aMode == TransparencyMode::Transparent)
+ exStyle |= WS_EX_LAYERED;
+ else
+ exStyle &= ~WS_EX_LAYERED;
+
+ VERIFY_WINDOW_STYLE(style);
+ ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
+ ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
+
+ mTransparencyMode = aMode;
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->UpdateTransparency(aMode);
+ }
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Popup rollup hooks
+ **
+ ** Deals with CaptureRollup on popup windows.
+ **
+ **************************************************************
+ **************************************************************/
+
+// Schedules a timer for a window, so we can rollup after processing the hook
+// event
+void nsWindow::ScheduleHookTimer(HWND aWnd, UINT aMsgId) {
+ // In some cases multiple hooks may be scheduled
+ // so ignore any other requests once one timer is scheduled
+ if (sHookTimerId == 0) {
+ // Remember the window handle and the message ID to be used later
+ sRollupMsgId = aMsgId;
+ sRollupMsgWnd = aWnd;
+ // Schedule native timer for doing the rollup after
+ // this event is done being processed
+ sHookTimerId = ::SetTimer(nullptr, 0, 0, (TIMERPROC)HookTimerForPopups);
+ NS_ASSERTION(sHookTimerId, "Timer couldn't be created.");
+ }
+}
+
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+int gLastMsgCode = 0;
+extern MSGFEventMsgInfo gMSGFEvents[];
+#endif
+
+// Process Menu messages, rollup when popup is clicked.
+LRESULT CALLBACK nsWindow::MozSpecialMsgFilter(int code, WPARAM wParam,
+ LPARAM lParam) {
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (sProcessHook) {
+ MSG* pMsg = (MSG*)lParam;
+
+ int inx = 0;
+ while (gMSGFEvents[inx].mId != code && gMSGFEvents[inx].mStr != nullptr) {
+ inx++;
+ }
+ if (code != gLastMsgCode) {
+ if (gMSGFEvents[inx].mId == code) {
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("MozSpecialMessageProc - code: 0x%X - %s hw: %p\n", code,
+ gMSGFEvents[inx].mStr, pMsg->hwnd));
+# endif
+ } else {
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("MozSpecialMessageProc - code: 0x%X - %d hw: %p\n", code,
+ gMSGFEvents[inx].mId, pMsg->hwnd));
+# endif
+ }
+ gLastMsgCode = code;
+ }
+ PrintEvent(pMsg->message, FALSE, FALSE);
+ }
+#endif // #ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+
+ if (sProcessHook && code == MSGF_MENU) {
+ MSG* pMsg = (MSG*)lParam;
+ ScheduleHookTimer(pMsg->hwnd, pMsg->message);
+ }
+
+ return ::CallNextHookEx(sMsgFilterHook, code, wParam, lParam);
+}
+
+// Process all mouse messages. Roll up when a click is in a native window
+// that doesn't have an nsIWidget.
+LRESULT CALLBACK nsWindow::MozSpecialMouseProc(int code, WPARAM wParam,
+ LPARAM lParam) {
+ if (sProcessHook) {
+ switch (WinUtils::GetNativeMessage(wParam)) {
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL: {
+ MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
+ nsIWidget* mozWin = WinUtils::GetNSWindowPtr(ms->hwnd);
+ if (!mozWin) {
+ ScheduleHookTimer(ms->hwnd, (UINT)wParam);
+ }
+ break;
+ }
+ }
+ }
+ return ::CallNextHookEx(sCallMouseHook, code, wParam, lParam);
+}
+
+// Process all messages. Roll up when the window is moving, or
+// is resizing or when maximized or mininized.
+LRESULT CALLBACK nsWindow::MozSpecialWndProc(int code, WPARAM wParam,
+ LPARAM lParam) {
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (sProcessHook) {
+ CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
+ PrintEvent(cwpt->message, FALSE, FALSE);
+ }
+#endif
+
+ if (sProcessHook) {
+ CWPSTRUCT* cwpt = (CWPSTRUCT*)lParam;
+ if (cwpt->message == WM_MOVING || cwpt->message == WM_SIZING ||
+ cwpt->message == WM_GETMINMAXINFO) {
+ ScheduleHookTimer(cwpt->hwnd, (UINT)cwpt->message);
+ }
+ }
+
+ return ::CallNextHookEx(sCallProcHook, code, wParam, lParam);
+}
+
+// Register the special "hooks" for dropdown processing.
+void nsWindow::RegisterSpecialDropdownHooks() {
+ NS_ASSERTION(!sMsgFilterHook, "sMsgFilterHook must be NULL!");
+ NS_ASSERTION(!sCallProcHook, "sCallProcHook must be NULL!");
+
+ DISPLAY_NMM_PRT("***************** Installing Msg Hooks ***************\n");
+
+ // Install msg hook for moving the window and resizing
+ if (!sMsgFilterHook) {
+ DISPLAY_NMM_PRT("***** Hooking sMsgFilterHook!\n");
+ sMsgFilterHook = SetWindowsHookEx(WH_MSGFILTER, MozSpecialMsgFilter,
+ nullptr, GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sMsgFilterHook) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_MSGFILTER!\n"));
+ }
+#endif
+ }
+
+ // Install msg hook for menus
+ if (!sCallProcHook) {
+ DISPLAY_NMM_PRT("***** Hooking sCallProcHook!\n");
+ sCallProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MozSpecialWndProc, nullptr,
+ GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sCallProcHook) {
+ MOZ_LOG(
+ gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_CALLWNDPROC!\n"));
+ }
+#endif
+ }
+
+ // Install msg hook for the mouse
+ if (!sCallMouseHook) {
+ DISPLAY_NMM_PRT("***** Hooking sCallMouseHook!\n");
+ sCallMouseHook = SetWindowsHookEx(WH_MOUSE, MozSpecialMouseProc, nullptr,
+ GetCurrentThreadId());
+#ifdef POPUP_ROLLUP_DEBUG_OUTPUT
+ if (!sCallMouseHook) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("***** SetWindowsHookEx is NOT installed for WH_MOUSE!\n"));
+ }
+#endif
+ }
+}
+
+// Unhook special message hooks for dropdowns.
+void nsWindow::UnregisterSpecialDropdownHooks() {
+ DISPLAY_NMM_PRT(
+ "***************** De-installing Msg Hooks ***************\n");
+
+ if (sCallProcHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sCallProcHook!\n");
+ if (!::UnhookWindowsHookEx(sCallProcHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallProcHook!\n");
+ }
+ sCallProcHook = nullptr;
+ }
+
+ if (sMsgFilterHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sMsgFilterHook!\n");
+ if (!::UnhookWindowsHookEx(sMsgFilterHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sMsgFilterHook!\n");
+ }
+ sMsgFilterHook = nullptr;
+ }
+
+ if (sCallMouseHook) {
+ DISPLAY_NMM_PRT("***** Unhooking sCallMouseHook!\n");
+ if (!::UnhookWindowsHookEx(sCallMouseHook)) {
+ DISPLAY_NMM_PRT("***** UnhookWindowsHookEx failed for sCallMouseHook!\n");
+ }
+ sCallMouseHook = nullptr;
+ }
+}
+
+// This timer is designed to only fire one time at most each time a "hook"
+// function is used to rollup the dropdown. In some cases, the timer may be
+// scheduled from the hook, but that hook event or a subsequent event may roll
+// up the dropdown before this timer function is executed.
+//
+// For example, if an MFC control takes focus, the combobox will lose focus and
+// rollup before this function fires.
+VOID CALLBACK nsWindow::HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
+ DWORD dwTime) {
+ if (sHookTimerId != 0) {
+ // if the window is nullptr then we need to use the ID to kill the timer
+ DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId);
+ NS_ASSERTION(status, "Hook Timer was not killed.");
+ sHookTimerId = 0;
+ }
+
+ if (sRollupMsgId != 0) {
+ // Note: DealWithPopups does the check to make sure that the rollup widget
+ // is set.
+ LRESULT popupHandlingResult;
+ nsAutoRollup autoRollup;
+ DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult);
+ sRollupMsgId = 0;
+ sRollupMsgWnd = nullptr;
+ }
+}
+
+static bool IsDifferentThreadWindow(HWND aWnd) {
+ return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
+}
+
+// static
+bool nsWindow::EventIsInsideWindow(nsWindow* aWindow,
+ Maybe<POINT> aEventPoint) {
+ RECT r;
+ ::GetWindowRect(aWindow->mWnd, &r);
+ POINT mp;
+ if (aEventPoint) {
+ mp = *aEventPoint;
+ } else {
+ DWORD pos = ::GetMessagePos();
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ }
+
+ auto margin = aWindow->mInputRegion.mMargin;
+ if (margin > 0) {
+ r.top += margin;
+ r.bottom -= margin;
+ r.left += margin;
+ r.right -= margin;
+ }
+
+ // was the event inside this window?
+ return static_cast<bool>(::PtInRect(&r, mp));
+}
+
+// static
+bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
+ uint32_t* aPopupsToRollup,
+ Maybe<POINT> aEventPoint) {
+ // If we're dealing with menus, we probably have submenus and we don't want
+ // to rollup some of them if the click is in a parent menu of the current
+ // submenu.
+ *aPopupsToRollup = UINT32_MAX;
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i = 0; i < widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) {
+ // Don't roll up if the mouse event occurred within a menu of the
+ // same type. If the mouse event occurred in a menu higher than that,
+ // roll up, but pass the number of popups to Rollup so that only those
+ // of the same type close up.
+ if (i < sameTypeCount) {
+ return false;
+ }
+
+ *aPopupsToRollup = sameTypeCount;
+ break;
+ }
+ }
+ return true;
+}
+
+// static
+bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) {
+ // While popup is open, popup window might be activated by other application.
+ // At this time, we need to take back focus to the previous window but it
+ // causes flickering its nonclient area because WM_NCACTIVATE comes before
+ // WM_ACTIVATE and we cannot know which window will take focus at receiving
+ // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling.
+ //
+ // If non-popup window receives WM_NCACTIVATE at deactivating, default
+ // wndproc shouldn't handle it as deactivating. Instead, at receiving
+ // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually.
+ // This returns true if the window needs to handle WM_NCACTIVATE later.
+
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ return window && !window->IsPopup();
+}
+
+static bool IsTouchSupportEnabled(HWND aWnd) {
+ nsWindow* topWindow =
+ WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true));
+ return topWindow ? topWindow->IsTouchWindow() : false;
+}
+
+static Maybe<POINT> GetSingleTouch(WPARAM wParam, LPARAM lParam) {
+ Maybe<POINT> ret;
+ uint32_t cInputs = LOWORD(wParam);
+ if (cInputs != 1) {
+ return ret;
+ }
+ TOUCHINPUT input;
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, &input,
+ sizeof(TOUCHINPUT))) {
+ ret.emplace();
+ ret->x = TOUCH_COORD_TO_PIXEL(input.x);
+ ret->y = TOUCH_COORD_TO_PIXEL(input.y);
+ }
+ // Note that we don't call CloseTouchInputHandle here because we need
+ // to read the touch input info again in OnTouch later.
+ return ret;
+}
+
+// static
+bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
+ LPARAM aLParam, LRESULT* aResult) {
+ NS_ASSERTION(aResult, "Bad outResult");
+
+ // XXX Why do we use the return value of WM_MOUSEACTIVATE for all messages?
+ *aResult = MA_NOACTIVATE;
+
+ if (!::IsWindowVisible(aWnd)) {
+ return false;
+ }
+
+ if (MOZ_UNLIKELY(aMessage == WM_KILLFOCUS)) {
+ // NOTE: We deal with this here rather than on the switch below because we
+ // want to do this even if there are no menus to rollup (tooltips don't set
+ // the rollup listener etc).
+ if (RefPtr pm = nsXULPopupManager::GetInstance()) {
+ pm->RollupTooltips();
+ }
+ }
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, false);
+
+ nsCOMPtr<nsIWidget> popup = rollupListener->GetRollupWidget();
+ if (!popup) {
+ return false;
+ }
+
+ static bool sSendingNCACTIVATE = false;
+ static bool sPendingNCACTIVATE = false;
+ uint32_t popupsToRollup = UINT32_MAX;
+
+ bool consumeRollupEvent = false;
+ Maybe<POINT> touchPoint; // In screen coords.
+
+ // If we rollup with animations but get occluded right away, we might not
+ // advance the refresh driver enough for the animation to finish.
+ auto allowAnimations = nsIRollupListener::AllowAnimations::Yes;
+ nsWindow* popupWindow = static_cast<nsWindow*>(popup.get());
+ UINT nativeMessage = WinUtils::GetNativeMessage(aMessage);
+ switch (nativeMessage) {
+ case WM_TOUCH:
+ if (!IsTouchSupportEnabled(aWnd)) {
+ // If APZ is disabled, don't allow touch inputs to dismiss popups. The
+ // compatibility mouse events will do it instead.
+ return false;
+ }
+ touchPoint = GetSingleTouch(aWParam, aLParam);
+ if (!touchPoint) {
+ return false;
+ }
+ [[fallthrough]];
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_NCLBUTTONDOWN:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCMBUTTONDOWN:
+ if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) &&
+ MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ // If any of these mouse events are really compatibility events that
+ // Windows is sending for touch inputs, then don't allow them to dismiss
+ // popups when APZ is enabled (instead we do the dismissing as part of
+ // WM_TOUCH handling which is more correct).
+ // If we don't do this, then when the user lifts their finger after a
+ // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends
+ // us will dismiss the contextmenu popup that we displayed as part of
+ // handling the long-tap-up.
+ return false;
+ }
+ if (!EventIsInsideWindow(popupWindow, touchPoint) &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup, touchPoint)) {
+ break;
+ }
+ return false;
+ case WM_POINTERDOWN: {
+ WinPointerEvents pointerEvents;
+ if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) {
+ return false;
+ }
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
+ return false;
+ }
+ if (EventIsInsideWindow(popupWindow, Some(pt))) {
+ // Don't roll up if the event is inside the popup window.
+ return false;
+ }
+ } break;
+ case MOZ_WM_DMANIP: {
+ POINT pt;
+ ::GetCursorPos(&pt);
+ if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) {
+ return false;
+ }
+ if (EventIsInsideWindow(popupWindow, Some(pt))) {
+ // Don't roll up if the event is inside the popup window
+ return false;
+ }
+ } break;
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ // We need to check if the popup thinks that it should cause closing
+ // itself when mouse wheel events are fired outside the rollup widget.
+ if (!EventIsInsideWindow(popupWindow)) {
+ // Check if we should consume this event even if we don't roll-up:
+ consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ *aResult = MA_ACTIVATE;
+ if (rollupListener->ShouldRollupOnMouseWheelEvent() &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ break;
+ }
+ }
+ return consumeRollupEvent;
+
+ case WM_ACTIVATEAPP:
+ allowAnimations = nsIRollupListener::AllowAnimations::No;
+ break;
+
+ case WM_ACTIVATE: {
+ WndProcUrgentInvocation::Marker _marker;
+
+ // NOTE: Don't handle WA_INACTIVE for preventing popup taking focus
+ // because we cannot distinguish it's caused by mouse or not.
+ if (LOWORD(aWParam) == WA_ACTIVE && aLParam) {
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ if (window && (window->IsPopup() || window->mIsAlert)) {
+ // Cancel notifying widget listeners of deactivating the previous
+ // active window (see WM_KILLFOCUS case in ProcessMessage()).
+ sJustGotDeactivate = false;
+ // Reactivate the window later.
+ ::PostMessageW(aWnd, MOZ_WM_REACTIVATE, aWParam, aLParam);
+ return true;
+ }
+ // Don't rollup the popup when focus moves back to the parent window
+ // from a popup because such case is caused by strange mouse drivers.
+ nsWindow* prevWindow =
+ WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
+ if (prevWindow && prevWindow->IsPopup()) {
+ // Consume this message here since previous window must not have
+ // been inactivated since we've already stopped accepting the
+ // inactivation below.
+ return true;
+ }
+ } else if (LOWORD(aWParam) == WA_INACTIVE) {
+ nsWindow* activeWindow =
+ WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam));
+ if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) {
+ // If focus moves to non-popup widget or focusable popup, the window
+ // needs to update its nonclient area.
+ if (!activeWindow || !activeWindow->IsPopup()) {
+ sSendingNCACTIVATE = true;
+ ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0);
+ sSendingNCACTIVATE = false;
+ }
+ sPendingNCACTIVATE = false;
+ }
+ // If focus moves from/to popup, we don't need to rollup the popup
+ // because such case is caused by strange mouse drivers. And in
+ // such case, we should consume the message here since we need to
+ // hide this odd focus move from our content. (If we didn't consume
+ // the message here, ProcessMessage() will notify widget listener of
+ // inactivation and that causes unnecessary reflow for supporting
+ // -moz-window-inactive pseudo class.
+ if (activeWindow) {
+ if (activeWindow->IsPopup()) {
+ return true;
+ }
+ nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
+ if (deactiveWindow && deactiveWindow->IsPopup()) {
+ return true;
+ }
+ }
+ } else if (LOWORD(aWParam) == WA_CLICKACTIVE) {
+ // If the WM_ACTIVATE message is caused by a click in a popup,
+ // we should not rollup any popups.
+ nsWindow* window = WinUtils::GetNSWindowPtr(aWnd);
+ if ((window && window->IsPopup()) ||
+ !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ return false;
+ }
+ }
+ allowAnimations = nsIRollupListener::AllowAnimations::No;
+ } break;
+
+ case MOZ_WM_REACTIVATE:
+ // The previous active window should take back focus.
+ if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
+ // FYI: Even without this API call, you see expected result (e.g., the
+ // owner window of the popup keeps active without flickering
+ // the non-client area). And also this causes initializing
+ // TSF and it causes using CPU time a lot. However, even if we
+ // consume WM_ACTIVE messages, native focus change has already
+ // been occurred. I.e., a popup window is active now. Therefore,
+ // you'll see some odd behavior if we don't reactivate the owner
+ // window here. For example, if you do:
+ // 1. Turn wheel on a bookmark panel.
+ // 2. Turn wheel on another window.
+ // then, you'll see that the another window becomes active but the
+ // owner window of the bookmark panel looks still active and the
+ // bookmark panel keeps open. The reason is that the first wheel
+ // operation gives focus to the bookmark panel. Therefore, when
+ // the next operation gives focus to the another window, previous
+ // focus window is the bookmark panel (i.e., a popup window).
+ // So, in this case, our hack around here prevents to inactivate
+ // the owner window and roll up the bookmark panel.
+ ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam));
+ }
+ return true;
+
+ case WM_NCACTIVATE:
+ if (!aWParam && !sSendingNCACTIVATE &&
+ NeedsToHandleNCActivateDelayed(aWnd)) {
+ // Don't just consume WM_NCACTIVATE. It doesn't handle only the
+ // nonclient area state change.
+ ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam);
+ // Accept the deactivating because it's necessary to receive following
+ // WM_ACTIVATE.
+ *aResult = TRUE;
+ sPendingNCACTIVATE = true;
+ return true;
+ }
+ return false;
+
+ case WM_MOUSEACTIVATE:
+ if (!EventIsInsideWindow(popupWindow) &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse
+ // of TweakUI is enabled. Then, check if the popup should be rolled up
+ // with rollup listener. If not, just consume the message.
+ if (HIWORD(aLParam) == WM_MOUSEMOVE &&
+ !rollupListener->ShouldRollupOnMouseActivate()) {
+ return true;
+ }
+ // Otherwise, it should be handled by wndproc.
+ return false;
+ }
+
+ // Prevent the click inside the popup from causing a change in window
+ // activation. Since the popup is shown non-activated, we need to eat any
+ // requests to activate the window while it is displayed. Windows will
+ // automatically activate the popup on the mousedown otherwise.
+ return true;
+
+ case WM_SHOWWINDOW:
+ // If the window is being minimized, close popups.
+ if (aLParam == SW_PARENTCLOSING) {
+ allowAnimations = nsIRollupListener::AllowAnimations::No;
+ break;
+ }
+ return false;
+
+ case WM_KILLFOCUS:
+ // If focus moves to other window created in different process/thread,
+ // e.g., a plugin window, popups should be rolled up.
+ if (IsDifferentThreadWindow(reinterpret_cast<HWND>(aWParam))) {
+ allowAnimations = nsIRollupListener::AllowAnimations::No;
+ break;
+ }
+ return false;
+
+ case WM_MOVING:
+ case WM_MENUSELECT:
+ break;
+
+ default:
+ return false;
+ }
+
+ // Only need to deal with the last rollup for left mouse down events.
+ NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
+
+ nsIRollupListener::RollupOptions rollupOptions{
+ popupsToRollup,
+ nsIRollupListener::FlushViews::Yes,
+ /* mPoint = */ nullptr,
+ allowAnimations,
+ };
+
+ if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
+ nativeMessage == WM_POINTERDOWN) {
+ LayoutDeviceIntPoint pos;
+ if (nativeMessage == WM_TOUCH) {
+ pos.x = touchPoint->x;
+ pos.y = touchPoint->y;
+ } else {
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ // POINTERDOWN is already in screen coords.
+ if (nativeMessage == WM_LBUTTONDOWN) {
+ ::ClientToScreen(aWnd, &pt);
+ }
+ pos = LayoutDeviceIntPoint(pt.x, pt.y);
+ }
+
+ rollupOptions.mPoint = &pos;
+ nsIContent* lastRollup = nullptr;
+ consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
+ nsAutoRollup::SetLastRollup(lastRollup);
+ } else {
+ consumeRollupEvent = rollupListener->Rollup(rollupOptions);
+ }
+
+ // Tell hook to stop processing messages
+ sProcessHook = false;
+ sRollupMsgId = 0;
+ sRollupMsgWnd = nullptr;
+
+ // If we are NOT supposed to be consuming events, let it go through
+ if (consumeRollupEvent && nativeMessage != WM_RBUTTONDOWN) {
+ *aResult = MA_ACTIVATE;
+ return true;
+ }
+
+ return false;
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Misc. utility methods and functions.
+ **
+ ** General use.
+ **
+ **************************************************************
+ **************************************************************/
+
+// Note that the result of GetTopLevelWindow method can be different from the
+// result of WinUtils::GetTopLevelHWND(). The result can be non-floating
+// window. Because our top level window may be contained in another window
+// which is not managed by us.
+nsWindow* nsWindow::GetTopLevelWindow(bool aStopOnDialogOrPopup) {
+ nsWindow* curWindow = this;
+
+ while (true) {
+ if (aStopOnDialogOrPopup) {
+ switch (curWindow->mWindowType) {
+ case WindowType::Dialog:
+ case WindowType::Popup:
+ return curWindow;
+ default:
+ break;
+ }
+ }
+
+ // Retrieve the top level parent or owner window
+ nsWindow* parentWindow = curWindow->GetParentWindow(true);
+
+ if (!parentWindow) return curWindow;
+
+ curWindow = parentWindow;
+ }
+}
+
+// Set a flag if hwnd is a (non-popup) visible window from this process,
+// and bail out of the enumeration. Otherwise leave the flag unmodified
+// and continue the enumeration.
+// lParam must be a bool* pointing at the flag to be set.
+static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) {
+ DWORD pid;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) {
+ // Don't count popups as visible windows, since they don't take focus,
+ // in case we only have a popup visible (see bug 1554490 where the gfx
+ // test window is an offscreen popup).
+ nsWindow* window = WinUtils::GetNSWindowPtr(hwnd);
+ if (!window || !window->IsPopup()) {
+ bool* windowsVisible = reinterpret_cast<bool*>(lParam);
+ *windowsVisible = true;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+// Determine if it would be ok to activate a window, taking focus.
+// We want to avoid stealing focus from another app (bug 225305).
+bool nsWindow::CanTakeFocus() {
+ HWND fgWnd = ::GetForegroundWindow();
+ if (!fgWnd) {
+ // There is no foreground window, so don't worry about stealing focus.
+ return true;
+ }
+ // We can take focus if the current foreground window is already from
+ // this process.
+ DWORD pid;
+ ::GetWindowThreadProcessId(fgWnd, &pid);
+ if (pid == ::GetCurrentProcessId()) {
+ return true;
+ }
+
+ bool windowsVisible = false;
+ ::EnumWindows(EnumVisibleWindowsProc,
+ reinterpret_cast<LPARAM>(&windowsVisible));
+
+ if (!windowsVisible) {
+ // We're probably creating our first visible window, allow that to
+ // take focus.
+ return true;
+ }
+ return false;
+}
+
+/* static */ const wchar_t* nsWindow::GetMainWindowClass() {
+ static const wchar_t* sMainWindowClass = nullptr;
+ if (!sMainWindowClass) {
+ nsAutoString className;
+ Preferences::GetString("ui.window_class_override", className);
+ if (!className.IsEmpty()) {
+ sMainWindowClass = wcsdup(className.get());
+ } else {
+ sMainWindowClass = kClassNameGeneral;
+ }
+ }
+ return sMainWindowClass;
+}
+
+LPARAM nsWindow::lParamToScreen(LPARAM lParam) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ::ClientToScreen(mWnd, &pt);
+ return MAKELPARAM(pt.x, pt.y);
+}
+
+LPARAM nsWindow::lParamToClient(LPARAM lParam) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ::ScreenToClient(mWnd, &pt);
+ return MAKELPARAM(pt.x, pt.y);
+}
+
+WPARAM nsWindow::wParamFromGlobalMouseState() {
+ WPARAM result = 0;
+
+ if (!!::GetKeyState(VK_CONTROL)) {
+ result |= MK_CONTROL;
+ }
+
+ if (!!::GetKeyState(VK_SHIFT)) {
+ result |= MK_SHIFT;
+ }
+
+ if (!!::GetKeyState(VK_LBUTTON)) {
+ result |= MK_LBUTTON;
+ }
+
+ if (!!::GetKeyState(VK_MBUTTON)) {
+ result |= MK_MBUTTON;
+ }
+
+ if (!!::GetKeyState(VK_RBUTTON)) {
+ result |= MK_RBUTTON;
+ }
+
+ if (!!::GetKeyState(VK_XBUTTON1)) {
+ result |= MK_XBUTTON1;
+ }
+
+ if (!!::GetKeyState(VK_XBUTTON2)) {
+ result |= MK_XBUTTON2;
+ }
+
+ return result;
+}
+
+void nsWindow::PickerOpen() {
+ AssertIsOnMainThread();
+ mPickerDisplayCount++;
+}
+
+void nsWindow::PickerClosed() {
+ AssertIsOnMainThread();
+ NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
+ if (!mPickerDisplayCount) return;
+ mPickerDisplayCount--;
+
+ // WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
+ // top-level ancestor of its provided owner-window. If the modal window's
+ // container process crashes, it will never get a chance to undo that, so we
+ // do it manually here.
+ //
+ // Note that this may cause problems in the embedded case if you reparent a
+ // subtree of the native window hierarchy containing a Gecko window while that
+ // Gecko window has a file-dialog open.
+ if (!mPickerDisplayCount) {
+ ::EnableWindow(::GetAncestor(GetWindowHandle(), GA_ROOT), TRUE);
+ }
+
+ if (!mPickerDisplayCount && mDestroyCalled) {
+ Destroy();
+ }
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ // We don't currently support using an accelerated layer manager with
+ // transparent windows so don't even try. I'm also not sure if we even
+ // want to support this case. See bug 593471.
+ //
+ // Windows' support for transparent accelerated surfaces isn't great.
+ // Some possible approaches:
+ // - Readback the data and update it using
+ // UpdateLayeredWindow/UpdateLayeredWindowIndirect
+ // This is what WPF does. See
+ // CD3DDeviceLevel1::PresentWithGDI/CD3DSwapChainWithSwDC in WpfGfx. The
+ // rationale for not using IDirect3DSurface9::GetDC is explained here:
+ // https://web.archive.org/web/20160521191104/https://blogs.msdn.microsoft.com/dwayneneed/2008/09/08/transparent-windows-in-wpf/
+ // - Use D3D11_RESOURCE_MISC_GDI_COMPATIBLE, IDXGISurface1::GetDC(),
+ // and UpdateLayeredWindowIndirect.
+ // This is suggested here:
+ // https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/december/windows-with-c-layered-windows-with-direct2d
+ // but might have the same problem that IDirect3DSurface9::GetDC has.
+ // - Creating the window with the WS_EX_NOREDIRECTIONBITMAP flag and use
+ // DirectComposition.
+ // Not supported on Win7.
+ // - Using DwmExtendFrameIntoClientArea with negative margins and something
+ // to turn off the glass effect.
+ // This doesn't work when the DWM is not running (Win7)
+ //
+ // Also see bug 1150376, D3D11 composition can cause issues on some devices
+ // on Windows 7 where presentation fails randomly for windows with drop
+ // shadows.
+ return mTransparencyMode != TransparencyMode::Transparent &&
+ !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
+}
+
+bool nsWindow::DispatchTouchEventFromWMPointer(
+ UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo,
+ mozilla::MouseButton aButton) {
+ MultiTouchInput::MultiTouchType touchType;
+ switch (msg) {
+ case WM_POINTERDOWN:
+ touchType = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case WM_POINTERUPDATE:
+ if (aPointerInfo.mPressure == 0) {
+ return false; // hover
+ }
+ touchType = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case WM_POINTERUP:
+ touchType = MultiTouchInput::MULTITOUCH_END;
+ break;
+ default:
+ return false;
+ }
+
+ nsPointWin touchPoint;
+ touchPoint.x = GET_X_LPARAM(aLParam);
+ touchPoint.y = GET_Y_LPARAM(aLParam);
+ touchPoint.ScreenToClient(mWnd);
+
+ SingleTouchData touchData(static_cast<int32_t>(aPointerInfo.pointerId),
+ ScreenIntPoint::FromUnknownPoint(touchPoint),
+ ScreenSize(1, 1), // pixel size radius for pen
+ 0.0f, // no radius rotation
+ aPointerInfo.mPressure);
+ touchData.mTiltX = aPointerInfo.tiltX;
+ touchData.mTiltY = aPointerInfo.tiltY;
+ touchData.mTwist = aPointerInfo.twist;
+
+ MultiTouchInput touchInput;
+ touchInput.mType = touchType;
+ touchInput.mTimeStamp = GetMessageTimeStamp(::GetMessageTime());
+ touchInput.mTouches.AppendElement(touchData);
+ touchInput.mButton = aButton;
+ touchInput.mButtons = aPointerInfo.mButtons;
+
+ // POINTER_INFO.dwKeyStates can't be used as it only supports Shift and Ctrl
+ ModifierKeyState modifierKeyState;
+ touchInput.modifiers = modifierKeyState.GetModifiers();
+
+ DispatchTouchInput(touchInput, MouseEvent_Binding::MOZ_SOURCE_PEN);
+ return true;
+}
+
+static MouseButton PenFlagsToMouseButton(PEN_FLAGS aPenFlags) {
+ // Theoretically flags can be set together but they do not
+ if (aPenFlags & PEN_FLAG_BARREL) {
+ return MouseButton::eSecondary;
+ }
+ if (aPenFlags & PEN_FLAG_ERASER) {
+ return MouseButton::eEraser;
+ }
+ return MouseButton::ePrimary;
+}
+
+bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) {
+ if (!mAPZC) {
+ // APZ is not available on context menu. Follow the behavior of touch input
+ // which fallbacks to WM_LBUTTON* and WM_GESTURE, to keep consistency.
+ return false;
+ }
+ if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) {
+ return false;
+ }
+ if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) {
+ // We have to handle WM_POINTER* to fetch and cache pen related information
+ // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN
+ // handler. This is because Windows doesn't support ::DoDragDrop in the
+ // touch or pen message handlers.
+ mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam);
+ // Don't consume the Windows WM_POINTER* messages
+ return false;
+ }
+
+ uint32_t pointerId = mPointerEvents.GetPointerId(aWParam);
+ POINTER_PEN_INFO penInfo{};
+ if (!mPointerEvents.GetPointerPenInfo(pointerId, &penInfo)) {
+ return false;
+ }
+
+ // When dispatching mouse events with pen, there may be some
+ // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with
+ // small movements. Those events will reset sLastMousePoint and reset
+ // sLastClickCount. To prevent that, we keep the last pen down position
+ // and compare it with the subsequent WM_POINTERUPDATE. If the movement is
+ // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing
+ // eMouseMove for WM_POINTERUPDATE.
+ static POINT sLastPointerDownPoint = {0};
+
+ // We don't support chorded buttons for pen. Keep the button at
+ // WM_POINTERDOWN.
+ static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary;
+ static bool sPointerDown = false;
+
+ EventMessage message;
+ mozilla::MouseButton button = MouseButton::ePrimary;
+ switch (msg) {
+ case WM_POINTERDOWN: {
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
+ GET_Y_LPARAM(aLParam));
+ sLastPointerDownPoint.x = eventPoint.x;
+ sLastPointerDownPoint.y = eventPoint.y;
+ message = eMouseDown;
+ button = PenFlagsToMouseButton(penInfo.penFlags);
+ sLastPenDownButton = button;
+ sPointerDown = true;
+ } break;
+ case WM_POINTERUP:
+ message = eMouseUp;
+ MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN");
+ button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary;
+ sPointerDown = false;
+ break;
+ case WM_POINTERUPDATE:
+ message = eMouseMove;
+ if (sPointerDown) {
+ LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam),
+ GET_Y_LPARAM(aLParam));
+ int32_t movementX = sLastPointerDownPoint.x > eventPoint.x
+ ? sLastPointerDownPoint.x - eventPoint.x.value
+ : eventPoint.x.value - sLastPointerDownPoint.x;
+ int32_t movementY = sLastPointerDownPoint.y > eventPoint.y
+ ? sLastPointerDownPoint.y - eventPoint.y.value
+ : eventPoint.y.value - sLastPointerDownPoint.y;
+ bool insideMovementThreshold =
+ movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) &&
+ movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG);
+
+ if (insideMovementThreshold) {
+ // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement
+ // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG
+ return false;
+ }
+ button = sLastPenDownButton;
+ }
+ break;
+ case WM_POINTERLEAVE:
+ message = eMouseExitFromWidget;
+ break;
+ default:
+ return false;
+ }
+
+ // Windows defines the pen pressure is normalized to a range between 0 and
+ // 1024. Convert it to float.
+ float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
+ int16_t buttons = sPointerDown
+ ? nsContentUtils::GetButtonsFlagForButton(button)
+ : MouseButtonsFlag::eNoButtons;
+ WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure,
+ buttons);
+ // Per
+ // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_pen_info,
+ // the rotation is normalized in a range of 0 to 359.
+ MOZ_ASSERT(penInfo.rotation <= 359);
+ pointerInfo.twist = (int32_t)penInfo.rotation;
+
+ // Fire touch events but not when the barrel button is pressed.
+ if (button != MouseButton::eSecondary &&
+ StaticPrefs::dom_w3c_pointer_events_scroll_by_pen_enabled() &&
+ DispatchTouchEventFromWMPointer(msg, aLParam, pointerInfo, button)) {
+ return true;
+ }
+
+ // The aLParam of WM_POINTER* is the screen location. Convert it to client
+ // location
+ LPARAM newLParam = lParamToClient(aLParam);
+ DispatchMouseEvent(message, aWParam, newLParam, false, button,
+ MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
+
+ if (button == MouseButton::eSecondary && message == eMouseUp) {
+ // Fire eContextMenu manually since consuming WM_POINTER* blocks
+ // WM_CONTEXTMENU
+ DispatchMouseEvent(eContextMenu, aWParam, newLParam, false, button,
+ MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo);
+ }
+ // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP
+ // WM_MOUSEMOVE.
+ return true;
+}
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ *aInitData = WinCompositorWidgetInitData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode, mFrameState->GetSizeMode());
+}
+
+bool nsWindow::SynchronouslyRepaintOnResize() { return false; }
+
+void nsWindow::MaybeDispatchInitialFocusEvent() {
+ if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) {
+ DispatchFocusToTopLevelWindow(true);
+ }
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow(true);
+ return window.forget();
+}
+
+// static
+bool nsWindow::InitTouchInjection() {
+ if (!sTouchInjectInitialized) {
+ // Initialize touch injection on the first call
+ HMODULE hMod = LoadLibraryW(kUser32LibName);
+ if (!hMod) {
+ return false;
+ }
+
+ InitializeTouchInjectionPtr func =
+ (InitializeTouchInjectionPtr)GetProcAddress(hMod,
+ "InitializeTouchInjection");
+ if (!func) {
+ WinUtils::Log("InitializeTouchInjection not available.");
+ return false;
+ }
+
+ if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
+ WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d",
+ GetLastError());
+ return false;
+ }
+
+ sInjectTouchFuncPtr =
+ (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
+ if (!sInjectTouchFuncPtr) {
+ WinUtils::Log("InjectTouchInput not available.");
+ return false;
+ }
+ sTouchInjectInitialized = true;
+ }
+ return true;
+}
+
+bool nsWindow::InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
+ POINTER_FLAGS aFlags, uint32_t aPressure,
+ uint32_t aOrientation) {
+ if (aId > TOUCH_INJECT_MAX_POINTS) {
+ WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
+ return false;
+ }
+
+ POINTER_TOUCH_INFO info{};
+
+ info.touchFlags = TOUCH_FLAG_NONE;
+ info.touchMask =
+ TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
+ info.pressure = aPressure;
+ info.orientation = aOrientation;
+
+ info.pointerInfo.pointerFlags = aFlags;
+ info.pointerInfo.pointerType = PT_TOUCH;
+ info.pointerInfo.pointerId = aId;
+ info.pointerInfo.ptPixelLocation.x = aPoint.x;
+ info.pointerInfo.ptPixelLocation.y = aPoint.y;
+
+ info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
+ info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
+ info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
+ info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
+
+ for (int i = 0; i < 3; i++) {
+ if (sInjectTouchFuncPtr(1, &info)) {
+ break;
+ }
+ DWORD error = GetLastError();
+ if (error == ERROR_NOT_READY && i < 2) {
+ // We sent it too quickly after the previous injection (see bug 1535140
+ // comment 10). On the first loop iteration we just yield (via Sleep(0))
+ // and try again. If it happens again on the second loop iteration we
+ // explicitly Sleep(1) and try again. If that doesn't work either we just
+ // error out.
+ ::Sleep(i);
+ continue;
+ }
+ WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error);
+ return false;
+ }
+ return true;
+}
+
+void nsWindow::ChangedDPI() {
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+}
+
+static Result<POINTER_FLAGS, nsresult> PointerStateToFlag(
+ nsWindow::TouchPointerState aPointerState, bool isUpdate) {
+ bool hover = aPointerState & nsWindow::TOUCH_HOVER;
+ bool contact = aPointerState & nsWindow::TOUCH_CONTACT;
+ bool remove = aPointerState & nsWindow::TOUCH_REMOVE;
+ bool cancel = aPointerState & nsWindow::TOUCH_CANCEL;
+
+ POINTER_FLAGS flags;
+ if (isUpdate) {
+ // We know about this pointer, send an update
+ flags = POINTER_FLAG_UPDATE;
+ if (hover) {
+ flags |= POINTER_FLAG_INRANGE;
+ } else if (contact) {
+ flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_INRANGE;
+ } else if (remove) {
+ flags = POINTER_FLAG_UP;
+ }
+
+ if (cancel) {
+ flags |= POINTER_FLAG_CANCELED;
+ }
+ } else {
+ // Missing init state, error out
+ if (remove || cancel) {
+ return Err(NS_ERROR_INVALID_ARG);
+ }
+
+ // Create a new pointer
+ flags = POINTER_FLAG_INRANGE;
+ if (contact) {
+ flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN;
+ }
+ }
+ return flags;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(
+ uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPointerPressure,
+ uint32_t aPointerOrientation, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (StaticPrefs::apz_test_fails_with_native_injection() ||
+ !InitTouchInjection()) {
+ // If we don't have touch injection from the OS, or if we are running a test
+ // that cannot properly inject events to satisfy the OS requirements (see
+ // bug 1313170) we can just fake it and synthesize the events from here.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ WidgetEventTime time = CurrentMessageWidgetEventTime();
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), time.mTimeStamp, aPointerId,
+ aPointerState, pointInWindow, aPointerPressure, aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+ }
+
+ // win api expects a value from 0 to 1024. aPointerPressure is a value
+ // from 0.0 to 1.0.
+ uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
+
+ // If we already know about this pointer id get it's record
+ return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
+ POINTER_FLAGS flags;
+ // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
+ auto result = PointerStateToFlag(aPointerState, !!entry);
+ if (result.isOk()) {
+ flags = result.unwrap();
+ } else {
+ return result.unwrapErr();
+ }
+
+ if (!entry) {
+ entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
+ PointerInfo::PointerType::TOUCH));
+ } else {
+ if (entry.Data()->mType != PointerInfo::PointerType::TOUCH) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (aPointerState & TOUCH_REMOVE) {
+ // Remove the pointer from our tracking list. This is UniquePtr wrapped,
+ // so shouldn't leak.
+ entry.Remove();
+ }
+ }
+
+ return !InjectTouchPoint(aPointerId, aPoint, flags, pressure,
+ aPointerOrientation)
+ ? NS_ERROR_UNEXPECTED
+ : NS_OK;
+ });
+}
+
+nsresult nsWindow::ClearNativeTouchSequence(nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "cleartouch");
+ if (!sTouchInjectInitialized) {
+ return NS_OK;
+ }
+
+ // cancel all input points
+ for (auto iter = mActivePointers.Iter(); !iter.Done(); iter.Next()) {
+ auto* info = iter.UserData();
+ if (info->mType != PointerInfo::PointerType::TOUCH) {
+ continue;
+ }
+ InjectTouchPoint(info->mPointerId, info->mPosition, POINTER_FLAG_CANCELED);
+ iter.Remove();
+ }
+
+ nsBaseWidget::ClearNativeTouchSequence(nullptr);
+
+ return NS_OK;
+}
+
+#if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
+static CreateSyntheticPointerDevicePtr CreateSyntheticPointerDevice;
+static DestroySyntheticPointerDevicePtr DestroySyntheticPointerDevice;
+static InjectSyntheticPointerInputPtr InjectSyntheticPointerInput;
+#endif
+static HSYNTHETICPOINTERDEVICE sSyntheticPenDevice;
+
+static bool InitPenInjection() {
+ if (sSyntheticPenDevice) {
+ return true;
+ }
+#if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
+ HMODULE hMod = LoadLibraryW(kUser32LibName);
+ if (!hMod) {
+ return false;
+ }
+ CreateSyntheticPointerDevice =
+ (CreateSyntheticPointerDevicePtr)GetProcAddress(
+ hMod, "CreateSyntheticPointerDevice");
+ if (!CreateSyntheticPointerDevice) {
+ WinUtils::Log("CreateSyntheticPointerDevice not available.");
+ return false;
+ }
+ DestroySyntheticPointerDevice =
+ (DestroySyntheticPointerDevicePtr)GetProcAddress(
+ hMod, "DestroySyntheticPointerDevice");
+ if (!DestroySyntheticPointerDevice) {
+ WinUtils::Log("DestroySyntheticPointerDevice not available.");
+ return false;
+ }
+ InjectSyntheticPointerInput = (InjectSyntheticPointerInputPtr)GetProcAddress(
+ hMod, "InjectSyntheticPointerInput");
+ if (!InjectSyntheticPointerInput) {
+ WinUtils::Log("InjectSyntheticPointerInput not available.");
+ return false;
+ }
+#endif
+ sSyntheticPenDevice =
+ CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_DEFAULT);
+ return !!sSyntheticPenDevice;
+}
+
+nsresult nsWindow::SynthesizeNativePenInput(
+ uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint, double aPressure, uint32_t aRotation,
+ int32_t aTiltX, int32_t aTiltY, int32_t aButton, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "peninput");
+ if (!InitPenInjection()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // win api expects a value from 0 to 1024. aPointerPressure is a value
+ // from 0.0 to 1.0.
+ uint32_t pressure = (uint32_t)ceil(aPressure * 1024);
+
+ // If we already know about this pointer id get it's record
+ return mActivePointers.WithEntryHandle(aPointerId, [&](auto&& entry) {
+ POINTER_FLAGS flags;
+ // Can't use MOZ_TRY_VAR because it confuses WithEntryHandle
+ auto result = PointerStateToFlag(aPointerState, !!entry);
+ if (result.isOk()) {
+ flags = result.unwrap();
+ } else {
+ return result.unwrapErr();
+ }
+
+ if (!entry) {
+ entry.Insert(MakeUnique<PointerInfo>(aPointerId, aPoint,
+ PointerInfo::PointerType::PEN));
+ } else {
+ if (entry.Data()->mType != PointerInfo::PointerType::PEN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (aPointerState & TOUCH_REMOVE) {
+ // Remove the pointer from our tracking list. This is UniquePtr wrapped,
+ // so shouldn't leak.
+ entry.Remove();
+ }
+ }
+
+ POINTER_TYPE_INFO info{};
+
+ info.type = PT_PEN;
+ info.penInfo.pointerInfo.pointerType = PT_PEN;
+ info.penInfo.pointerInfo.pointerFlags = flags;
+ info.penInfo.pointerInfo.pointerId = aPointerId;
+ info.penInfo.pointerInfo.ptPixelLocation.x = aPoint.x;
+ info.penInfo.pointerInfo.ptPixelLocation.y = aPoint.y;
+
+ info.penInfo.penFlags = PEN_FLAG_NONE;
+ // PEN_FLAG_ERASER is not supported this way, unfortunately.
+ if (aButton == 2) {
+ info.penInfo.penFlags |= PEN_FLAG_BARREL;
+ }
+ info.penInfo.penMask = PEN_MASK_PRESSURE | PEN_MASK_ROTATION |
+ PEN_MASK_TILT_X | PEN_MASK_TILT_Y;
+ info.penInfo.pressure = pressure;
+ info.penInfo.rotation = aRotation;
+ info.penInfo.tiltX = aTiltX;
+ info.penInfo.tiltY = aTiltY;
+
+ return InjectSyntheticPointerInput(sSyntheticPenDevice, &info, 1)
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+ });
+};
+
+bool nsWindow::HandleAppCommandMsg(const MSG& aAppCommandMsg,
+ LRESULT* aRetValue) {
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
+ bool consumed = nativeKey.HandleAppCommandMessage();
+ *aRetValue = consumed ? 1 : 0;
+ return consumed;
+}
+
+#ifdef DEBUG
+nsresult nsWindow::SetHiDPIMode(bool aHiDPI) {
+ return WinUtils::SetHiDPIMode(aHiDPI);
+}
+
+nsresult nsWindow::RestoreHiDPIMode() { return WinUtils::RestoreHiDPIMode(); }
+#endif
+
+mozilla::Maybe<UINT> nsWindow::GetHiddenTaskbarEdge() {
+ HMONITOR windowMonitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONEAREST);
+
+ // Check all four sides of our monitor for an appbar. Skip any that aren't
+ // the system taskbar.
+ MONITORINFO mi;
+ mi.cbSize = sizeof(MONITORINFO);
+ ::GetMonitorInfo(windowMonitor, &mi);
+
+ APPBARDATA appBarData;
+ appBarData.cbSize = sizeof(appBarData);
+ appBarData.rc = mi.rcMonitor;
+ const auto kEdges = {ABE_BOTTOM, ABE_TOP, ABE_LEFT, ABE_RIGHT};
+ for (auto edge : kEdges) {
+ appBarData.uEdge = edge;
+ HWND appBarHwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &appBarData);
+ if (appBarHwnd) {
+ nsAutoString className;
+ if (WinUtils::GetClassName(appBarHwnd, className)) {
+ if (className.Equals(L"Shell_TrayWnd") ||
+ className.Equals(L"Shell_SecondaryTrayWnd")) {
+ return Some(edge);
+ }
+ }
+ }
+ }
+
+ return Nothing();
+}
+
+static nsSizeMode GetSizeModeForWindowFrame(HWND aWnd, bool aFullscreenMode) {
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(aWnd, &pl);
+
+ if (pl.showCmd == SW_SHOWMINIMIZED) {
+ return nsSizeMode_Minimized;
+ } else if (aFullscreenMode) {
+ return nsSizeMode_Fullscreen;
+ } else if (pl.showCmd == SW_SHOWMAXIMIZED) {
+ return nsSizeMode_Maximized;
+ } else {
+ return nsSizeMode_Normal;
+ }
+}
+
+static void ShowWindowWithMode(HWND aWnd, nsSizeMode aMode) {
+ // This will likely cause a callback to
+ // nsWindow::FrameState::{OnFrameChanging() and OnFrameChanged()}
+ switch (aMode) {
+ case nsSizeMode_Fullscreen:
+ ::ShowWindow(aWnd, SW_SHOW);
+ break;
+
+ case nsSizeMode_Maximized:
+ ::ShowWindow(aWnd, SW_MAXIMIZE);
+ break;
+
+ case nsSizeMode_Minimized:
+ ::ShowWindow(aWnd, SW_MINIMIZE);
+ break;
+
+ default:
+ // Don't call ::ShowWindow if we're trying to "restore" a window that is
+ // already in a normal state. Prevents a bug where snapping to one side
+ // of the screen and then minimizing would cause Windows to forget our
+ // window's correct restored position/size.
+ if (GetCurrentShowCmd(aWnd) != SW_SHOWNORMAL) {
+ ::ShowWindow(aWnd, SW_RESTORE);
+ }
+ }
+}
+
+nsWindow::FrameState::FrameState(nsWindow* aWindow) : mWindow(aWindow) {}
+
+nsSizeMode nsWindow::FrameState::GetSizeMode() const { return mSizeMode; }
+
+void nsWindow::FrameState::CheckInvariant() const {
+ MOZ_ASSERT(mSizeMode >= 0 && mSizeMode < nsSizeMode_Invalid);
+ MOZ_ASSERT(mLastSizeMode >= 0 && mLastSizeMode < nsSizeMode_Invalid);
+ MOZ_ASSERT(mPreFullscreenSizeMode >= 0 &&
+ mPreFullscreenSizeMode < nsSizeMode_Invalid);
+ MOZ_ASSERT(mWindow);
+
+ // We should never observe fullscreen sizemode unless fullscreen is enabled
+ MOZ_ASSERT_IF(mSizeMode == nsSizeMode_Fullscreen, mFullscreenMode);
+ MOZ_ASSERT_IF(!mFullscreenMode, mSizeMode != nsSizeMode_Fullscreen);
+
+ // Something went wrong if we somehow saved fullscreen mode when we are
+ // changing into fullscreen mode
+ MOZ_ASSERT(mPreFullscreenSizeMode != nsSizeMode_Fullscreen);
+}
+
+void nsWindow::FrameState::ConsumePreXULSkeletonState(bool aWasMaximized) {
+ mSizeMode = aWasMaximized ? nsSizeMode_Maximized : nsSizeMode_Normal;
+}
+
+void nsWindow::FrameState::EnsureSizeMode(nsSizeMode aMode,
+ DoShowWindow aDoShowWindow) {
+ if (mSizeMode == aMode) {
+ return;
+ }
+
+ if (StaticPrefs::widget_windows_fullscreen_remind_taskbar()) {
+ // If we're unminimizing a window, asynchronously notify the taskbar after
+ // the message has been processed. This redundant notification works around
+ // a race condition in explorer.exe. (See bug 1835851, or comments in
+ // TaskbarConcealer.)
+ //
+ // Note that we notify regardless of `aMode`: unminimizing a non-fullscreen
+ // window can also affect the correct taskbar state, yet fail to affect the
+ // current taskbar state.
+ if (mSizeMode == nsSizeMode_Minimized) {
+ ::PostMessage(mWindow->mWnd, MOZ_WM_FULLSCREEN_STATE_UPDATE, 0, 0);
+ }
+ }
+
+ if (aMode == nsSizeMode_Fullscreen) {
+ EnsureFullscreenMode(true, aDoShowWindow);
+ MOZ_ASSERT(mSizeMode == nsSizeMode_Fullscreen);
+ } else if (mSizeMode == nsSizeMode_Fullscreen && aMode == nsSizeMode_Normal) {
+ // If we are in fullscreen mode, minimize should work like normal and
+ // return us to fullscreen mode when unminimized. Maximize isn't really
+ // available and won't do anything. "Restore" should do the same thing as
+ // requesting to end fullscreen.
+ EnsureFullscreenMode(false, aDoShowWindow);
+ } else {
+ SetSizeModeInternal(aMode, aDoShowWindow);
+ }
+}
+
+void nsWindow::FrameState::EnsureFullscreenMode(bool aFullScreen,
+ DoShowWindow aDoShowWindow) {
+ const bool changed = aFullScreen != mFullscreenMode;
+ if (changed && aFullScreen) {
+ // Save the size mode from before fullscreen.
+ mPreFullscreenSizeMode = mSizeMode;
+ }
+ mFullscreenMode = aFullScreen;
+ if (changed || aFullScreen) {
+ // NOTE(emilio): When minimizing a fullscreen window we remain with
+ // mFullscreenMode = true, but mSizeMode = nsSizeMode_Minimized. We need to
+ // make sure to call SetSizeModeInternal even if mFullscreenMode didn't
+ // change, to ensure we actually end up with a fullscreen sizemode when
+ // restoring a window from that state.
+ SetSizeModeInternal(
+ aFullScreen ? nsSizeMode_Fullscreen : mPreFullscreenSizeMode,
+ aDoShowWindow);
+ }
+}
+
+void nsWindow::FrameState::OnFrameChanging() {
+ const nsSizeMode newSizeMode =
+ GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
+ EnsureSizeMode(newSizeMode);
+ mWindow->UpdateNonClientMargins(false);
+}
+
+void nsWindow::FrameState::OnFrameChanged() {
+ // We don't want to perform the ShowWindow ourselves if we're on the frame
+ // changed message. Windows has done the frame change for us, and we take care
+ // of activating as needed. We also don't want to potentially trigger
+ // more focus / restore. Among other things, this addresses a bug on Win7
+ // related to window docking. (bug 489258)
+ const auto newSizeMode =
+ GetSizeModeForWindowFrame(mWindow->mWnd, mFullscreenMode);
+ EnsureSizeMode(newSizeMode, DoShowWindow::No);
+
+ // If window was restored, activate the window now to get correct attributes.
+ if (mWindow->mIsVisible && mWindow->IsForegroundWindow() &&
+ mLastSizeMode == nsSizeMode_Minimized &&
+ mSizeMode != nsSizeMode_Minimized) {
+ mWindow->DispatchFocusToTopLevelWindow(true);
+ }
+ mLastSizeMode = mSizeMode;
+}
+
+static void MaybeLogSizeMode(nsSizeMode aMode) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** SizeMode: %d\n", int(aMode)));
+#endif
+}
+
+void nsWindow::FrameState::SetSizeModeInternal(nsSizeMode aMode,
+ DoShowWindow aDoShowWindow) {
+ if (mSizeMode == aMode) {
+ return;
+ }
+
+ const auto oldSizeMode = mSizeMode;
+ const bool fullscreenChange =
+ mSizeMode == nsSizeMode_Fullscreen || aMode == nsSizeMode_Fullscreen;
+ const bool fullscreen = aMode == nsSizeMode_Fullscreen;
+
+ mLastSizeMode = mSizeMode;
+ mSizeMode = aMode;
+
+ MaybeLogSizeMode(mSizeMode);
+
+ if (bool(aDoShowWindow) && mWindow->mIsVisible) {
+ ShowWindowWithMode(mWindow->mWnd, aMode);
+ }
+
+ mWindow->UpdateNonClientMargins(false);
+
+ if (fullscreenChange) {
+ mWindow->OnFullscreenChanged(oldSizeMode, fullscreen);
+ }
+
+ mWindow->OnSizeModeChange();
+}
+
+void nsWindow::ContextMenuPreventer::Update(
+ const WidgetMouseEvent& aEvent,
+ const nsIWidget::ContentAndAPZEventStatus& aEventStatus) {
+ mNeedsToPreventContextMenu =
+ aEvent.mMessage == eMouseUp &&
+ aEvent.mButton == MouseButton::eSecondary &&
+ aEvent.mInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE &&
+ aEventStatus.mApzStatus == nsEventStatus_eConsumeNoDefault;
+}
diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
new file mode 100644
index 0000000000..c75c9d174d
--- /dev/null
+++ b/widget/windows/nsWindow.h
@@ -0,0 +1,905 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WIDGET_WINDOWS_NSWINDOW_H_
+#define WIDGET_WINDOWS_NSWINDOW_H_
+
+/*
+ * nsWindow - Native window management and event handling.
+ */
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "CompositorWidget.h"
+#include "mozilla/EventForwards.h"
+#include "nsClassHashtable.h"
+#include <windows.h>
+#include "touchinjection_sdk80.h"
+#include "nsdefs.h"
+#include "nsUserIdleService.h"
+#include "nsToolkit.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "nsWindowDbg.h"
+#include "cairo.h"
+#include "nsRegion.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMargin.h"
+#include "nsRegionFwd.h"
+
+#include "nsWinGesture.h"
+#include "WinPointerEvents.h"
+#include "WinUtils.h"
+#include "WindowHook.h"
+#include "TaskbarWindowPreview.h"
+
+#ifdef ACCESSIBILITY
+# include "oleacc.h"
+# include "mozilla/a11y/LocalAccessible.h"
+#endif
+
+#include "nsUXThemeData.h"
+#include "nsIUserIdleServiceInternal.h"
+
+#include "IMMHandler.h"
+#include "CheckInvariantWrapper.h"
+
+/**
+ * Forward class definitions
+ */
+
+class nsNativeDragTarget;
+class nsIRollupListener;
+class imgIContainer;
+
+namespace mozilla {
+class WidgetMouseEvent;
+namespace widget {
+class NativeKey;
+class InProcessWinCompositorWidget;
+struct MSGResult;
+class DirectManipulationOwner;
+} // namespace widget
+} // namespace mozilla
+
+/**
+ * Forward Windows-internal definitions of otherwise incomplete ones provided by
+ * the SDK.
+ */
+const CLSID CLSID_ImmersiveShell = {
+ 0xC2F03A33,
+ 0x21F5,
+ 0x47FA,
+ {0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39}};
+
+/**
+ * Native WIN32 window wrapper.
+ */
+
+class nsWindow final : public nsBaseWidget {
+ public:
+ using WindowHook = mozilla::widget::WindowHook;
+ using IMEContext = mozilla::widget::IMEContext;
+ using WidgetEventTime = mozilla::WidgetEventTime;
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
+
+ explicit nsWindow(bool aIsChildWindow = false);
+
+ void SendAnAPZEvent(mozilla::InputData& aEvent);
+
+ /*
+ * Init a standard gecko event for this widget.
+ * @param aEvent the event to initialize.
+ * @param aPoint message position in physical coordinates.
+ */
+ void InitEvent(mozilla::WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr);
+
+ /*
+ * Returns WidgetEventTime instance which is initialized with current message
+ * time.
+ */
+ WidgetEventTime CurrentMessageWidgetEventTime() const;
+
+ /*
+ * Dispatch a gecko keyboard event for this widget. This
+ * is called by KeyboardLayout to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent);
+
+ /*
+ * Dispatch a gecko wheel event for this widget. This
+ * is called by ScrollHandler to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent);
+
+ /*
+ * Dispatch a gecko content command event for this widget. This
+ * is called by ScrollHandler to dispatch gecko events.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent);
+
+ /*
+ * Return the parent window, if it exists.
+ */
+ nsWindow* GetParentWindowBase(bool aIncludeOwner);
+
+ /*
+ * Return true if this is a top level widget.
+ */
+ bool IsTopLevelWidget() { return mIsTopWidgetWindow; }
+
+ // nsIWidget interface
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ InitData* aInitData = nullptr) override;
+ void Destroy() override;
+ void SetParent(nsIWidget* aNewParent) override;
+ nsIWidget* GetParent(void) override;
+ float GetDPI() override;
+ double GetDefaultScaleInternal() override;
+ int32_t LogToPhys(double aValue);
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override {
+ if (mozilla::widget::WinUtils::IsPerMonitorDPIAware()) {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ } else {
+ return mozilla::DesktopToLayoutDeviceScale(GetDefaultScaleInternal());
+ }
+ }
+
+ void Show(bool aState) override;
+ bool IsVisible() const override;
+ void ConstrainPosition(DesktopIntPoint&) override;
+ void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ void LockAspectRatio(bool aShouldLock) override;
+ const SizeConstraints GetSizeConstraints() override;
+ void SetInputRegion(const InputRegion&) override;
+ void Move(double aX, double aY) override;
+ void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ mozilla::Maybe<bool> IsResizingNativeWidget() override;
+ void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget* aWidget,
+ bool aActivate) override;
+ void SetSizeMode(nsSizeMode aMode) override;
+ nsSizeMode SizeMode() override;
+ void GetWorkspaceID(nsAString& workspaceID) override;
+ void MoveToWorkspace(const nsAString& workspaceID) override;
+ void SuppressAnimation(bool aSuppress) override;
+ void Enable(bool aState) override;
+ bool IsEnabled() const override;
+ void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ LayoutDeviceIntRect GetBounds() override;
+ LayoutDeviceIntRect GetScreenBounds() override;
+ [[nodiscard]] nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override;
+ LayoutDeviceIntRect GetClientBounds() override;
+ LayoutDeviceIntPoint GetClientOffset() override;
+ void SetBackgroundColor(const nscolor& aColor) override;
+ void SetCursor(const Cursor&) override;
+ bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ void CleanupFullscreenTransition() override;
+ nsresult MakeFullScreen(bool aFullScreen) override;
+ void HideWindowChrome(bool aShouldHide) override;
+ void Invalidate(bool aEraseBackground = false, bool aUpdateNCArea = false,
+ bool aIncludeChildren = false);
+ void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ void* GetNativeData(uint32_t aDataType) override;
+ void FreeNativeData(void* data, uint32_t aDataType) override;
+ nsresult SetTitle(const nsAString& aTitle) override;
+ void SetIcon(const nsAString& aIconSpec) override;
+ LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ LayoutDeviceIntMargin ClientToWindowMargin() override;
+ nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ void EnableDragDrop(bool aEnable) override;
+ void CaptureMouse(bool aCapture);
+ void CaptureRollupEvents(bool aDoCapture) override;
+ [[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override;
+ bool HasPendingInputEvent() override;
+ WindowRenderer* GetWindowRenderer() override;
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+ [[nodiscard]] nsresult OnDefaultButtonLoaded(
+ const LayoutDeviceIntRect& aButtonRect) override;
+ nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver) override;
+ nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ NativeMouseMessage aNativeMessage,
+ mozilla::MouseButton aButton,
+ nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override {
+ return SynthesizeNativeMouseEvent(
+ aPoint, NativeMouseMessage::Move, mozilla::MouseButton::eNotPressed,
+ nsIWidget::Modifiers::NO_MODIFIERS, aObserver);
+ }
+
+ nsresult SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY,
+ int32_t aModifierFlagsn,
+ nsIObserver* aObserver) override;
+
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ InputContext GetInputContext() override;
+ TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
+ void SetTransparencyMode(TransparencyMode aMode) override;
+ TransparencyMode GetTransparencyMode() override;
+ nsresult SetNonClientMargins(const LayoutDeviceIntMargin&) override;
+ void SetResizeMargin(mozilla::LayoutDeviceIntCoord aResizeMargin) override;
+ void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) override;
+
+ uint32_t GetMaxTouchPoints() const override;
+ void SetWindowClass(const nsAString& xulWinType, const nsAString& xulWinClass,
+ const nsAString& xulWinName) override;
+
+ /**
+ * Event helpers
+ */
+ bool DispatchMouseEvent(mozilla::EventMessage aEventMessage, WPARAM wParam,
+ LPARAM lParam, bool aIsContextMenuKey,
+ int16_t aButton, uint16_t aInputSource,
+ WinPointerInfo* aPointerInfo = nullptr,
+ bool aIgnoreAPZ = false);
+ void DispatchPendingEvents();
+ void DispatchCustomEvent(const nsString& eventName);
+
+#ifdef ACCESSIBILITY
+ /**
+ * Return an accessible associated with the window.
+ */
+ mozilla::a11y::LocalAccessible* GetAccessible();
+#endif // ACCESSIBILITY
+
+ /**
+ * Window utilities
+ */
+ nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup);
+ WNDPROC GetPrevWindowProc() { return mPrevWndProc.valueOr(nullptr); }
+ WindowHook& GetWindowHook() { return mWindowHook; }
+ nsWindow* GetParentWindow(bool aIncludeOwner);
+
+ /**
+ * Misc.
+ */
+ bool WidgetTypeSupportsAcceleration() override;
+
+ void ForcePresent();
+ bool TouchEventShouldStartDrag(mozilla::EventMessage aEventMessage,
+ LayoutDeviceIntPoint aEventPoint);
+
+ void SetSmallIcon(HICON aIcon);
+ void SetBigIcon(HICON aIcon);
+ void SetSmallIconNoData();
+ void SetBigIconNoData();
+
+ static void SetIsRestoringSession(const bool aIsRestoringSession) {
+ sIsRestoringSession = aIsRestoringSession;
+ }
+
+ bool IsRTL() const { return mIsRTL; }
+
+ /**
+ * AssociateDefaultIMC() associates or disassociates the default IMC for
+ * the window.
+ *
+ * @param aAssociate TRUE, associates the default IMC with the window.
+ * Otherwise, disassociates the default IMC from the
+ * window.
+ * @return TRUE if this method associated the default IMC with
+ * disassociated window or disassociated the default IMC
+ * from associated window.
+ * Otherwise, i.e., if this method did nothing actually,
+ * FALSE.
+ */
+ bool AssociateDefaultIMC(bool aAssociate);
+
+ bool HasTaskbarIconBeenCreated() { return mHasTaskbarIconBeenCreated; }
+ // Called when either the nsWindow or an nsITaskbarTabPreview receives the
+ // noticiation that this window has its icon placed on the taskbar.
+ void SetHasTaskbarIconBeenCreated(bool created = true) {
+ mHasTaskbarIconBeenCreated = created;
+ }
+
+ // Getter/setter for the nsITaskbarWindowPreview for this nsWindow
+ already_AddRefed<nsITaskbarWindowPreview> GetTaskbarPreview() {
+ nsCOMPtr<nsITaskbarWindowPreview> preview(
+ do_QueryReferent(mTaskbarPreview));
+ return preview.forget();
+ }
+ void SetTaskbarPreview(nsITaskbarWindowPreview* preview) {
+ mTaskbarPreview = do_GetWeakReference(preview);
+ }
+
+ void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ // Open file picker tracking
+ void PickerOpen();
+ void PickerClosed();
+
+ bool DestroyCalled() { return mDestroyCalled; }
+
+ bool IsPopup();
+ bool ShouldUseOffMainThreadCompositing() override;
+
+ const IMEContext& DefaultIMC() const { return mDefaultIMC; }
+
+ void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+ bool IsTouchWindow() const { return mTouchWindow; }
+ bool SynchronouslyRepaintOnResize() override;
+ void MaybeDispatchInitialFocusEvent() override;
+
+ void LocalesChanged() override;
+
+ void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override;
+ void MaybeEnableWindowOcclusion(bool aEnable);
+
+ /*
+ * Return the HWND or null for this widget.
+ */
+ HWND GetWindowHandle() {
+ return static_cast<HWND>(GetNativeData(NS_NATIVE_WINDOW));
+ }
+
+ /*
+ * Touch input injection apis
+ */
+ nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+ nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativePenInput(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPressure, uint32_t aRotation,
+ int32_t aTiltX, int32_t aTiltY,
+ int32_t aButton,
+ nsIObserver* aObserver) override;
+
+ /*
+ * WM_APPCOMMAND common handler.
+ * Sends events via NativeKey::HandleAppCommandMessage().
+ */
+ bool HandleAppCommandMsg(const MSG& aAppCommandMsg, LRESULT* aRetValue);
+
+ const InputContext& InputContextRef() const { return mInputContext; }
+
+ private:
+ using TimeStamp = mozilla::TimeStamp;
+ using TimeDuration = mozilla::TimeDuration;
+ using TaskbarWindowPreview = mozilla::widget::TaskbarWindowPreview;
+ using NativeKey = mozilla::widget::NativeKey;
+ using MSGResult = mozilla::widget::MSGResult;
+ using PlatformCompositorWidgetDelegate =
+ mozilla::widget::PlatformCompositorWidgetDelegate;
+
+ struct Desktop {
+ // Cached GUID of the virtual desktop this window should be on.
+ // This value may be stale.
+ nsString mID;
+ bool mUpdateIsQueued = false;
+ };
+
+ class PointerInfo {
+ public:
+ enum class PointerType : uint8_t {
+ TOUCH,
+ PEN,
+ };
+
+ PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint,
+ PointerType aType)
+ : mPointerId(aPointerId), mPosition(aPoint), mType(aType) {}
+
+ int32_t mPointerId;
+ LayoutDeviceIntPoint mPosition;
+ PointerType mType;
+ };
+
+ class FrameState {
+ public:
+ explicit FrameState(nsWindow* aWindow);
+
+ void ConsumePreXULSkeletonState(bool aWasMaximized);
+
+ // Whether we should call ShowWindow with the relevant size mode if needed.
+ // We want to avoid that when Windows is already performing the change for
+ // us (via the SWP_FRAMECHANGED messages).
+ enum class DoShowWindow : bool { No, Yes };
+
+ void EnsureSizeMode(nsSizeMode, DoShowWindow = DoShowWindow::Yes);
+ void EnsureFullscreenMode(bool, DoShowWindow = DoShowWindow::Yes);
+ void OnFrameChanging();
+ void OnFrameChanged();
+
+ nsSizeMode GetSizeMode() const;
+
+ void CheckInvariant() const;
+
+ private:
+ void SetSizeModeInternal(nsSizeMode, DoShowWindow);
+
+ nsSizeMode mSizeMode = nsSizeMode_Normal;
+ // XXX mLastSizeMode is rather bizarre and needs some documentation.
+ nsSizeMode mLastSizeMode = nsSizeMode_Normal;
+ // The old size mode before going into fullscreen mode. This should never
+ // be nsSizeMode_Fullscreen.
+ nsSizeMode mPreFullscreenSizeMode = nsSizeMode_Normal;
+ // Whether we're in fullscreen. We need to keep this state out of band,
+ // rather than just using mSizeMode, because a window can be minimized
+ // while fullscreen, and we don't store the fullscreen state anywhere else.
+ bool mFullscreenMode = false;
+ nsWindow* mWindow;
+ };
+
+ // Manager for taskbar-hiding. No persistent state.
+ class TaskbarConcealer;
+
+ // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created
+ // when the trackpoint hack is enabled.
+ enum { eFakeTrackPointScrollableID = 0x46545053 };
+
+ // Used for displayport suppression during window resize
+ enum ResizeState { NOT_RESIZING, IN_SIZEMOVE, RESIZING, MOVING };
+
+ ~nsWindow() override;
+
+ void WindowUsesOMTC() override;
+ void RegisterTouchWindow() override;
+
+ /**
+ * Callbacks
+ */
+ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK WindowProcInternal(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam);
+
+ static BOOL CALLBACK DispatchStarvedPaints(HWND aTopWindow, LPARAM aMsg);
+ static BOOL CALLBACK RegisterTouchForDescendants(HWND aTopWindow,
+ LPARAM aMsg);
+ static BOOL CALLBACK UnregisterTouchForDescendants(HWND aTopWindow,
+ LPARAM aMsg);
+ static LRESULT CALLBACK MozSpecialMsgFilter(int code, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK MozSpecialWndProc(int code, WPARAM wParam,
+ LPARAM lParam);
+ static LRESULT CALLBACK MozSpecialMouseProc(int code, WPARAM wParam,
+ LPARAM lParam);
+ static VOID CALLBACK HookTimerForPopups(HWND hwnd, UINT uMsg, UINT idEvent,
+ DWORD dwTime);
+
+ /**
+ * Window utilities
+ */
+ LPARAM lParamToScreen(LPARAM lParam);
+ LPARAM lParamToClient(LPARAM lParam);
+
+ WPARAM wParamFromGlobalMouseState();
+
+ bool AssociateWithNativeWindow();
+ void DissociateFromNativeWindow();
+ bool CanTakeFocus();
+ bool UpdateNonClientMargins(bool aReflowWindow = true);
+ void UpdateDarkModeToolbar();
+ void UpdateGetWindowInfoCaptionStatus(bool aActiveCaption);
+ void ResetLayout();
+ void InvalidateNonClientRegion();
+ static const wchar_t* GetMainWindowClass();
+ HWND GetOwnerWnd() const { return ::GetWindow(mWnd, GW_OWNER); }
+ bool IsOwnerForegroundWindow() const {
+ HWND owner = GetOwnerWnd();
+ return owner && owner == ::GetForegroundWindow();
+ }
+ bool IsForegroundWindow() const { return mWnd == ::GetForegroundWindow(); }
+ bool IsPopup() const { return mWindowType == WindowType::Popup; }
+ bool IsCloaked() const { return mIsCloaked; }
+
+ /**
+ * Event processing helpers
+ */
+ HWND GetTopLevelForFocus(HWND aCurWnd);
+ void DispatchFocusToTopLevelWindow(bool aIsActivate);
+ bool DispatchStandardEvent(mozilla::EventMessage aMsg);
+ void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam);
+ bool ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue);
+ // We wrap this in ProcessMessage so we can log the return value
+ bool ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT* aRetValue);
+ bool ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+ LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched);
+ LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched);
+ LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched);
+ static bool EventIsInsideWindow(
+ nsWindow* aWindow,
+ mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing());
+ static void PostSleepWakeNotification(const bool aIsSleepMode);
+ int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my);
+ void SetWindowButtonRect(WindowButtonType aButtonType,
+ const LayoutDeviceIntRect& aClientRect) override {
+ mWindowBtnRect[aButtonType] = aClientRect;
+ }
+ TimeStamp GetMessageTimeStamp(LONG aEventTime) const;
+ static void UpdateFirstEventTime(DWORD aEventTime);
+ void FinishLiveResizing(ResizeState aNewState);
+ mozilla::Maybe<mozilla::PanGestureInput> ConvertTouchToPanGesture(
+ const mozilla::MultiTouchInput& aTouchInput, PTOUCHINPUT aOriginalEvent);
+ void DispatchTouchOrPanGestureInput(mozilla::MultiTouchInput& aTouchInput,
+ PTOUCHINPUT aOSEvent);
+
+ /**
+ * Event handlers
+ */
+ void OnDestroy() override;
+ bool OnResize(const LayoutDeviceIntSize& aSize);
+ void OnSizeModeChange();
+ bool OnGesture(WPARAM wParam, LPARAM lParam);
+ bool OnTouch(WPARAM wParam, LPARAM lParam);
+ bool OnHotKey(WPARAM wParam, LPARAM lParam);
+ bool OnPaint(uint32_t aNestingLevel);
+ void OnWindowPosChanging(WINDOWPOS* info);
+ void OnWindowPosChanged(WINDOWPOS* wp);
+ void OnSysColorChanged();
+ void OnDPIChanged(int32_t x, int32_t y, int32_t width, int32_t height);
+ bool OnPointerEvents(UINT msg, WPARAM wParam, LPARAM lParam);
+
+ /**
+ * Function that registers when the user has been active (used for detecting
+ * when the user is idle).
+ */
+ void UserActivity();
+
+ int32_t GetHeight(int32_t aProposedHeight);
+
+ DWORD WindowStyle();
+ DWORD WindowExStyle();
+
+ static const wchar_t* ChooseWindowClass(WindowType);
+ // This method registers the given window class, and returns the class name.
+ static const wchar_t* RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle, LPWSTR aIconID);
+
+ /**
+ * Popup hooks
+ */
+ static void ScheduleHookTimer(HWND aWnd, UINT aMsgId);
+ static void RegisterSpecialDropdownHooks();
+ static void UnregisterSpecialDropdownHooks();
+ static bool GetPopupsToRollup(
+ nsIRollupListener* aRollupListener, uint32_t* aPopupsToRollup,
+ mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing());
+ static bool NeedsToHandleNCActivateDelayed(HWND aWnd);
+ static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam,
+ LPARAM inLParam, LRESULT* outResult);
+
+ /**
+ * Window transparency helpers
+ */
+ void SetWindowTranslucencyInner(TransparencyMode aMode);
+ TransparencyMode GetWindowTranslucencyInner() const {
+ return mTransparencyMode;
+ }
+ bool IsSimulatedClientArea(int32_t clientX, int32_t clientY);
+ bool IsWindowButton(int32_t hitTestResult);
+
+ bool DispatchTouchEventFromWMPointer(UINT msg, LPARAM aLParam,
+ const WinPointerInfo& aPointerInfo,
+ mozilla::MouseButton aButton);
+
+ static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult);
+ void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam);
+
+ /**
+ * Misc.
+ */
+ void StopFlashing();
+ static HWND WindowAtMouse();
+ static bool IsTopLevelMouseExit(HWND aWnd);
+ LayoutDeviceIntRegion GetRegionToPaint(bool aForceFullRepaint, PAINTSTRUCT ps,
+ HDC aDC);
+ nsIWidgetListener* GetPaintListener();
+
+ void CreateCompositor() override;
+ void DestroyCompositor() override;
+ void RequestFxrOutput() override;
+
+ void RecreateDirectManipulationIfNeeded();
+ void ResizeDirectManipulationViewport();
+ void DestroyDirectManipulation();
+
+ bool NeedsToTrackWindowOcclusionState();
+
+ void AsyncUpdateWorkspaceID(Desktop& aDesktop);
+
+ // See bug 603793
+ static bool HasBogusPopupsDropShadowOnMultiMonitor();
+
+ static void InitMouseWheelScrollData();
+
+ void ChangedDPI();
+
+ static bool InitTouchInjection();
+
+ bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
+ POINTER_FLAGS aFlags, uint32_t aPressure = 1024,
+ uint32_t aOrientation = 90);
+
+ void OnFullscreenChanged(nsSizeMode aOldSizeMode, bool aFullScreen);
+ void TryDwmResizeHack();
+
+ static void OnCloakEvent(HWND aWnd, bool aCloaked);
+ void OnCloakChanged(bool aCloaked);
+
+#ifdef DEBUG
+ virtual nsresult SetHiDPIMode(bool aHiDPI) override;
+ virtual nsresult RestoreHiDPIMode() override;
+#endif
+
+ // Get the orientation of the hidden taskbar, on the screen that this window
+ // is on, or Nothing if taskbar isn't hidden.
+ mozilla::Maybe<UINT> GetHiddenTaskbarEdge();
+
+ static bool sTouchInjectInitialized;
+ static InjectTouchInputPtr sInjectTouchFuncPtr;
+ static uint32_t sInstanceCount;
+ static nsWindow* sCurrentWindow;
+ static bool sIsOleInitialized;
+ static Cursor sCurrentCursor;
+ static bool sJustGotDeactivate;
+ static bool sJustGotActivate;
+ static bool sIsInMouseCapture;
+ static bool sIsRestoringSession;
+
+ // Message postponement hack. See the definition-site of
+ // WndProcUrgentInvocation::sDepth for details.
+ struct MOZ_STACK_CLASS WndProcUrgentInvocation {
+ struct Marker {
+ Marker() { ++sDepth; }
+ ~Marker() { --sDepth; }
+ };
+ inline static bool IsActive() { return sDepth > 0; }
+ static size_t sDepth;
+ };
+
+ // Hook Data Members for Dropdowns. sProcessHook Tells the
+ // hook methods whether they should be processing the hook
+ // messages.
+ static HHOOK sMsgFilterHook;
+ static HHOOK sCallProcHook;
+ static HHOOK sCallMouseHook;
+ static bool sProcessHook;
+ static UINT sRollupMsgId;
+ static HWND sRollupMsgWnd;
+ static UINT sHookTimerId;
+
+ // Used to prevent dispatching mouse events that do not originate from user
+ // input.
+ static POINT sLastMouseMovePoint;
+
+ nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers;
+
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points, in the case where we can't call InjectTouch
+ // directly.
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+
+ InputContext mInputContext;
+
+ nsCOMPtr<nsIWidget> mParent;
+ nsIntSize mLastSize = nsIntSize(0, 0);
+ nsIntPoint mLastPoint;
+ HWND mWnd = nullptr;
+ HWND mTransitionWnd = nullptr;
+ mozilla::Maybe<WNDPROC> mPrevWndProc;
+ HBRUSH mBrush;
+ IMEContext mDefaultIMC;
+ HDEVNOTIFY mDeviceNotifyHandle = nullptr;
+ bool mIsTopWidgetWindow = false;
+ bool mInDtor = false;
+ bool mIsVisible = false;
+ bool mIsCloaked = false;
+ bool mTouchWindow = false;
+ bool mDisplayPanFeedback = false;
+ bool mHideChrome = false;
+ bool mIsRTL;
+ bool mMousePresent = false;
+ bool mSimulatedClientArea = false;
+ bool mDestroyCalled = false;
+ bool mOpeningAnimationSuppressed;
+ bool mAlwaysOnTop;
+ bool mIsEarlyBlankWindow = false;
+ bool mIsShowingPreXULSkeletonUI = false;
+ bool mResizable = false;
+ // Whether we're an alert window. Alert windows don't have taskbar icons and
+ // don't steal focus from other windows when opened. They're also expected to
+ // be of type WindowType::Dialog.
+ bool mIsAlert = false;
+ bool mIsPerformingDwmFlushHack = false;
+ bool mDraggingWindowWithMouse = false;
+ DWORD_PTR mOldStyle = 0;
+ DWORD_PTR mOldExStyle = 0;
+ nsNativeDragTarget* mNativeDragTarget = nullptr;
+ HKL mLastKeyboardLayout = 0;
+ mozilla::CheckInvariantWrapper<FrameState> mFrameState;
+ WindowHook mWindowHook;
+ uint32_t mPickerDisplayCount = 0;
+ HICON mIconSmall = nullptr;
+ HICON mIconBig = nullptr;
+ HWND mLastKillFocusWindow = nullptr;
+ PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate = nullptr;
+
+ LayoutDeviceIntMargin NonClientSizeMargin() const {
+ return NonClientSizeMargin(mNonClientOffset);
+ }
+ LayoutDeviceIntMargin NonClientSizeMargin(
+ const LayoutDeviceIntMargin& aNonClientOffset) const;
+ LayoutDeviceIntMargin NormalWindowNonClientOffset() const;
+
+ // Non-client margin settings
+ // Pre-calculated outward offset applied to default frames
+ LayoutDeviceIntMargin mNonClientOffset;
+ // Margins set by the owner
+ LayoutDeviceIntMargin mNonClientMargins;
+ // Margins we'd like to set once chrome is reshown:
+ LayoutDeviceIntMargin mFutureMarginsOnceChromeShows;
+ // Indicates we need to apply margins once toggling chrome into showing:
+ bool mFutureMarginsToUse = false;
+
+ // Indicates custom frames are enabled
+ bool mCustomNonClient = false;
+ // Indicates custom resize margins are in effect
+ bool mUseResizeMarginOverrides = false;
+ // Width of the left and right portions of the resize region
+ mozilla::LayoutDeviceIntCoord mHorResizeMargin;
+ // Height of the top and bottom portions of the resize region
+ mozilla::LayoutDeviceIntCoord mVertResizeMargin;
+ // Height of the caption plus border
+ mozilla::LayoutDeviceIntCoord mCaptionHeight;
+
+ // not yet set, will be calculated on first use
+ double mDefaultScale = -1.0;
+
+ // not yet set, will be calculated on first use
+ float mAspectRatio = 0.0;
+
+ nsCOMPtr<nsIUserIdleServiceInternal> mIdleService;
+
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+
+ // Graphics
+ LayoutDeviceIntRect mLastPaintBounds;
+
+ ResizeState mResizeState = NOT_RESIZING;
+
+ // Transparency
+ TransparencyMode mTransparencyMode = TransparencyMode::Opaque;
+ nsIntRegion mPossiblyTransparentRegion;
+
+ // Win7 Gesture processing and management
+ nsWinGesture mGesture;
+
+ // Weak ref to the nsITaskbarWindowPreview associated with this window
+ nsWeakPtr mTaskbarPreview = nullptr;
+
+ // The input region that determines whether mouse events should be ignored
+ // and pass through to the window below. This is currently only used for
+ // popups.
+ InputRegion mInputRegion;
+
+ // True if the taskbar (possibly through the tab preview) tells us that the
+ // icon has been created on the taskbar.
+ bool mHasTaskbarIconBeenCreated = false;
+
+ // Whether we're in the process of sending a WM_SETTEXT ourselves
+ bool mSendingSetText = false;
+
+ // Whether we were created as a child window (aka ChildWindow) or not.
+ bool mIsChildWindow : 1;
+
+ int32_t mCachedHitTestResult = 0;
+
+ // The point in time at which the last paint completed. We use this to avoid
+ // painting too rapidly in response to frequent input events.
+ TimeStamp mLastPaintEndTime;
+
+ // Caching for hit test results (in client coordinates)
+ LayoutDeviceIntPoint mCachedHitTestPoint;
+ TimeStamp mCachedHitTestTime;
+
+ RefPtr<mozilla::widget::InProcessWinCompositorWidget> mBasicLayersSurface;
+
+ double mSizeConstraintsScale; // scale in effect when setting constraints
+
+ // Will be calculated when layer manager is created.
+ int32_t mMaxTextureSize = -1;
+
+ // Pointer events processing and management
+ WinPointerEvents mPointerEvents;
+
+ ScreenPoint mLastPanGestureFocus;
+
+ // When true, used to indicate an async call to RequestFxrOutput to the GPU
+ // process after the Compositor is created
+ bool mRequestFxrOutputPending = false;
+
+ // A stack based class used in DispatchMouseEvent() to tell whether we should
+ // NOT open context menu when we receives WM_CONTEXTMENU after the
+ // DispatchMouseEvent calls.
+ // This class now works only in the case where a mouse up event happened in
+ // the overscroll gutter.
+ class MOZ_STACK_CLASS ContextMenuPreventer final {
+ public:
+ explicit ContextMenuPreventer(nsWindow* aWindow)
+ : mWindow(aWindow), mNeedsToPreventContextMenu(false){};
+ ~ContextMenuPreventer() {
+ mWindow->mNeedsToPreventContextMenu = mNeedsToPreventContextMenu;
+ }
+ void Update(const mozilla::WidgetMouseEvent& aEvent,
+ const nsIWidget::ContentAndAPZEventStatus& aEventStatus);
+
+ private:
+ nsWindow* mWindow;
+ bool mNeedsToPreventContextMenu = false;
+ };
+ friend class ContextMenuPreventer;
+ bool mNeedsToPreventContextMenu = false;
+
+ mozilla::UniquePtr<mozilla::widget::DirectManipulationOwner> mDmOwner;
+
+ // Client rect for minimize, maximize and close buttons.
+ mozilla::EnumeratedArray<WindowButtonType, WindowButtonType::Count,
+ LayoutDeviceIntRect>
+ mWindowBtnRect;
+
+ mozilla::DataMutex<Desktop> mDesktopId;
+
+ // If set, indicates the edge of the NC region we should clear to black
+ // on next paint. One of: ABE_TOP, ABE_BOTTOM, ABE_LEFT or ABE_RIGHT.
+ mozilla::Maybe<UINT> mClearNCEdge;
+
+ friend class nsWindowGfx;
+
+ static constexpr int kHiddenTaskbarSize = 2;
+};
+
+#endif // WIDGET_WINDOWS_NSWINDOW_H_
diff --git a/widget/windows/nsWindowDbg.cpp b/widget/windows/nsWindowDbg.cpp
new file mode 100644
index 0000000000..8125a8532b
--- /dev/null
+++ b/widget/windows/nsWindowDbg.cpp
@@ -0,0 +1,1612 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "nsWindowDbg.h"
+#include "nsToolkit.h"
+#include "WinPointerEvents.h"
+#include "nsWindowLoggedMessages.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "nsWindow.h"
+#include "GeckoProfiler.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+
+#include <winuser.h>
+#include <dbt.h>
+#include <imm.h>
+#include <tpcshrd.h>
+
+#include <unordered_set>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+extern mozilla::LazyLogModule gWindowsLog;
+static mozilla::LazyLogModule gWindowsEventLog("WindowsEvent");
+
+// currently defined in widget/windows/nsAppShell.cpp
+extern UINT sAppShellGeckoMsgId;
+
+#if defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+MSGFEventMsgInfo gMSGFEvents[] = {
+ "MSGF_DIALOGBOX", 0, "MSGF_MESSAGEBOX", 1, "MSGF_MENU", 2,
+ "MSGF_SCROLLBAR", 5, "MSGF_NEXTWINDOW", 6, "MSGF_MAX", 8,
+ "MSGF_USER", 4096, nullptr, 0};
+#endif
+
+static long gEventCounter = 0;
+static UINT gLastEventMsg = 0;
+
+namespace geckoprofiler::markers {
+
+struct WindowProcMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("WindowProc");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString8View& aMsgLoopName,
+ UINT aMsg, WPARAM aWParam, LPARAM aLParam) {
+ aWriter.StringProperty("messageLoop", aMsgLoopName);
+ aWriter.IntProperty("uMsg", aMsg);
+ const char* name;
+ if (aMsg < WM_USER) {
+ const auto eventMsgInfo = mozilla::widget::gAllEvents.find(aMsg);
+ if (eventMsgInfo != mozilla::widget::gAllEvents.end()) {
+ name = eventMsgInfo->second.mStr;
+ } else {
+ name = "ui message";
+ }
+ } else if (aMsg >= WM_USER && aMsg < WM_APP) {
+ name = "WM_USER message";
+ } else if (aMsg >= WM_APP && aMsg < 0xC000) {
+ name = "WM_APP message";
+ } else if (aMsg >= 0xC000 && aMsg < 0x10000) {
+ if (aMsg == sAppShellGeckoMsgId) {
+ name = "nsAppShell:EventID";
+ } else {
+ name = "registered Windows message";
+ }
+ } else {
+ name = "system message";
+ }
+ aWriter.StringProperty("name", MakeStringSpan(name));
+
+ if (aWParam) {
+ aWriter.IntProperty("wParam", aWParam);
+ }
+ if (aLParam) {
+ aWriter.IntProperty("lParam", aLParam);
+ }
+ }
+
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyFormat("uMsg", MS::Format::Integer);
+ schema.SetChartLabel(
+ "{marker.data.messageLoop} | {marker.data.name} ({marker.data.uMsg})");
+ schema.SetTableLabel(
+ "{marker.name} - {marker.data.messageLoop} - {marker.data.name} "
+ "({marker.data.uMsg})");
+ schema.SetTooltipLabel(
+ "{marker.data.messageLoop} - {marker.name} - {marker.data.name}");
+ schema.AddKeyFormat("wParam", MS::Format::Integer);
+ schema.AddKeyFormat("lParam", MS::Format::Integer);
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+
+namespace mozilla::widget {
+
+AutoProfilerMessageMarker::AutoProfilerMessageMarker(
+ Span<const char> aMsgLoopName, HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam)
+ : mMsgLoopName(aMsgLoopName), mMsg(msg), mWParam(wParam), mLParam(lParam) {
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ mOptions.emplace(MarkerOptions(MarkerTiming::IntervalStart()));
+ nsWindow* win = WinUtils::GetNSWindowPtr(hWnd);
+ if (win) {
+ nsIWidgetListener* wl = win->GetWidgetListener();
+ if (wl) {
+ PresShell* presShell = wl->GetPresShell();
+ if (presShell) {
+ dom::Document* doc = presShell->GetDocument();
+ if (doc) {
+ mOptions->Set(MarkerInnerWindowId(doc->InnerWindowID()));
+ }
+ }
+ }
+ }
+ }
+}
+
+AutoProfilerMessageMarker::~AutoProfilerMessageMarker() {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ if (mOptions) {
+ mOptions->TimingRef().SetIntervalEnd();
+ } else {
+ mOptions.emplace(MarkerOptions(MarkerTiming::IntervalEnd()));
+ }
+ profiler_add_marker(
+ "WindowProc", ::mozilla::baseprofiler::category::OTHER,
+ std::move(*mOptions), geckoprofiler::markers::WindowProcMarker{},
+ ProfilerString8View::WrapNullTerminatedString(mMsgLoopName.data()), mMsg,
+ mWParam, mLParam);
+}
+
+// Using an unordered_set so we can initialize this with nice syntax instead of
+// having to add them one at a time to a mozilla::HashSet.
+std::unordered_set<UINT> gEventsToLogOriginalParams = {
+ WM_WINDOWPOSCHANGING, // (dummy comments for clang-format)
+ WM_SIZING, //
+ WM_STYLECHANGING,
+ WM_GETTEXT,
+ WM_GETMINMAXINFO,
+ WM_MEASUREITEM,
+ WM_NCCALCSIZE,
+};
+
+// If you add an event here, you must add cases for these to
+// MakeMessageSpecificData() and AppendFriendlyMessageSpecificData()
+// in nsWindowLoggedMessages.cpp.
+std::unordered_set<UINT> gEventsToRecordInAboutPage = {
+ WM_WINDOWPOSCHANGING, // (dummy comments for clang-format)
+ WM_WINDOWPOSCHANGED, //
+ WM_SIZING,
+ WM_SIZE,
+ WM_DPICHANGED,
+ WM_SETTINGCHANGE,
+ WM_NCCALCSIZE,
+ WM_MOVE,
+ WM_MOVING,
+ WM_GETMINMAXINFO,
+};
+
+NativeEventLogger::NativeEventLogger(Span<const char> aMsgLoopName, HWND hwnd,
+ UINT msg, WPARAM wParam, LPARAM lParam)
+ : mProfilerMarker(aMsgLoopName, hwnd, msg, wParam, lParam),
+ mMsgLoopName(aMsgLoopName.data()),
+ mHwnd(hwnd),
+ mMsg(msg),
+ mWParam(wParam),
+ mLParam(lParam),
+ mResult(mozilla::Nothing()),
+ mShouldLogPostCall(false) {
+ if (NativeEventLoggerInternal()) {
+ // this event was logged, so reserve this counter number for the post-call
+ mEventCounter = mozilla::Some(gEventCounter);
+ ++gEventCounter;
+ }
+}
+
+NativeEventLogger::~NativeEventLogger() {
+ // If mResult is Nothing, perhaps an exception was thrown or something
+ // before SetResult() was supposed to be called.
+ if (mResult.isSome()) {
+ if (NativeEventLoggerInternal() && mEventCounter.isNothing()) {
+ // We didn't reserve a counter in the pre-call, so reserve it here.
+ ++gEventCounter;
+ }
+ }
+ if (mMsg == WM_DESTROY) {
+ // Remove any logged messages for this window.
+ WindowClosed(mHwnd);
+ }
+}
+
+void EventMsgInfo::LogParameters(nsCString& str, WPARAM wParam, LPARAM lParam,
+ bool isPreCall) {
+ if (mParamInfoFn) {
+ str = mParamInfoFn(wParam, lParam, isPreCall);
+ } else {
+ if (mWParamInfoFn) {
+ mWParamInfoFn(str, wParam, mWParamName, isPreCall);
+ }
+ if (mLParamInfoFn) {
+ if (mWParamInfoFn) {
+ str.AppendASCII(" ");
+ }
+ mLParamInfoFn(str, lParam, mLParamName, isPreCall);
+ }
+ }
+}
+
+nsAutoCString DefaultParamInfo(uint64_t wParam, uint64_t lParam,
+ bool /* isPreCall */) {
+ nsAutoCString result;
+ result.AppendPrintf("wParam=0x%08llX lParam=0x%08llX", wParam, lParam);
+ return result;
+}
+
+void AppendEnumValueInfo(
+ nsCString& str, uint64_t value,
+ const std::unordered_map<uint64_t, const char*>& valuesAndNames,
+ const char* name) {
+ if (name != nullptr) {
+ str.AppendPrintf("%s=", name);
+ }
+ auto entry = valuesAndNames.find(value);
+ if (entry == valuesAndNames.end()) {
+ str.AppendPrintf("Unknown (0x%08llX)", value);
+ } else {
+ str.AppendASCII(entry->second);
+ }
+}
+
+bool AppendFlagsInfo(nsCString& str, uint64_t flags,
+ const nsTArray<EnumValueAndName>& flagsAndNames,
+ const char* name) {
+ if (name != nullptr) {
+ str.AppendPrintf("%s=", name);
+ }
+ bool firstAppend = true;
+ for (const EnumValueAndName& flagAndName : flagsAndNames) {
+ if (MOZ_UNLIKELY(flagAndName.mFlag == 0)) {
+ // Special case - only want to write this if nothing else was set.
+ // For this to make sense, 0 values should come at the end of
+ // flagsAndNames.
+ if (flags == 0 && firstAppend) {
+ firstAppend = false;
+ str.AppendASCII(flagAndName.mName);
+ }
+ } else if ((flags & flagAndName.mFlag) == flagAndName.mFlag) {
+ if (MOZ_LIKELY(!firstAppend)) {
+ str.Append('|');
+ }
+ firstAppend = false;
+ str.AppendASCII(flagAndName.mName);
+ flags = flags & ~flagAndName.mFlag;
+ }
+ }
+ if (flags != 0) {
+ if (MOZ_LIKELY(!firstAppend)) {
+ str.Append('|');
+ }
+ firstAppend = false;
+ str.AppendPrintf("Unknown (0x%08llX)", flags);
+ }
+ return !firstAppend;
+}
+
+// if mResult is not set, this is used to log the parameters passed in the
+// message, otherwise we are logging the parameters after we have handled the
+// message. This is useful for events where we might change the parameters while
+// handling the message (for example WM_GETTEXT and WM_NCCALCSIZE)
+// Returns whether this message was logged, so we need to reserve a
+// counter number for it.
+bool NativeEventLogger::NativeEventLoggerInternal() {
+ mozilla::LogLevel const targetLogLevel = [&] {
+ // These messages often take up more than 90% of logs if not filtered out.
+ if (mMsg == WM_SETCURSOR || mMsg == WM_MOUSEMOVE || mMsg == WM_NCHITTEST) {
+ return LogLevel::Verbose;
+ }
+ if (gLastEventMsg == mMsg) {
+ return LogLevel::Debug;
+ }
+ return LogLevel::Info;
+ }();
+
+ bool isPreCall = mResult.isNothing();
+ if (isPreCall || mShouldLogPostCall) {
+ bool recordInAboutPage = gEventsToRecordInAboutPage.find(mMsg) !=
+ gEventsToRecordInAboutPage.end();
+ bool writeToWindowsLog;
+ if (isPreCall) {
+ writeToWindowsLog = MOZ_LOG_TEST(gWindowsEventLog, targetLogLevel);
+ bool shouldLogAtAll = recordInAboutPage || writeToWindowsLog;
+ // Since calling mParamInfoFn() allocates a string, only go down this code
+ // path if we're going to log this message to reduce allocations.
+ if (!shouldLogAtAll) {
+ return false;
+ }
+ mShouldLogPostCall = true;
+ bool shouldLogPreCall = gEventsToLogOriginalParams.find(mMsg) !=
+ gEventsToLogOriginalParams.end();
+ if (!shouldLogPreCall) {
+ // Pre-call and we don't want to log both, so skip this one.
+ return false;
+ }
+ } else {
+ writeToWindowsLog = true;
+ }
+ if (recordInAboutPage) {
+ LogWindowMessage(mHwnd, mMsg, isPreCall,
+ mEventCounter.valueOr(gEventCounter), mWParam, mLParam,
+ mResult, mRetValue);
+ }
+ gLastEventMsg = mMsg;
+ if (writeToWindowsLog) {
+ const auto& eventMsgInfo = gAllEvents.find(mMsg);
+ const char* msgText = eventMsgInfo != gAllEvents.end()
+ ? eventMsgInfo->second.mStr
+ : nullptr;
+ nsAutoCString paramInfo;
+ if (eventMsgInfo != gAllEvents.end()) {
+ eventMsgInfo->second.LogParameters(paramInfo, mWParam, mLParam,
+ isPreCall);
+ } else {
+ paramInfo = DefaultParamInfo(mWParam, mLParam, isPreCall);
+ }
+ const char* resultMsg = mResult.isSome()
+ ? (mResult.value() ? "true" : "false")
+ : "initial call";
+ nsAutoCString logMessage;
+ logMessage.AppendPrintf(
+ "%s | %6ld %08" PRIX64 " - 0x%04X %s%s%s: 0x%08" PRIX64 " (%s)\n",
+ mMsgLoopName, mEventCounter.valueOr(gEventCounter),
+ reinterpret_cast<uint64_t>(mHwnd), mMsg,
+ msgText ? msgText : "Unknown", paramInfo.IsEmpty() ? "" : " ",
+ paramInfo.get(),
+ mResult.isSome() ? static_cast<uint64_t>(mRetValue) : 0, resultMsg);
+ const char* logMessageData = logMessage.Data();
+ MOZ_LOG(gWindowsEventLog, targetLogLevel, ("%s", logMessageData));
+ }
+ return true;
+ }
+ return false;
+}
+
+void TrueFalseParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ result.AppendPrintf("%s=%s", name, value == TRUE ? "TRUE" : "FALSE");
+}
+
+void TrueFalseLowOrderWordParamInfo(nsCString& result, uint64_t value,
+ const char* name, bool /* isPreCall */) {
+ result.AppendPrintf("%s=%s", name, LOWORD(value) == TRUE ? "TRUE" : "FALSE");
+}
+
+void HexParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ result.AppendPrintf("%s=0x%08llX", name, value);
+}
+
+void IntParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ result.AppendPrintf("%s=%lld", name, value);
+}
+
+void RectParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ LPRECT rect = reinterpret_cast<LPRECT>(value);
+ if (rect == nullptr) {
+ str.AppendPrintf("NULL rect?");
+ return;
+ }
+ if (name != nullptr) {
+ str.AppendPrintf("%s ", name);
+ }
+ str.AppendPrintf("left=%ld top=%ld right=%ld bottom=%ld", rect->left,
+ rect->top, rect->right, rect->bottom);
+}
+
+#define VALANDNAME_ENTRY(_msg) \
+ { _msg, #_msg }
+
+void CreateStructParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ CREATESTRUCT* createStruct = reinterpret_cast<CREATESTRUCT*>(value);
+ if (createStruct == nullptr) {
+ str.AppendASCII("NULL createStruct?");
+ return;
+ }
+ str.AppendPrintf(
+ "%s: hInstance=%p hMenu=%p hwndParent=%p lpszName=%S lpszClass=%S x=%d "
+ "y=%d cx=%d cy=%d",
+ name, createStruct->hInstance, createStruct->hMenu,
+ createStruct->hwndParent, createStruct->lpszName, createStruct->lpszClass,
+ createStruct->x, createStruct->y, createStruct->cx, createStruct->cy);
+ str.AppendASCII(" ");
+ const static nsTArray<EnumValueAndName> windowStyles = {
+ // these combinations of other flags need to come first
+ VALANDNAME_ENTRY(WS_OVERLAPPEDWINDOW), VALANDNAME_ENTRY(WS_POPUPWINDOW),
+ VALANDNAME_ENTRY(WS_CAPTION),
+ // regular flags
+ VALANDNAME_ENTRY(WS_POPUP), VALANDNAME_ENTRY(WS_CHILD),
+ VALANDNAME_ENTRY(WS_MINIMIZE), VALANDNAME_ENTRY(WS_VISIBLE),
+ VALANDNAME_ENTRY(WS_DISABLED), VALANDNAME_ENTRY(WS_CLIPSIBLINGS),
+ VALANDNAME_ENTRY(WS_CLIPCHILDREN), VALANDNAME_ENTRY(WS_MAXIMIZE),
+ VALANDNAME_ENTRY(WS_BORDER), VALANDNAME_ENTRY(WS_DLGFRAME),
+ VALANDNAME_ENTRY(WS_VSCROLL), VALANDNAME_ENTRY(WS_HSCROLL),
+ VALANDNAME_ENTRY(WS_SYSMENU), VALANDNAME_ENTRY(WS_THICKFRAME),
+ VALANDNAME_ENTRY(WS_GROUP), VALANDNAME_ENTRY(WS_TABSTOP),
+ // zero value needs to come last
+ VALANDNAME_ENTRY(WS_OVERLAPPED)};
+ AppendFlagsInfo(str, createStruct->style, windowStyles, "style");
+ str.AppendASCII(" ");
+ const nsTArray<EnumValueAndName> extendedWindowStyles = {
+ // these combinations of other flags need to come first
+ VALANDNAME_ENTRY(WS_EX_OVERLAPPEDWINDOW),
+ VALANDNAME_ENTRY(WS_EX_PALETTEWINDOW),
+ // regular flags
+ VALANDNAME_ENTRY(WS_EX_DLGMODALFRAME),
+ VALANDNAME_ENTRY(WS_EX_NOPARENTNOTIFY),
+ VALANDNAME_ENTRY(WS_EX_TOPMOST),
+ VALANDNAME_ENTRY(WS_EX_ACCEPTFILES),
+ VALANDNAME_ENTRY(WS_EX_TRANSPARENT),
+ VALANDNAME_ENTRY(WS_EX_MDICHILD),
+ VALANDNAME_ENTRY(WS_EX_TOOLWINDOW),
+ VALANDNAME_ENTRY(WS_EX_WINDOWEDGE),
+ VALANDNAME_ENTRY(WS_EX_CLIENTEDGE),
+ VALANDNAME_ENTRY(WS_EX_CONTEXTHELP),
+ VALANDNAME_ENTRY(WS_EX_RIGHT),
+ VALANDNAME_ENTRY(WS_EX_LEFT),
+ VALANDNAME_ENTRY(WS_EX_RTLREADING),
+ VALANDNAME_ENTRY(WS_EX_LTRREADING),
+ VALANDNAME_ENTRY(WS_EX_LEFTSCROLLBAR),
+ VALANDNAME_ENTRY(WS_EX_RIGHTSCROLLBAR),
+ VALANDNAME_ENTRY(WS_EX_CONTROLPARENT),
+ VALANDNAME_ENTRY(WS_EX_STATICEDGE),
+ VALANDNAME_ENTRY(WS_EX_APPWINDOW),
+ VALANDNAME_ENTRY(WS_EX_LAYERED),
+ VALANDNAME_ENTRY(WS_EX_NOINHERITLAYOUT),
+ VALANDNAME_ENTRY(WS_EX_LAYOUTRTL),
+ VALANDNAME_ENTRY(WS_EX_NOACTIVATE),
+ VALANDNAME_ENTRY(WS_EX_COMPOSITED),
+ VALANDNAME_ENTRY(WS_EX_NOREDIRECTIONBITMAP),
+ };
+ AppendFlagsInfo(str, createStruct->dwExStyle, extendedWindowStyles,
+ "dwExStyle");
+}
+
+void XLowWordYHighWordParamInfo(nsCString& str, uint64_t value,
+ const char* name, bool /* isPreCall */) {
+ str.AppendPrintf("%s: x=%d y=%d", name, static_cast<int>(LOWORD(value)),
+ static_cast<int>(HIWORD(value)));
+}
+
+void PointParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ str.AppendPrintf("%s: x=%d y=%d", name, static_cast<int>(GET_X_LPARAM(value)),
+ static_cast<int>(GET_Y_LPARAM(value)));
+}
+
+void PointExplicitParamInfo(nsCString& str, POINT point, const char* name) {
+ str.AppendPrintf("%s: x=%ld y=%ld", name, point.x, point.y);
+}
+
+void PointsParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ PPOINTS points = reinterpret_cast<PPOINTS>(&value);
+ str.AppendPrintf("%s: x=%d y=%d", name, points->x, points->y);
+}
+
+void VirtualKeyParamInfo(nsCString& result, uint64_t param, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> virtualKeys{
+ VALANDNAME_ENTRY(VK_LBUTTON),
+ VALANDNAME_ENTRY(VK_RBUTTON),
+ VALANDNAME_ENTRY(VK_CANCEL),
+ VALANDNAME_ENTRY(VK_MBUTTON),
+ VALANDNAME_ENTRY(VK_XBUTTON1),
+ VALANDNAME_ENTRY(VK_XBUTTON2),
+ VALANDNAME_ENTRY(VK_BACK),
+ VALANDNAME_ENTRY(VK_TAB),
+ VALANDNAME_ENTRY(VK_CLEAR),
+ VALANDNAME_ENTRY(VK_RETURN),
+ VALANDNAME_ENTRY(VK_SHIFT),
+ VALANDNAME_ENTRY(VK_CONTROL),
+ VALANDNAME_ENTRY(VK_MENU),
+ VALANDNAME_ENTRY(VK_PAUSE),
+ VALANDNAME_ENTRY(VK_CAPITAL),
+ VALANDNAME_ENTRY(VK_KANA),
+ VALANDNAME_ENTRY(VK_HANGUL),
+#ifdef VK_IME_ON
+ VALANDNAME_ENTRY(VK_IME_ON),
+#endif
+ VALANDNAME_ENTRY(VK_JUNJA),
+ VALANDNAME_ENTRY(VK_FINAL),
+ VALANDNAME_ENTRY(VK_HANJA),
+ VALANDNAME_ENTRY(VK_KANJI),
+#ifdef VK_IME_OFF
+ VALANDNAME_ENTRY(VK_IME_OFF),
+#endif
+ VALANDNAME_ENTRY(VK_ESCAPE),
+ VALANDNAME_ENTRY(VK_CONVERT),
+ VALANDNAME_ENTRY(VK_NONCONVERT),
+ VALANDNAME_ENTRY(VK_ACCEPT),
+ VALANDNAME_ENTRY(VK_MODECHANGE),
+ VALANDNAME_ENTRY(VK_SPACE),
+ VALANDNAME_ENTRY(VK_PRIOR),
+ VALANDNAME_ENTRY(VK_NEXT),
+ VALANDNAME_ENTRY(VK_END),
+ VALANDNAME_ENTRY(VK_HOME),
+ VALANDNAME_ENTRY(VK_LEFT),
+ VALANDNAME_ENTRY(VK_UP),
+ VALANDNAME_ENTRY(VK_RIGHT),
+ VALANDNAME_ENTRY(VK_DOWN),
+ VALANDNAME_ENTRY(VK_SELECT),
+ VALANDNAME_ENTRY(VK_PRINT),
+ VALANDNAME_ENTRY(VK_EXECUTE),
+ VALANDNAME_ENTRY(VK_SNAPSHOT),
+ VALANDNAME_ENTRY(VK_INSERT),
+ VALANDNAME_ENTRY(VK_DELETE),
+ VALANDNAME_ENTRY(VK_HELP),
+ VALANDNAME_ENTRY(VK_LWIN),
+ VALANDNAME_ENTRY(VK_RWIN),
+ VALANDNAME_ENTRY(VK_APPS),
+ VALANDNAME_ENTRY(VK_SLEEP),
+ VALANDNAME_ENTRY(VK_NUMPAD0),
+ VALANDNAME_ENTRY(VK_NUMPAD1),
+ VALANDNAME_ENTRY(VK_NUMPAD2),
+ VALANDNAME_ENTRY(VK_NUMPAD3),
+ VALANDNAME_ENTRY(VK_NUMPAD4),
+ VALANDNAME_ENTRY(VK_NUMPAD5),
+ VALANDNAME_ENTRY(VK_NUMPAD6),
+ VALANDNAME_ENTRY(VK_NUMPAD7),
+ VALANDNAME_ENTRY(VK_NUMPAD8),
+ VALANDNAME_ENTRY(VK_NUMPAD9),
+ VALANDNAME_ENTRY(VK_MULTIPLY),
+ VALANDNAME_ENTRY(VK_ADD),
+ VALANDNAME_ENTRY(VK_SEPARATOR),
+ VALANDNAME_ENTRY(VK_SUBTRACT),
+ VALANDNAME_ENTRY(VK_DECIMAL),
+ VALANDNAME_ENTRY(VK_DIVIDE),
+ VALANDNAME_ENTRY(VK_F1),
+ VALANDNAME_ENTRY(VK_F2),
+ VALANDNAME_ENTRY(VK_F3),
+ VALANDNAME_ENTRY(VK_F4),
+ VALANDNAME_ENTRY(VK_F5),
+ VALANDNAME_ENTRY(VK_F6),
+ VALANDNAME_ENTRY(VK_F7),
+ VALANDNAME_ENTRY(VK_F8),
+ VALANDNAME_ENTRY(VK_F9),
+ VALANDNAME_ENTRY(VK_F10),
+ VALANDNAME_ENTRY(VK_F11),
+ VALANDNAME_ENTRY(VK_F12),
+ VALANDNAME_ENTRY(VK_F13),
+ VALANDNAME_ENTRY(VK_F14),
+ VALANDNAME_ENTRY(VK_F15),
+ VALANDNAME_ENTRY(VK_F16),
+ VALANDNAME_ENTRY(VK_F17),
+ VALANDNAME_ENTRY(VK_F18),
+ VALANDNAME_ENTRY(VK_F19),
+ VALANDNAME_ENTRY(VK_F20),
+ VALANDNAME_ENTRY(VK_F21),
+ VALANDNAME_ENTRY(VK_F22),
+ VALANDNAME_ENTRY(VK_F23),
+ VALANDNAME_ENTRY(VK_F24),
+ VALANDNAME_ENTRY(VK_NUMLOCK),
+ VALANDNAME_ENTRY(VK_SCROLL),
+ VALANDNAME_ENTRY(VK_LSHIFT),
+ VALANDNAME_ENTRY(VK_RSHIFT),
+ VALANDNAME_ENTRY(VK_LCONTROL),
+ VALANDNAME_ENTRY(VK_RCONTROL),
+ VALANDNAME_ENTRY(VK_LMENU),
+ VALANDNAME_ENTRY(VK_RMENU),
+ VALANDNAME_ENTRY(VK_BROWSER_BACK),
+ VALANDNAME_ENTRY(VK_BROWSER_FORWARD),
+ VALANDNAME_ENTRY(VK_BROWSER_REFRESH),
+ VALANDNAME_ENTRY(VK_BROWSER_STOP),
+ VALANDNAME_ENTRY(VK_BROWSER_SEARCH),
+ VALANDNAME_ENTRY(VK_BROWSER_FAVORITES),
+ VALANDNAME_ENTRY(VK_BROWSER_HOME),
+ VALANDNAME_ENTRY(VK_VOLUME_MUTE),
+ VALANDNAME_ENTRY(VK_VOLUME_DOWN),
+ VALANDNAME_ENTRY(VK_VOLUME_UP),
+ VALANDNAME_ENTRY(VK_MEDIA_NEXT_TRACK),
+ VALANDNAME_ENTRY(VK_MEDIA_PREV_TRACK),
+ VALANDNAME_ENTRY(VK_MEDIA_STOP),
+ VALANDNAME_ENTRY(VK_MEDIA_PLAY_PAUSE),
+ VALANDNAME_ENTRY(VK_LAUNCH_MAIL),
+ VALANDNAME_ENTRY(VK_LAUNCH_MEDIA_SELECT),
+ VALANDNAME_ENTRY(VK_LAUNCH_APP1),
+ VALANDNAME_ENTRY(VK_LAUNCH_APP2),
+ VALANDNAME_ENTRY(VK_OEM_1),
+ VALANDNAME_ENTRY(VK_OEM_PLUS),
+ VALANDNAME_ENTRY(VK_OEM_COMMA),
+ VALANDNAME_ENTRY(VK_OEM_MINUS),
+ VALANDNAME_ENTRY(VK_OEM_PERIOD),
+ VALANDNAME_ENTRY(VK_OEM_2),
+ VALANDNAME_ENTRY(VK_OEM_3),
+ VALANDNAME_ENTRY(VK_OEM_4),
+ VALANDNAME_ENTRY(VK_OEM_5),
+ VALANDNAME_ENTRY(VK_OEM_6),
+ VALANDNAME_ENTRY(VK_OEM_7),
+ VALANDNAME_ENTRY(VK_OEM_8),
+ VALANDNAME_ENTRY(VK_OEM_102),
+ VALANDNAME_ENTRY(VK_PROCESSKEY),
+ VALANDNAME_ENTRY(VK_PACKET),
+ VALANDNAME_ENTRY(VK_ATTN),
+ VALANDNAME_ENTRY(VK_CRSEL),
+ VALANDNAME_ENTRY(VK_EXSEL),
+ VALANDNAME_ENTRY(VK_EREOF),
+ VALANDNAME_ENTRY(VK_PLAY),
+ VALANDNAME_ENTRY(VK_ZOOM),
+ VALANDNAME_ENTRY(VK_NONAME),
+ VALANDNAME_ENTRY(VK_PA1),
+ VALANDNAME_ENTRY(VK_OEM_CLEAR),
+ {0x30, "0"},
+ {0x31, "1"},
+ {0x32, "2"},
+ {0x33, "3"},
+ {0x34, "4"},
+ {0x35, "5"},
+ {0x36, "6"},
+ {0x37, "7"},
+ {0x38, "8"},
+ {0x39, "9"},
+ {0x41, "A"},
+ {0x42, "B"},
+ {0x43, "C"},
+ {0x44, "D"},
+ {0x45, "E"},
+ {0x46, "F"},
+ {0x47, "G"},
+ {0x48, "H"},
+ {0x49, "I"},
+ {0x4A, "J"},
+ {0x4B, "K"},
+ {0x4C, "L"},
+ {0x4D, "M"},
+ {0x4E, "N"},
+ {0x4F, "O"},
+ {0x50, "P"},
+ {0x51, "Q"},
+ {0x52, "S"},
+ {0x53, "T"},
+ {0x54, "U"},
+ {0x55, "V"},
+ {0x56, "W"},
+ {0x57, "X"},
+ {0x58, "Y"},
+ {0x59, "Z"},
+ };
+ AppendEnumValueInfo(result, param, virtualKeys, name);
+}
+
+void VirtualModifierKeysParamInfo(nsCString& result, uint64_t param,
+ const char* name, bool /* isPreCall */) {
+ const static nsTArray<EnumValueAndName> virtualKeys{
+ VALANDNAME_ENTRY(MK_CONTROL), VALANDNAME_ENTRY(MK_LBUTTON),
+ VALANDNAME_ENTRY(MK_MBUTTON), VALANDNAME_ENTRY(MK_RBUTTON),
+ VALANDNAME_ENTRY(MK_SHIFT), VALANDNAME_ENTRY(MK_XBUTTON1),
+ VALANDNAME_ENTRY(MK_XBUTTON2), {0, "(none)"}};
+ AppendFlagsInfo(result, param, virtualKeys, name);
+}
+
+void ParentNotifyEventParamInfo(nsCString& str, uint64_t param,
+ const char* /* name */, bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> eventValues{
+ VALANDNAME_ENTRY(WM_CREATE), VALANDNAME_ENTRY(WM_DESTROY),
+ VALANDNAME_ENTRY(WM_LBUTTONDOWN), VALANDNAME_ENTRY(WM_MBUTTONDOWN),
+ VALANDNAME_ENTRY(WM_RBUTTONDOWN), VALANDNAME_ENTRY(WM_XBUTTONDOWN),
+ VALANDNAME_ENTRY(WM_POINTERDOWN)};
+ AppendEnumValueInfo(str, LOWORD(param), eventValues, "event");
+ str.AppendASCII(" ");
+ HexParamInfo(str, HIWORD(param), "hiWord", false);
+}
+
+void KeystrokeFlagsParamInfo(nsCString& str, uint64_t param,
+ const char* /* name */, bool /* isPreCall */) {
+ WORD repeatCount = LOWORD(param);
+ WORD keyFlags = HIWORD(param);
+ WORD scanCode = LOBYTE(keyFlags);
+ bool isExtendedKey = (keyFlags & KF_EXTENDED) == KF_EXTENDED;
+ if (isExtendedKey) {
+ scanCode = MAKEWORD(scanCode, 0xE0);
+ }
+ bool contextCode = (keyFlags & KF_ALTDOWN) == KF_ALTDOWN;
+ bool wasKeyDown = (keyFlags & KF_REPEAT) == KF_REPEAT;
+ bool transitionState = (keyFlags & KF_UP) == KF_UP;
+
+ str.AppendPrintf(
+ "repeatCount: %d scanCode: %d isExtended: %d, contextCode: %d "
+ "previousKeyState: %d transitionState: %d",
+ repeatCount, scanCode, isExtendedKey ? 1 : 0, contextCode ? 1 : 0,
+ wasKeyDown ? 1 : 0, transitionState ? 1 : 0);
+};
+
+void VirtualKeysLowWordDistanceHighWordParamInfo(nsCString& str, uint64_t value,
+ const char* /* name */,
+ bool isPreCall) {
+ VirtualModifierKeysParamInfo(str, LOWORD(value), "virtualKeys", isPreCall);
+ str.AppendASCII(" ");
+ IntParamInfo(str, HIWORD(value), "distance", isPreCall);
+}
+
+void ShowWindowReasonParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> showWindowReasonValues{
+ VALANDNAME_ENTRY(SW_OTHERUNZOOM),
+ VALANDNAME_ENTRY(SW_OTHERZOOM),
+ VALANDNAME_ENTRY(SW_PARENTCLOSING),
+ VALANDNAME_ENTRY(SW_PARENTOPENING),
+ {0, "Call to ShowWindow()"}};
+ AppendEnumValueInfo(str, value, showWindowReasonValues, name);
+}
+
+void WindowEdgeParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> windowEdgeValues{
+ VALANDNAME_ENTRY(WMSZ_BOTTOM), VALANDNAME_ENTRY(WMSZ_BOTTOMLEFT),
+ VALANDNAME_ENTRY(WMSZ_BOTTOMRIGHT), VALANDNAME_ENTRY(WMSZ_LEFT),
+ VALANDNAME_ENTRY(WMSZ_RIGHT), VALANDNAME_ENTRY(WMSZ_TOP),
+ VALANDNAME_ENTRY(WMSZ_TOPLEFT), VALANDNAME_ENTRY(WMSZ_TOPRIGHT)};
+ AppendEnumValueInfo(str, value, windowEdgeValues, name);
+}
+
+void UiActionParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> uiActionValues{
+ VALANDNAME_ENTRY(SPI_GETACCESSTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETAUDIODESCRIPTION),
+ VALANDNAME_ENTRY(SPI_GETCLIENTAREAANIMATION),
+ VALANDNAME_ENTRY(SPI_GETDISABLEOVERLAPPEDCONTENT),
+ VALANDNAME_ENTRY(SPI_GETFILTERKEYS),
+ VALANDNAME_ENTRY(SPI_GETFOCUSBORDERHEIGHT),
+ VALANDNAME_ENTRY(SPI_GETFOCUSBORDERWIDTH),
+ VALANDNAME_ENTRY(SPI_GETHIGHCONTRAST),
+ VALANDNAME_ENTRY(SPI_GETLOGICALDPIOVERRIDE),
+ VALANDNAME_ENTRY(SPI_SETLOGICALDPIOVERRIDE),
+ VALANDNAME_ENTRY(SPI_GETMESSAGEDURATION),
+ VALANDNAME_ENTRY(SPI_GETMOUSECLICKLOCK),
+ VALANDNAME_ENTRY(SPI_GETMOUSECLICKLOCKTIME),
+ VALANDNAME_ENTRY(SPI_GETMOUSEKEYS),
+ VALANDNAME_ENTRY(SPI_GETMOUSESONAR),
+ VALANDNAME_ENTRY(SPI_GETMOUSEVANISH),
+ VALANDNAME_ENTRY(SPI_GETSCREENREADER),
+ VALANDNAME_ENTRY(SPI_GETSERIALKEYS),
+ VALANDNAME_ENTRY(SPI_GETSHOWSOUNDS),
+ VALANDNAME_ENTRY(SPI_GETSOUNDSENTRY),
+ VALANDNAME_ENTRY(SPI_GETSTICKYKEYS),
+ VALANDNAME_ENTRY(SPI_GETTOGGLEKEYS),
+ VALANDNAME_ENTRY(SPI_SETACCESSTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETAUDIODESCRIPTION),
+ VALANDNAME_ENTRY(SPI_SETCLIENTAREAANIMATION),
+ VALANDNAME_ENTRY(SPI_SETDISABLEOVERLAPPEDCONTENT),
+ VALANDNAME_ENTRY(SPI_SETFILTERKEYS),
+ VALANDNAME_ENTRY(SPI_SETFOCUSBORDERHEIGHT),
+ VALANDNAME_ENTRY(SPI_SETFOCUSBORDERWIDTH),
+ VALANDNAME_ENTRY(SPI_SETHIGHCONTRAST),
+ VALANDNAME_ENTRY(SPI_SETMESSAGEDURATION),
+ VALANDNAME_ENTRY(SPI_SETMOUSECLICKLOCK),
+ VALANDNAME_ENTRY(SPI_SETMOUSECLICKLOCKTIME),
+ VALANDNAME_ENTRY(SPI_SETMOUSEKEYS),
+ VALANDNAME_ENTRY(SPI_SETMOUSESONAR),
+ VALANDNAME_ENTRY(SPI_SETMOUSEVANISH),
+ VALANDNAME_ENTRY(SPI_SETSCREENREADER),
+ VALANDNAME_ENTRY(SPI_SETSERIALKEYS),
+ VALANDNAME_ENTRY(SPI_SETSHOWSOUNDS),
+ VALANDNAME_ENTRY(SPI_SETSOUNDSENTRY),
+ VALANDNAME_ENTRY(SPI_SETSTICKYKEYS),
+ VALANDNAME_ENTRY(SPI_SETTOGGLEKEYS),
+ VALANDNAME_ENTRY(SPI_GETCLEARTYPE),
+ VALANDNAME_ENTRY(SPI_GETDESKWALLPAPER),
+ VALANDNAME_ENTRY(SPI_GETDROPSHADOW),
+ VALANDNAME_ENTRY(SPI_GETFLATMENU),
+ VALANDNAME_ENTRY(SPI_GETFONTSMOOTHING),
+ VALANDNAME_ENTRY(SPI_GETFONTSMOOTHINGCONTRAST),
+ VALANDNAME_ENTRY(SPI_GETFONTSMOOTHINGORIENTATION),
+ VALANDNAME_ENTRY(SPI_GETFONTSMOOTHINGTYPE),
+ VALANDNAME_ENTRY(SPI_GETWORKAREA),
+ VALANDNAME_ENTRY(SPI_SETCLEARTYPE),
+ VALANDNAME_ENTRY(SPI_SETCURSORS),
+ VALANDNAME_ENTRY(SPI_SETDESKPATTERN),
+ VALANDNAME_ENTRY(SPI_SETDESKWALLPAPER),
+ VALANDNAME_ENTRY(SPI_SETDROPSHADOW),
+ VALANDNAME_ENTRY(SPI_SETFLATMENU),
+ VALANDNAME_ENTRY(SPI_SETFONTSMOOTHING),
+ VALANDNAME_ENTRY(SPI_SETFONTSMOOTHINGCONTRAST),
+ VALANDNAME_ENTRY(SPI_SETFONTSMOOTHINGORIENTATION),
+ VALANDNAME_ENTRY(SPI_SETFONTSMOOTHINGTYPE),
+ VALANDNAME_ENTRY(SPI_SETWORKAREA),
+ VALANDNAME_ENTRY(SPI_GETICONMETRICS),
+ VALANDNAME_ENTRY(SPI_GETICONTITLELOGFONT),
+ VALANDNAME_ENTRY(SPI_GETICONTITLEWRAP),
+ VALANDNAME_ENTRY(SPI_ICONHORIZONTALSPACING),
+ VALANDNAME_ENTRY(SPI_ICONVERTICALSPACING),
+ VALANDNAME_ENTRY(SPI_SETICONMETRICS),
+ VALANDNAME_ENTRY(SPI_SETICONS),
+ VALANDNAME_ENTRY(SPI_SETICONTITLELOGFONT),
+ VALANDNAME_ENTRY(SPI_SETICONTITLEWRAP),
+ VALANDNAME_ENTRY(SPI_GETBEEP),
+ VALANDNAME_ENTRY(SPI_GETBLOCKSENDINPUTRESETS),
+ VALANDNAME_ENTRY(SPI_GETCONTACTVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_SETCONTACTVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_GETDEFAULTINPUTLANG),
+ VALANDNAME_ENTRY(SPI_GETGESTUREVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_SETGESTUREVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_GETKEYBOARDCUES),
+ VALANDNAME_ENTRY(SPI_GETKEYBOARDDELAY),
+ VALANDNAME_ENTRY(SPI_GETKEYBOARDPREF),
+ VALANDNAME_ENTRY(SPI_GETKEYBOARDSPEED),
+ VALANDNAME_ENTRY(SPI_GETMOUSE),
+ VALANDNAME_ENTRY(SPI_GETMOUSEHOVERHEIGHT),
+ VALANDNAME_ENTRY(SPI_GETMOUSEHOVERTIME),
+ VALANDNAME_ENTRY(SPI_GETMOUSEHOVERWIDTH),
+ VALANDNAME_ENTRY(SPI_GETMOUSESPEED),
+ VALANDNAME_ENTRY(SPI_GETMOUSETRAILS),
+ VALANDNAME_ENTRY(SPI_GETMOUSEWHEELROUTING),
+ VALANDNAME_ENTRY(SPI_SETMOUSEWHEELROUTING),
+ VALANDNAME_ENTRY(SPI_GETPENVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_SETPENVISUALIZATION),
+ VALANDNAME_ENTRY(SPI_GETSNAPTODEFBUTTON),
+ VALANDNAME_ENTRY(SPI_GETSYSTEMLANGUAGEBAR),
+ VALANDNAME_ENTRY(SPI_SETSYSTEMLANGUAGEBAR),
+ VALANDNAME_ENTRY(SPI_GETTHREADLOCALINPUTSETTINGS),
+ VALANDNAME_ENTRY(SPI_SETTHREADLOCALINPUTSETTINGS),
+ VALANDNAME_ENTRY(SPI_GETWHEELSCROLLCHARS),
+ VALANDNAME_ENTRY(SPI_GETWHEELSCROLLLINES),
+ VALANDNAME_ENTRY(SPI_SETBEEP),
+ VALANDNAME_ENTRY(SPI_SETBLOCKSENDINPUTRESETS),
+ VALANDNAME_ENTRY(SPI_SETDEFAULTINPUTLANG),
+ VALANDNAME_ENTRY(SPI_SETDOUBLECLICKTIME),
+ VALANDNAME_ENTRY(SPI_SETDOUBLECLKHEIGHT),
+ VALANDNAME_ENTRY(SPI_SETDOUBLECLKWIDTH),
+ VALANDNAME_ENTRY(SPI_SETKEYBOARDCUES),
+ VALANDNAME_ENTRY(SPI_SETKEYBOARDDELAY),
+ VALANDNAME_ENTRY(SPI_SETKEYBOARDPREF),
+ VALANDNAME_ENTRY(SPI_SETKEYBOARDSPEED),
+ VALANDNAME_ENTRY(SPI_SETLANGTOGGLE),
+ VALANDNAME_ENTRY(SPI_SETMOUSE),
+ VALANDNAME_ENTRY(SPI_SETMOUSEBUTTONSWAP),
+ VALANDNAME_ENTRY(SPI_SETMOUSEHOVERHEIGHT),
+ VALANDNAME_ENTRY(SPI_SETMOUSEHOVERTIME),
+ VALANDNAME_ENTRY(SPI_SETMOUSEHOVERWIDTH),
+ VALANDNAME_ENTRY(SPI_SETMOUSESPEED),
+ VALANDNAME_ENTRY(SPI_SETMOUSETRAILS),
+ VALANDNAME_ENTRY(SPI_SETSNAPTODEFBUTTON),
+ VALANDNAME_ENTRY(SPI_SETWHEELSCROLLCHARS),
+ VALANDNAME_ENTRY(SPI_SETWHEELSCROLLLINES),
+ VALANDNAME_ENTRY(SPI_GETMENUDROPALIGNMENT),
+ VALANDNAME_ENTRY(SPI_GETMENUFADE),
+ VALANDNAME_ENTRY(SPI_GETMENUSHOWDELAY),
+ VALANDNAME_ENTRY(SPI_SETMENUDROPALIGNMENT),
+ VALANDNAME_ENTRY(SPI_SETMENUFADE),
+ VALANDNAME_ENTRY(SPI_SETMENUSHOWDELAY),
+ VALANDNAME_ENTRY(SPI_GETLOWPOWERACTIVE),
+ VALANDNAME_ENTRY(SPI_GETLOWPOWERTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETPOWEROFFACTIVE),
+ VALANDNAME_ENTRY(SPI_GETPOWEROFFTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETLOWPOWERACTIVE),
+ VALANDNAME_ENTRY(SPI_SETLOWPOWERTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETPOWEROFFACTIVE),
+ VALANDNAME_ENTRY(SPI_SETPOWEROFFTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETSCREENSAVEACTIVE),
+ VALANDNAME_ENTRY(SPI_GETSCREENSAVERRUNNING),
+ VALANDNAME_ENTRY(SPI_GETSCREENSAVESECURE),
+ VALANDNAME_ENTRY(SPI_GETSCREENSAVETIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETSCREENSAVEACTIVE),
+ VALANDNAME_ENTRY(SPI_SETSCREENSAVERRUNNING),
+ VALANDNAME_ENTRY(SPI_SETSCREENSAVESECURE),
+ VALANDNAME_ENTRY(SPI_SETSCREENSAVETIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETHUNGAPPTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETWAITTOKILLTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETWAITTOKILLSERVICETIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETHUNGAPPTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETWAITTOKILLTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETWAITTOKILLSERVICETIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETCOMBOBOXANIMATION),
+ VALANDNAME_ENTRY(SPI_GETCURSORSHADOW),
+ VALANDNAME_ENTRY(SPI_GETGRADIENTCAPTIONS),
+ VALANDNAME_ENTRY(SPI_GETHOTTRACKING),
+ VALANDNAME_ENTRY(SPI_GETLISTBOXSMOOTHSCROLLING),
+ VALANDNAME_ENTRY(SPI_GETMENUANIMATION),
+ VALANDNAME_ENTRY(SPI_GETMENUUNDERLINES),
+ VALANDNAME_ENTRY(SPI_GETSELECTIONFADE),
+ VALANDNAME_ENTRY(SPI_GETTOOLTIPANIMATION),
+ VALANDNAME_ENTRY(SPI_GETTOOLTIPFADE),
+ VALANDNAME_ENTRY(SPI_GETUIEFFECTS),
+ VALANDNAME_ENTRY(SPI_SETCOMBOBOXANIMATION),
+ VALANDNAME_ENTRY(SPI_SETCURSORSHADOW),
+ VALANDNAME_ENTRY(SPI_SETGRADIENTCAPTIONS),
+ VALANDNAME_ENTRY(SPI_SETHOTTRACKING),
+ VALANDNAME_ENTRY(SPI_SETLISTBOXSMOOTHSCROLLING),
+ VALANDNAME_ENTRY(SPI_SETMENUANIMATION),
+ VALANDNAME_ENTRY(SPI_SETMENUUNDERLINES),
+ VALANDNAME_ENTRY(SPI_SETSELECTIONFADE),
+ VALANDNAME_ENTRY(SPI_SETTOOLTIPANIMATION),
+ VALANDNAME_ENTRY(SPI_SETTOOLTIPFADE),
+ VALANDNAME_ENTRY(SPI_SETUIEFFECTS),
+ VALANDNAME_ENTRY(SPI_GETACTIVEWINDOWTRACKING),
+ VALANDNAME_ENTRY(SPI_GETACTIVEWNDTRKZORDER),
+ VALANDNAME_ENTRY(SPI_GETACTIVEWNDTRKTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETANIMATION),
+ VALANDNAME_ENTRY(SPI_GETBORDER),
+ VALANDNAME_ENTRY(SPI_GETCARETWIDTH),
+ VALANDNAME_ENTRY(SPI_GETDOCKMOVING),
+ VALANDNAME_ENTRY(SPI_GETDRAGFROMMAXIMIZE),
+ VALANDNAME_ENTRY(SPI_GETDRAGFULLWINDOWS),
+ VALANDNAME_ENTRY(SPI_GETFOREGROUNDFLASHCOUNT),
+ VALANDNAME_ENTRY(SPI_GETFOREGROUNDLOCKTIMEOUT),
+ VALANDNAME_ENTRY(SPI_GETMINIMIZEDMETRICS),
+ VALANDNAME_ENTRY(SPI_GETMOUSEDOCKTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETMOUSEDRAGOUTTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETMOUSESIDEMOVETHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETNONCLIENTMETRICS),
+ VALANDNAME_ENTRY(SPI_GETPENDOCKTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETPENDRAGOUTTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETPENSIDEMOVETHRESHOLD),
+ VALANDNAME_ENTRY(SPI_GETSHOWIMEUI),
+ VALANDNAME_ENTRY(SPI_GETSNAPSIZING),
+ VALANDNAME_ENTRY(SPI_GETWINARRANGING),
+ VALANDNAME_ENTRY(SPI_SETACTIVEWINDOWTRACKING),
+ VALANDNAME_ENTRY(SPI_SETACTIVEWNDTRKZORDER),
+ VALANDNAME_ENTRY(SPI_SETACTIVEWNDTRKTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETANIMATION),
+ VALANDNAME_ENTRY(SPI_SETBORDER),
+ VALANDNAME_ENTRY(SPI_SETCARETWIDTH),
+ VALANDNAME_ENTRY(SPI_SETDOCKMOVING),
+ VALANDNAME_ENTRY(SPI_SETDRAGFROMMAXIMIZE),
+ VALANDNAME_ENTRY(SPI_SETDRAGFULLWINDOWS),
+ VALANDNAME_ENTRY(SPI_SETFOREGROUNDFLASHCOUNT),
+ VALANDNAME_ENTRY(SPI_SETFOREGROUNDLOCKTIMEOUT),
+ VALANDNAME_ENTRY(SPI_SETMINIMIZEDMETRICS),
+ VALANDNAME_ENTRY(SPI_SETMOUSEDOCKTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETMOUSEDRAGOUTTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETMOUSESIDEMOVETHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETNONCLIENTMETRICS),
+ VALANDNAME_ENTRY(SPI_SETPENDOCKTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETPENDRAGOUTTHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETPENSIDEMOVETHRESHOLD),
+ VALANDNAME_ENTRY(SPI_SETSHOWIMEUI),
+ VALANDNAME_ENTRY(SPI_SETSNAPSIZING),
+ VALANDNAME_ENTRY(SPI_SETWINARRANGING),
+ };
+ AppendEnumValueInfo(str, value, uiActionValues, name);
+}
+
+nsAutoCString WmSizeParamInfo(uint64_t wParam, uint64_t lParam,
+ bool /* isPreCall */) {
+ nsAutoCString result;
+ const static std::unordered_map<uint64_t, const char*> sizeValues{
+ VALANDNAME_ENTRY(SIZE_RESTORED), VALANDNAME_ENTRY(SIZE_MINIMIZED),
+ VALANDNAME_ENTRY(SIZE_MAXIMIZED), VALANDNAME_ENTRY(SIZE_MAXSHOW),
+ VALANDNAME_ENTRY(SIZE_MAXHIDE)};
+ AppendEnumValueInfo(result, wParam, sizeValues, "size");
+ result.AppendPrintf(" width=%d height=%d", static_cast<int>(LOWORD(lParam)),
+ static_cast<int>(HIWORD(lParam)));
+ return result;
+}
+
+const nsTArray<EnumValueAndName> windowPositionFlags = {
+ VALANDNAME_ENTRY(SWP_DRAWFRAME), VALANDNAME_ENTRY(SWP_HIDEWINDOW),
+ VALANDNAME_ENTRY(SWP_NOACTIVATE), VALANDNAME_ENTRY(SWP_NOCOPYBITS),
+ VALANDNAME_ENTRY(SWP_NOMOVE), VALANDNAME_ENTRY(SWP_NOOWNERZORDER),
+ VALANDNAME_ENTRY(SWP_NOREDRAW), VALANDNAME_ENTRY(SWP_NOSENDCHANGING),
+ VALANDNAME_ENTRY(SWP_NOSIZE), VALANDNAME_ENTRY(SWP_NOZORDER),
+ VALANDNAME_ENTRY(SWP_SHOWWINDOW),
+};
+
+void WindowPosParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ LPWINDOWPOS windowPos = reinterpret_cast<LPWINDOWPOS>(value);
+ if (windowPos == nullptr) {
+ str.AppendASCII("null windowPos?");
+ return;
+ }
+ HexParamInfo(str, reinterpret_cast<uint64_t>(windowPos->hwnd), "hwnd", false);
+ str.AppendASCII(" ");
+ HexParamInfo(str, reinterpret_cast<uint64_t>(windowPos->hwndInsertAfter),
+ "hwndInsertAfter", false);
+ str.AppendASCII(" ");
+ IntParamInfo(str, windowPos->x, "x", false);
+ str.AppendASCII(" ");
+ IntParamInfo(str, windowPos->y, "y", false);
+ str.AppendASCII(" ");
+ IntParamInfo(str, windowPos->cx, "cx", false);
+ str.AppendASCII(" ");
+ IntParamInfo(str, windowPos->cy, "cy", false);
+ str.AppendASCII(" ");
+ AppendFlagsInfo(str, windowPos->flags, windowPositionFlags, "flags");
+}
+
+void StyleOrExtendedParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> styleOrExtended{
+ VALANDNAME_ENTRY(GWL_EXSTYLE), VALANDNAME_ENTRY(GWL_STYLE)};
+ AppendEnumValueInfo(str, value, styleOrExtended, name);
+}
+
+void StyleStructParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ LPSTYLESTRUCT styleStruct = reinterpret_cast<LPSTYLESTRUCT>(value);
+ if (styleStruct == nullptr) {
+ str.AppendASCII("null STYLESTRUCT?");
+ return;
+ }
+ HexParamInfo(str, styleStruct->styleOld, "styleOld", false);
+ str.AppendASCII(" ");
+ HexParamInfo(str, styleStruct->styleNew, "styleNew", false);
+}
+
+void NcCalcSizeParamsParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ LPNCCALCSIZE_PARAMS params = reinterpret_cast<LPNCCALCSIZE_PARAMS>(value);
+ if (params == nullptr) {
+ str.AppendASCII("null NCCALCSIZE_PARAMS?");
+ return;
+ }
+ str.AppendPrintf("%s[0]: ", name);
+ RectParamInfo(str, reinterpret_cast<uintptr_t>(&params->rgrc[0]), nullptr,
+ false);
+ str.AppendPrintf(" %s[1]: ", name);
+ RectParamInfo(str, reinterpret_cast<uintptr_t>(&params->rgrc[1]), nullptr,
+ false);
+ str.AppendPrintf(" %s[2]: ", name);
+ RectParamInfo(str, reinterpret_cast<uintptr_t>(&params->rgrc[2]), nullptr,
+ false);
+ str.AppendASCII(" ");
+ WindowPosParamInfo(str, reinterpret_cast<uintptr_t>(params->lppos), nullptr,
+ false);
+}
+
+nsAutoCString WmNcCalcSizeParamInfo(uint64_t wParam, uint64_t lParam,
+ bool /* isPreCall */) {
+ nsAutoCString result;
+ TrueFalseParamInfo(result, wParam, "shouldIndicateValidArea", false);
+ result.AppendASCII(" ");
+ if (wParam == TRUE) {
+ NcCalcSizeParamsParamInfo(result, lParam, "ncCalcSizeParams", false);
+ } else {
+ RectParamInfo(result, lParam, "rect", false);
+ }
+ return result;
+}
+
+void ActivateWParamInfo(nsCString& result, uint64_t wParam, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> activateValues{
+ VALANDNAME_ENTRY(WA_ACTIVE), VALANDNAME_ENTRY(WA_CLICKACTIVE),
+ VALANDNAME_ENTRY(WA_INACTIVE)};
+ AppendEnumValueInfo(result, wParam, activateValues, name);
+}
+
+void HitTestParamInfo(nsCString& result, uint64_t param, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> hitTestResults{
+ VALANDNAME_ENTRY(HTBORDER), VALANDNAME_ENTRY(HTBOTTOM),
+ VALANDNAME_ENTRY(HTBOTTOMLEFT), VALANDNAME_ENTRY(HTBOTTOMRIGHT),
+ VALANDNAME_ENTRY(HTCAPTION), VALANDNAME_ENTRY(HTCLIENT),
+ VALANDNAME_ENTRY(HTCLOSE), VALANDNAME_ENTRY(HTERROR),
+ VALANDNAME_ENTRY(HTGROWBOX), VALANDNAME_ENTRY(HTHELP),
+ VALANDNAME_ENTRY(HTHSCROLL), VALANDNAME_ENTRY(HTLEFT),
+ VALANDNAME_ENTRY(HTMENU), VALANDNAME_ENTRY(HTMAXBUTTON),
+ VALANDNAME_ENTRY(HTMINBUTTON), VALANDNAME_ENTRY(HTNOWHERE),
+ VALANDNAME_ENTRY(HTREDUCE), VALANDNAME_ENTRY(HTRIGHT),
+ VALANDNAME_ENTRY(HTSIZE), VALANDNAME_ENTRY(HTSYSMENU),
+ VALANDNAME_ENTRY(HTTOP), VALANDNAME_ENTRY(HTTOPLEFT),
+ VALANDNAME_ENTRY(HTTOPRIGHT), VALANDNAME_ENTRY(HTTRANSPARENT),
+ VALANDNAME_ENTRY(HTVSCROLL), VALANDNAME_ENTRY(HTZOOM),
+ };
+ AppendEnumValueInfo(result, param, hitTestResults, name);
+}
+
+void SetCursorLParamInfo(nsCString& result, uint64_t lParam,
+ const char* /* name */, bool /* isPreCall */) {
+ HitTestParamInfo(result, LOWORD(lParam), "hitTestResult", false);
+ result.AppendASCII(" ");
+ HexParamInfo(result, HIWORD(lParam), "message", false);
+}
+
+void MinMaxInfoParamInfo(nsCString& result, uint64_t value,
+ const char* /* name */, bool /* isPreCall */) {
+ PMINMAXINFO minMaxInfo = reinterpret_cast<PMINMAXINFO>(value);
+ if (minMaxInfo == nullptr) {
+ result.AppendPrintf("NULL minMaxInfo?");
+ return;
+ }
+ PointExplicitParamInfo(result, minMaxInfo->ptMaxSize, "maxSize");
+ result.AppendASCII(" ");
+ PointExplicitParamInfo(result, minMaxInfo->ptMaxPosition, "maxPosition");
+ result.AppendASCII(" ");
+ PointExplicitParamInfo(result, minMaxInfo->ptMinTrackSize, "minTrackSize");
+ result.AppendASCII(" ");
+ PointExplicitParamInfo(result, minMaxInfo->ptMaxTrackSize, "maxTrackSize");
+}
+
+void WideStringParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ result.AppendPrintf("%s=%S", name, reinterpret_cast<LPCWSTR>(value));
+}
+
+void DeviceEventParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ const static std::unordered_map<uint64_t, const char*> deviceEventValues{
+ VALANDNAME_ENTRY(DBT_DEVNODES_CHANGED),
+ VALANDNAME_ENTRY(DBT_QUERYCHANGECONFIG),
+ VALANDNAME_ENTRY(DBT_CONFIGCHANGED),
+ VALANDNAME_ENTRY(DBT_CONFIGCHANGECANCELED),
+ VALANDNAME_ENTRY(DBT_DEVICEARRIVAL),
+ VALANDNAME_ENTRY(DBT_DEVICEQUERYREMOVE),
+ VALANDNAME_ENTRY(DBT_DEVICEQUERYREMOVEFAILED),
+ VALANDNAME_ENTRY(DBT_DEVICEREMOVEPENDING),
+ VALANDNAME_ENTRY(DBT_DEVICEREMOVECOMPLETE),
+ VALANDNAME_ENTRY(DBT_DEVICETYPESPECIFIC),
+ VALANDNAME_ENTRY(DBT_CUSTOMEVENT),
+ VALANDNAME_ENTRY(DBT_USERDEFINED)};
+ AppendEnumValueInfo(result, value, deviceEventValues, name);
+}
+
+void ResolutionParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool /* isPreCall */) {
+ result.AppendPrintf("horizontalRes=%d verticalRes=%d", LOWORD(value),
+ HIWORD(value));
+}
+
+// Window message with default wParam/lParam logging
+#define ENTRY(_msg) \
+ { \
+ _msg, { #_msg, _msg, DefaultParamInfo } \
+ }
+// Window message with no parameters
+#define ENTRY_WITH_NO_PARAM_INFO(_msg) \
+ { \
+ _msg, { #_msg, _msg, nullptr } \
+ }
+// Window message with custom parameter logging functions
+#define ENTRY_WITH_CUSTOM_PARAM_INFO(_msg, paramInfoFn) \
+ { \
+ _msg, { #_msg, _msg, paramInfoFn } \
+ }
+// Window message with separate custom wParam and lParam logging functions
+#define ENTRY_WITH_SPLIT_PARAM_INFOS(_msg, wParamInfoFn, wParamName, \
+ lParamInfoFn, lParamName) \
+ { \
+ _msg, { \
+ #_msg, _msg, nullptr, wParamInfoFn, wParamName, lParamInfoFn, lParamName \
+ } \
+ }
+std::unordered_map<UINT, EventMsgInfo> gAllEvents = {
+ ENTRY_WITH_NO_PARAM_INFO(WM_NULL),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_CREATE, nullptr, nullptr,
+ CreateStructParamInfo, "createStruct"),
+ ENTRY_WITH_NO_PARAM_INFO(WM_DESTROY),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOVE, nullptr, nullptr,
+ XLowWordYHighWordParamInfo, "upperLeft"),
+ ENTRY_WITH_CUSTOM_PARAM_INFO(WM_SIZE, WmSizeParamInfo),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_ACTIVATE, ActivateWParamInfo, "wParam",
+ HexParamInfo, "handle"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SETFOCUS, HexParamInfo, "handle", nullptr,
+ nullptr),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_KILLFOCUS, HexParamInfo, "handle", nullptr,
+ nullptr),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_ENABLE, TrueFalseParamInfo, "enabled",
+ nullptr, nullptr),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SETREDRAW, TrueFalseParamInfo,
+ "redrawState", nullptr, nullptr),
+ ENTRY(WM_SETTEXT),
+ ENTRY(WM_GETTEXT),
+ ENTRY(WM_GETTEXTLENGTH),
+ ENTRY_WITH_NO_PARAM_INFO(WM_PAINT),
+ ENTRY_WITH_NO_PARAM_INFO(WM_CLOSE),
+ ENTRY(WM_QUERYENDSESSION),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_QUIT, HexParamInfo, "exitCode", nullptr,
+ nullptr),
+ ENTRY(WM_QUERYOPEN),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_ERASEBKGND, HexParamInfo, "deviceContext",
+ nullptr, nullptr),
+ ENTRY(WM_SYSCOLORCHANGE),
+ ENTRY(WM_ENDSESSION),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SHOWWINDOW, TrueFalseParamInfo,
+ "windowBeingShown", ShowWindowReasonParamInfo,
+ "status"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SETTINGCHANGE, UiActionParamInfo,
+ "uiAction", WideStringParamInfo,
+ "paramChanged"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DEVMODECHANGE, nullptr, nullptr,
+ WideStringParamInfo, "deviceName"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_ACTIVATEAPP, TrueFalseParamInfo,
+ "activated", HexParamInfo, "threadId"),
+ ENTRY(WM_FONTCHANGE),
+ ENTRY(WM_TIMECHANGE),
+ ENTRY(WM_CANCELMODE),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SETCURSOR, HexParamInfo, "windowHandle",
+ SetCursorLParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOUSEACTIVATE, HexParamInfo, "windowHandle",
+ SetCursorLParamInfo, ""),
+ ENTRY(WM_CHILDACTIVATE),
+ ENTRY(WM_QUEUESYNC),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_GETMINMAXINFO, nullptr, nullptr,
+ MinMaxInfoParamInfo, ""),
+ ENTRY(WM_PAINTICON),
+ ENTRY(WM_ICONERASEBKGND),
+ ENTRY(WM_NEXTDLGCTL),
+ ENTRY(WM_SPOOLERSTATUS),
+ ENTRY(WM_DRAWITEM),
+ ENTRY(WM_MEASUREITEM),
+ ENTRY(WM_DELETEITEM),
+ ENTRY(WM_VKEYTOITEM),
+ ENTRY(WM_CHARTOITEM),
+ ENTRY(WM_SETFONT),
+ ENTRY(WM_GETFONT),
+ ENTRY(WM_SETHOTKEY),
+ ENTRY(WM_GETHOTKEY),
+ ENTRY(WM_QUERYDRAGICON),
+ ENTRY(WM_COMPAREITEM),
+ ENTRY(WM_GETOBJECT),
+ ENTRY(WM_COMPACTING),
+ ENTRY(WM_COMMNOTIFY),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_WINDOWPOSCHANGING, nullptr, nullptr,
+ WindowPosParamInfo, "newSizeAndPos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_WINDOWPOSCHANGED, nullptr, nullptr,
+ WindowPosParamInfo, "newSizeAndPos"),
+ ENTRY(WM_POWER),
+ ENTRY(WM_COPYDATA),
+ ENTRY(WM_CANCELJOURNAL),
+ ENTRY(WM_NOTIFY),
+ ENTRY(WM_INPUTLANGCHANGEREQUEST),
+ ENTRY(WM_INPUTLANGCHANGE),
+ ENTRY(WM_TCARD),
+ ENTRY(WM_HELP),
+ ENTRY(WM_USERCHANGED),
+ ENTRY(WM_NOTIFYFORMAT),
+ ENTRY(WM_CONTEXTMENU),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_STYLECHANGING, StyleOrExtendedParamInfo,
+ "styleOrExtended", StyleStructParamInfo,
+ "newStyles"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_STYLECHANGED, StyleOrExtendedParamInfo,
+ "styleOrExtended", StyleStructParamInfo,
+ "newStyles"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DISPLAYCHANGE, IntParamInfo, "bitsPerPixel",
+ ResolutionParamInfo, ""),
+ ENTRY(WM_GETICON),
+ ENTRY(WM_SETICON),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCCREATE, nullptr, nullptr,
+ CreateStructParamInfo, "createStruct"),
+ ENTRY_WITH_NO_PARAM_INFO(WM_NCDESTROY),
+ ENTRY_WITH_CUSTOM_PARAM_INFO(WM_NCCALCSIZE, WmNcCalcSizeParamInfo),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCHITTEST, nullptr, nullptr,
+ XLowWordYHighWordParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCPAINT, HexParamInfo, "updateRegionHandle",
+ nullptr, nullptr),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCACTIVATE, TrueFalseParamInfo,
+ "isTitleBarOrIconActive", HexParamInfo,
+ "updateRegion"),
+ ENTRY(WM_GETDLGCODE),
+ ENTRY(WM_SYNCPAINT),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCMOUSEMOVE, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCLBUTTONDOWN, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCLBUTTONUP, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCLBUTTONDBLCLK, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCRBUTTONDOWN, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCRBUTTONUP, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCRBUTTONDBLCLK, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCMBUTTONDOWN, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCMBUTTONUP, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCMBUTTONDBLCLK, HitTestParamInfo,
+ "hitTestValue", PointsParamInfo, "mousePos"),
+ ENTRY(EM_GETSEL),
+ ENTRY(EM_SETSEL),
+ ENTRY(EM_GETRECT),
+ ENTRY(EM_SETRECT),
+ ENTRY(EM_SETRECTNP),
+ ENTRY(EM_SCROLL),
+ ENTRY(EM_LINESCROLL),
+ ENTRY(EM_SCROLLCARET),
+ ENTRY(EM_GETMODIFY),
+ ENTRY(EM_SETMODIFY),
+ ENTRY(EM_GETLINECOUNT),
+ ENTRY(EM_LINEINDEX),
+ ENTRY(EM_SETHANDLE),
+ ENTRY(EM_GETHANDLE),
+ ENTRY(EM_GETTHUMB),
+ ENTRY(EM_LINELENGTH),
+ ENTRY(EM_REPLACESEL),
+ ENTRY(EM_GETLINE),
+ ENTRY(EM_LIMITTEXT),
+ ENTRY(EM_CANUNDO),
+ ENTRY(EM_UNDO),
+ ENTRY(EM_FMTLINES),
+ ENTRY(EM_LINEFROMCHAR),
+ ENTRY(EM_SETTABSTOPS),
+ ENTRY(EM_SETPASSWORDCHAR),
+ ENTRY(EM_EMPTYUNDOBUFFER),
+ ENTRY(EM_GETFIRSTVISIBLELINE),
+ ENTRY(EM_SETREADONLY),
+ ENTRY(EM_SETWORDBREAKPROC),
+ ENTRY(EM_GETWORDBREAKPROC),
+ ENTRY(EM_GETPASSWORDCHAR),
+ ENTRY(EM_SETMARGINS),
+ ENTRY(EM_GETMARGINS),
+ ENTRY(EM_GETLIMITTEXT),
+ ENTRY(EM_POSFROMCHAR),
+ ENTRY(EM_CHARFROMPOS),
+ ENTRY(EM_SETIMESTATUS),
+ ENTRY(EM_GETIMESTATUS),
+ ENTRY(SBM_SETPOS),
+ ENTRY(SBM_GETPOS),
+ ENTRY(SBM_SETRANGE),
+ ENTRY(SBM_SETRANGEREDRAW),
+ ENTRY(SBM_GETRANGE),
+ ENTRY(SBM_ENABLE_ARROWS),
+ ENTRY(SBM_SETSCROLLINFO),
+ ENTRY(SBM_GETSCROLLINFO),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_KEYDOWN, VirtualKeyParamInfo, "vKey",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_KEYUP, VirtualKeyParamInfo, "vKey",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_CHAR, IntParamInfo, "charCode",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DEADCHAR, IntParamInfo, "charCode",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SYSKEYDOWN, VirtualKeyParamInfo, "vKey",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SYSKEYUP, VirtualKeyParamInfo, "vKey",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SYSCHAR, IntParamInfo, "charCode",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SYSDEADCHAR, IntParamInfo, "charCode",
+ KeystrokeFlagsParamInfo, ""),
+ ENTRY(WM_KEYLAST),
+ ENTRY(WM_IME_STARTCOMPOSITION),
+ ENTRY(WM_IME_ENDCOMPOSITION),
+ ENTRY(WM_IME_COMPOSITION),
+ ENTRY(WM_INITDIALOG),
+ ENTRY(WM_COMMAND),
+ ENTRY(WM_SYSCOMMAND),
+ ENTRY(WM_TIMER),
+ ENTRY(WM_HSCROLL),
+ ENTRY(WM_VSCROLL),
+ ENTRY(WM_INITMENU),
+ ENTRY(WM_INITMENUPOPUP),
+ ENTRY(WM_MENUSELECT),
+ ENTRY(WM_MENUCHAR),
+ ENTRY(WM_ENTERIDLE),
+ ENTRY(WM_MENURBUTTONUP),
+ ENTRY(WM_MENUDRAG),
+ ENTRY(WM_MENUGETOBJECT),
+ ENTRY(WM_UNINITMENUPOPUP),
+ ENTRY(WM_MENUCOMMAND),
+ ENTRY(WM_CHANGEUISTATE),
+ ENTRY(WM_QUERYUISTATE),
+ ENTRY(WM_UPDATEUISTATE),
+ ENTRY(WM_CTLCOLORMSGBOX),
+ ENTRY(WM_CTLCOLOREDIT),
+ ENTRY(WM_CTLCOLORLISTBOX),
+ ENTRY(WM_CTLCOLORBTN),
+ ENTRY(WM_CTLCOLORDLG),
+ ENTRY(WM_CTLCOLORSCROLLBAR),
+ ENTRY(WM_CTLCOLORSTATIC),
+ ENTRY(CB_GETEDITSEL),
+ ENTRY(CB_LIMITTEXT),
+ ENTRY(CB_SETEDITSEL),
+ ENTRY(CB_ADDSTRING),
+ ENTRY(CB_DELETESTRING),
+ ENTRY(CB_DIR),
+ ENTRY(CB_GETCOUNT),
+ ENTRY(CB_GETCURSEL),
+ ENTRY(CB_GETLBTEXT),
+ ENTRY(CB_GETLBTEXTLEN),
+ ENTRY(CB_INSERTSTRING),
+ ENTRY(CB_RESETCONTENT),
+ ENTRY(CB_FINDSTRING),
+ ENTRY(CB_SELECTSTRING),
+ ENTRY(CB_SETCURSEL),
+ ENTRY(CB_SHOWDROPDOWN),
+ ENTRY(CB_GETITEMDATA),
+ ENTRY(CB_SETITEMDATA),
+ ENTRY(CB_GETDROPPEDCONTROLRECT),
+ ENTRY(CB_SETITEMHEIGHT),
+ ENTRY(CB_GETITEMHEIGHT),
+ ENTRY(CB_SETEXTENDEDUI),
+ ENTRY(CB_GETEXTENDEDUI),
+ ENTRY(CB_GETDROPPEDSTATE),
+ ENTRY(CB_FINDSTRINGEXACT),
+ ENTRY(CB_SETLOCALE),
+ ENTRY(CB_GETLOCALE),
+ ENTRY(CB_GETTOPINDEX),
+ ENTRY(CB_SETTOPINDEX),
+ ENTRY(CB_GETHORIZONTALEXTENT),
+ ENTRY(CB_SETHORIZONTALEXTENT),
+ ENTRY(CB_GETDROPPEDWIDTH),
+ ENTRY(CB_SETDROPPEDWIDTH),
+ ENTRY(CB_INITSTORAGE),
+ ENTRY(CB_MSGMAX),
+ ENTRY(LB_ADDSTRING),
+ ENTRY(LB_INSERTSTRING),
+ ENTRY(LB_DELETESTRING),
+ ENTRY(LB_SELITEMRANGEEX),
+ ENTRY(LB_RESETCONTENT),
+ ENTRY(LB_SETSEL),
+ ENTRY(LB_SETCURSEL),
+ ENTRY(LB_GETSEL),
+ ENTRY(LB_GETCURSEL),
+ ENTRY(LB_GETTEXT),
+ ENTRY(LB_GETTEXTLEN),
+ ENTRY(LB_GETCOUNT),
+ ENTRY(LB_SELECTSTRING),
+ ENTRY(LB_DIR),
+ ENTRY(LB_GETTOPINDEX),
+ ENTRY(LB_FINDSTRING),
+ ENTRY(LB_GETSELCOUNT),
+ ENTRY(LB_GETSELITEMS),
+ ENTRY(LB_SETTABSTOPS),
+ ENTRY(LB_GETHORIZONTALEXTENT),
+ ENTRY(LB_SETHORIZONTALEXTENT),
+ ENTRY(LB_SETCOLUMNWIDTH),
+ ENTRY(LB_ADDFILE),
+ ENTRY(LB_SETTOPINDEX),
+ ENTRY(LB_GETITEMRECT),
+ ENTRY(LB_GETITEMDATA),
+ ENTRY(LB_SETITEMDATA),
+ ENTRY(LB_SELITEMRANGE),
+ ENTRY(LB_SETANCHORINDEX),
+ ENTRY(LB_GETANCHORINDEX),
+ ENTRY(LB_SETCARETINDEX),
+ ENTRY(LB_GETCARETINDEX),
+ ENTRY(LB_SETITEMHEIGHT),
+ ENTRY(LB_GETITEMHEIGHT),
+ ENTRY(LB_FINDSTRINGEXACT),
+ ENTRY(LB_SETLOCALE),
+ ENTRY(LB_GETLOCALE),
+ ENTRY(LB_SETCOUNT),
+ ENTRY(LB_INITSTORAGE),
+ ENTRY(LB_ITEMFROMPOINT),
+ ENTRY(LB_MSGMAX),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOUSEMOVE, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_LBUTTONDOWN, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_LBUTTONUP, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_LBUTTONDBLCLK, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_RBUTTONDOWN, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_RBUTTONUP, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_RBUTTONDBLCLK, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MBUTTONDOWN, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MBUTTONUP, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MBUTTONDBLCLK, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOUSEWHEEL,
+ VirtualKeysLowWordDistanceHighWordParamInfo,
+ "", XLowWordYHighWordParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOUSEHWHEEL,
+ VirtualKeysLowWordDistanceHighWordParamInfo,
+ "", XLowWordYHighWordParamInfo, "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_PARENTNOTIFY, ParentNotifyEventParamInfo,
+ "", PointParamInfo, "pointerLocation"),
+ ENTRY(WM_ENTERMENULOOP),
+ ENTRY(WM_EXITMENULOOP),
+ ENTRY(WM_NEXTMENU),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_SIZING, WindowEdgeParamInfo, "edge",
+ RectParamInfo, "rect"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_CAPTURECHANGED, nullptr, nullptr,
+ HexParamInfo, "windowHandle"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOVING, nullptr, nullptr, RectParamInfo,
+ "rect"),
+ ENTRY(WM_POWERBROADCAST),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DEVICECHANGE, DeviceEventParamInfo, "event",
+ HexParamInfo, "data"),
+ ENTRY(WM_MDICREATE),
+ ENTRY(WM_MDIDESTROY),
+ ENTRY(WM_MDIACTIVATE),
+ ENTRY(WM_MDIRESTORE),
+ ENTRY(WM_MDINEXT),
+ ENTRY(WM_MDIMAXIMIZE),
+ ENTRY(WM_MDITILE),
+ ENTRY(WM_MDICASCADE),
+ ENTRY(WM_MDIICONARRANGE),
+ ENTRY(WM_MDIGETACTIVE),
+ ENTRY(WM_MDISETMENU),
+ ENTRY(WM_ENTERSIZEMOVE),
+ ENTRY(WM_EXITSIZEMOVE),
+ ENTRY(WM_DROPFILES),
+ ENTRY(WM_MDIREFRESHMENU),
+ ENTRY(WM_IME_SETCONTEXT),
+ ENTRY(WM_IME_NOTIFY),
+ ENTRY(WM_IME_CONTROL),
+ ENTRY(WM_IME_COMPOSITIONFULL),
+ ENTRY(WM_IME_SELECT),
+ ENTRY(WM_IME_CHAR),
+ ENTRY(WM_IME_REQUEST),
+ ENTRY(WM_IME_KEYDOWN),
+ ENTRY(WM_IME_KEYUP),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_NCMOUSEHOVER, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_MOUSEHOVER, VirtualModifierKeysParamInfo,
+ "virtualKeys", XLowWordYHighWordParamInfo,
+ "mousePos"),
+ ENTRY_WITH_NO_PARAM_INFO(WM_NCMOUSELEAVE),
+ ENTRY_WITH_NO_PARAM_INFO(WM_MOUSELEAVE),
+ ENTRY(WM_CUT),
+ ENTRY(WM_COPY),
+ ENTRY(WM_PASTE),
+ ENTRY(WM_CLEAR),
+ ENTRY(WM_UNDO),
+ ENTRY(WM_RENDERFORMAT),
+ ENTRY(WM_RENDERALLFORMATS),
+ ENTRY(WM_DESTROYCLIPBOARD),
+ ENTRY(WM_DRAWCLIPBOARD),
+ ENTRY(WM_PAINTCLIPBOARD),
+ ENTRY(WM_VSCROLLCLIPBOARD),
+ ENTRY(WM_SIZECLIPBOARD),
+ ENTRY(WM_ASKCBFORMATNAME),
+ ENTRY(WM_CHANGECBCHAIN),
+ ENTRY(WM_HSCROLLCLIPBOARD),
+ ENTRY(WM_QUERYNEWPALETTE),
+ ENTRY(WM_PALETTEISCHANGING),
+ ENTRY(WM_PALETTECHANGED),
+ ENTRY(WM_HOTKEY),
+ ENTRY(WM_PRINT),
+ ENTRY(WM_PRINTCLIENT),
+ ENTRY(WM_THEMECHANGED),
+ ENTRY(WM_HANDHELDFIRST),
+ ENTRY(WM_HANDHELDLAST),
+ ENTRY(WM_AFXFIRST),
+ ENTRY(WM_AFXLAST),
+ ENTRY(WM_PENWINFIRST),
+ ENTRY(WM_PENWINLAST),
+ ENTRY(WM_APP),
+ ENTRY_WITH_NO_PARAM_INFO(WM_DWMCOMPOSITIONCHANGED),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DWMNCRENDERINGCHANGED, TrueFalseParamInfo,
+ "DwmNcRendering", nullptr, nullptr),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DWMCOLORIZATIONCOLORCHANGED, HexParamInfo,
+ "color:AARRGGBB", TrueFalseParamInfo,
+ "isOpaque"),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DWMWINDOWMAXIMIZEDCHANGE,
+ TrueFalseParamInfo, "maximized", nullptr,
+ nullptr),
+ ENTRY(WM_DWMSENDICONICTHUMBNAIL), // lParam: HIWORD is x, LOWORD is y
+ ENTRY_WITH_NO_PARAM_INFO(WM_DWMSENDICONICLIVEPREVIEWBITMAP),
+ ENTRY(WM_TABLET_QUERYSYSTEMGESTURESTATUS),
+ ENTRY(WM_GESTURE),
+ ENTRY(WM_GESTURENOTIFY),
+ ENTRY(WM_GETTITLEBARINFOEX),
+ ENTRY_WITH_SPLIT_PARAM_INFOS(WM_DPICHANGED, XLowWordYHighWordParamInfo,
+ "newDPI", RectParamInfo,
+ "suggestedSizeAndPos"),
+};
+#undef ENTRY
+#undef ENTRY_WITH_NO_PARAM_INFO
+#undef ENTRY_WITH_CUSTOM_PARAM_INFO
+#undef ENTRY_WITH_SPLIT_PARAM_INFO
+
+} // namespace mozilla::widget
+
+#ifdef DEBUG
+void DDError(const char* msg, HRESULT hr) {
+ /*XXX make nicer */
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ ("DirectDraw error %s: 0x%08lx\n", msg, hr));
+}
+#endif
+
+#ifdef DEBUG_VK
+bool is_vk_down(int vk) {
+ SHORT st = GetKeyState(vk);
+# ifdef DEBUG
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("is_vk_down vk=%x st=%x\n", vk, st));
+# endif
+ return (st < 0);
+}
+#endif
diff --git a/widget/windows/nsWindowDbg.h b/widget/windows/nsWindowDbg.h
new file mode 100644
index 0000000000..c739966fd5
--- /dev/null
+++ b/widget/windows/nsWindowDbg.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WindowDbg_h__
+#define WindowDbg_h__
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "nsWindowDefs.h"
+#include "mozilla/BaseProfilerMarkersPrerequisites.h"
+
+// Enables debug output for popup rollup hooks
+// #define POPUP_ROLLUP_DEBUG_OUTPUT
+
+// Enable window size and state debug output
+// #define WINSTATE_DEBUG_OUTPUT
+
+// nsIWidget defines a set of debug output statements
+// that are called in various places within the code.
+// #define WIDGET_DEBUG_OUTPUT
+
+// Enable IS_VK_DOWN debug output
+// #define DEBUG_VK
+
+namespace mozilla::widget {
+
+class MOZ_RAII AutoProfilerMessageMarker {
+ public:
+ explicit AutoProfilerMessageMarker(Span<const char> aMsgLoopName, HWND hWnd,
+ UINT msg, WPARAM wParam, LPARAM lParam);
+
+ ~AutoProfilerMessageMarker();
+
+ protected:
+ Maybe<MarkerOptions> mOptions;
+ Span<const char> mMsgLoopName;
+ UINT mMsg;
+ WPARAM mWParam;
+ LPARAM mLParam;
+};
+
+// Windows message debugging data
+struct EventMsgInfo {
+ const char* mStr;
+ UINT mId;
+ std::function<nsAutoCString(WPARAM, LPARAM, bool)> mParamInfoFn;
+ std::function<void(nsCString&, WPARAM, const char*, bool)> mWParamInfoFn;
+ const char* mWParamName;
+ std::function<void(nsCString&, LPARAM, const char*, bool)> mLParamInfoFn;
+ const char* mLParamName;
+ void LogParameters(nsCString& str, WPARAM wParam, LPARAM lParam,
+ bool isPreCall);
+};
+extern std::unordered_map<UINT, EventMsgInfo> gAllEvents;
+
+// RAII-style class to log before and after an event is handled.
+class NativeEventLogger final {
+ public:
+ template <size_t N>
+ NativeEventLogger(const char (&aMsgLoopName)[N], HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+ : NativeEventLogger(Span(aMsgLoopName), hwnd, msg, wParam, lParam) {}
+
+ void SetResult(LRESULT lresult, bool result) {
+ mRetValue = lresult;
+ mResult = mozilla::Some(result);
+ }
+ ~NativeEventLogger();
+
+ private:
+ NativeEventLogger(Span<const char> aMsgLoopName, HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+ bool NativeEventLoggerInternal();
+
+ AutoProfilerMessageMarker mProfilerMarker;
+ const char* mMsgLoopName;
+ const HWND mHwnd;
+ const UINT mMsg;
+ const WPARAM mWParam;
+ const LPARAM mLParam;
+ mozilla::Maybe<long> mEventCounter;
+ // not const because these will be set after the event is handled
+ mozilla::Maybe<bool> mResult;
+ LRESULT mRetValue = 0;
+
+ bool mShouldLogPostCall;
+};
+
+struct EnumValueAndName {
+ uint64_t mFlag;
+ const char* mName;
+};
+
+// Appends to str a description of the flags passed in.
+// flagsAndNames is a list of flag values with a string description
+// for each one. These are processed in order, so if there are
+// flag values that are combination of individual values (for example
+// something like WS_OVERLAPPEDWINDOW) they need to come first
+// in the flagsAndNames array.
+// A 0 flag value will only be written if the flags input is exactly
+// 0, and it must come last in the flagsAndNames array.
+// Returns whether any info was appended to str.
+bool AppendFlagsInfo(nsCString& str, uint64_t flags,
+ const nsTArray<EnumValueAndName>& flagsAndNames,
+ const char* name);
+
+nsAutoCString WmSizeParamInfo(uint64_t wParam, uint64_t lParam, bool isPreCall);
+void XLowWordYHighWordParamInfo(nsCString& str, uint64_t value,
+ const char* name, bool isPreCall);
+void WindowPosParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool isPreCall);
+void WindowEdgeParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool isPreCall);
+void RectParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool isPreCall);
+void UiActionParamInfo(nsCString& str, uint64_t value, const char* name,
+ bool isPreCall);
+void WideStringParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool isPreCall);
+void MinMaxInfoParamInfo(nsCString& result, uint64_t value, const char* name,
+ bool isPreCall);
+nsAutoCString WmNcCalcSizeParamInfo(uint64_t wParam, uint64_t lParam,
+ bool isPreCall);
+} // namespace mozilla::widget
+
+#if defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+typedef struct {
+ char* mStr;
+ int mId;
+} MSGFEventMsgInfo;
+
+# define DISPLAY_NMM_PRT(_arg) \
+ MOZ_LOG(gWindowsLog, mozilla::LogLevel::Info, ((_arg)));
+#else
+# define DISPLAY_NMM_PRT(_arg)
+#endif // defined(POPUP_ROLLUP_DEBUG_OUTPUT)
+
+#if defined(DEBUG)
+void DDError(const char* msg, HRESULT hr);
+#endif // defined(DEBUG)
+
+#if defined(DEBUG_VK)
+bool is_vk_down(int vk);
+# define IS_VK_DOWN is_vk_down
+#else
+# define IS_VK_DOWN(a) (GetKeyState(a) < 0)
+#endif // defined(DEBUG_VK)
+
+#endif /* WindowDbg_h__ */
diff --git a/widget/windows/nsWindowDefs.h b/widget/windows/nsWindowDefs.h
new file mode 100644
index 0000000000..320d6ef07b
--- /dev/null
+++ b/widget/windows/nsWindowDefs.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WindowDefs_h__
+#define WindowDefs_h__
+
+/*
+ * nsWindowDefs - nsWindow related definitions, consts, and macros.
+ */
+
+#include "mozilla/widget/WinMessages.h"
+#include "nsBaseWidget.h"
+#include "nsdefs.h"
+#include "resource.h"
+
+/**************************************************************
+ *
+ * SECTION: defines
+ *
+ **************************************************************/
+
+// ConstrainPosition window positioning slop value
+#define kWindowPositionSlop 20
+
+// Origin of the system context menu when displayed in full screen mode
+#define MOZ_SYSCONTEXT_X_POS 20
+#define MOZ_SYSCONTEXT_Y_POS 20
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+// Tablet PC Mouse Input Source
+#define TABLET_INK_SIGNATURE 0xFFFFFF00
+#define TABLET_INK_CHECK 0xFF515700
+#define TABLET_INK_TOUCH 0x00000080
+#define TABLET_INK_ID_MASK 0x0000007F
+#define MOUSE_INPUT_SOURCE() WinUtils::GetMouseInputSource()
+#define MOUSE_POINTERID() WinUtils::GetMousePointerID()
+
+/**************************************************************
+ *
+ * SECTION: constants
+ *
+ **************************************************************/
+
+/*
+ * Native windows class names
+ *
+ * ::: IMPORTANT :::
+ *
+ * External apps and drivers depend on window class names.
+ * For example, changing the window classes could break
+ * touchpad scrolling or screen readers.
+ *
+ * See bug 1776498.
+ */
+const wchar_t kClassNameHidden[] = L"MozillaHiddenWindowClass";
+const wchar_t kClassNameGeneral[] = L"MozillaWindowClass";
+const wchar_t kClassNameDialog[] = L"MozillaDialogClass";
+const wchar_t kClassNameDropShadow[] = L"MozillaDropShadowWindowClass";
+const wchar_t kClassNameTransition[] = L"MozillaTransitionWindowClass";
+
+/**************************************************************
+ *
+ * SECTION: structs
+ *
+ **************************************************************/
+
+// Used for synthesizing events
+struct KeyPair {
+ uint8_t mGeneral;
+ uint8_t mSpecific;
+ uint16_t mScanCode;
+ KeyPair(uint32_t aGeneral, uint32_t aSpecific)
+ : mGeneral(aGeneral & 0xFF),
+ mSpecific(aSpecific & 0xFF),
+ mScanCode((aGeneral & 0xFFFF0000) >> 16) {}
+ KeyPair(uint8_t aGeneral, uint8_t aSpecific, uint16_t aScanCode)
+ : mGeneral(aGeneral), mSpecific(aSpecific), mScanCode(aScanCode) {}
+};
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult {
+ // Result for the message.
+ LRESULT& mResult;
+ // If mConsumed is true, the caller shouldn't call next wndproc.
+ bool mConsumed;
+
+ explicit MSGResult(LRESULT* aResult = nullptr)
+ : mResult(aResult ? *aResult : mDefaultResult), mConsumed(false) {}
+
+ private:
+ LRESULT mDefaultResult;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+/**************************************************************
+ *
+ * SECTION: macros
+ *
+ **************************************************************/
+
+#define NSRGB_2_COLOREF(color) \
+ RGB(NS_GET_R(color), NS_GET_G(color), NS_GET_B(color))
+#define COLOREF_2_NSRGB(color) \
+ NS_RGB(GetRValue(color), GetGValue(color), GetBValue(color))
+
+#define VERIFY_WINDOW_STYLE(s) \
+ NS_ASSERTION(((s) & (WS_CHILD | WS_POPUP)) != (WS_CHILD | WS_POPUP), \
+ "WS_POPUP and WS_CHILD are mutually exclusive")
+
+#endif /* WindowDefs_h__ */
diff --git a/widget/windows/nsWindowGfx.cpp b/widget/windows/nsWindowGfx.cpp
new file mode 100644
index 0000000000..c2a91dcf6b
--- /dev/null
+++ b/widget/windows/nsWindowGfx.cpp
@@ -0,0 +1,718 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/*
+ * nsWindowGfx - Painting and aceleration.
+ */
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Includes
+ **
+ ** Include headers.
+ **
+ **************************************************************
+ **************************************************************/
+
+#include "mozilla/dom/ContentParent.h"
+
+#include "nsWindowGfx.h"
+#include "nsAppRunner.h"
+#include <windows.h>
+#include <shellapi.h>
+#include "gfxEnv.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "gfxConfig.h"
+#include "gfxWindowsSurface.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxDWriteFonts.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsGfxCIID.h"
+#include "gfxContext.h"
+#include "WinUtils.h"
+#include "WinWindowOcclusionTracker.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "WindowRenderer.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "InProcessWinCompositorWidget.h"
+
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla::plugins;
+extern mozilla::LazyLogModule gWindowsLog;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+
+struct IconMetrics {
+ int32_t xMetric;
+ int32_t yMetric;
+ int32_t defaultSize;
+};
+
+// Corresponds 1:1 to the IconSizeType enum
+static IconMetrics sIconMetrics[] = {
+ {SM_CXSMICON, SM_CYSMICON, 16}, // small icon
+ {SM_CXICON, SM_CYICON, 32} // regular icon
+};
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsWindow impl.
+ **
+ ** Paint related nsWindow methods.
+ **
+ **************************************************************
+ **************************************************************/
+
+// GetRegionToPaint returns the invalidated region that needs to be painted
+LayoutDeviceIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC) {
+ if (aForceFullRepaint) {
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ return LayoutDeviceIntRegion(WinUtils::ToIntRect(paintRect));
+ }
+
+ HRGN paintRgn = ::CreateRectRgn(0, 0, 0, 0);
+ if (paintRgn != nullptr) {
+ int result = GetRandomRgn(aDC, paintRgn, SYSRGN);
+ if (result == 1) {
+ POINT pt = {0, 0};
+ ::MapWindowPoints(nullptr, mWnd, &pt, 1);
+ ::OffsetRgn(paintRgn, pt.x, pt.y);
+ }
+ LayoutDeviceIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn));
+ ::DeleteObject(paintRgn);
+ return rgn;
+ }
+ return LayoutDeviceIntRegion(WinUtils::ToIntRect(ps.rcPaint));
+}
+
+nsIWidgetListener* nsWindow::GetPaintListener() {
+ if (mDestroyCalled) return nullptr;
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+void nsWindow::ForcePresent() {
+ if (mResizeState != RESIZING) {
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
+ }
+ }
+}
+
+bool nsWindow::OnPaint(uint32_t aNestingLevel) {
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset(
+ &resetReason)) {
+ gfxCriticalNote << "(nsWindow) Detected device reset: " << (int)resetReason;
+
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+
+ bool guilty;
+ switch (resetReason) {
+ case DeviceResetReason::HUNG:
+ case DeviceResetReason::RESET:
+ case DeviceResetReason::INVALID_CALL:
+ guilty = true;
+ break;
+ default:
+ guilty = false;
+ break;
+ }
+
+ GPUProcessManager::Get()->OnInProcessDeviceReset(guilty);
+
+ gfxCriticalNote << "(nsWindow) Finished device reset.";
+ return false;
+ }
+
+ PAINTSTRUCT ps;
+
+ // Avoid starting the GPU process for the initial navigator:blank window.
+ if (mIsEarlyBlankWindow) {
+ // Call BeginPaint/EndPaint or Windows will keep sending us messages.
+ ::BeginPaint(mWnd, &ps);
+ ::EndPaint(mWnd, &ps);
+ return true;
+ }
+
+ WindowRenderer* renderer = GetWindowRenderer();
+ KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
+ WebRenderLayerManager* layerManager = renderer->AsWebRender();
+
+ if (mClearNCEdge) {
+ // We need to clear this edge of the non-client region to black (once).
+ HDC hdc;
+ RECT rect;
+ hdc = ::GetWindowDC(mWnd);
+ ::GetWindowRect(mWnd, &rect);
+ ::MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2);
+ switch (mClearNCEdge.value()) {
+ case ABE_TOP:
+ rect.bottom = rect.top + kHiddenTaskbarSize;
+ break;
+ case ABE_LEFT:
+ rect.right = rect.left + kHiddenTaskbarSize;
+ break;
+ case ABE_BOTTOM:
+ rect.top = rect.bottom - kHiddenTaskbarSize;
+ break;
+ case ABE_RIGHT:
+ rect.left = rect.right - kHiddenTaskbarSize;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid edge value");
+ break;
+ }
+ ::FillRect(hdc, &rect,
+ reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH)));
+ ::ReleaseDC(mWnd, hdc);
+
+ mClearNCEdge.reset();
+ }
+
+ if (knowsCompositor && layerManager &&
+ !mBounds.IsEqualEdges(mLastPaintBounds)) {
+ // Do an early async composite so that we at least have something on the
+ // screen in the right place, even if the content is out of date.
+ layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
+ }
+ mLastPaintBounds = mBounds;
+
+ // For layered translucent windows all drawing should go to memory DC and no
+ // WM_PAINT messages are normally generated. To support asynchronous painting
+ // we force generation of WM_PAINT messages by invalidating window areas with
+ // RedrawWindow, InvalidateRect or InvalidateRgn function calls.
+ const bool usingMemoryDC =
+ renderer->GetBackendType() == LayersBackend::LAYERS_NONE &&
+ mTransparencyMode == TransparencyMode::Transparent;
+
+ HDC hDC = nullptr;
+ if (usingMemoryDC) {
+ // BeginPaint/EndPaint must be called to make Windows think that invalid
+ // area is painted. Otherwise it will continue sending the same message
+ // endlessly.
+ ::BeginPaint(mWnd, &ps);
+ ::EndPaint(mWnd, &ps);
+
+ // We're guaranteed to have a widget proxy since we called
+ // GetLayerManager().
+ hDC = mBasicLayersSurface->GetTransparentDC();
+ } else {
+ hDC = ::BeginPaint(mWnd, &ps);
+ }
+
+ const bool forceRepaint = mTransparencyMode == TransparencyMode::Transparent;
+ const LayoutDeviceIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC);
+
+ if (knowsCompositor && layerManager) {
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ layerManager->SetNeedsComposite(true);
+ layerManager->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ nsIWidgetListener* listener = GetPaintListener();
+ if (listener) {
+ listener->WillPaintWindow(this);
+ }
+ // Re-get the listener since the will paint notification may have killed it.
+ listener = GetPaintListener();
+ if (!listener) {
+ return false;
+ }
+
+ if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
+ layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
+ layerManager->SetNeedsComposite(false);
+ }
+
+ bool result = true;
+ if (!region.IsEmpty() && listener) {
+ // Should probably pass in a real region here, using GetRandomRgn
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/clipping_4q0e.asp
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ debug_DumpPaintEvent(stdout, this, region.ToUnknownRegion(), "noname",
+ (int32_t)mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ switch (renderer->GetBackendType()) {
+ case LayersBackend::LAYERS_NONE: {
+ RefPtr<gfxASurface> targetSurface;
+
+ // don't support transparency for non-GDI rendering, for now
+ if (TransparencyMode::Transparent == mTransparencyMode) {
+ // This mutex needs to be held when EnsureTransparentSurface is
+ // called.
+ MutexAutoLock lock(mBasicLayersSurface->GetTransparentSurfaceLock());
+ targetSurface = mBasicLayersSurface->EnsureTransparentSurface();
+ }
+
+ RefPtr<gfxWindowsSurface> targetSurfaceWin;
+ if (!targetSurface) {
+ uint32_t flags = (mTransparencyMode == TransparencyMode::Opaque)
+ ? 0
+ : gfxWindowsSurface::FLAG_IS_TRANSPARENT;
+ targetSurfaceWin = new gfxWindowsSurface(hDC, flags);
+ targetSurface = targetSurfaceWin;
+ }
+
+ if (!targetSurface) {
+ NS_ERROR("Invalid RenderMode!");
+ return false;
+ }
+
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface(
+ targetSurface, IntSize(paintRect.right - paintRect.left,
+ paintRect.bottom - paintRect.top));
+ if (!dt || !dt->IsValid()) {
+ gfxWarning()
+ << "nsWindow::OnPaint failed in CreateDrawTargetForSurface";
+ return false;
+ }
+
+ // don't need to double buffer with anything but GDI
+ BufferMode doubleBuffering = mozilla::layers::BufferMode::BUFFER_NONE;
+ switch (mTransparencyMode) {
+ case TransparencyMode::Transparent:
+ // If we're rendering with translucency, we're going to be
+ // rendering the whole window; make sure we clear it first
+ dt->ClearRect(
+ Rect(0.f, 0.f, dt->GetSize().width, dt->GetSize().height));
+ break;
+ default:
+ // If we're not doing translucency, then double buffer
+ doubleBuffering = mozilla::layers::BufferMode::BUFFERED;
+ break;
+ }
+
+ gfxContext thebesContext(dt);
+
+ {
+ AutoLayerManagerSetup setupLayerManager(this, &thebesContext,
+ doubleBuffering);
+ result = listener->PaintWindow(this, region);
+ }
+
+ if (TransparencyMode::Transparent == mTransparencyMode) {
+ // Data from offscreen drawing surface was copied to memory bitmap of
+ // transparent bitmap. Now it can be read from memory bitmap to apply
+ // alpha channel and after that displayed on the screen.
+ mBasicLayersSurface->RedrawTransparentWindow();
+ }
+ } break;
+ case LayersBackend::LAYERS_WR: {
+ result = listener->PaintWindow(this, region);
+ if (!gfxEnv::MOZ_DISABLE_FORCE_PRESENT()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "nsWindow::ForcePresent", this, &nsWindow::ForcePresent);
+ NS_DispatchToMainThread(event);
+ }
+ } break;
+ default:
+ NS_ERROR("Unknown layers backend used!");
+ break;
+ }
+ }
+
+ if (!usingMemoryDC) {
+ ::EndPaint(mWnd, &ps);
+ }
+
+ mLastPaintEndTime = TimeStamp::Now();
+
+ // Re-get the listener since painting may have killed it.
+ listener = GetPaintListener();
+ if (listener) listener->DidPaintWindow();
+
+ if (aNestingLevel == 0 && ::GetUpdateRect(mWnd, nullptr, false)) {
+ OnPaint(1);
+ }
+
+ return result;
+}
+
+bool nsWindow::NeedsToTrackWindowOcclusionState() {
+ if (!WinWindowOcclusionTracker::Get()) {
+ return false;
+ }
+
+ if (mCompositorSession && mWindowType == WindowType::TopLevel) {
+ return true;
+ }
+
+ return false;
+}
+
+void nsWindow::NotifyOcclusionState(mozilla::widget::OcclusionState aState) {
+ MOZ_ASSERT(NeedsToTrackWindowOcclusionState());
+
+ bool isFullyOccluded = aState == mozilla::widget::OcclusionState::OCCLUDED;
+ // When window is minimized, it is not set as fully occluded.
+ if (mFrameState->GetSizeMode() == nsSizeMode_Minimized) {
+ isFullyOccluded = false;
+ }
+
+ // Don't dispatch if the new occlustion state is the same as the current
+ // state.
+ if (mIsFullyOccluded == isFullyOccluded) {
+ return;
+ }
+
+ mIsFullyOccluded = isFullyOccluded;
+
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("nsWindow::NotifyOcclusionState() mIsFullyOccluded %d "
+ "mFrameState->GetSizeMode() %d",
+ mIsFullyOccluded, mFrameState->GetSizeMode()));
+
+ wr::DebugFlags flags{0};
+ flags._0 = gfx::gfxVars::WebRenderDebugFlags();
+ bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
+ if (debugEnabled && mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyVisibilityUpdated(
+ mFrameState->GetSizeMode(), mIsFullyOccluded);
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
+ }
+}
+
+void nsWindow::MaybeEnableWindowOcclusion(bool aEnable) {
+ // WindowOcclusion is enabled/disabled only when compositor session exists.
+ // See nsWindow::NeedsToTrackWindowOcclusionState().
+ if (!mCompositorSession) {
+ return;
+ }
+
+ bool enabled = gfxConfig::IsEnabled(gfx::Feature::WINDOW_OCCLUSION);
+
+ if (aEnable) {
+ // Enable window occlusion.
+ if (enabled && NeedsToTrackWindowOcclusionState()) {
+ WinWindowOcclusionTracker::Get()->Enable(this, mWnd);
+
+ wr::DebugFlags flags{0};
+ flags._0 = gfx::gfxVars::WebRenderDebugFlags();
+ bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
+ if (debugEnabled && mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyVisibilityUpdated(
+ mFrameState->GetSizeMode(), mIsFullyOccluded);
+ }
+ }
+ return;
+ }
+
+ // Disable window occlusion.
+ MOZ_ASSERT(!aEnable);
+
+ if (!NeedsToTrackWindowOcclusionState()) {
+ return;
+ }
+
+ WinWindowOcclusionTracker::Get()->Disable(this, mWnd);
+ NotifyOcclusionState(OcclusionState::VISIBLE);
+
+ wr::DebugFlags flags{0};
+ flags._0 = gfx::gfxVars::WebRenderDebugFlags();
+ bool debugEnabled = bool(flags & wr::DebugFlags::WINDOW_VISIBILITY_DBG);
+ if (debugEnabled && mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyVisibilityUpdated(
+ mFrameState->GetSizeMode(), mIsFullyOccluded);
+ }
+}
+
+// This override of CreateCompositor is to add support for sending the IPC
+// call for RequesetFxrOutput as soon as the compositor for this widget is
+// available.
+void nsWindow::CreateCompositor() {
+ nsBaseWidget::CreateCompositor();
+
+ MaybeEnableWindowOcclusion(/* aEnable */ true);
+
+ if (mRequestFxrOutputPending) {
+ GetRemoteRenderer()->SendRequestFxrOutput();
+ }
+}
+
+void nsWindow::DestroyCompositor() {
+ MaybeEnableWindowOcclusion(/* aEnable */ false);
+
+ nsBaseWidget::DestroyCompositor();
+}
+
+void nsWindow::RequestFxrOutput() {
+ if (GetRemoteRenderer() != nullptr) {
+ MOZ_CRASH("RequestFxrOutput should happen before Compositor is created.");
+ } else {
+ // The compositor isn't ready, so indicate to make the IPC call when
+ // it is available.
+ mRequestFxrOutputPending = true;
+ }
+}
+
+LayoutDeviceIntSize nsWindowGfx::GetIconMetrics(IconSizeType aSizeType) {
+ int32_t width = ::GetSystemMetrics(sIconMetrics[aSizeType].xMetric);
+ int32_t height = ::GetSystemMetrics(sIconMetrics[aSizeType].yMetric);
+
+ if (width == 0 || height == 0) {
+ width = height = sIconMetrics[aSizeType].defaultSize;
+ }
+
+ return LayoutDeviceIntSize(width, height);
+}
+
+nsresult nsWindowGfx::CreateIcon(imgIContainer* aContainer, bool aIsCursor,
+ LayoutDeviceIntPoint aHotspot,
+ LayoutDeviceIntSize aScaledSize,
+ HICON* aIcon) {
+ MOZ_ASSERT(aHotspot.x >= 0 && aHotspot.y >= 0);
+ MOZ_ASSERT((aScaledSize.width > 0 && aScaledSize.height > 0) ||
+ (aScaledSize.width == 0 && aScaledSize.height == 0));
+
+ // Get the image data
+ RefPtr<SourceSurface> surface = aContainer->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+ NS_ENSURE_TRUE(surface, NS_ERROR_NOT_AVAILABLE);
+
+ IntSize frameSize = surface->GetSize();
+ if (frameSize.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ IntSize iconSize(aScaledSize.width, aScaledSize.height);
+ if (iconSize == IntSize(0, 0)) { // use frame's intrinsic size
+ iconSize = frameSize;
+ }
+
+ RefPtr<DataSourceSurface> dataSurface;
+ bool mappedOK;
+ DataSourceSurface::MappedSurface map;
+
+ if (iconSize != frameSize) {
+ // Scale the surface
+ dataSurface =
+ Factory::CreateDataSourceSurface(iconSize, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map);
+ NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
+ SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ gfxWarning()
+ << "nsWindowGfx::CreatesIcon failed in CreateDrawTargetForData";
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ dt->DrawSurface(surface, Rect(0, 0, iconSize.width, iconSize.height),
+ Rect(0, 0, frameSize.width, frameSize.height),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ } else if (surface->GetFormat() != SurfaceFormat::B8G8R8A8) {
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
+ surface, SurfaceFormat::B8G8R8A8);
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ } else {
+ dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+ mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ }
+ NS_ENSURE_TRUE(dataSurface && mappedOK, NS_ERROR_FAILURE);
+ MOZ_ASSERT(dataSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
+
+ uint8_t* data = nullptr;
+ UniquePtr<uint8_t[]> autoDeleteArray;
+ if (map.mStride == BytesPerPixel(dataSurface->GetFormat()) * iconSize.width) {
+ // Mapped data is already packed
+ data = map.mData;
+ } else {
+ // We can't use map.mData since the pixels are not packed (as required by
+ // CreateDIBitmap, which is called under the DataToBitmap call below).
+ //
+ // We must unmap before calling SurfaceToPackedBGRA because it needs access
+ // to the pixel data.
+ dataSurface->Unmap();
+ map.mData = nullptr;
+
+ autoDeleteArray = SurfaceToPackedBGRA(dataSurface);
+ data = autoDeleteArray.get();
+ NS_ENSURE_TRUE(data, NS_ERROR_FAILURE);
+ }
+
+ HBITMAP bmp = DataToBitmap(data, iconSize.width, -iconSize.height, 32);
+ uint8_t* a1data = Data32BitTo1Bit(data, iconSize.width, iconSize.height);
+ if (map.mData) {
+ dataSurface->Unmap();
+ }
+ if (!a1data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HBITMAP mbmp = DataToBitmap(a1data, iconSize.width, -iconSize.height, 1);
+ free(a1data);
+
+ ICONINFO info = {0};
+ info.fIcon = !aIsCursor;
+ info.xHotspot = aHotspot.x;
+ info.yHotspot = aHotspot.y;
+ info.hbmMask = mbmp;
+ info.hbmColor = bmp;
+
+ HCURSOR icon = ::CreateIconIndirect(&info);
+ ::DeleteObject(mbmp);
+ ::DeleteObject(bmp);
+ if (!icon) return NS_ERROR_FAILURE;
+ *aIcon = icon;
+ return NS_OK;
+}
+
+// Adjust cursor image data
+uint8_t* nsWindowGfx::Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight) {
+ // We need (aWidth + 7) / 8 bytes plus zero-padding up to a multiple of
+ // 4 bytes for each row (HBITMAP requirement). Bug 353553.
+ uint32_t outBpr = ((aWidth + 31) / 8) & ~3;
+
+ // Allocate and clear mask buffer
+ uint8_t* outData = (uint8_t*)calloc(outBpr, aHeight);
+ if (!outData) return nullptr;
+
+ int32_t* imageRow = (int32_t*)aImageData;
+ for (uint32_t curRow = 0; curRow < aHeight; curRow++) {
+ uint8_t* outRow = outData + curRow * outBpr;
+ uint8_t mask = 0x80;
+ for (uint32_t curCol = 0; curCol < aWidth; curCol++) {
+ // Use sign bit to test for transparency, as alpha byte is highest byte
+ if (*imageRow++ < 0) *outRow |= mask;
+
+ mask >>= 1;
+ if (!mask) {
+ outRow++;
+ mask = 0x80;
+ }
+ }
+ }
+
+ return outData;
+}
+
+/**
+ * Convert the given image data to a HBITMAP. If the requested depth is
+ * 32 bit, a bitmap with an alpha channel will be returned.
+ *
+ * @param aImageData The image data to convert. Must use the format accepted
+ * by CreateDIBitmap.
+ * @param aWidth With of the bitmap, in pixels.
+ * @param aHeight Height of the image, in pixels.
+ * @param aDepth Image depth, in bits. Should be one of 1, 24 and 32.
+ *
+ * @return The HBITMAP representing the image. Caller should call
+ * DeleteObject when done with the bitmap.
+ * On failure, nullptr will be returned.
+ */
+HBITMAP nsWindowGfx::DataToBitmap(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight, uint32_t aDepth) {
+ HDC dc = ::GetDC(nullptr);
+
+ if (aDepth == 32) {
+ // Alpha channel. We need the new header.
+ BITMAPV4HEADER head = {0};
+ head.bV4Size = sizeof(head);
+ head.bV4Width = aWidth;
+ head.bV4Height = aHeight;
+ head.bV4Planes = 1;
+ head.bV4BitCount = aDepth;
+ head.bV4V4Compression = BI_BITFIELDS;
+ head.bV4SizeImage = 0; // Uncompressed
+ head.bV4XPelsPerMeter = 0;
+ head.bV4YPelsPerMeter = 0;
+ head.bV4ClrUsed = 0;
+ head.bV4ClrImportant = 0;
+
+ head.bV4RedMask = 0x00FF0000;
+ head.bV4GreenMask = 0x0000FF00;
+ head.bV4BlueMask = 0x000000FF;
+ head.bV4AlphaMask = 0xFF000000;
+
+ HBITMAP bmp = ::CreateDIBitmap(
+ dc, reinterpret_cast<CONST BITMAPINFOHEADER*>(&head), CBM_INIT,
+ aImageData, reinterpret_cast<CONST BITMAPINFO*>(&head), DIB_RGB_COLORS);
+ ::ReleaseDC(nullptr, dc);
+ return bmp;
+ }
+
+ char reserved_space[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2];
+ BITMAPINFOHEADER& head = *(BITMAPINFOHEADER*)reserved_space;
+
+ head.biSize = sizeof(BITMAPINFOHEADER);
+ head.biWidth = aWidth;
+ head.biHeight = aHeight;
+ head.biPlanes = 1;
+ head.biBitCount = (WORD)aDepth;
+ head.biCompression = BI_RGB;
+ head.biSizeImage = 0; // Uncompressed
+ head.biXPelsPerMeter = 0;
+ head.biYPelsPerMeter = 0;
+ head.biClrUsed = 0;
+ head.biClrImportant = 0;
+
+ BITMAPINFO& bi = *(BITMAPINFO*)reserved_space;
+
+ if (aDepth == 1) {
+ RGBQUAD black = {0, 0, 0, 0};
+ RGBQUAD white = {255, 255, 255, 0};
+
+ bi.bmiColors[0] = white;
+ bi.bmiColors[1] = black;
+ }
+
+ HBITMAP bmp =
+ ::CreateDIBitmap(dc, &head, CBM_INIT, aImageData, &bi, DIB_RGB_COLORS);
+ ::ReleaseDC(nullptr, dc);
+ return bmp;
+}
diff --git a/widget/windows/nsWindowGfx.h b/widget/windows/nsWindowGfx.h
new file mode 100644
index 0000000000..0d5fd9e01a
--- /dev/null
+++ b/widget/windows/nsWindowGfx.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WindowGfx_h__
+#define WindowGfx_h__
+
+/*
+ * nsWindowGfx - Painting and aceleration.
+ */
+
+#include "nsWindow.h"
+#include <imgIContainer.h>
+
+class nsWindowGfx {
+ public:
+ enum IconSizeType { kSmallIcon, kRegularIcon };
+ static mozilla::LayoutDeviceIntSize GetIconMetrics(IconSizeType aSizeType);
+ static nsresult CreateIcon(imgIContainer* aContainer, bool aIsCursor,
+ mozilla::LayoutDeviceIntPoint aHotspot,
+ mozilla::LayoutDeviceIntSize aScaledSize,
+ HICON* aIcon);
+
+ private:
+ /**
+ * Cursor helpers
+ */
+ static uint8_t* Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight);
+ static HBITMAP DataToBitmap(uint8_t* aImageData, uint32_t aWidth,
+ uint32_t aHeight, uint32_t aDepth);
+};
+
+#endif // WindowGfx_h__
diff --git a/widget/windows/nsWindowLoggedMessages.cpp b/widget/windows/nsWindowLoggedMessages.cpp
new file mode 100644
index 0000000000..ac0f05a875
--- /dev/null
+++ b/widget/windows/nsWindowLoggedMessages.cpp
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include <windef.h>
+#include <winuser.h>
+#include "mozilla/StaticPrefs_storage.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsWindowLoggedMessages.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include <map>
+#include <algorithm>
+
+namespace mozilla::widget {
+
+// NCCALCSIZE_PARAMS and WINDOWPOS are relatively large structures, so store
+// them as a pointer to save memory
+using NcCalcSizeVariantData =
+ Variant<UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>, RECT>;
+// to save memory, hold the raw data and only convert to string
+// when requested
+using MessageSpecificData =
+ Variant<std::pair<WPARAM, LPARAM>, // WM_SIZE, WM_MOVE
+ WINDOWPOS, // WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED
+ std::pair<WPARAM, RECT>, // WM_SIZING, WM_DPICHANGED, WM_MOVING
+ std::pair<WPARAM, nsString>, // WM_SETTINGCHANGE
+ std::pair<bool, NcCalcSizeVariantData>, // WM_NCCALCSIZE
+ MINMAXINFO // WM_GETMINMAXINFO
+ >;
+
+struct WindowMessageData {
+ long mEventCounter;
+ bool mIsPreEvent;
+ MessageSpecificData mSpecificData;
+ mozilla::Maybe<bool> mResult;
+ LRESULT mRetValue;
+ WindowMessageData(long eventCounter, bool isPreEvent,
+ MessageSpecificData&& specificData,
+ mozilla::Maybe<bool> result, LRESULT retValue)
+ : mEventCounter(eventCounter),
+ mIsPreEvent(isPreEvent),
+ mSpecificData(std::move(specificData)),
+ mResult(result),
+ mRetValue(retValue) {}
+ // Disallow copy constructor/operator since MessageSpecificData has a
+ // UniquePtr
+ WindowMessageData(const WindowMessageData&) = delete;
+ WindowMessageData& operator=(const WindowMessageData&) = delete;
+ WindowMessageData(WindowMessageData&&) = default;
+ WindowMessageData& operator=(WindowMessageData&&) = default;
+};
+
+struct WindowMessageDataSortKey {
+ long mEventCounter;
+ bool mIsPreEvent;
+ explicit WindowMessageDataSortKey(const WindowMessageData& data)
+ : mEventCounter(data.mEventCounter), mIsPreEvent(data.mIsPreEvent) {}
+ bool operator<(const WindowMessageDataSortKey& other) const {
+ if (mEventCounter < other.mEventCounter) {
+ return true;
+ }
+ if (other.mEventCounter < mEventCounter) {
+ return false;
+ }
+ if (mIsPreEvent && !other.mIsPreEvent) {
+ return true;
+ }
+ if (other.mIsPreEvent && !mIsPreEvent) {
+ return false;
+ }
+ // they're equal
+ return false;
+ }
+};
+
+struct CircularMessageBuffer {
+ // Only used when the vector is at its maximum size
+ size_t mNextFreeIndex = 0;
+ std::vector<WindowMessageData> mMessages;
+};
+static std::map<HWND, std::map<UINT, CircularMessageBuffer>> gWindowMessages;
+
+static HWND GetHwndFromWidget(nsIWidget* windowWidget) {
+ nsWindow* window = static_cast<nsWindow*>(windowWidget);
+ return window->GetWindowHandle();
+}
+
+MessageSpecificData MakeMessageSpecificData(UINT event, WPARAM wParam,
+ LPARAM lParam) {
+ // Since we store this data for every message we log, make sure it's of a
+ // reasonable size. Keep in mind we're storing up to 10 (number of message
+ // types)
+ // * 6 (default number of messages per type to keep) of these messages per
+ // window.
+ static_assert(sizeof(MessageSpecificData) <= 48);
+ switch (event) {
+ case WM_SIZE:
+ case WM_MOVE:
+ return MessageSpecificData(std::make_pair(wParam, lParam));
+ case WM_WINDOWPOSCHANGING:
+ case WM_WINDOWPOSCHANGED: {
+ LPWINDOWPOS windowPosPtr = reinterpret_cast<LPWINDOWPOS>(lParam);
+ WINDOWPOS windowPos = *windowPosPtr;
+ return MessageSpecificData(std::move(windowPos));
+ }
+ case WM_SIZING:
+ case WM_DPICHANGED:
+ case WM_MOVING: {
+ LPRECT rectPtr = reinterpret_cast<LPRECT>(lParam);
+ RECT rect = *rectPtr;
+ return MessageSpecificData(std::make_pair(wParam, std::move(rect)));
+ }
+ case WM_SETTINGCHANGE: {
+ LPCWSTR wideStrPtr = reinterpret_cast<LPCWSTR>(lParam);
+ nsString str(wideStrPtr);
+ return MessageSpecificData(std::make_pair(wParam, std::move(str)));
+ }
+ case WM_NCCALCSIZE: {
+ bool shouldIndicateValidArea = wParam == TRUE;
+ if (shouldIndicateValidArea) {
+ LPNCCALCSIZE_PARAMS ncCalcSizeParamsPtr =
+ reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam);
+ NCCALCSIZE_PARAMS ncCalcSizeParams = *ncCalcSizeParamsPtr;
+ WINDOWPOS windowPos = *ncCalcSizeParams.lppos;
+ UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>> ncCalcSizeData =
+ MakeUnique<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>(
+ std::pair(std::move(ncCalcSizeParams), std::move(windowPos)));
+ return MessageSpecificData(
+ std::make_pair(shouldIndicateValidArea,
+ NcCalcSizeVariantData(std::move(ncCalcSizeData))));
+ } else {
+ LPRECT rectPtr = reinterpret_cast<LPRECT>(lParam);
+ RECT rect = *rectPtr;
+ return MessageSpecificData(std::make_pair(
+ shouldIndicateValidArea, NcCalcSizeVariantData(std::move(rect))));
+ }
+ }
+ case WM_GETMINMAXINFO: {
+ PMINMAXINFO minMaxInfoPtr = reinterpret_cast<PMINMAXINFO>(lParam);
+ MINMAXINFO minMaxInfo = *minMaxInfoPtr;
+ return MessageSpecificData(std::move(minMaxInfo));
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "Unhandled message type in MakeMessageSpecificData");
+ return MessageSpecificData(std::make_pair(wParam, lParam));
+ }
+}
+
+void AppendFriendlyMessageSpecificData(nsCString& str, UINT event,
+ bool isPreEvent,
+ const MessageSpecificData& data) {
+ switch (event) {
+ case WM_SIZE: {
+ const auto& params = data.as<std::pair<WPARAM, LPARAM>>();
+ nsAutoCString tempStr =
+ WmSizeParamInfo(params.first, params.second, isPreEvent);
+ str.AppendASCII(tempStr);
+ break;
+ }
+ case WM_MOVE: {
+ const auto& params = data.as<std::pair<WPARAM, LPARAM>>();
+ XLowWordYHighWordParamInfo(str, params.second, "upperLeft", isPreEvent);
+ break;
+ }
+ case WM_WINDOWPOSCHANGING:
+ case WM_WINDOWPOSCHANGED: {
+ const auto& params = data.as<WINDOWPOS>();
+ WindowPosParamInfo(str, reinterpret_cast<uint64_t>(&params),
+ "newSizeAndPos", isPreEvent);
+ break;
+ }
+ case WM_SIZING: {
+ const auto& params = data.as<std::pair<WPARAM, RECT>>();
+ WindowEdgeParamInfo(str, params.first, "edge", isPreEvent);
+ str.AppendASCII(" ");
+ RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second), "rect",
+ isPreEvent);
+ break;
+ }
+ case WM_DPICHANGED: {
+ const auto& params = data.as<std::pair<WPARAM, RECT>>();
+ XLowWordYHighWordParamInfo(str, params.first, "newDPI", isPreEvent);
+ str.AppendASCII(" ");
+ RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second),
+ "suggestedSizeAndPos", isPreEvent);
+ break;
+ }
+ case WM_MOVING: {
+ const auto& params = data.as<std::pair<WPARAM, RECT>>();
+ RectParamInfo(str, reinterpret_cast<uint64_t>(&params.second), "rect",
+ isPreEvent);
+ break;
+ }
+ case WM_SETTINGCHANGE: {
+ const auto& params = data.as<std::pair<WPARAM, nsString>>();
+ UiActionParamInfo(str, params.first, "uiAction", isPreEvent);
+ str.AppendASCII(" ");
+ WideStringParamInfo(
+ str,
+ reinterpret_cast<uint64_t>((const wchar_t*)(params.second.Data())),
+ "paramChanged", isPreEvent);
+ break;
+ }
+ case WM_NCCALCSIZE: {
+ const auto& params = data.as<std::pair<bool, NcCalcSizeVariantData>>();
+ bool shouldIndicateValidArea = params.first;
+ if (shouldIndicateValidArea) {
+ const auto& validAreaParams =
+ params.second
+ .as<UniquePtr<std::pair<NCCALCSIZE_PARAMS, WINDOWPOS>>>();
+ // Make pointer point to the cached data
+ validAreaParams->first.lppos = &validAreaParams->second;
+ nsAutoCString tempStr = WmNcCalcSizeParamInfo(
+ TRUE, reinterpret_cast<uint64_t>(&validAreaParams->first),
+ isPreEvent);
+ str.AppendASCII(tempStr);
+ } else {
+ RECT rect = params.second.as<RECT>();
+ nsAutoCString tempStr = WmNcCalcSizeParamInfo(
+ FALSE, reinterpret_cast<uint64_t>(&rect), isPreEvent);
+ str.AppendASCII(tempStr);
+ }
+ break;
+ }
+ case WM_GETMINMAXINFO: {
+ const auto& params = data.as<MINMAXINFO>();
+ MinMaxInfoParamInfo(str, reinterpret_cast<uint64_t>(&params), "",
+ isPreEvent);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false,
+ "Unhandled message type in AppendFriendlyMessageSpecificData");
+ str.AppendASCII("???");
+ }
+}
+
+nsCString MakeFriendlyMessage(UINT event, bool isPreEvent, long eventCounter,
+ const MessageSpecificData& data,
+ mozilla::Maybe<bool> result, LRESULT retValue) {
+ nsCString str;
+ const char* eventName = mozilla::widget::WinUtils::WinEventToEventName(event);
+ MOZ_ASSERT(eventName, "Unknown event name in MakeFriendlyMessage");
+ eventName = eventName ? eventName : "(unknown)";
+ str.AppendPrintf("%6ld %04x (%s) - ", eventCounter, event, eventName);
+ AppendFriendlyMessageSpecificData(str, event, isPreEvent, data);
+ const char* resultMsg =
+ result.isSome() ? (result.value() ? "true" : "false") : "initial call";
+ str.AppendPrintf(" 0x%08llX (%s)",
+ result.isSome() ? static_cast<uint64_t>(retValue) : 0,
+ resultMsg);
+ return str;
+}
+
+void WindowClosed(HWND hwnd) { gWindowMessages.erase(hwnd); }
+
+void LogWindowMessage(HWND hwnd, UINT event, bool isPreEvent, long eventCounter,
+ WPARAM wParam, LPARAM lParam, mozilla::Maybe<bool> result,
+ LRESULT retValue) {
+ auto& hwndMessages = gWindowMessages[hwnd];
+ auto& hwndWindowMessages = hwndMessages[event];
+ WindowMessageData messageData = {
+ eventCounter, isPreEvent, MakeMessageSpecificData(event, wParam, lParam),
+ result, retValue};
+ uint32_t numberOfMessagesToKeep =
+ StaticPrefs::widget_windows_messages_to_log();
+ if (hwndWindowMessages.mMessages.size() < numberOfMessagesToKeep) {
+ // haven't reached limit yet
+ hwndWindowMessages.mMessages.push_back(std::move(messageData));
+ } else {
+ hwndWindowMessages.mMessages[hwndWindowMessages.mNextFreeIndex] =
+ std::move(messageData);
+ }
+ hwndWindowMessages.mNextFreeIndex =
+ (hwndWindowMessages.mNextFreeIndex + 1) % numberOfMessagesToKeep;
+}
+
+void GetLatestWindowMessages(RefPtr<nsIWidget> windowWidget,
+ nsTArray<nsCString>& messages) {
+ HWND hwnd = GetHwndFromWidget(windowWidget);
+ const auto& rawMessages = gWindowMessages[hwnd];
+ nsTArray<std::pair<WindowMessageDataSortKey, nsCString>>
+ sortKeyAndMessageArray;
+ sortKeyAndMessageArray.SetCapacity(
+ rawMessages.size() * StaticPrefs::widget_windows_messages_to_log());
+ for (const auto& eventAndMessage : rawMessages) {
+ for (const auto& messageData : eventAndMessage.second.mMessages) {
+ nsCString message = MakeFriendlyMessage(
+ eventAndMessage.first, messageData.mIsPreEvent,
+ messageData.mEventCounter, messageData.mSpecificData,
+ messageData.mResult, messageData.mRetValue);
+ WindowMessageDataSortKey sortKey(messageData);
+ sortKeyAndMessageArray.AppendElement(
+ std::make_pair(sortKey, std::move(message)));
+ }
+ }
+ std::sort(sortKeyAndMessageArray.begin(), sortKeyAndMessageArray.end());
+ messages.SetCapacity(sortKeyAndMessageArray.Length());
+ for (const std::pair<WindowMessageDataSortKey, nsCString>& entry :
+ sortKeyAndMessageArray) {
+ messages.AppendElement(std::move(entry.second));
+ }
+}
+} // namespace mozilla::widget
diff --git a/widget/windows/nsWindowLoggedMessages.h b/widget/windows/nsWindowLoggedMessages.h
new file mode 100644
index 0000000000..c0ea632bb0
--- /dev/null
+++ b/widget/windows/nsWindowLoggedMessages.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WindowLoggedMessages_h__
+#define WindowLoggedMessages_h__
+
+#include "minwindef.h"
+#include "wtypes.h"
+
+#include "nsIWidget.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::widget {
+
+void LogWindowMessage(HWND hwnd, UINT event, bool isPreEvent, long eventCounter,
+ WPARAM wParam, LPARAM lParam, mozilla::Maybe<bool> result,
+ LRESULT retValue);
+void WindowClosed(HWND hwnd);
+void GetLatestWindowMessages(RefPtr<nsIWidget> windowWidget,
+ nsTArray<nsCString>& messages);
+
+} // namespace mozilla::widget
+
+#endif /* WindowLoggedMessages */
diff --git a/widget/windows/nsWindowTaskbarConcealer.cpp b/widget/windows/nsWindowTaskbarConcealer.cpp
new file mode 100644
index 0000000000..b2b6b13a1d
--- /dev/null
+++ b/widget/windows/nsWindowTaskbarConcealer.cpp
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsWindowTaskbarConcealer.h"
+
+#include "nsIWinTaskbar.h"
+#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+/**
+ * TaskbarConcealerImpl
+ *
+ * Implement Windows-fullscreen marking.
+ *
+ * nsWindow::TaskbarConcealer implements logic determining _whether_ to tell
+ * Windows that a given window is fullscreen. TaskbarConcealerImpl performs the
+ * platform-specific work of actually communicating that fact to Windows.
+ *
+ * (This object is not persistent; it's constructed on the stack when needed.)
+ */
+struct TaskbarConcealerImpl {
+ void MarkAsHidingTaskbar(HWND aWnd, bool aMark);
+
+ private:
+ nsCOMPtr<nsIWinTaskbar> mTaskbarInfo;
+};
+
+/**
+ * nsWindow::TaskbarConcealer
+ *
+ * Issue taskbar-hide requests to the OS as needed.
+ */
+
+/*
+ Per MSDN [0], one should mark and unmark fullscreen windows via the
+ ITaskbarList2::MarkFullscreenWindow method. Unfortunately, Windows pays less
+ attention to this than one might prefer -- in particular, it typically fails
+ to show the taskbar when switching focus from a window marked as fullscreen to
+ one not thus marked. [1]
+
+ Experimentation has (so far) suggested that its behavior is reasonable when
+ switching between multiple monitors, or between a set of windows which are all
+ from different processes [2]. This leaves us to handle the same-monitor, same-
+ process case.
+
+ Rather than do anything subtle here, we take the blanket approach of simply
+ listening for every potentially-relevant state change, and then explicitly
+ marking or unmarking every potentially-visible toplevel window.
+
+ ----
+
+ [0] Relevant link:
+ https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist2-markfullscreenwindow
+
+ The "NonRudeHWND" property described therein doesn't help with anything
+ in this comment, unfortunately. (See its use in MarkAsHidingTaskbar for
+ more details.)
+
+ [1] This is an oversimplification; Windows' actual behavior here is...
+ complicated. See bug 1732517 comment 6 for some examples.
+
+ [2] A comment in Chromium asserts that this is actually different threads. For
+ us, of course, that makes no difference.
+ https://github.com/chromium/chromium/blob/2b822268bd3/ui/views/win/hwnd_message_handler.cc#L1342
+*/
+
+/**************************************************************
+ *
+ * SECTION: TaskbarConcealer utilities
+ *
+ **************************************************************/
+
+static mozilla::LazyLogModule sTaskbarConcealerLog("TaskbarConcealer");
+
+// Map of all relevant Gecko windows, along with the monitor on which each
+// window was last known to be located.
+/* static */
+nsTHashMap<HWND, HMONITOR> nsWindow::TaskbarConcealer::sKnownWindows;
+
+// Returns Nothing if the window in question is irrelevant (for any reason),
+// or Some(the window's current state) otherwise.
+/* static */
+Maybe<nsWindow::TaskbarConcealer::WindowState>
+nsWindow::TaskbarConcealer::GetWindowState(HWND aWnd) {
+ // Classical Win32 visibility conditions.
+ if (!::IsWindowVisible(aWnd)) {
+ return Nothing();
+ }
+ if (::IsIconic(aWnd)) {
+ return Nothing();
+ }
+
+ // Non-nsWindow windows associated with this thread may include file dialogs
+ // and IME input popups.
+ nsWindow* pWin = widget::WinUtils::GetNSWindowPtr(aWnd);
+ if (!pWin) {
+ return Nothing();
+ }
+
+ // nsWindows of other window-classes include tooltips and drop-shadow-bearing
+ // menus.
+ if (pWin->mWindowType != WindowType::TopLevel) {
+ return Nothing();
+ }
+
+ // Cloaked windows are (presumably) on a different virtual desktop.
+ // https://devblogs.microsoft.com/oldnewthing/20200302-00/?p=103507
+ if (pWin->mIsCloaked) {
+ return Nothing();
+ }
+
+ return Some(
+ WindowState{::MonitorFromWindow(aWnd, MONITOR_DEFAULTTONULL),
+ pWin->mFrameState->GetSizeMode() == nsSizeMode_Fullscreen});
+}
+
+/**************************************************************
+ *
+ * SECTION: TaskbarConcealer::UpdateAllState
+ *
+ **************************************************************/
+
+// Update all Windows-fullscreen-marking state and internal caches to represent
+// the current state of the system.
+/* static */
+void nsWindow::TaskbarConcealer::UpdateAllState(
+ HWND destroyedHwnd /* = nullptr */
+) {
+ // sKnownWindows is otherwise-unprotected shared state
+ MOZ_ASSERT(NS_IsMainThread(),
+ "TaskbarConcealer can only be used from the main thread!");
+
+ if (MOZ_LOG_TEST(sTaskbarConcealerLog, LogLevel::Info)) {
+ static size_t sLogCounter = 0;
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("Calling UpdateAllState() for the %zuth time", sLogCounter++));
+
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info, ("Last known state:"));
+ if (sKnownWindows.IsEmpty()) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ (" none (no windows known)"));
+ } else {
+ for (const auto& entry : sKnownWindows) {
+ MOZ_LOG(
+ sTaskbarConcealerLog, LogLevel::Info,
+ (" window %p was on monitor %p", entry.GetKey(), entry.GetData()));
+ }
+ }
+ }
+
+ // Array of all our potentially-relevant HWNDs, in Z-order (topmost first),
+ // along with their associated relevant state.
+ struct Item {
+ HWND hwnd;
+ HMONITOR monitor;
+ bool isGkFullscreen;
+ };
+ const nsTArray<Item> windows = [&] {
+ nsTArray<Item> windows;
+
+ // USE OF UNDOCUMENTED BEHAVIOR: The EnumWindows family of functions
+ // enumerates windows in Z-order, topmost first. (This has been true since
+ // at least Windows 2000, and possibly since Windows 3.0.)
+ //
+ // It's necessarily unreliable if windows are reordered while being
+ // enumerated; but in that case we'll get a message informing us of that
+ // fact, and can redo our state-calculations then.
+ //
+ // There exists no documented interface to acquire this information (other
+ // than ::GetWindow(), which is racy).
+ mozilla::EnumerateThreadWindows([&](HWND hwnd) {
+ // Depending on details of window-destruction that probably shouldn't be
+ // relied on, this HWND may or may not still be in the window list.
+ // Pretend it's not.
+ if (hwnd == destroyedHwnd) {
+ return;
+ }
+
+ const auto maybeState = GetWindowState(hwnd);
+ if (!maybeState) {
+ return;
+ }
+ const WindowState& state = *maybeState;
+
+ windows.AppendElement(Item{.hwnd = hwnd,
+ .monitor = state.monitor,
+ .isGkFullscreen = state.isGkFullscreen});
+ });
+
+ return windows;
+ }();
+
+ // Relevant monitors are exactly those with relevant windows.
+ const nsTHashSet<HMONITOR> relevantMonitors = [&]() {
+ nsTHashSet<HMONITOR> relevantMonitors;
+ for (const Item& item : windows) {
+ relevantMonitors.Insert(item.monitor);
+ }
+ return relevantMonitors;
+ }();
+
+ // Update the cached mapping from windows to monitors. (This is only used as
+ // an optimization in TaskbarConcealer::OnWindowPosChanged().)
+ sKnownWindows.Clear();
+ for (const Item& item : windows) {
+ MOZ_LOG(
+ sTaskbarConcealerLog, LogLevel::Debug,
+ ("Found relevant window %p on monitor %p", item.hwnd, item.monitor));
+ sKnownWindows.InsertOrUpdate(item.hwnd, item.monitor);
+ }
+
+ // Auxiliary function. Does what it says on the tin.
+ const auto FindUppermostWindowOn = [&windows](HMONITOR aMonitor) -> HWND {
+ for (const Item& item : windows) {
+ if (item.monitor == aMonitor) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("on monitor %p, uppermost relevant HWND is %p", aMonitor,
+ item.hwnd));
+ return item.hwnd;
+ }
+ }
+
+ // This should never happen, since we're drawing our monitor-set from the
+ // set of relevant windows.
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Warning,
+ ("on monitor %p, no relevant windows were found", aMonitor));
+ return nullptr;
+ };
+
+ TaskbarConcealerImpl impl;
+
+ // Mark all relevant windows as not hiding the taskbar, unless they're both
+ // Gecko-fullscreen and the uppermost relevant window on their monitor.
+ for (HMONITOR monitor : relevantMonitors) {
+ const HWND topmost = FindUppermostWindowOn(monitor);
+
+ for (const Item& item : windows) {
+ if (item.monitor != monitor) continue;
+ impl.MarkAsHidingTaskbar(item.hwnd,
+ item.isGkFullscreen && item.hwnd == topmost);
+ }
+ }
+} // nsWindow::TaskbarConcealer::UpdateAllState()
+
+// Mark this window as requesting to occlude the taskbar. (The caller is
+// responsible for keeping any local state up-to-date.)
+void TaskbarConcealerImpl::MarkAsHidingTaskbar(HWND aWnd, bool aMark) {
+ const char* const sMark = aMark ? "true" : "false";
+
+ if (!mTaskbarInfo) {
+ mTaskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
+
+ if (!mTaskbarInfo) {
+ MOZ_LOG(
+ sTaskbarConcealerLog, LogLevel::Warning,
+ ("could not acquire IWinTaskbar (aWnd %p, aMark %s)", aWnd, sMark));
+ return;
+ }
+ }
+
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("Calling PrepareFullScreen(%p, %s)", aWnd, sMark));
+
+ const nsresult hr = mTaskbarInfo->PrepareFullScreen(aWnd, aMark);
+
+ if (FAILED(hr)) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Error,
+ ("Call to PrepareFullScreen(%p, %s) failed with nsresult %x", aWnd,
+ sMark, uint32_t(hr)));
+ }
+};
+
+/**************************************************************
+ *
+ * SECTION: TaskbarConcealer event callbacks
+ *
+ **************************************************************/
+
+void nsWindow::TaskbarConcealer::OnWindowDestroyed(HWND aWnd) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("==> OnWindowDestroyed() for HWND %p", aWnd));
+
+ UpdateAllState(aWnd);
+}
+
+void nsWindow::TaskbarConcealer::OnFocusAcquired(nsWindow* aWin) {
+ // Update state unconditionally.
+ //
+ // This is partially because focus-acquisition only updates the z-order, which
+ // we don't cache and therefore can't notice changes to -- but also because
+ // it's probably a good idea to give the user a natural way to refresh the
+ // current fullscreen-marking state if it's somehow gone bad.
+
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("==> OnFocusAcquired() for HWND %p on HMONITOR %p", aWin->mWnd,
+ ::MonitorFromWindow(aWin->mWnd, MONITOR_DEFAULTTONULL)));
+
+ UpdateAllState();
+}
+
+void nsWindow::TaskbarConcealer::OnFullscreenChanged(nsWindow* aWin,
+ bool enteredFullscreen) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("==> OnFullscreenChanged() for HWND %p on HMONITOR %p", aWin->mWnd,
+ ::MonitorFromWindow(aWin->mWnd, MONITOR_DEFAULTTONULL)));
+
+ UpdateAllState();
+}
+
+void nsWindow::TaskbarConcealer::OnWindowPosChanged(nsWindow* aWin) {
+ // Optimization: don't bother updating the state if the window hasn't moved
+ // (including appearances and disappearances).
+ const HWND myHwnd = aWin->mWnd;
+ const HMONITOR oldMonitor = sKnownWindows.Get(myHwnd); // or nullptr
+ const HMONITOR newMonitor = GetWindowState(myHwnd)
+ .map([](auto state) { return state.monitor; })
+ .valueOr(nullptr);
+
+ if (oldMonitor == newMonitor) {
+ return;
+ }
+
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("==> OnWindowPosChanged() for HWND %p (HMONITOR %p -> %p)", myHwnd,
+ oldMonitor, newMonitor));
+
+ UpdateAllState();
+}
+
+void nsWindow::TaskbarConcealer::OnAsyncStateUpdateRequest(HWND hwnd) {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
+ ("==> OnAsyncStateUpdateRequest()"));
+
+ // Work around a race condition in explorer.exe.
+ //
+ // When a window is unminimized (and on several other events), the taskbar
+ // receives a notification that it needs to recalculate the current
+ // is-a-fullscreen-window-active-here-state ("rudeness") of each monitor.
+ // Unfortunately, this notification is sent concurrently with the
+ // WM_WINDOWPOSCHANGING message that performs the unminimization.
+ //
+ // Until that message is resolved, the window's position is still "minimized".
+ // If the taskbar processes its notification faster than the window handles
+ // its WM_WINDOWPOSCHANGING message, then the window will appear to the
+ // taskbar to still be minimized, and won't be taken into account for
+ // computing rudeness. This usually presents as a just-unminimized Firefox
+ // fullscreen-window occasionally having the taskbar stuck above it.
+ //
+ // Unfortunately, it's a bit difficult to improve Firefox's speed-of-response
+ // to WM_WINDOWPOSCHANGING messages (we can, and do, execute JavaScript during
+ // these), and even if we could that wouldn't always fix it. We instead adopt
+ // a variant of a strategy by Etienne Duchamps, who has investigated and
+ // documented this issue extensively[0]: we simply send another signal to the
+ // shell to notify it to recalculate the current rudeness state of all
+ // monitors.
+ //
+ // [0]
+ // https://github.com/dechamps/RudeWindowFixer#a-race-condition-activating-a-minimized-window
+ //
+ static UINT const shellHookMsg = ::RegisterWindowMessageW(L"SHELLHOOK");
+ if (shellHookMsg != 0) {
+ // Identifying the particular thread of the particular instance of the
+ // shell associated with our current desktop is probably possible, but
+ // also probably not worth the effort. Just broadcast the message
+ // globally.
+ DWORD info = BSM_APPLICATIONS;
+ ::BroadcastSystemMessage(BSF_POSTMESSAGE | BSF_IGNORECURRENTTASK, &info,
+ shellHookMsg, HSHELL_WINDOWACTIVATED,
+ (LPARAM)hwnd);
+ }
+}
+
+void nsWindow::TaskbarConcealer::OnCloakChanged() {
+ MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info, ("==> OnCloakChanged()"));
+
+ UpdateAllState();
+}
diff --git a/widget/windows/nsWindowTaskbarConcealer.h b/widget/windows/nsWindowTaskbarConcealer.h
new file mode 100644
index 0000000000..84567fc857
--- /dev/null
+++ b/widget/windows/nsWindowTaskbarConcealer.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 WIDGET_WINDOWS_NSWINDOWTASKBARCONCEALER_H_
+#define WIDGET_WINDOWS_NSWINDOWTASKBARCONCEALER_H_
+
+#include "nsWindow.h"
+#include "mozilla/Maybe.h"
+
+/**
+ * nsWindow::TaskbarConcealer
+ *
+ * Fullscreen-state (and, thus, taskbar-occlusion) manager.
+ */
+class nsWindow::TaskbarConcealer {
+ public:
+ // To be called when a window acquires focus. (Note that no action need be
+ // taken when focus is lost.)
+ static void OnFocusAcquired(nsWindow* aWin);
+
+ // To be called during or after a window's destruction. The corresponding
+ // nsWindow pointer is not needed, and will not be acquired or accessed.
+ static void OnWindowDestroyed(HWND aWnd);
+
+ // To be called when the Gecko-fullscreen state of a window changes.
+ static void OnFullscreenChanged(nsWindow* aWin, bool enteredFullscreen);
+
+ // To be called when the position of a window changes. (Performs its own
+ // batching; irrelevant movements will be cheap.)
+ static void OnWindowPosChanged(nsWindow* aWin);
+
+ // To be called when the cloaking state of any window changes. (Expects that
+ // all windows' internal cloaking-state mirror variables are up-to-date.)
+ static void OnCloakChanged();
+
+ // To be called upon receipt of MOZ_WM_FULLSCREEN_STATE_UPDATE.
+ static void OnAsyncStateUpdateRequest(HWND);
+
+ private:
+ static void UpdateAllState(HWND destroyedHwnd = nullptr);
+
+ struct WindowState {
+ HMONITOR monitor;
+ bool isGkFullscreen;
+ };
+ static mozilla::Maybe<WindowState> GetWindowState(HWND);
+
+ static nsTHashMap<HWND, HMONITOR> sKnownWindows;
+};
+
+#endif // WIDGET_WINDOWS_NSWINDOWTASKBARCONCEALER_H_
diff --git a/widget/windows/nsdefs.h b/widget/windows/nsdefs.h
new file mode 100644
index 0000000000..ebf7892fda
--- /dev/null
+++ b/widget/windows/nsdefs.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 NSDEFS_H
+#define NSDEFS_H
+
+#include <windows.h>
+
+#ifdef _DEBUG
+# define BREAK_TO_DEBUGGER DebugBreak()
+#else
+# define BREAK_TO_DEBUGGER
+#endif
+
+#ifdef _DEBUG
+# define VERIFY(exp) \
+ if (!(exp)) { \
+ GetLastError(); \
+ BREAK_TO_DEBUGGER; \
+ }
+#else // !_DEBUG
+# define VERIFY(exp) (exp)
+#endif // !_DEBUG
+
+// inttypes.h-like macro for LONG_PTR/LPARAM formatting.
+#ifdef HAVE_64BIT_BUILD
+# define PRIdLPTR "lld"
+# define PRIxLPTR "llx"
+# define PRIXLPTR "llX"
+#else
+# define PRIdLPTR "ld"
+# define PRIxLPTR "lx"
+# define PRIXLPTR "lX"
+#endif
+
+// Win32 logging modules:
+// nsWindow, nsSound, and nsClipboard
+//
+// Logging can be changed at runtime without recompiling in the General
+// property page of Visual Studio under the "Environment" property.
+//
+// Two variables are of importance to be set:
+// MOZ_LOG and MOZ_LOG_FILE
+//
+// MOZ_LOG:
+// MOZ_LOG=all:5 (To log everything completely)
+// MOZ_LOG=nsWindow:5,nsSound:5,nsClipboard:5
+// (To log windows widget stuff)
+// MOZ_LOG= (To turn off logging)
+//
+// MOZ_LOG_FILE:
+// MOZ_LOG_FILE=C:\log.txt (To a file on disk)
+// MOZ_LOG_FILE=WinDebug (To the debug window)
+// MOZ_LOG_FILE= (To stdout/stderr)
+
+#endif // NSDEFS_H
diff --git a/widget/windows/res/aliasb.cur b/widget/windows/res/aliasb.cur
new file mode 100644
index 0000000000..8d9ac9478c
--- /dev/null
+++ b/widget/windows/res/aliasb.cur
Binary files differ
diff --git a/widget/windows/res/cell.cur b/widget/windows/res/cell.cur
new file mode 100644
index 0000000000..decfbdcac5
--- /dev/null
+++ b/widget/windows/res/cell.cur
Binary files differ
diff --git a/widget/windows/res/col_resize.cur b/widget/windows/res/col_resize.cur
new file mode 100644
index 0000000000..8f7f675122
--- /dev/null
+++ b/widget/windows/res/col_resize.cur
Binary files differ
diff --git a/widget/windows/res/copy.cur b/widget/windows/res/copy.cur
new file mode 100644
index 0000000000..87f1519cd1
--- /dev/null
+++ b/widget/windows/res/copy.cur
Binary files differ
diff --git a/widget/windows/res/grab.cur b/widget/windows/res/grab.cur
new file mode 100644
index 0000000000..db7ad5aed3
--- /dev/null
+++ b/widget/windows/res/grab.cur
Binary files differ
diff --git a/widget/windows/res/grabbing.cur b/widget/windows/res/grabbing.cur
new file mode 100644
index 0000000000..e0dfd04e4d
--- /dev/null
+++ b/widget/windows/res/grabbing.cur
Binary files differ
diff --git a/widget/windows/res/none.cur b/widget/windows/res/none.cur
new file mode 100644
index 0000000000..2114dfaee3
--- /dev/null
+++ b/widget/windows/res/none.cur
Binary files differ
diff --git a/widget/windows/res/row_resize.cur b/widget/windows/res/row_resize.cur
new file mode 100644
index 0000000000..a7369d32d1
--- /dev/null
+++ b/widget/windows/res/row_resize.cur
Binary files differ
diff --git a/widget/windows/res/select.cur b/widget/windows/res/select.cur
new file mode 100644
index 0000000000..5a88b3707e
--- /dev/null
+++ b/widget/windows/res/select.cur
Binary files differ
diff --git a/widget/windows/res/vertical_text.cur b/widget/windows/res/vertical_text.cur
new file mode 100644
index 0000000000..3de04ebec3
--- /dev/null
+++ b/widget/windows/res/vertical_text.cur
Binary files differ
diff --git a/widget/windows/res/zoom_in.cur b/widget/windows/res/zoom_in.cur
new file mode 100644
index 0000000000..b594d79271
--- /dev/null
+++ b/widget/windows/res/zoom_in.cur
Binary files differ
diff --git a/widget/windows/res/zoom_out.cur b/widget/windows/res/zoom_out.cur
new file mode 100644
index 0000000000..7e495fbaa4
--- /dev/null
+++ b/widget/windows/res/zoom_out.cur
Binary files differ
diff --git a/widget/windows/resource.h b/widget/windows/resource.h
new file mode 100644
index 0000000000..a367e9f3e9
--- /dev/null
+++ b/widget/windows/resource.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#define IDC_GRAB 4101
+#define IDC_GRABBING 4102
+#define IDC_CELL 4103
+#define IDC_COPY 4104
+#define IDC_ALIAS 4105
+#define IDC_ZOOMIN 4106
+#define IDC_ZOOMOUT 4107
+#define IDC_COLRESIZE 4108
+#define IDC_ROWRESIZE 4109
+#define IDC_VERTICALTEXT 4110
+#define IDC_DUMMY_CE_MENUBAR 4111
+#define IDC_NONE 4112
diff --git a/widget/windows/tests/TestUriValidation.cpp b/widget/windows/tests/TestUriValidation.cpp
new file mode 100644
index 0000000000..d8a0ca09ce
--- /dev/null
+++ b/widget/windows/tests/TestUriValidation.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#define UNICODE
+#include "mozilla/UrlmonHeaderOnlyUtils.h"
+#include "TestUrisToValidate.h"
+
+#include <urlmon.h>
+
+using namespace mozilla;
+
+static LauncherResult<_bstr_t> ShellValidateUri(const wchar_t* aUri) {
+ LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri);
+ if (pidlResult.isErr()) {
+ return pidlResult.propagateErr();
+ }
+ UniqueAbsolutePidl pidl = pidlResult.unwrap();
+
+ // |pidl| is an absolute path. IShellFolder::GetDisplayNameOf requires a
+ // valid child ID, so the first thing we need to resolve is the IShellFolder
+ // for |pidl|'s parent, as well as the childId that represents |pidl|.
+ // Fortunately SHBindToParent does exactly that!
+ PCUITEMID_CHILD childId = nullptr;
+ RefPtr<IShellFolder> parentFolder;
+ HRESULT hr = SHBindToParent(pidl.get(), IID_IShellFolder,
+ getter_AddRefs(parentFolder), &childId);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // Now we retrieve the display name of |childId|, telling the shell that we
+ // plan to have the string parsed.
+ STRRET strret;
+ hr = parentFolder->GetDisplayNameOf(childId, SHGDN_FORPARSING, &strret);
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ // StrRetToBSTR automatically takes care of freeing any dynamically
+ // allocated memory in |strret|.
+ _bstr_t bstrUri;
+ hr = StrRetToBSTR(&strret, nullptr, bstrUri.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ return bstrUri;
+}
+
+static LauncherResult<_bstr_t> GetFragment(const wchar_t* aUri) {
+ constexpr DWORD flags =
+ Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+ Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+ Uri_CREATE_IE_SETTINGS;
+ RefPtr<IUri> uri;
+ HRESULT hr = CreateUri(aUri, flags, 0, getter_AddRefs(uri));
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ _bstr_t bstrFragment;
+ hr = uri->GetFragment(bstrFragment.GetAddress());
+ if (FAILED(hr)) {
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+ return bstrFragment;
+}
+
+static bool RunSingleTest(const wchar_t* aUri) {
+ LauncherResult<_bstr_t> uriOld = ShellValidateUri(aUri),
+ uriNew = UrlmonValidateUri(aUri);
+ if (uriOld.isErr() != uriNew.isErr()) {
+ printf("TEST-FAILED | UriValidation | Validation result mismatch on %S\n",
+ aUri);
+ return false;
+ }
+
+ if (uriOld.isErr()) {
+ if (uriOld.unwrapErr().mError != uriNew.unwrapErr().mError) {
+ printf("TEST-FAILED | UriValidation | Error code mismatch on %S\n", aUri);
+ return false;
+ }
+ return true;
+ }
+
+ LauncherResult<_bstr_t> bstrFragment = GetFragment(aUri);
+ if (bstrFragment.isErr()) {
+ printf("TEST-FAILED | UriValidation | Failed to get a fragment from %S\n",
+ aUri);
+ return false;
+ }
+
+ // We validate a uri with two logics: the current one UrlmonValidateUri and
+ // the older one ShellValidateUri, to make sure the same validation result.
+ // We introduced UrlmonValidateUri because ShellValidateUri drops a fragment
+ // in a uri due to the design of Windows. To bypass the fragment issue, we
+ // extract a fragment and appends it into the validated string, and compare.
+ _bstr_t bstrUriOldCorrected = uriOld.unwrap() + bstrFragment.unwrap();
+ const _bstr_t& bstrUriNew = uriNew.unwrap();
+ if (bstrUriOldCorrected != bstrUriNew) {
+ printf("TEST-FAILED | UriValidation | %S %S %S\n", aUri,
+ static_cast<const wchar_t*>(bstrUriOldCorrected),
+ static_cast<const wchar_t*>(bstrUriNew));
+ return false;
+ }
+
+ return true;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+ HRESULT hr = CoInitialize(nullptr);
+ if (FAILED(hr)) {
+ return 1;
+ }
+
+ bool isOk = true;
+
+ if (argc == 2) {
+ isOk = RunSingleTest(argv[1]);
+ } else {
+ for (const wchar_t*& testUri : kTestUris) {
+ if (!RunSingleTest(testUri)) {
+ isOk = false;
+ }
+ }
+ }
+
+ CoUninitialize();
+ return isOk ? 0 : 1;
+}
diff --git a/widget/windows/tests/TestUrisToValidate.h b/widget/windows/tests/TestUrisToValidate.h
new file mode 100644
index 0000000000..cb00366d1e
--- /dev/null
+++ b/widget/windows/tests/TestUrisToValidate.h
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TestUrisToValidate_h
+#define mozilla_TestUrisToValidate_h
+
+const wchar_t* kTestUris[] = {
+ L"callto:%.txt",
+ L"callto:%00.txt",
+ L"callto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"fdaction:%.txt",
+ L"fdaction:%00.txt",
+ L"fdaction:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"feed:%.txt",
+ L"feed:%00.txt",
+ L"feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"feeds:%.txt",
+ L"feeds:%00.txt",
+ L"feeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"file:///%.txt",
+ L"file:///%00.txt",
+ L"file:///%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"firefox.url:%.txt",
+ L"firefox.url:%00.txt",
+ L"firefox.url:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"firefoxurl:%.txt",
+ L"firefoxurl:%00.txt",
+ L"firefoxurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ftp:%.txt",
+ L"ftp:%00.txt",
+ L"ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gopher:%.txt",
+ L"gopher:%00.txt",
+ L"gopher:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gtalk:%.txt",
+ L"gtalk:%00.txt",
+ L"gtalk:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"HTTP:%.txt",
+ L"HTTP:%00.txt",
+ L"HTTP:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"http:%.txt",
+ L"http:%00.txt",
+ L"http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/%.txt",
+ L"https://bug389580.bmoattachments.org/%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"ie.ftp:%.txt",
+ L"ie.ftp:%00.txt",
+ L"ie.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ie.http:%.txt",
+ L"ie.http:%00.txt",
+ L"ie.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ie.https:%.txt",
+ L"ie.https:%00.txt",
+ L"ie.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"irc:%.txt",
+ L"irc:%00.txt",
+ L"irc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ircs:%.txt",
+ L"ircs:%00.txt",
+ L"ircs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itms:%.txt",
+ L"itms:%00.txt",
+ L"itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itmss:%.txt",
+ L"itmss:%00.txt",
+ L"itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itpc:%.txt",
+ L"itpc:%00.txt",
+ L"itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itms:%.txt",
+ L"itunes.assocprotocol.itms:%00.txt",
+ L"itunes.assocprotocol.itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itmss:%.txt",
+ L"itunes.assocprotocol.itmss:%00.txt",
+ L"itunes.assocprotocol.itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itpc:%.txt",
+ L"itunes.assocprotocol.itpc:%00.txt",
+ L"itunes.assocprotocol.itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ldap:%.txt",
+ L"ldap:%00.txt",
+ L"ldap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mailto:%.txt",
+ L"mailto:%00.txt",
+ L"mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mms:%.txt",
+ L"mms:%00.txt",
+ L"mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"navigatorurl:%.txt",
+ L"navigatorurl:%00.txt",
+ L"navigatorurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"news:%.txt",
+ L"news:%00.txt",
+ L"news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"nntp:%.txt",
+ L"nntp:%00.txt",
+ L"nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"oms:%.txt",
+ L"oms:%00.txt",
+ L"oms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook:%.txt",
+ L"outlook:%00.txt",
+ L"outlook:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.feed:%.txt",
+ L"outlook.url.feed:%00.txt",
+ L"outlook.url.feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.mailto:%.txt",
+ L"outlook.url.mailto:%00.txt",
+ L"outlook.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.webcal:%.txt",
+ L"outlook.url.webcal:%00.txt",
+ L"outlook.url.webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlookfeed:%.txt",
+ L"outlookfeed:%00.txt",
+ L"outlookfeed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlookfeeds:%.txt",
+ L"outlookfeeds:%00.txt",
+ L"outlookfeeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pnm:%.txt",
+ L"pnm:%00.txt",
+ L"pnm:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.ftp:%.txt",
+ L"prls.intappfile.ftp:%00.txt",
+ L"prls.intappfile.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.http:%.txt",
+ L"prls.intappfile.http:%00.txt",
+ L"prls.intappfile.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.https:%.txt",
+ L"prls.intappfile.https:%00.txt",
+ L"prls.intappfile.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"prls.intappfile.mailto:%.txt",
+ L"prls.intappfile.mailto:%00.txt",
+ L"prls.intappfile.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rlogin:%.txt",
+ L"rlogin:%00.txt",
+ L"rlogin:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rtsp:%.txt",
+ L"rtsp:%00.txt",
+ L"rtsp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"scp:%.txt",
+ L"scp:%00.txt",
+ L"scp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sftp:%.txt",
+ L"sftp:%00.txt",
+ L"sftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sip:%.txt",
+ L"sip:%00.txt",
+ L"sip:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"skype:%.txt",
+ L"skype:%00.txt",
+ L"skype:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"snews:%.txt",
+ L"snews:%00.txt",
+ L"snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"telnet:%.txt",
+ L"telnet:%00.txt",
+ L"telnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"thunderbird.url.mailto:%.txt",
+ L"thunderbird.url.mailto:%00.txt",
+ L"thunderbird.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"thunderbird.url.news:%.txt",
+ L"thunderbird.url.news:%00.txt",
+ L"thunderbird.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tn3270:%.txt",
+ L"tn3270:%00.txt",
+ L"tn3270:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tscrec4:%.txt",
+ L"tscrec4:%00.txt",
+ L"tscrec4:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"webcals:%.txt",
+ L"webcals:%00.txt",
+ L"webcals:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowscalendar.urlwebcal.1:%.txt",
+ L"windowscalendar.urlwebcal.1:%00.txt",
+ L"windowscalendar.urlwebcal.1:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.mailto:%.txt",
+ L"windowsmail.url.mailto:%00.txt",
+ L"windowsmail.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.news:%.txt",
+ L"windowsmail.url.news:%00.txt",
+ L"windowsmail.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.nntp:%.txt",
+ L"windowsmail.url.nntp:%00.txt",
+ L"windowsmail.url.nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"windowsmail.url.snews:%.txt",
+ L"windowsmail.url.snews:%00.txt",
+ L"windowsmail.url.snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"wmp11.assocprotocol.mms:%.txt",
+ L"wmp11.assocprotocol.mms:%00.txt",
+ L"wmp11.assocprotocol.mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"wpc:%.txt",
+ L"wpc:%00.txt",
+ L"wpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ymsgr:%.txt",
+ L"ymsgr:%00.txt",
+ L"ymsgr:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"acrobat:%.txt",
+ L"acrobat:%00.txt",
+ L"acrobat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"acsui:%.txt",
+ L"acsui:%00.txt",
+ L"acsui:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allc8.commands.2:%.txt",
+ L"allc8.commands.2:%00.txt",
+ L"allc8.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allholdem.commands.2:%.txt",
+ L"allholdem.commands.2:%00.txt",
+ L"allholdem.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"allpoker.commands.2:%.txt",
+ L"allpoker.commands.2:%00.txt",
+ L"allpoker.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aolautofix:%.txt",
+ L"aolautofix:%00.txt",
+ L"aolautofix:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"aolds:%.txt",
+ L"aolds:%00.txt",
+ L"aolds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bc:%.txt",
+ L"bc:%00.txt",
+ L"bc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bctp:%.txt",
+ L"bctp:%00.txt",
+ L"bctp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"bittorrent:%.txt",
+ L"bittorrent:%00.txt",
+ L"bittorrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"camfrog:%.txt",
+ L"camfrog:%00.txt",
+ L"camfrog:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"csi:%.txt",
+ L"csi:%00.txt",
+ L"csi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"cvs:%.txt",
+ L"cvs:%00.txt",
+ L"cvs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"daap:%.txt",
+ L"daap:%00.txt",
+ L"daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ed2k:%.txt",
+ L"ed2k:%00.txt",
+ L"ed2k:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"explorer.assocprotocol.search-ms:%.txt",
+ L"explorer.assocprotocol.search-ms:%00.txt",
+ L"explorer.assocprotocol.search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gizmoproject:%.txt",
+ L"gizmoproject:%00.txt",
+ L"gizmoproject:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gnet:%.txt",
+ L"gnet:%00.txt",
+ L"gnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gnutella:%.txt",
+ L"gnutella:%00.txt",
+ L"gnutella:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"gsarcade:%.txt",
+ L"gsarcade:%00.txt",
+ L"gsarcade:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"hcp:%.txt",
+ L"hcp:%00.txt",
+ L"hcp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"icquser:%.txt",
+ L"icquser:%00.txt",
+ L"icquser:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"icy:%.txt",
+ L"icy:%00.txt",
+ L"icy:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"imesync:%.txt",
+ L"imesync:%00.txt",
+ L"imesync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.daap:%.txt",
+ L"itunes.assocprotocol.daap:%00.txt",
+ L"itunes.assocprotocol.daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"itunes.assocprotocol.pcast:%.txt",
+ L"itunes.assocprotocol.pcast:%00.txt",
+ L"itunes.assocprotocol.pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"joost:%.txt",
+ L"joost:%00.txt",
+ L"joost:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"m4macdrive:%.txt",
+ L"m4macdrive:%00.txt",
+ L"m4macdrive:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"magnet:%.txt",
+ L"magnet:%00.txt",
+ L"magnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mapi:%.txt",
+ L"mapi:%00.txt",
+ L"mapi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mc12:%.txt",
+ L"mc12:%00.txt",
+ L"mc12:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mediajukebox:%.txt",
+ L"mediajukebox:%00.txt",
+ L"mediajukebox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"morpheus:%.txt",
+ L"morpheus:%00.txt",
+ L"morpheus:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mp2p:%.txt",
+ L"mp2p:%00.txt",
+ L"mp2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"mpodcast:%.txt",
+ L"mpodcast:%00.txt",
+ L"mpodcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"msdigitallocker:%.txt",
+ L"msdigitallocker:%00.txt",
+ L"msdigitallocker:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"outlook.url.stssync:%.txt",
+ L"outlook.url.stssync:%00.txt",
+ L"outlook.url.stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"p2p:%.txt",
+ L"p2p:%00.txt",
+ L"p2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pando:%.txt",
+ L"pando:%00.txt",
+ L"pando:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"pcast:%.txt",
+ L"pcast:%00.txt",
+ L"pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"picasa:%.txt",
+ L"picasa:%00.txt",
+ L"picasa:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"plaxo:%.txt",
+ L"plaxo:%00.txt",
+ L"plaxo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"play:%.txt",
+ L"play:%00.txt",
+ L"play:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"podcast:%.txt",
+ L"podcast:%00.txt",
+ L"podcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppmate:%.txt",
+ L"ppmate:%00.txt",
+ L"ppmate:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppmates:%.txt",
+ L"ppmates:%00.txt",
+ L"ppmates:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ppstream:%.txt",
+ L"ppstream:%00.txt",
+ L"ppstream:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"quicktime:%.txt",
+ L"quicktime:%00.txt",
+ L"quicktime:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"realplayer.autoplay.6:%.txt",
+ L"realplayer.autoplay.6:%00.txt",
+ L"realplayer.autoplay.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"realplayer.cdburn.6:%.txt",
+ L"realplayer.cdburn.6:%00.txt",
+ L"realplayer.cdburn.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"rhap:%.txt",
+ L"rhap:%00.txt",
+ L"rhap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sc:%.txt",
+ L"sc:%00.txt",
+ L"sc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"search-ms:%.txt",
+ L"search-ms:%00.txt",
+ L"search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shareaza:%.txt",
+ L"shareaza:%00.txt",
+ L"shareaza:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shell:%.txt",
+ L"shell:%00.txt",
+ L"shell:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"shout:%.txt",
+ L"shout:%00.txt",
+ L"shout:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sig2dat:%.txt",
+ L"sig2dat:%00.txt",
+ L"sig2dat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"sop:%.txt",
+ L"sop:%00.txt",
+ L"sop:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"steam:%.txt",
+ L"steam:%00.txt",
+ L"steam:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"stssync:%.txt",
+ L"stssync:%00.txt",
+ L"stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"svn:%.txt",
+ L"svn:%00.txt",
+ L"svn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"svn+ssh:%.txt",
+ L"svn+ssh:%00.txt",
+ L"svn+ssh:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"synacast:%.txt",
+ L"synacast:%00.txt",
+ L"synacast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"torrent:%.txt",
+ L"torrent:%00.txt",
+ L"torrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tsvn:%.txt",
+ L"tsvn:%00.txt",
+ L"tsvn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tvants:%.txt",
+ L"tvants:%00.txt",
+ L"tvants:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"tvu:%.txt",
+ L"tvu:%00.txt",
+ L"tvu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"unsv:%.txt",
+ L"unsv:%00.txt",
+ L"unsv:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"uvox:%.txt",
+ L"uvox:%00.txt",
+ L"uvox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"ventrilo:%.txt",
+ L"ventrilo:%00.txt",
+ L"ventrilo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"vs:%.txt",
+ L"vs:%00.txt",
+ L"vs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"zune:%.txt",
+ L"zune:%00.txt",
+ L"zune:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+ L"https://example.com/?a=123&b=456",
+ L"https://example.com/#123?a=123&b=456",
+ L"https://example.com/?#123a=123&b=456",
+ L"https://example.com/?a=123&b=456#123",
+ L"mailto:%41%42%23%31",
+ L"mailto:%41%42%23%31#fragment",
+ L"news:%41%42%23%31",
+ L"news:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31",
+ L"microsoft-edge:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31#fragment#",
+ L"microsoft-edge:%41%42%23%31####",
+ L"something-unknown:",
+ L"something-unknown:x=123",
+ L"something-unknown:?=123",
+ L"something-unknown:#code=0123456789%200123456789&x=01234567890123456789",
+};
+
+#endif // mozilla_TestUrisToValidate_h
diff --git a/widget/windows/tests/gtest/TestJumpListBuilder.cpp b/widget/windows/tests/gtest/TestJumpListBuilder.cpp
new file mode 100644
index 0000000000..5494c42d37
--- /dev/null
+++ b/widget/windows/tests/gtest/TestJumpListBuilder.cpp
@@ -0,0 +1,823 @@
+/* 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/. */
+
+#include <objectarray.h>
+#include <shobjidl.h>
+#include <windows.h>
+#include <string.h>
+#include <propvarutil.h>
+#include <propkey.h>
+
+#ifdef __MINGW32__
+// MinGW-w64 headers are missing PropVariantToString.
+# include <propsys.h>
+PSSTDAPI PropVariantToString(REFPROPVARIANT propvar, PWSTR psz, UINT cch);
+#endif
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/WindowsJumpListShortcutDescriptionBinding.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "JumpListBuilder.h"
+
+using namespace mozilla;
+using namespace testing;
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::Promise;
+using mozilla::dom::PromiseNativeHandler;
+using mozilla::dom::ToJSValue;
+using mozilla::dom::WindowsJumpListShortcutDescription;
+using mozilla::widget::JumpListBackend;
+using mozilla::widget::JumpListBuilder;
+
+/**
+ * GMock matcher that ensures that two LPCWSTRs match.
+ */
+MATCHER_P(LPCWSTREq, value, "The equivalent of StrEq for LPCWSTRs") {
+ return (wcscmp(arg, value)) == 0;
+}
+
+/**
+ * GMock matcher that ensures that a IObjectArray* contains nsIShellLinkW's
+ * that match an equivalent set of nsTArray<WindowsJumpListShortcutDescriptions>
+ */
+MATCHER_P(ShellLinksEq, descs,
+ "Comparing generated IShellLinkW with "
+ "WindowsJumpListShortcutDescription definitions") {
+ uint32_t count = 0;
+ HRESULT hr = arg->GetCount(&count);
+ if (FAILED(hr) || count != descs->Length()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < descs->Length(); ++i) {
+ RefPtr<IShellLinkW> link;
+ if (FAILED(arg->GetAt(i, IID_IShellLinkW,
+ static_cast<void**>(getter_AddRefs(link))))) {
+ return false;
+ }
+
+ if (!link) {
+ return false;
+ }
+
+ const WindowsJumpListShortcutDescription& desc = descs->ElementAt(i);
+
+ // We'll now compare each member of the WindowsJumpListShortcutDescription
+ // with what is stored in the IShellLink.
+
+ // WindowsJumpListShortcutDescription.title
+ IPropertyStore* propStore = nullptr;
+ hr = link->QueryInterface(IID_IPropertyStore, (LPVOID*)&propStore);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ PROPVARIANT pv;
+ hr = propStore->GetValue(PKEY_Title, &pv);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ wchar_t title[PKEYSTR_MAX];
+ hr = PropVariantToString(pv, title, PKEYSTR_MAX);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (!desc.mTitle.Equals(title)) {
+ return false;
+ }
+
+ // WindowsJumpListShortcutDescription.path
+ wchar_t pathBuf[MAX_PATH];
+ hr = link->GetPath(pathBuf, MAX_PATH, nullptr, SLGP_SHORTPATH);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (!desc.mPath.Equals(pathBuf)) {
+ return false;
+ }
+
+ // WindowsJumpListShortcutDescription.arguments (optional)
+ wchar_t argsBuf[MAX_PATH];
+ hr = link->GetArguments(argsBuf, MAX_PATH);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (desc.mArguments.WasPassed()) {
+ if (!desc.mArguments.Value().Equals(argsBuf)) {
+ return false;
+ }
+ } else {
+ // Otherwise, the arguments should be empty.
+ if (wcsnlen(argsBuf, MAX_PATH) != 0) {
+ return false;
+ }
+ }
+
+ // WindowsJumpListShortcutDescription.description
+ wchar_t descBuf[MAX_PATH];
+ hr = link->GetDescription(descBuf, MAX_PATH);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (!desc.mDescription.Equals(descBuf)) {
+ return false;
+ }
+
+ // WindowsJumpListShortcutDescription.iconPath and
+ // WindowsJumpListShortcutDescription.fallbackIconIndex
+ int iconIdx = 0;
+ wchar_t iconPathBuf[MAX_PATH];
+ hr = link->GetIconLocation(iconPathBuf, MAX_PATH, &iconIdx);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (desc.mIconPath.WasPassed() && !desc.mIconPath.Value().IsEmpty()) {
+ // If the WindowsJumpListShortcutDescription supplied an iconPath,
+ // then it should match iconPathBuf and have an icon index of 0.
+ if (!desc.mIconPath.Value().Equals(iconPathBuf) || iconIdx != 0) {
+ return false;
+ }
+ } else {
+ // Otherwise, the iconPathBuf should equal the
+ // WindowsJumpListShortcutDescription path, and the iconIdx should match
+ // the fallbackIconIndex.
+ if (!desc.mPath.Equals(iconPathBuf) ||
+ desc.mFallbackIconIndex != iconIdx) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * This is a helper class that allows our tests to wait for a native DOM Promise
+ * to resolve, and get the JS::Value that the Promise resolves with. This is
+ * expected to run on the main thread.
+ */
+class WaitForResolver : public PromiseNativeHandler {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WaitForResolver, override)
+
+ NS_IMETHODIMP QueryInterface(REFNSIID aIID, void** aInstancePtr) override {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ NS_INTERFACE_TABLE0(WaitForResolver)
+
+ return rv;
+ }
+
+ WaitForResolver() : mIsDone(false) {}
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aError) override {
+ mResult = aValue;
+ mIsDone = true;
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aError) override {
+ ASSERT_TRUE(false); // Should never reach here.
+ }
+
+ /**
+ * Spins a nested event loop and blocks until the Promise has resolved.
+ */
+ void SpinUntilResolved() {
+ SpinEventLoopUntil("WaitForResolver::SpinUntilResolved"_ns,
+ [&]() { return mIsDone; });
+ }
+
+ /**
+ * Spins a nested event loop and blocks until the Promise has resolved,
+ * after which the JS::Value that the Promise resolves with is returned via
+ * the aRetval outparam.
+ *
+ * @param {JS::MutableHandle<JS::Value>} aRetval
+ * The outparam for the JS::Value that the Promise resolves with.
+ */
+ void SpinUntilResolvedWithResult(JS::MutableHandle<JS::Value> aRetval) {
+ SpinEventLoopUntil("WaitForResolver::SpinUntilResolved"_ns,
+ [&]() { return mIsDone; });
+ aRetval.set(mResult);
+ }
+
+ private:
+ virtual ~WaitForResolver() = default;
+
+ JS::Heap<JS::Value> mResult;
+ bool mIsDone;
+};
+
+/**
+ * An implementation of JumpListBackend that is instrumented using the GMock
+ * framework to record calls. Unlike the NativeJumpListBackend, this backend
+ * is expected to be instantiated on the main thread and passed as an argument
+ * to the JumpListBuilder's worker thread. Testers should wait for the methods
+ * that call these functions to resolve their Promises before checking the
+ * recorded values.
+ */
+class TestingJumpListBackend : public JumpListBackend {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JumpListBackend, override)
+
+ TestingJumpListBackend() : mMonitor("TestingJumpListBackend::mMonitor") {}
+
+ virtual bool IsAvailable() override { return true; }
+
+ MOCK_METHOD(HRESULT, SetAppID, (LPCWSTR));
+ MOCK_METHOD(HRESULT, BeginList, (UINT*, REFIID, void**));
+ MOCK_METHOD(HRESULT, AddUserTasks, (IObjectArray*));
+ MOCK_METHOD(HRESULT, AppendCategory, (LPCWSTR, IObjectArray*));
+ MOCK_METHOD(HRESULT, CommitList, ());
+ MOCK_METHOD(HRESULT, AbortList, ());
+ MOCK_METHOD(HRESULT, DeleteList, (LPCWSTR));
+
+ virtual HRESULT AppendKnownCategory(KNOWNDESTCATEGORY category) override {
+ return 0;
+ }
+
+ // In one case (construction), an operation occurs off of the main thread that
+ // we must wait for without an associated Promise.
+ Monitor& GetMonitor() { return mMonitor; }
+
+ protected:
+ virtual ~TestingJumpListBackend() override{};
+
+ private:
+ Monitor mMonitor;
+};
+
+/**
+ * A helper function that creates some fake WindowsJumpListShortcutDescription
+ * objects as well as JS::Value representations of those objects. These are
+ * returned to the caller through outparams.
+ *
+ * @param {JSContext*} aCx
+ * The current JSContext in the execution environment.
+ * @param {uint32_t} howMany
+ * The number of WindowsJumpListShortcutDescriptions to generate.
+ * @param {boolean} longDescription
+ * True if the description should be greater than MAX_PATH (260 characters).
+ * @param {nsTArray<WindowsJumpListShortcutDescription>&} aArray
+ * The outparam for the array of generated
+ * WindowsJumpListShortcutDescriptions.
+ * @param {nsTArray<JS::Value>&} aJSValArray
+ * The outparam for the array of JS::Value's representing the generated
+ * WindowsJumpListShortcutDescriptions.
+ */
+void GenerateWindowsJumpListShortcutDescriptions(
+ JSContext* aCx, uint32_t howMany, bool longDescription,
+ nsTArray<WindowsJumpListShortcutDescription>& aArray,
+ nsTArray<JS::Value>& aJSValArray) {
+ for (uint32_t i = 0; i < howMany; ++i) {
+ WindowsJumpListShortcutDescription desc;
+ nsAutoString title(u"Test Task #");
+ title.AppendInt(i);
+ desc.mTitle = title;
+
+ nsAutoString path(u"C:\\Some\\Test\\Path.exe");
+ desc.mPath = path;
+ nsAutoString description;
+
+ if (longDescription) {
+ description.AppendPrintf(
+ "For item #%i, this is a very very very very VERY VERY very very "
+ "very very very very very very very very very very VERY VERY very "
+ "very very very very very very very very very very very VERY VERY "
+ "very very very very very very very very very very very very VERY "
+ "VERY very very very very very very very very very very very very "
+ "VERY VERY very very very very very very very very very very very "
+ "very VERY VERY very very very very very very very very long test "
+ "description for an item",
+ i);
+ } else {
+ description.AppendPrintf("This is a test description for an item #%i", i);
+ }
+
+ desc.mDescription = description;
+ desc.mFallbackIconIndex = 0;
+
+ if (!(i % 2)) {
+ nsAutoString arguments(u"-arg1 -arg2 -arg3");
+ desc.mArguments.Construct(arguments);
+ nsAutoString iconPath(u"C:\\Some\\icon.png");
+ desc.mIconPath.Construct(iconPath);
+ }
+
+ aArray.AppendElement(desc);
+ JS::Rooted<JS::Value> descJSValue(aCx);
+ ASSERT_TRUE(ToJSValue(aCx, desc, &descJSValue));
+ aJSValArray.AppendElement(std::move(descJSValue));
+ }
+}
+
+/**
+ * Tests construction and that the application ID is properly passed to the
+ * backend.
+ */
+TEST(JumpListBuilder, Construction)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ ASSERT_TRUE(testBackend);
+
+ nsAutoString aumid(u"TestApplicationID");
+ LPCWSTR passedID = aumid.get();
+ // Construction of our class (or any class of that matter) does not return a
+ // Promise that we can wait on to ensure that the background thread got the
+ // right information. We therefore use a monitor on the testing backend as
+ // well as an EXPECT_CALL to block execution of the test until the background
+ // work has completed.
+ Monitor& mon = testBackend->GetMonitor();
+ MonitorAutoLock lock(mon);
+ EXPECT_CALL(*testBackend, SetAppID(LPCWSTREq(passedID))).WillOnce([&mon] {
+ MonitorAutoLock lock(mon);
+ mon.Notify();
+ return S_OK;
+ });
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ // This is the amount of time that we will wait for the background thread to
+ // respond before considering it a timeout failure.
+ const int kWaitTimeoutMs = 5000;
+
+ ASSERT_TRUE(mon.Wait(TimeDuration::FromMilliseconds(kWaitTimeoutMs)) !=
+ CVStatus::Timeout);
+}
+
+/**
+ * Tests calling CheckForRemovals and receiving a series of removed jump list
+ * entries. Calling CheckForRemovals should call the following methods on the
+ * backend, in order:
+ *
+ * - SetAppID
+ * - AbortList
+ * - BeginList
+ * - AbortList
+ */
+TEST(JumpListBuilder, CheckForRemovals)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(2);
+
+ // Let's prepare BeginList to return a two entry collection of IShellLinks.
+ // The first IShellLink will have the URL be https://example.com, the second
+ // will have the URL be https://mozilla.org.
+ EXPECT_CALL(*testBackend, BeginList)
+ .WillOnce([](UINT* pcMinSlots, REFIID riid, void** ppv) {
+ RefPtr<IObjectCollection> collection;
+ DebugOnly<HRESULT> hr = CoCreateInstance(
+ CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IObjectCollection, getter_AddRefs(collection));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ RefPtr<IShellLinkW> link;
+ hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW, getter_AddRefs(link));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ nsAutoString firstLinkHref(u"https://example.com"_ns);
+ link->SetArguments(firstLinkHref.get());
+
+ nsAutoString appPath(u"C:\\Tmp\\firefox.exe"_ns);
+ link->SetIconLocation(appPath.get(), 0);
+
+ collection->AddObject(link);
+
+ // Let's re-use the same IShellLink, but change the URL to add our
+ // second entry. The values of the IShellLink are ultimately copied
+ // over to the items being added to the collection.
+ nsAutoString secondLinkHref(u"https://mozilla.org"_ns);
+ link->SetArguments(secondLinkHref.get());
+ collection->AddObject(link);
+
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray,
+ getter_AddRefs(pArray));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ *ppv = static_cast<IObjectArray*>(pArray);
+ (static_cast<IUnknown*>(*ppv))->AddRef();
+
+ // This is the default value to return, according to
+ // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-beginlist
+ *pcMinSlots = 10;
+
+ return S_OK;
+ });
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+ nsresult rv = builder->CheckForRemovals(cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolvedWithResult(&result);
+
+ ASSERT_TRUE(result.isObject());
+ JS::Rooted<JSObject*> obj(cx, result.toObjectOrNull());
+
+ bool isArray;
+ ASSERT_TRUE(JS::IsArrayObject(cx, obj, &isArray));
+ ASSERT_TRUE(isArray);
+
+ // We should expect to see 2 URL strings returned in the array.
+ uint32_t length = 0;
+ ASSERT_TRUE(JS::GetArrayLength(cx, obj, &length));
+ ASSERT_EQ(length, 2U);
+
+ // The first one should be https://example.com
+ JS::Rooted<JS::Value> firstURLValue(cx);
+ ASSERT_TRUE(JS_GetElement(cx, obj, 0, &firstURLValue));
+ JS::Rooted<JSString*> firstURLJSString(cx, firstURLValue.toString());
+ nsAutoJSString firstURLAutoString;
+ ASSERT_TRUE(firstURLAutoString.init(cx, firstURLJSString));
+
+ ASSERT_TRUE(firstURLAutoString.EqualsLiteral("https://example.com"));
+
+ // The second one should be https://mozilla.org
+ JS::Rooted<JS::Value> secondURLValue(cx);
+ ASSERT_TRUE(JS_GetElement(cx, obj, 1, &secondURLValue));
+ JS::Rooted<JSString*> secondURLJSString(cx, secondURLValue.toString());
+ nsAutoJSString secondURLAutoString;
+ ASSERT_TRUE(secondURLAutoString.init(cx, secondURLJSString));
+
+ ASSERT_TRUE(secondURLAutoString.EqualsLiteral("https://mozilla.org"));
+}
+
+/**
+ * Tests calling PopulateJumpList with empty arguments, which should call the
+ * following methods on the backend, in order:
+ *
+ * - SetAppID
+ * - AbortList
+ * - BeginList
+ * - CommitList
+ *
+ * This should result in an empty jump list for the user.
+ */
+TEST(JumpListBuilder, PopulateJumpListEmpty)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ nsTArray<JS::Value> taskDescJSVals;
+ nsAutoString customTitle(u"");
+ nsTArray<JS::Value> customDescJSVals;
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(1);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
+ EXPECT_CALL(*testBackend, CommitList()).Times(1);
+ EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);
+
+ nsresult rv =
+ builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals,
+ cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
+
+/**
+ * Tests calling PopulateJumpList with only tasks, and no custom items.
+ * This should call the following methods on the backend, in order:
+ *
+ * - SetAppID
+ * - AbortList
+ * - BeginList
+ * - AddUserTasks
+ * - CommitList
+ *
+ * This should result in a jump list with just tasks shown to the user, and
+ * no custom section.
+ */
+TEST(JumpListBuilder, PopulateJumpListOnlyTasks)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ nsTArray<JS::Value> taskDescJSVals;
+ nsTArray<WindowsJumpListShortcutDescription> taskDescs;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs,
+ taskDescJSVals);
+
+ nsAutoString customTitle(u"");
+ nsTArray<JS::Value> customDescJSVals;
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(1);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
+ EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);
+
+ EXPECT_CALL(*testBackend, AppendCategory(_, _)).Times(0);
+ EXPECT_CALL(*testBackend, CommitList()).Times(1);
+ EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);
+
+ nsresult rv =
+ builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals,
+ cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
+
+/**
+ * Tests calling PopulateJumpList with only custom items, and no tasks.
+ * This should call the following methods on the backend, in order:
+ *
+ * - SetAppID
+ * - AbortList
+ * - BeginList
+ * - AppendCategory
+ * - CommitList
+ *
+ * This should result in a jump list with just custom items shown to the user,
+ * and no tasks.
+ */
+TEST(JumpListBuilder, PopulateJumpListOnlyCustomItems)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ nsTArray<WindowsJumpListShortcutDescription> descs;
+ nsTArray<JS::Value> customDescJSVals;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, descs,
+ customDescJSVals);
+
+ nsAutoString customTitle(u"My custom title");
+ nsTArray<JS::Value> taskDescJSVals;
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(1);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
+ EXPECT_CALL(*testBackend, AddUserTasks(_)).Times(0);
+
+ EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
+ ShellLinksEq(&descs)))
+ .Times(1);
+ EXPECT_CALL(*testBackend, CommitList()).Times(1);
+ EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);
+
+ nsresult rv =
+ builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals,
+ cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
+
+/**
+ * Tests calling PopulateJumpList with tasks and custom items.
+ * This should call the following methods on the backend, in order:
+ *
+ * - SetAppID
+ * - AbortList
+ * - BeginList
+ * - AddUserTasks
+ * - AppendCategory
+ * - CommitList
+ *
+ * This should result in a jump list with both built-in tasks as well as
+ * custom tasks with a custom label.
+ */
+TEST(JumpListBuilder, PopulateJumpList)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ nsTArray<WindowsJumpListShortcutDescription> taskDescs;
+ nsTArray<JS::Value> taskDescJSVals;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs,
+ taskDescJSVals);
+
+ nsTArray<WindowsJumpListShortcutDescription> customDescs;
+ nsTArray<JS::Value> customDescJSVals;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, customDescs,
+ customDescJSVals);
+
+ nsAutoString customTitle(u"My custom title");
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(1);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
+ EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);
+
+ EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
+ ShellLinksEq(&customDescs)))
+ .Times(1);
+ EXPECT_CALL(*testBackend, CommitList()).Times(1);
+ EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);
+
+ nsresult rv =
+ builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals,
+ cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
+
+/**
+ * Tests calling ClearJumpList calls the following:
+ *
+ * - SetAppID
+ * - DeleteList (passing the aumid)
+ *
+ * This results in an empty jump list for the user.
+ */
+TEST(JumpListBuilder, ClearJumpList)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(0);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(0);
+ EXPECT_CALL(*testBackend, AddUserTasks(_)).Times(0);
+
+ EXPECT_CALL(*testBackend, AppendCategory(_, _)).Times(0);
+ EXPECT_CALL(*testBackend, CommitList()).Times(0);
+ EXPECT_CALL(*testBackend, DeleteList(LPCWSTREq(aumid.get()))).Times(1);
+
+ nsresult rv = builder->ClearJumpList(cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
+
+/**
+ * Test that a WindowsJumpListShortcutDescription with a description
+ * longer than MAX_PATH gets truncated to MAX_PATH. This is because a
+ * description longer than MAX_PATH will cause CommitList to fail.
+ */
+TEST(JumpListBuilder, TruncateDescription)
+{
+ RefPtr<StrictMock<TestingJumpListBackend>> testBackend =
+ new StrictMock<TestingJumpListBackend>();
+ nsAutoString aumid(u"TestApplicationID");
+ // We set up this expectation here because SetAppID will be called soon
+ // after construction of the JumpListBuilder via the background thread.
+ EXPECT_CALL(*testBackend, SetAppID(_)).Times(1);
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ new JumpListBuilder(aumid, testBackend);
+ ASSERT_TRUE(builder);
+
+ AutoJSAPI jsapi;
+ MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
+ JSContext* cx = jsapi.cx();
+ RefPtr<Promise> promise;
+
+ nsTArray<WindowsJumpListShortcutDescription> taskDescs;
+ nsTArray<JS::Value> taskDescJSVals;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, taskDescs,
+ taskDescJSVals);
+
+ nsTArray<WindowsJumpListShortcutDescription> customDescs;
+ nsTArray<JS::Value> customDescJSVals;
+ GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, customDescs,
+ customDescJSVals);
+ // We expect the long descriptions to be truncated to 260 characters, so
+ // we'll truncate the descriptions here ourselves.
+ for (auto& taskDesc : taskDescs) {
+ taskDesc.mDescription.SetLength(MAX_PATH - 1);
+ }
+ for (auto& customDesc : customDescs) {
+ customDesc.mDescription.SetLength(MAX_PATH - 1);
+ }
+
+ nsAutoString customTitle(u"My custom title");
+
+ EXPECT_CALL(*testBackend, AbortList()).Times(1);
+ EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1);
+ EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1);
+
+ EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()),
+ ShellLinksEq(&customDescs)))
+ .Times(1);
+ EXPECT_CALL(*testBackend, CommitList()).Times(1);
+ EXPECT_CALL(*testBackend, DeleteList(_)).Times(0);
+
+ nsresult rv =
+ builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals,
+ cx, getter_AddRefs(promise));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(promise);
+
+ RefPtr<WaitForResolver> resolver = new WaitForResolver();
+ promise->AppendNativeHandler(resolver);
+ JS::Rooted<JS::Value> result(cx);
+ resolver->SpinUntilResolved();
+}
diff --git a/widget/windows/tests/gtest/TestWinDND.cpp b/widget/windows/tests/gtest/TestWinDND.cpp
new file mode 100644
index 0000000000..fb7849fd79
--- /dev/null
+++ b/widget/windows/tests/gtest/TestWinDND.cpp
@@ -0,0 +1,728 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+// #include <windows.h>
+#include <ole2.h>
+#include <shlobj.h>
+
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
+
+#include "nsClipboard.h"
+#include "nsDataObjCollection.h"
+
+#include "gtest/gtest.h"
+
+// shims for conversion from cppunittest to gtest
+template <size_t N>
+void fail(const char (&msg)[N]) {
+ ADD_FAILURE() << "TEST-UNEXPECTED-FAIL | " << msg;
+}
+template <size_t N>
+void passed(const char (&msg)[N]) {
+ GTEST_SUCCEED() << "TEST-PASS | " << msg;
+}
+
+nsIFile* xferFile;
+
+nsresult CheckValidHDROP(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ DROPFILES* pDropFiles;
+ pDropFiles = (DROPFILES*)GlobalLock(hGlobal);
+ if (!pDropFiles) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (pDropFiles->pFiles != sizeof(DROPFILES)) {
+ fail("DROPFILES struct has wrong size");
+ }
+
+ if (!pDropFiles->fWide) {
+ fail("Received data is not Unicode");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsString s;
+ unsigned long offset = 0;
+ while (true) {
+ s = (char16_t*)((char*)pDropFiles + pDropFiles->pFiles + offset);
+ if (s.IsEmpty()) break;
+ nsresult rv;
+ nsCOMPtr<nsIFile> localFile(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ rv = localFile->InitWithPath(s);
+ if (NS_FAILED(rv)) {
+ fail("File could not be opened");
+ return NS_ERROR_UNEXPECTED;
+ }
+ offset += sizeof(char16_t) * (s.Length() + 1);
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidTEXT(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char* pText;
+ pText = (char*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidTEXTTwo(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char* pText;
+ pText = (char*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidUNICODE(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char16_t* pText;
+ pText = (char16_t*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult CheckValidUNICODETwo(STGMEDIUM* pSTG) {
+ if (pSTG->tymed != TYMED_HGLOBAL) {
+ fail("Received data is not in an HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ HGLOBAL hGlobal = pSTG->hGlobal;
+ char16_t* pText;
+ pText = (char16_t*)GlobalLock(hGlobal);
+ if (!pText) {
+ fail("There is no data at the given HGLOBAL");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString string;
+ string = pText;
+
+ if (!string.EqualsLiteral("Mozilla can drag and drop twice over")) {
+ fail("Text passed through drop object wrong");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult GetTransferableFile(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferFile);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("application/x-moz-file", genericWrapper);
+ return rv;
+}
+
+nsresult GetTransferableText(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+ constexpr auto mozString = u"Mozilla can drag and drop"_ns;
+ nsCOMPtr<nsISupportsString> xferString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ rv = xferString->SetData(mozString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/plain", genericWrapper);
+ return rv;
+}
+
+nsresult GetTransferableTextTwo(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+ constexpr auto mozString = u" twice over"_ns;
+ nsCOMPtr<nsISupportsString> xferString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ rv = xferString->SetData(mozString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/plain", genericWrapper);
+ return rv;
+}
+
+nsresult GetTransferableURI(nsCOMPtr<nsITransferable>& pTransferable) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> xferURI;
+
+ rv = NS_NewURI(getter_AddRefs(xferURI), "http://www.mozilla.org");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferURI);
+
+ pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1");
+ pTransferable->Init(nullptr);
+ rv = pTransferable->SetTransferData("text/x-moz-url", genericWrapper);
+ return rv;
+}
+
+nsresult MakeDataObject(nsIArray* transferableArray,
+ RefPtr<IDataObject>& itemToDrag) {
+ nsresult rv;
+ uint32_t itemCount = 0;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), "http://www.mozilla.org");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transferableArray->GetLength(&itemCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copied more or less exactly from nsDragService::InvokeDragSession
+ // This is what lets us play fake Drag Service for the test
+ if (itemCount > 1) {
+ nsDataObjCollection* dataObjCollection = new nsDataObjCollection();
+ if (!dataObjCollection) return NS_ERROR_OUT_OF_MEMORY;
+ itemToDrag = dataObjCollection;
+ for (uint32_t i = 0; i < itemCount; ++i) {
+ nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, i);
+ if (trans) {
+ RefPtr<IDataObject> dataObj;
+ rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj),
+ uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Add the flavors to the collection object too
+ rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dataObjCollection->AddDataObject(dataObj);
+ }
+ }
+ } // if dragging multiple items
+ else {
+ nsCOMPtr<nsITransferable> trans = do_QueryElementAt(transferableArray, 0);
+ if (trans) {
+ rv = nsClipboard::CreateNativeDataObject(trans,
+ getter_AddRefs(itemToDrag), uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } // else dragging a single object
+ return rv;
+}
+
+nsresult Do_CheckOneFile() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("File data object does not support the file data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("File data object did not provide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidHDROP(stg);
+ if (NS_FAILED(rv)) {
+ fail("HDROP was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ return NS_OK;
+}
+
+nsresult Do_CheckTwoFiles() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = GetTransferableFile(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("File data object does not support the file data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("File data object did not provide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidHDROP(stg);
+ if (NS_FAILED(rv)) {
+ fail("HDROP was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ return NS_OK;
+}
+
+nsresult Do_CheckOneString() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the ASCII text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide ASCII data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidTEXT(stg);
+ if (NS_FAILED(rv)) {
+ fail("TEXT was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the wide text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide wide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidUNICODE(stg);
+ if (NS_FAILED(rv)) {
+ fail("UNICODE was invalid");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Do_CheckTwoStrings() {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = GetTransferableTextTwo(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the ASCII text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg;
+ stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide ASCII data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidTEXTTwo(stg);
+ if (NS_FAILED(rv)) {
+ fail("TEXT was invalid");
+ return rv;
+ }
+
+ ReleaseStgMedium(stg);
+
+ SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("String data object does not support the wide text data type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->GetData(&fe, stg) != S_OK) {
+ fail("String data object did not provide wide data on request");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = CheckValidUNICODETwo(stg);
+ if (NS_FAILED(rv)) {
+ fail("UNICODE was invalid");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult Do_CheckSetArbitraryData(bool aMultiple) {
+ nsresult rv;
+ nsCOMPtr<nsITransferable> transferable;
+ nsCOMPtr<nsIMutableArray> transferableArray = nsArray::Create();
+ nsCOMPtr<nsISupports> genericWrapper;
+ RefPtr<IDataObject> dataObj;
+
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+
+ if (aMultiple) {
+ rv = GetTransferableText(transferable);
+ if (NS_FAILED(rv)) {
+ fail("Could not create the proper nsITransferable!");
+ return rv;
+ }
+ genericWrapper = do_QueryInterface(transferable);
+ rv = transferableArray->AppendElement(genericWrapper);
+ if (NS_FAILED(rv)) {
+ fail("Could not append element to transferable array");
+ return rv;
+ }
+ }
+
+ rv = MakeDataObject(transferableArray, dataObj);
+ if (NS_FAILED(rv)) {
+ fail("Could not create data object");
+ return rv;
+ }
+
+ static CLIPFORMAT mozArbitraryFormat =
+ ::RegisterClipboardFormatW(L"MozillaTestFormat");
+ FORMATETC fe;
+ STGMEDIUM stg;
+ SET_FORMATETC(fe, mozArbitraryFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+
+ HGLOBAL hg = GlobalAlloc(GPTR, 1024);
+ stg.tymed = TYMED_HGLOBAL;
+ stg.hGlobal = hg;
+ stg.pUnkForRelease = nullptr;
+
+ if (dataObj->SetData(&fe, &stg, true) != S_OK) {
+ if (aMultiple) {
+ fail("Unable to set arbitrary data type on data object collection!");
+ } else {
+ fail("Unable to set arbitrary data type on data object!");
+ }
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (dataObj->QueryGetData(&fe) != S_OK) {
+ fail("Arbitrary data set on data object is not advertised!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ STGMEDIUM* stg2;
+ stg2 = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM));
+ if (dataObj->GetData(&fe, stg2) != S_OK) {
+ fail("Data object did not provide arbitrary data upon request!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (stg2->hGlobal != hg) {
+ fail("Arbitrary data was not returned properly!");
+ return rv;
+ }
+ ReleaseStgMedium(stg2);
+
+ return NS_OK;
+}
+
+// This function performs basic drop tests, testing a data object consisting
+// of one transferable
+nsresult Do_Test1() {
+ nsresult rv = NS_OK;
+ nsresult workingrv;
+
+ workingrv = Do_CheckOneFile();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on a single file");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working file drag object!");
+ }
+
+ workingrv = Do_CheckOneString();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on a single string");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working string drag object!");
+ }
+
+ workingrv = Do_CheckSetArbitraryData(false);
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on setting arbitrary data");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully set arbitrary data on a drag object");
+ }
+
+ return rv;
+}
+
+// This function performs basic drop tests, testing a data object consisting of
+// two transferables.
+nsresult Do_Test2() {
+ nsresult rv = NS_OK;
+ nsresult workingrv;
+
+ workingrv = Do_CheckTwoFiles();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on multiple files");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working multiple file drag object!");
+ }
+
+ workingrv = Do_CheckTwoStrings();
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on multiple strings");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully created a working multiple string drag object!");
+ }
+
+ workingrv = Do_CheckSetArbitraryData(true);
+ if (NS_FAILED(workingrv)) {
+ fail("Drag object tests failed on setting arbitrary data");
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ passed("Successfully set arbitrary data on a drag object");
+ }
+
+ return rv;
+}
+
+// This function performs advanced drag and drop tests, testing a data object
+// consisting of multiple transferables that have different data types
+nsresult Do_Test3() {
+ nsresult rv = NS_OK;
+ // nsresult workingrv;
+
+ // XXX TODO Write more advanced tests in Bug 535860
+ return rv;
+}
+
+nsCOMPtr<nsIFile> GetTemporaryDirectory() {
+ nsCOMPtr<nsIFile> tmpdir;
+
+#define ENSURE(expr) NS_ENSURE_SUCCESS(expr, nullptr);
+
+ ENSURE(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpdir)));
+ MOZ_ASSERT(tmpdir);
+
+ ENSURE(tmpdir->AppendNative("TestWinDND"_ns));
+ ENSURE(tmpdir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0777));
+
+#undef ENSURE
+
+ return tmpdir;
+}
+
+TEST(TestWinDND, All)
+{
+ nsCOMPtr<nsIFile> file = GetTemporaryDirectory();
+ if (!file) {
+ fail("could not create temporary directory!");
+ return;
+ }
+ xferFile = file;
+
+ if (NS_SUCCEEDED(Do_Test1())) {
+ passed(
+ "Basic Drag and Drop data type tests (single transferable) succeeded!");
+ }
+
+ if (NS_SUCCEEDED(Do_Test2())) {
+ passed(
+ "Basic Drag and Drop data type tests (multiple transferables) "
+ "succeeded!");
+ }
+
+ // if (NS_SUCCEEDED(Do_Test3()))
+ // passed("Advanced Drag and Drop data type tests succeeded!");
+}
diff --git a/widget/windows/tests/gtest/moz.build b/widget/windows/tests/gtest/moz.build
new file mode 100644
index 0000000000..4057b7ae57
--- /dev/null
+++ b/widget/windows/tests/gtest/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "TestJumpListBuilder.cpp",
+ "TestWinDND.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/widget",
+ "/widget/windows",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/widget/windows/tests/moz.build b/widget/windows/tests/moz.build
new file mode 100644
index 0000000000..2c7d200571
--- /dev/null
+++ b/widget/windows/tests/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+GeckoCppUnitTests(
+ [
+ "TestUriValidation",
+ ],
+ linkage=None,
+)
+
+DIRS = ["gtest"]
+
+LOCAL_INCLUDES += []
+
+OS_LIBS += [
+ "oleaut32",
+ "ole32",
+ "shell32",
+ "shlwapi",
+ "urlmon",
+ "uuid",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"]
diff --git a/widget/windows/tests/unit/test_windows_alert_service.js b/widget/windows/tests/unit/test_windows_alert_service.js
new file mode 100644
index 0000000000..0ba0d2a4d4
--- /dev/null
+++ b/widget/windows/tests/unit/test_windows_alert_service.js
@@ -0,0 +1,667 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test that Windows alert notifications generate expected XML.
+ */
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+let gProfD = do_get_profile();
+
+// Setup that allows to use the profile service in xpcshell tests,
+// lifted from `toolkit/profile/xpcshell/head.js`.
+function setupProfileService() {
+ let gDataHome = gProfD.clone();
+ gDataHome.append("data");
+ gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ let gDataHomeLocal = gProfD.clone();
+ gDataHomeLocal.append("local");
+ gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
+ Ci.nsIXREDirProvider
+ );
+ xreDirProvider.setUserDataDirectory(gDataHome, false);
+ xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
+}
+
+add_setup(setupProfileService);
+
+function makeAlert(options) {
+ var alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
+ Ci.nsIAlertNotification
+ );
+ alert.init(
+ options.name,
+ options.imageURL,
+ options.title,
+ options.text,
+ options.textClickable,
+ options.cookie,
+ options.dir,
+ options.lang,
+ options.data,
+ options.principal,
+ options.inPrivateBrowsing,
+ options.requireInteraction,
+ options.silent,
+ options.vibrate || []
+ );
+ if (options.actions) {
+ alert.actions = options.actions;
+ }
+ if (options.opaqueRelaunchData) {
+ alert.opaqueRelaunchData = options.opaqueRelaunchData;
+ }
+ return alert;
+}
+
+/**
+ * Take a `key1\nvalue1\n...` string encoding as used by the Windows native
+ * notification server DLL, and split it into an object, keeping `action\n...`
+ * intact.
+ *
+ * @param {string} t string encoding.
+ * @returns {object} an object with keys and values.
+ */
+function parseOneEncoded(t) {
+ var launch = {};
+
+ var lines = t.split("\n");
+ while (lines.length) {
+ var key = lines.shift();
+ var value;
+ if (key === "action") {
+ value = lines.join("\n");
+ lines = [];
+ } else {
+ value = lines.shift();
+ }
+ launch[key] = value;
+ }
+
+ return launch;
+}
+
+/**
+ * This complicated-looking function takes a (XML) string representation of a
+ * Windows alert (toast notification), parses it into XML, extracts and further
+ * parses internal data, and returns a simplified XML representation together
+ * with the parsed internals.
+ *
+ * Doing this lets us compare JSON objects rather than stringified-JSON further
+ * encoded as XML strings, which have lots of slashes and `&quot;` characters to
+ * contend with.
+ *
+ * @param {string} s XML string for Windows alert.
+
+ * @returns {Array} a pair of a simplified XML string and an object with
+ * `launch` and `actions` keys.
+ */
+function parseLaunchAndActions(s) {
+ var document = new DOMParser().parseFromString(s, "text/xml");
+ var root = document.documentElement;
+
+ var launchString = root.getAttribute("launch");
+ root.setAttribute("launch", "launch");
+ var launch = parseOneEncoded(launchString);
+
+ // `actions` is keyed by "content" attribute.
+ let actions = {};
+ for (var actionElement of root.querySelectorAll("action")) {
+ // `activationType="system"` is special. Leave them alone.
+ let systemActivationType =
+ actionElement.getAttribute("activationType") === "system";
+
+ let action = {};
+ let names = [...actionElement.attributes].map(attribute => attribute.name);
+
+ for (var name of names) {
+ let value = actionElement.getAttribute(name);
+
+ // Here is where we parse stringified-JSON to simplify comparisons.
+ if (value.startsWith("{")) {
+ value = JSON.parse(value);
+ if ("opaqueRelaunchData" in value) {
+ value.opaqueRelaunchData = JSON.parse(value.opaqueRelaunchData);
+ }
+ }
+
+ if (name == "arguments" && !systemActivationType) {
+ action[name] = parseOneEncoded(value);
+ } else {
+ action[name] = value;
+ }
+
+ if (name != "content" && !systemActivationType) {
+ actionElement.removeAttribute(name);
+ }
+ }
+
+ let actionName = actionElement.getAttribute("content");
+ actions[actionName] = action;
+ }
+
+ return [new XMLSerializer().serializeToString(document), { launch, actions }];
+}
+
+function escape(s) {
+ return s
+ .replace(/&/g, "&amp;")
+ .replace(/"/g, "&quot;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/\n/g, "&#xA;");
+}
+
+function unescape(s) {
+ return s
+ .replace(/&amp;/g, "&")
+ .replace(/&quot;/g, '"')
+ .replace(/&lt;/g, "<")
+ .replace(/&gt;/g, ">")
+ .replace(/&#xA;/g, "\n");
+}
+
+function testAlert(when, { serverEnabled, profD, isBackgroundTaskMode } = {}) {
+ let argumentString = action => {
+ // &#xA; is "\n".
+ let s = ``;
+ if (serverEnabled) {
+ s += `program&#xA;${AppConstants.MOZ_APP_NAME}`;
+ } else {
+ s += `invalid key&#xA;invalid value`;
+ }
+ if (serverEnabled && profD) {
+ s += `&#xA;profile&#xA;${profD.path}`;
+ }
+ if (serverEnabled) {
+ s += "&#xA;windowsTag&#xA;";
+ }
+ if (action) {
+ s += `&#xA;action&#xA;${escape(JSON.stringify(action))}`;
+ }
+
+ return s;
+ };
+
+ let parsedArgumentString = action =>
+ parseOneEncoded(unescape(argumentString(action)));
+
+ let settingsAction = isBackgroundTaskMode
+ ? ""
+ : `<action content="Notification settings"/>`;
+
+ let parsedSettingsAction = hostport => {
+ if (isBackgroundTaskMode) {
+ return [];
+ }
+ let content = "Notification settings";
+ return [
+ content,
+ {
+ content,
+ arguments: parsedArgumentString(
+ Object.assign(
+ {
+ action: "settings",
+ },
+ hostport && {
+ launchUrl: hostport,
+ }
+ )
+ ),
+ placement: "contextmenu",
+ },
+ ];
+ };
+
+ let parsedSnoozeAction = hostport => {
+ let content = `Disable notifications from ${hostport}`;
+ return [
+ content,
+ {
+ content,
+ arguments: parsedArgumentString(
+ Object.assign(
+ {
+ action: "snooze",
+ },
+ hostport && {
+ launchUrl: hostport,
+ }
+ )
+ ),
+ placement: "contextmenu",
+ },
+ ];
+ };
+
+ let alertsService = Cc["@mozilla.org/system-alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIWindowsAlertsService);
+
+ let name = "name";
+ let title = "title";
+ let text = "text";
+ let imageURL = "file:///image.png";
+ let actions = [
+ { action: "action1", title: "title1", iconURL: "file:///iconURL1.png" },
+ { action: "action2", title: "title2", iconURL: "file:///iconURL2.png" },
+ ];
+ let opaqueRelaunchData = { foo: 1, bar: "two" };
+
+ let alert = makeAlert({ name, title, text });
+ let expected = `<toast launch="launch"><visual><binding template="ToastText03"><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "" }),
+ actions: Object.fromEntries(
+ [parsedSettingsAction()].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ alert = makeAlert({ name, title, text, imageURL });
+ expected = `<toast launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "" }),
+ actions: Object.fromEntries(
+ [parsedSettingsAction()].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ alert = makeAlert({ name, title, text, imageURL, requireInteraction: true });
+ expected = `<toast scenario="reminder" launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="Dismiss" arguments="dismiss" activationType="system"/></actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "" }),
+ actions: Object.fromEntries(
+ [
+ parsedSettingsAction(),
+ [
+ "Dismiss",
+ {
+ content: "Dismiss",
+ arguments: "dismiss",
+ activationType: "system",
+ },
+ ],
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ alert = makeAlert({ name, title, text, imageURL, actions });
+ expected = `<toast launch="launch"><visual><binding template="ToastImageAndText03"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="title1"/><action content="title2"/></actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "" }),
+ actions: Object.fromEntries(
+ [
+ parsedSettingsAction(),
+ [
+ "title1",
+ {
+ content: "title1",
+ arguments: parsedArgumentString({ action: "action1" }),
+ },
+ ],
+ [
+ "title2",
+ {
+ content: "title2",
+ arguments: parsedArgumentString({ action: "action2" }),
+ },
+ ],
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ // Chrome privileged alerts can use `windowsSystemActivationType`.
+ let systemActions = [
+ {
+ action: "dismiss",
+ title: "dismissTitle",
+ windowsSystemActivationType: true,
+ },
+ {
+ action: "snooze",
+ title: "snoozeTitle",
+ windowsSystemActivationType: true,
+ },
+ ];
+ let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ alert = makeAlert({
+ name,
+ title,
+ text,
+ imageURL,
+ principal: systemPrincipal,
+ actions: systemActions,
+ });
+ let parsedSettingsActionWithPrivilegedName = isBackgroundTaskMode
+ ? []
+ : [
+ "Notification settings",
+ {
+ content: "Notification settings",
+ arguments: parsedArgumentString({
+ action: "settings",
+ privilegedName: name,
+ }),
+ placement: "contextmenu",
+ },
+ ];
+
+ expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="dismissTitle" arguments="dismiss" activationType="system"/><action content="snoozeTitle" arguments="snooze" activationType="system"/></actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "", privilegedName: name }),
+ actions: Object.fromEntries(
+ [
+ parsedSettingsActionWithPrivilegedName,
+ [
+ "dismissTitle",
+ {
+ content: "dismissTitle",
+ arguments: "dismiss",
+ activationType: "system",
+ },
+ ],
+ [
+ "snoozeTitle",
+ {
+ content: "snoozeTitle",
+ arguments: "snooze",
+ activationType: "system",
+ },
+ ],
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ // But content unprivileged alerts can't use `windowsSystemActivationType`.
+ let launchUrl = "https://example.com/foo/bar.html";
+ const principaluri = Services.io.newURI(launchUrl);
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ principaluri,
+ {}
+ );
+
+ alert = makeAlert({
+ name,
+ title,
+ text,
+ imageURL,
+ actions: systemActions,
+ principal,
+ });
+ expected = `<toast launch="launch"><visual><binding template="ToastImageAndText04"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text><text id="3" placement="attribution">via example.com</text></binding></visual><actions><action content="Disable notifications from example.com"/>${settingsAction}<action content="dismissTitle"/><action content="snoozeTitle"/></actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({
+ action: "",
+ launchUrl: principaluri.hostPort,
+ }),
+ actions: Object.fromEntries(
+ [
+ parsedSnoozeAction(principaluri.hostPort),
+ parsedSettingsAction(principaluri.hostPort),
+ [
+ "dismissTitle",
+ {
+ content: "dismissTitle",
+ arguments: parsedArgumentString({
+ action: "dismiss",
+ launchUrl: principaluri.hostPort,
+ }),
+ },
+ ],
+ [
+ "snoozeTitle",
+ {
+ content: "snoozeTitle",
+ arguments: parsedArgumentString({
+ action: "snooze",
+ launchUrl: principaluri.hostPort,
+ }),
+ },
+ ],
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ // Chrome privileged alerts can set `opaqueRelaunchData`.
+ alert = makeAlert({
+ name,
+ title,
+ text,
+ imageURL,
+ principal: systemPrincipal,
+ opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
+ });
+ expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}</actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({
+ action: "",
+ opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
+ privilegedName: name,
+ }),
+ actions: Object.fromEntries(
+ [parsedSettingsActionWithPrivilegedName].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ // But content unprivileged alerts can't set `opaqueRelaunchData`.
+ alert = makeAlert({
+ name,
+ title,
+ text,
+ imageURL,
+ principal,
+ opaqueRelaunchData: JSON.stringify(opaqueRelaunchData),
+ });
+ expected = `<toast launch="launch"><visual><binding template="ToastImageAndText04"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text><text id="3" placement="attribution">via example.com</text></binding></visual><actions><action content="Disable notifications from example.com"/>${settingsAction}</actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({
+ action: "",
+ launchUrl: principaluri.hostPort,
+ }),
+ actions: Object.fromEntries(
+ [
+ parsedSnoozeAction(principaluri.hostPort),
+ parsedSettingsAction(principaluri.hostPort),
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+
+ // Chrome privileged alerts can set action-specific relaunch parameters.
+ let systemRelaunchActions = [
+ {
+ action: "action1",
+ title: "title1",
+ opaqueRelaunchData: JSON.stringify({ json: "data1" }),
+ },
+ {
+ action: "action2",
+ title: "title2",
+ opaqueRelaunchData: JSON.stringify({ json: "data2" }),
+ },
+ ];
+ systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ alert = makeAlert({
+ name,
+ title,
+ text,
+ imageURL,
+ principal: systemPrincipal,
+ actions: systemRelaunchActions,
+ });
+ expected = `<toast launch="launch"><visual><binding template="ToastGeneric"><image id="1" src="file:///image.png"/><text id="1">title</text><text id="2">text</text></binding></visual><actions>${settingsAction}<action content="title1"/><action content="title2"/></actions></toast>`;
+ Assert.deepEqual(
+ [
+ expected.replace("<actions></actions>", "<actions/>"),
+ {
+ launch: parsedArgumentString({ action: "", privilegedName: name }),
+ actions: Object.fromEntries(
+ [
+ parsedSettingsActionWithPrivilegedName,
+ [
+ "title1",
+ {
+ content: "title1",
+ arguments: parsedArgumentString(
+ {
+ action: "action1",
+ opaqueRelaunchData: JSON.stringify({ json: "data1" }),
+ privilegedName: name,
+ },
+ null,
+ name
+ ),
+ },
+ ],
+
+ [
+ "title2",
+ {
+ content: "title2",
+ arguments: parsedArgumentString(
+ {
+ action: "action2",
+ opaqueRelaunchData: JSON.stringify({ json: "data2" }),
+ privilegedName: name,
+ },
+ null,
+ name
+ ),
+ },
+ ],
+ ].filter(x => x.length)
+ ),
+ },
+ ],
+ parseLaunchAndActions(alertsService.getXmlStringForWindowsAlert(alert)),
+ when
+ );
+}
+
+add_task(async () => {
+ Services.prefs.deleteBranch(
+ "alerts.useSystemBackend.windows.notificationserver.enabled"
+ );
+ testAlert("when notification server pref is unset", {
+ profD: gProfD,
+ });
+
+ Services.prefs.setBoolPref(
+ "alerts.useSystemBackend.windows.notificationserver.enabled",
+ false
+ );
+ testAlert("when notification server pref is false", { profD: gProfD });
+
+ Services.prefs.setBoolPref(
+ "alerts.useSystemBackend.windows.notificationserver.enabled",
+ true
+ );
+ testAlert("when notification server pref is true", {
+ serverEnabled: true,
+ profD: gProfD,
+ });
+});
+
+let condition = {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+};
+
+add_task(condition, async () => {
+ const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
+ Ci.nsIBackgroundTasks
+ );
+
+ // Pretend that this is a background task.
+ bts.overrideBackgroundTaskNameForTesting("taskname");
+
+ Services.prefs.setBoolPref(
+ "alerts.useSystemBackend.windows.notificationserver.enabled",
+ true
+ );
+ testAlert(
+ "when notification server pref is true in background task, no default profile",
+ { serverEnabled: true, isBackgroundTaskMode: true }
+ );
+
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+
+ let profilePath = do_get_profile();
+ profilePath.append(`test_windows_alert_service`);
+ let profile = profileService.createUniqueProfile(
+ profilePath,
+ "test_windows_alert_service"
+ );
+
+ profileService.defaultProfile = profile;
+
+ testAlert(
+ "when notification server pref is true in background task, default profile",
+ { serverEnabled: true, isBackgroundTaskMode: true, profD: profilePath }
+ );
+
+ // No longer a background task,
+ bts.overrideBackgroundTaskNameForTesting("");
+});
diff --git a/widget/windows/tests/unit/xpcshell.toml b/widget/windows/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..9943d5510e
--- /dev/null
+++ b/widget/windows/tests/unit/xpcshell.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["test_windows_alert_service.js"]
diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h
new file mode 100644
index 0000000000..7e9f5410d2
--- /dev/null
+++ b/widget/windows/touchinjection_sdk80.h
@@ -0,0 +1,171 @@
+/* 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 touchinjection_sdk80_h
+#define touchinjection_sdk80_h
+
+#include <windows.h>
+
+// Note, this isn't inclusive of all touch injection header info.
+// You may need to add more to expand on current apis.
+
+#ifndef TOUCH_FEEDBACK_DEFAULT
+
+# define TOUCH_FEEDBACK_DEFAULT 0x1
+# define TOUCH_FEEDBACK_INDIRECT 0x2
+# define TOUCH_FEEDBACK_NONE 0x3
+
+enum POINTER_FEEDBACK_MODE {
+ POINTER_FEEDBACK_DEFAULT =
+ 1, // The injected pointer input feedback may get suppressed by the
+ // end-user settings in the Pen and Touch control panel.
+ POINTER_FEEDBACK_INDIRECT =
+ 2, // The injected pointer input feedback overrides the end-user settings
+ // in the Pen and Touch control panel.
+ POINTER_FEEDBACK_NONE = 3, // No touch visualizations.
+};
+
+enum {
+ PT_POINTER = 0x00000001, // Generic pointer
+ PT_TOUCH = 0x00000002, // Touch
+ PT_PEN = 0x00000003, // Pen
+ PT_MOUSE = 0x00000004, // Mouse
+ PT_TOUCHPAD = 0x00000005, // Touch pad
+};
+
+using POINTER_INPUT_TYPE = DWORD;
+using POINTER_FLAGS = UINT32;
+
+enum POINTER_BUTTON_CHANGE_TYPE {
+ POINTER_CHANGE_NONE,
+ POINTER_CHANGE_FIRSTBUTTON_DOWN,
+ POINTER_CHANGE_FIRSTBUTTON_UP,
+ POINTER_CHANGE_SECONDBUTTON_DOWN,
+ POINTER_CHANGE_SECONDBUTTON_UP,
+ POINTER_CHANGE_THIRDBUTTON_DOWN,
+ POINTER_CHANGE_THIRDBUTTON_UP,
+ POINTER_CHANGE_FOURTHBUTTON_DOWN,
+ POINTER_CHANGE_FOURTHBUTTON_UP,
+ POINTER_CHANGE_FIFTHBUTTON_DOWN,
+ POINTER_CHANGE_FIFTHBUTTON_UP,
+};
+
+struct POINTER_INFO {
+ POINTER_INPUT_TYPE pointerType;
+ UINT32 pointerId;
+ UINT32 frameId;
+ POINTER_FLAGS pointerFlags;
+ HANDLE sourceDevice;
+ HWND hwndTarget;
+ POINT ptPixelLocation;
+ POINT ptHimetricLocation;
+ POINT ptPixelLocationRaw;
+ POINT ptHimetricLocationRaw;
+ DWORD dwTime;
+ UINT32 historyCount;
+ INT32 InputData;
+ DWORD dwKeyStates;
+ UINT64 PerformanceCount;
+ POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
+};
+
+using TOUCH_FLAGS = UINT32;
+using TOUCH_MASK = UINT32;
+
+struct POINTER_TOUCH_INFO {
+ POINTER_INFO pointerInfo;
+ TOUCH_FLAGS touchFlags;
+ TOUCH_MASK touchMask;
+ RECT rcContact;
+ RECT rcContactRaw;
+ UINT32 orientation;
+ UINT32 pressure;
+};
+
+# define PEN_FLAG_NONE 0x00000000 // Default
+# define PEN_FLAG_BARREL 0x00000001 // The barrel button is pressed
+# define PEN_FLAG_INVERTED 0x00000002 // The pen is inverted
+# define PEN_FLAG_ERASER 0x00000004 // The eraser button is pressed
+
+# define PEN_MASK_NONE \
+ 0x00000000 // Default - none of the optional fields are valid
+# define PEN_MASK_PRESSURE 0x00000001 // The pressure field is valid
+# define PEN_MASK_ROTATION 0x00000002 // The rotation field is valid
+# define PEN_MASK_TILT_X 0x00000004 // The tiltX field is valid
+# define PEN_MASK_TILT_Y 0x00000008 // The tiltY field is valid
+
+using PEN_FLAGS = UINT32;
+using PEN_MASK = UINT32;
+
+struct POINTER_PEN_INFO {
+ POINTER_INFO pointerInfo;
+ PEN_FLAGS penFlags;
+ PEN_MASK penMask;
+ UINT32 pressure;
+ UINT32 rotation;
+ INT32 tiltX;
+ INT32 tiltY;
+};
+
+struct POINTER_TYPE_INFO {
+ POINTER_INPUT_TYPE type;
+ union {
+ POINTER_TOUCH_INFO touchInfo;
+ POINTER_PEN_INFO penInfo;
+ };
+};
+
+# define TOUCH_FLAG_NONE 0x00000000 // Default
+
+# define TOUCH_MASK_NONE \
+ 0x00000000 // Default - none of the optional fields are valid
+# define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid
+# define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid
+# define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid
+
+# define POINTER_FLAG_NONE 0x00000000 // Default
+# define POINTER_FLAG_NEW 0x00000001 // New pointer
+# define POINTER_FLAG_INRANGE 0x00000002 // Pointer has not departed
+# define POINTER_FLAG_INCONTACT 0x00000004 // Pointer is in contact
+# define POINTER_FLAG_FIRSTBUTTON 0x00000010 // Primary action
+# define POINTER_FLAG_SECONDBUTTON 0x00000020 // Secondary action
+# define POINTER_FLAG_THIRDBUTTON 0x00000040 // Third button
+# define POINTER_FLAG_FOURTHBUTTON 0x00000080 // Fourth button
+# define POINTER_FLAG_FIFTHBUTTON 0x00000100 // Fifth button
+# define POINTER_FLAG_PRIMARY 0x00002000 // Pointer is primary
+# define POINTER_FLAG_CONFIDENCE \
+ 0x00004000 // Pointer is considered unlikely to be accidental
+# define POINTER_FLAG_CANCELED \
+ 0x00008000 // Pointer is departing in an abnormal manner
+# define POINTER_FLAG_DOWN \
+ 0x00010000 // Pointer transitioned to down state (made contact)
+# define POINTER_FLAG_UPDATE 0x00020000 // Pointer update
+# define POINTER_FLAG_UP \
+ 0x00040000 // Pointer transitioned from down state (broke contact)
+# define POINTER_FLAG_WHEEL 0x00080000 // Vertical wheel
+# define POINTER_FLAG_HWHEEL 0x00100000 // Horizontal wheel
+# define POINTER_FLAG_CAPTURECHANGED 0x00200000 // Lost capture
+
+#endif // TOUCH_FEEDBACK_DEFAULT
+
+#define TOUCH_FLAGS_CONTACTUPDATE \
+ (POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT)
+#define TOUCH_FLAGS_CONTACTDOWN \
+ (POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT)
+
+using InitializeTouchInjectionPtr = BOOL(WINAPI*)(UINT32, DWORD);
+using InjectTouchInputPtr = BOOL(WINAPI*)(UINT32, const POINTER_TOUCH_INFO*);
+
+#if !defined(NTDDI_WIN10_RS5) || (NTDDI_VERSION < NTDDI_WIN10_RS5)
+# define HSYNTHETICPOINTERDEVICE intptr_t
+#endif // NTDDI_VERSION < NTDDI_WIN10_RS5
+
+using CreateSyntheticPointerDevicePtr = HSYNTHETICPOINTERDEVICE(WINAPI*)(
+ POINTER_INPUT_TYPE, ULONG, POINTER_FEEDBACK_MODE);
+using DestroySyntheticPointerDevicePtr = void(WINAPI*)(HSYNTHETICPOINTERDEVICE);
+using InjectSyntheticPointerInputPtr = BOOL(WINAPI*)(HSYNTHETICPOINTERDEVICE,
+ const POINTER_TYPE_INFO*,
+ UINT32);
+
+#endif // touchinjection_sdk80_h
diff --git a/widget/windows/widget.rc b/widget/windows/widget.rc
new file mode 100644
index 0000000000..9361f9e48c
--- /dev/null
+++ b/widget/windows/widget.rc
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ */
+/* 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/. */
+
+#include "resource.h"
+#include <winresrc.h>
+#include <dlgs.h>
+
+IDC_GRAB CURSOR DISCARDABLE "res/grab.cur"
+IDC_GRABBING CURSOR DISCARDABLE "res/grabbing.cur"
+IDC_CELL CURSOR DISCARDABLE "res/cell.cur"
+IDC_COPY CURSOR DISCARDABLE "res/copy.cur"
+IDC_ALIAS CURSOR DISCARDABLE "res/aliasb.cur"
+IDC_ZOOMIN CURSOR DISCARDABLE "res/zoom_in.cur"
+IDC_ZOOMOUT CURSOR DISCARDABLE "res/zoom_out.cur"
+IDC_COLRESIZE CURSOR DISCARDABLE "res/col_resize.cur"
+IDC_ROWRESIZE CURSOR DISCARDABLE "res/row_resize.cur"
+IDC_VERTICALTEXT CURSOR DISCARDABLE "res/vertical_text.cur"
+IDC_NONE CURSOR DISCARDABLE "res/none.cur"
+
+OPTPROPSHEET DIALOG DISCARDABLE 32, 32, 288, 226
+STYLE DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE |
+ WS_CAPTION | WS_SYSMENU
+CAPTION "Options"
+FONT 8, "MS Sans Serif"
+BEGIN
+
+END