summaryrefslogtreecommitdiff
path: root/widget/windows
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows')
-rw-r--r--widget/windows/AudioSession.cpp453
-rw-r--r--widget/windows/AudioSession.h30
-rw-r--r--widget/windows/CompositorWidgetChild.cpp78
-rw-r--r--widget/windows/CompositorWidgetChild.h45
-rw-r--r--widget/windows/CompositorWidgetParent.cpp81
-rw-r--r--widget/windows/CompositorWidgetParent.h40
-rw-r--r--widget/windows/GfxInfo.cpp1450
-rw-r--r--widget/windows/GfxInfo.h102
-rw-r--r--widget/windows/IEnumFE.cpp173
-rw-r--r--widget/windows/IEnumFE.h90
-rw-r--r--widget/windows/IMMHandler.cpp2910
-rw-r--r--widget/windows/IMMHandler.h495
-rw-r--r--widget/windows/InProcessWinCompositorWidget.cpp41
-rw-r--r--widget/windows/InProcessWinCompositorWidget.h35
-rw-r--r--widget/windows/InkCollector.cpp253
-rw-r--r--widget/windows/InkCollector.h98
-rw-r--r--widget/windows/JumpListBuilder.cpp542
-rw-r--r--widget/windows/JumpListBuilder.h62
-rw-r--r--widget/windows/JumpListItem.cpp630
-rw-r--r--widget/windows/JumpListItem.h132
-rw-r--r--widget/windows/KeyboardLayout.cpp5232
-rw-r--r--widget/windows/KeyboardLayout.h1012
-rw-r--r--widget/windows/LSPAnnotator.cpp161
-rw-r--r--widget/windows/PCompositorWidget.ipdl31
-rw-r--r--widget/windows/PlatformWidgetTypes.ipdlh23
-rw-r--r--widget/windows/TSFTextStore.cpp6423
-rw-r--r--widget/windows/TSFTextStore.h1045
-rw-r--r--widget/windows/TaskbarPreview.cpp431
-rw-r--r--widget/windows/TaskbarPreview.h140
-rw-r--r--widget/windows/TaskbarPreviewButton.cpp145
-rw-r--r--widget/windows/TaskbarPreviewButton.h48
-rw-r--r--widget/windows/TaskbarTabPreview.cpp357
-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/WidgetTraceEvent.cpp132
-rw-r--r--widget/windows/WinCompositorWidget.cpp329
-rw-r--r--widget/windows/WinCompositorWidget.h112
-rw-r--r--widget/windows/WinIMEHandler.cpp1055
-rw-r--r--widget/windows/WinIMEHandler.h190
-rw-r--r--widget/windows/WinMessages.h187
-rw-r--r--widget/windows/WinModifierKeyState.h62
-rw-r--r--widget/windows/WinMouseScrollHandler.cpp1799
-rw-r--r--widget/windows/WinMouseScrollHandler.h625
-rw-r--r--widget/windows/WinNativeEventData.h56
-rw-r--r--widget/windows/WinTaskbar.cpp506
-rw-r--r--widget/windows/WinTaskbar.h46
-rw-r--r--widget/windows/WinTextEventDispatcherListener.cpp78
-rw-r--r--widget/windows/WinTextEventDispatcherListener.h50
-rw-r--r--widget/windows/WinUtils.cpp2110
-rw-r--r--widget/windows/WinUtils.h648
-rw-r--r--widget/windows/WindowHook.cpp129
-rw-r--r--widget/windows/WindowHook.h81
-rw-r--r--widget/windows/WindowsUIUtils.cpp182
-rw-r--r--widget/windows/WindowsUIUtils.h29
-rw-r--r--widget/windows/moz.build123
-rw-r--r--widget/windows/mozwrlbase.h77
-rw-r--r--widget/windows/nsAppShell.cpp436
-rw-r--r--widget/windows/nsAppShell.h59
-rw-r--r--widget/windows/nsBidiKeyboard.cpp187
-rw-r--r--widget/windows/nsBidiKeyboard.h37
-rw-r--r--widget/windows/nsClipboard.cpp1036
-rw-r--r--widget/windows/nsClipboard.h92
-rw-r--r--widget/windows/nsColorPicker.cpp212
-rw-r--r--widget/windows/nsColorPicker.h59
-rw-r--r--widget/windows/nsDataObj.cpp2166
-rw-r--r--widget/windows/nsDataObj.h296
-rw-r--r--widget/windows/nsDataObjCollection.cpp405
-rw-r--r--widget/windows/nsDataObjCollection.h95
-rw-r--r--widget/windows/nsDeviceContextSpecWin.cpp669
-rw-r--r--widget/windows/nsDeviceContextSpecWin.h84
-rw-r--r--widget/windows/nsDragService.cpp641
-rw-r--r--widget/windows/nsDragService.h61
-rw-r--r--widget/windows/nsFilePicker.cpp1383
-rw-r--r--widget/windows/nsFilePicker.h164
-rw-r--r--widget/windows/nsIdleServiceWin.cpp30
-rw-r--r--widget/windows/nsIdleServiceWin.h46
-rw-r--r--widget/windows/nsImageClipboard.cpp497
-rw-r--r--widget/windows/nsImageClipboard.h93
-rw-r--r--widget/windows/nsLookAndFeel.cpp701
-rw-r--r--widget/windows/nsLookAndFeel.h65
-rw-r--r--widget/windows/nsNativeDragSource.cpp109
-rw-r--r--widget/windows/nsNativeDragSource.h66
-rw-r--r--widget/windows/nsNativeDragTarget.cpp486
-rw-r--r--widget/windows/nsNativeDragTarget.h102
-rw-r--r--widget/windows/nsNativeThemeWin.cpp4168
-rw-r--r--widget/windows/nsNativeThemeWin.h128
-rw-r--r--widget/windows/nsPrintOptionsWin.cpp162
-rw-r--r--widget/windows/nsPrintOptionsWin.h36
-rw-r--r--widget/windows/nsPrintSettingsWin.cpp528
-rw-r--r--widget/windows/nsPrintSettingsWin.h59
-rw-r--r--widget/windows/nsScreenManagerWin.cpp181
-rw-r--r--widget/windows/nsScreenManagerWin.h50
-rw-r--r--widget/windows/nsScreenWin.cpp199
-rw-r--r--widget/windows/nsScreenWin.h45
-rw-r--r--widget/windows/nsSound.cpp305
-rw-r--r--widget/windows/nsSound.h37
-rw-r--r--widget/windows/nsToolkit.cpp81
-rw-r--r--widget/windows/nsToolkit.h49
-rw-r--r--widget/windows/nsUXThemeConstants.h251
-rw-r--r--widget/windows/nsUXThemeData.cpp405
-rw-r--r--widget/windows/nsUXThemeData.h120
-rw-r--r--widget/windows/nsWidgetFactory.cpp262
-rw-r--r--widget/windows/nsWinGesture.cpp590
-rw-r--r--widget/windows/nsWinGesture.h288
-rw-r--r--widget/windows/nsWindow.cpp8139
-rw-r--r--widget/windows/nsWindow.h671
-rw-r--r--widget/windows/nsWindowBase.cpp243
-rw-r--r--widget/windows/nsWindowBase.h145
-rw-r--r--widget/windows/nsWindowDbg.cpp67
-rw-r--r--widget/windows/nsWindowDbg.h61
-rw-r--r--widget/windows/nsWindowDefs.h147
-rw-r--r--widget/windows/nsWindowGfx.cpp686
-rw-r--r--widget/windows/nsWindowGfx.h35
-rw-r--r--widget/windows/nsdefs.h45
-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/TestWinDND.cpp716
-rw-r--r--widget/windows/tests/moz.build6
-rw-r--r--widget/windows/touchinjection_sdk80.h107
-rw-r--r--widget/windows/widget.rc30
132 files changed, 61210 insertions, 0 deletions
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp
new file mode 100644
index 0000000000..11e5ba50cd
--- /dev/null
+++ b/widget/windows/AudioSession.cpp
@@ -0,0 +1,453 @@
+/* -*- 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 <audiopolicy.h>
+#include <mmdeviceapi.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIXULAppInfo.h"
+
+//#include "AudioSession.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Attributes.h"
+
+#include <objbase.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 {
+private:
+ AudioSession();
+ ~AudioSession();
+public:
+ 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);
+private:
+ nsresult OnSessionDisconnectedInternal();
+public:
+ STDMETHODIMP OnSimpleVolumeChanged(float aVolume,
+ BOOL aMute,
+ LPCGUID aContext);
+ STDMETHODIMP OnStateChanged(AudioSessionState aState);
+
+ nsresult Start();
+ nsresult Stop();
+ void StopInternal();
+
+ nsresult GetSessionData(nsID& aID,
+ nsString& aSessionName,
+ nsString& aIconPath);
+
+ nsresult SetSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath);
+
+ enum SessionState {
+ UNINITIALIZED, // Has not been initialized yet
+ STARTED, // Started
+ CLONED, // SetSessionInfoCalled, Start not called
+ FAILED, // The audio session failed to start
+ STOPPED, // Stop called
+ AUDIO_SESSION_DISCONNECTED // Audio session disconnected
+ };
+protected:
+ RefPtr<IAudioSessionControl> mAudioSessionControl;
+ nsString mDisplayName;
+ nsString mIconPath;
+ nsID mSessionGroupingParameter;
+ SessionState mState;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ static AudioSession* sService;
+};
+
+nsresult
+StartAudioSession()
+{
+ return AudioSession::GetSingleton()->Start();
+}
+
+nsresult
+StopAudioSession()
+{
+ return AudioSession::GetSingleton()->Stop();
+}
+
+nsresult
+GetAudioSessionData(nsID& aID,
+ nsString& aSessionName,
+ nsString& aIconPath)
+{
+ return AudioSession::GetSingleton()->GetSessionData(aID,
+ aSessionName,
+ aIconPath);
+}
+
+nsresult
+RecvAudioSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath)
+{
+ return AudioSession::GetSingleton()->SetSessionData(aID,
+ aSessionName,
+ aIconPath);
+}
+
+AudioSession* AudioSession::sService = nullptr;
+
+AudioSession::AudioSession()
+{
+ mState = UNINITIALIZED;
+}
+
+AudioSession::~AudioSession()
+{
+
+}
+
+AudioSession*
+AudioSession::GetSingleton()
+{
+ if (!(AudioSession::sService)) {
+ RefPtr<AudioSession> service = new AudioSession();
+ service.forget(&AudioSession::sService);
+ }
+
+ // We don't refcount AudioSession on the Gecko side, we hold one single ref
+ // as long as the appshell is running.
+ return AudioSession::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;
+}
+
+// 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.
+nsresult
+AudioSession::Start()
+{
+ MOZ_ASSERT(mState == UNINITIALIZED ||
+ mState == CLONED ||
+ mState == AUDIO_SESSION_DISCONNECTED,
+ "State invariants violated");
+
+ const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+ const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
+
+ HRESULT hr;
+
+ // There's a matching CoUninit in Stop() for this tied to a state of
+ // UNINITIALIZED.
+ hr = CoInitialize(nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr), "CoInitialize failure in audio session control, unexpected");
+
+ if (mState == UNINITIALIZED) {
+ mState = FAILED;
+
+ // Content processes should be CLONED
+ if (XRE_IsContentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Should only get here in a chrome process!");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
+
+ bundle->GetStringFromName(u"brandFullName",
+ getter_Copies(mDisplayName));
+
+ wchar_t *buffer;
+ mIconPath.GetMutableData(&buffer, MAX_PATH);
+ ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1");
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+ uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
+ }
+
+ mState = FAILED;
+
+ MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
+ "Should never happen ...");
+
+ RefPtr<IMMDeviceEnumerator> enumerator;
+ hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator,
+ nullptr,
+ CLSCTX_ALL,
+ IID_IMMDeviceEnumerator,
+ getter_AddRefs(enumerator));
+ if (FAILED(hr))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<IMMDevice> device;
+ hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
+ ERole::eMultimedia,
+ getter_AddRefs(device));
+ if (FAILED(hr)) {
+ if (hr == E_NOTFOUND)
+ return NS_ERROR_NOT_AVAILABLE;
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<IAudioSessionManager> manager;
+ hr = device->Activate(IID_IAudioSessionManager,
+ CLSCTX_ALL,
+ nullptr,
+ getter_AddRefs(manager));
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = manager->GetAudioSessionControl(&GUID_NULL,
+ 0,
+ getter_AddRefs(mAudioSessionControl));
+
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetGroupingParam((LPGUID)&mSessionGroupingParameter,
+ nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ // Increments refcount of 'this'.
+ hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ mState = STARTED;
+
+ return NS_OK;
+}
+
+void
+AudioSession::StopInternal()
+{
+ if (mAudioSessionControl &&
+ (mState == STARTED || mState == STOPPED)) {
+ // Decrement refcount of 'this'
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ }
+ mAudioSessionControl = nullptr;
+}
+
+nsresult
+AudioSession::Stop()
+{
+ MOZ_ASSERT(mState == STARTED ||
+ mState == UNINITIALIZED || // XXXremove this
+ mState == FAILED,
+ "State invariants violated");
+ SessionState state = mState;
+ mState = STOPPED;
+
+ {
+ RefPtr<AudioSession> kungFuDeathGrip;
+ kungFuDeathGrip.swap(sService);
+
+ StopInternal();
+ }
+
+ if (state != UNINITIALIZED) {
+ ::CoUninitialize();
+ }
+ return NS_OK;
+}
+
+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)
+{
+ MOZ_ASSERT(mState == FAILED ||
+ mState == STARTED ||
+ mState == CLONED,
+ "State invariants violated");
+
+ CopynsID(aID, mSessionGroupingParameter);
+ aSessionName = mDisplayName;
+ aIconPath = mIconPath;
+
+ if (mState == FAILED)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+nsresult
+AudioSession::SetSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath)
+{
+ MOZ_ASSERT(mState == UNINITIALIZED,
+ "State invariants violated");
+ MOZ_ASSERT(!XRE_IsParentProcess(),
+ "Should never get here in a chrome process!");
+ mState = CLONED;
+
+ 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)
+{
+ // Run our code asynchronously. Per MSDN we can't do anything interesting
+ // in this callback.
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal);
+ NS_DispatchToMainThread(runnable);
+ return S_OK;
+}
+
+nsresult
+AudioSession::OnSessionDisconnectedInternal()
+{
+ if (!mAudioSessionControl)
+ return NS_OK;
+
+ // When successful, UnregisterAudioSessionNotification will decrement the
+ // refcount of 'this'. Start will re-increment it. In the interim,
+ // we'll need to reference ourselves.
+ RefPtr<AudioSession> kungFuDeathGrip(this);
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ mAudioSessionControl = nullptr;
+
+ mState = AUDIO_SESSION_DISCONNECTED;
+ CoUninitialize();
+ Start(); // If it fails there's not much we can do.
+ return NS_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..d7c1d3787a
--- /dev/null
+++ b/widget/windows/AudioSession.h
@@ -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 "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+// Start the audio session in the current process
+nsresult StartAudioSession();
+
+// Pass the information necessary to start an audio session in another process
+nsresult GetAudioSessionData(nsID& aID,
+ nsString& aSessionName,
+ nsString& aIconPath);
+
+// Receive the information necessary to start an audio session in a non-chrome
+// process
+nsresult RecvAudioSessionData(const nsID& aID,
+ const nsString& aSessionName,
+ const nsString& aIconPath);
+
+// Stop the audio session in the current process
+nsresult StopAudioSession();
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.cpp b/widget/windows/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..55d71d21e0
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.cpp
@@ -0,0 +1,78 @@
+/* -*- 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/widget/CompositorWidgetVsyncObserver.h"
+#include "nsBaseWidget.h"
+#include "VsyncDispatcher.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver)
+ : mVsyncDispatcher(aVsyncDispatcher),
+ mVsyncObserver(aVsyncObserver)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild()
+{
+}
+
+void
+CompositorWidgetChild::EnterPresentLock()
+{
+ Unused << SendEnterPresentLock();
+}
+
+void
+CompositorWidgetChild::LeavePresentLock()
+{
+ Unused << SendLeavePresentLock();
+}
+
+void
+CompositorWidgetChild::OnDestroyWindow()
+{
+}
+
+void
+CompositorWidgetChild::UpdateTransparency(nsTransparencyMode aMode)
+{
+ Unused << SendUpdateTransparency(static_cast<int32_t>(aMode));
+}
+
+void
+CompositorWidgetChild::ClearTransparentWindow()
+{
+ Unused << SendClearTransparentWindow();
+}
+
+HDC
+CompositorWidgetChild::GetTransparentDC() const
+{
+ // Not supported in out-of-process mode.
+ return nullptr;
+}
+
+bool
+CompositorWidgetChild::RecvObserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return true;
+}
+
+bool
+CompositorWidgetChild::RecvUnobserveVsync()
+{
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h
new file mode 100644
index 0000000000..ee6e90ca9c
--- /dev/null
+++ b/widget/windows/CompositorWidgetChild.h
@@ -0,0 +1,45 @@
+/* -*- 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 {
+
+class CompositorWidgetChild final
+ : public PCompositorWidgetChild,
+ public CompositorWidgetDelegate
+{
+public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver);
+ ~CompositorWidgetChild() override;
+
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ void UpdateTransparency(nsTransparencyMode aMode) override;
+ void ClearTransparentWindow() override;
+ HDC GetTransparentDC() const override;
+
+ bool RecvObserveVsync() override;
+ bool RecvUnobserveVsync() override;
+
+private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+};
+
+} // 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..c784ff72ef
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.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 "CompositorWidgetParent.h"
+
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetParent::CompositorWidgetParent(const CompositorWidgetInitData& aInitData)
+ : WinCompositorWidget(aInitData)
+{
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent()
+{
+}
+
+bool
+CompositorWidgetParent::RecvEnterPresentLock()
+{
+ EnterPresentLock();
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvLeavePresentLock()
+{
+ LeavePresentLock();
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvUpdateTransparency(const int32_t& aMode)
+{
+ UpdateTransparency(static_cast<nsTransparencyMode>(aMode));
+ return true;
+}
+
+bool
+CompositorWidgetParent::RecvClearTransparentWindow()
+{
+ ClearTransparentWindow();
+ return true;
+}
+
+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::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..a2f63bd72b
--- /dev/null
+++ b/widget/windows/CompositorWidgetParent.h
@@ -0,0 +1,40 @@
+/* -*- 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 "WinCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final
+ : public PCompositorWidgetParent,
+ public WinCompositorWidget
+{
+public:
+ CompositorWidgetParent(const CompositorWidgetInitData& aInitData);
+ ~CompositorWidgetParent() override;
+
+ bool RecvEnterPresentLock() override;
+ bool RecvLeavePresentLock() override;
+ bool RecvUpdateTransparency(const int32_t& aMode) override;
+ bool RecvClearTransparentWindow() override;
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsIWidget* RealWidget() override;
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _widget_windows_WinCompositorWidget_h__
diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp
new file mode 100644
index 0000000000..0cbd323ded
--- /dev/null
+++ b/widget/windows/GfxInfo.cpp
@@ -0,0 +1,1450 @@
+/* -*- 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 <windows.h>
+#include <setupapi.h>
+#include "gfxConfig.h"
+#include "gfxWindowsPlatform.h"
+#include "GfxInfo.h"
+#include "GfxInfoWebGL.h"
+#include "nsUnicharUtils.h"
+#include "prenv.h"
+#include "prprf.h"
+#include "GfxDriverInfo.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.h"
+#include "nsPrintfCString.h"
+#include "jsapi.h"
+
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#include "nsICrashReporter.h"
+#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+static const uint32_t allWindowsVersions = 0xffffffff;
+
+GfxInfo::GfxInfo()
+ : mWindowsVersion(0),
+ mHasDualGPU(false),
+ mIsGPU2Active(false)
+{
+}
+
+/* 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;
+}
+
+#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 [ ", 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
+ ? u"RGB" : u"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;
+}
+
+static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, nsAString& destString, int type)
+{
+ 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) {
+ dValue = dValue / 1024 / 1024;
+ destString.AppendInt(int32_t(dValue));
+ } 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) {
+ qValue = qValue / 1024 / 1024;
+ destString.AppendInt(int32_t(qValue));
+ } else {
+ retval = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+ case REG_MULTI_SZ: {
+ // 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;
+ }
+
+ break;
+ }
+ }
+ RegCloseKey(key);
+
+ return retval;
+}
+
+// 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 char *prefix, int length)
+{
+ nsAutoString id(key);
+ ToUpperCase(id);
+ int32_t start = id.Find(prefix);
+ if (start != -1) {
+ id.Cut(0, start + strlen(prefix));
+ id.Truncate(length);
+ }
+ 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,
+ kWindowsXP = 0x50001,
+ kWindowsServer2003 = 0x50002,
+ kWindowsVista = 0x60000,
+ kWindows7 = 0x60001,
+ kWindows8 = 0x60002,
+ kWindows8_1 = 0x60003,
+ kWindows10 = 0xA0000
+};
+
+static int32_t
+WindowsOSVersion()
+{
+ static int32_t winVersion = UNINITIALIZED_VALUE;
+
+ OSVERSIONINFO vinfo;
+
+ if (winVersion == UNINITIALIZED_VALUE) {
+ 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
+ winVersion = kWindowsUnknown;
+ } else {
+ winVersion = int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion;
+ }
+ }
+
+ return winVersion;
+}
+
+/* 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();
+
+ 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", &mWindowsVersion);
+ } else {
+ mWindowsVersion = WindowsOSVersion();
+ }
+
+ mDeviceKeyDebug = NS_LITERAL_STRING("PrimarySearch");
+
+ while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) {
+ if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
+ mDeviceKeyDebug = NS_LITERAL_STRING("NullSearch");
+ 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 (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, ArrayLength(DEVICE_KEY_PREFIX)-1) != 0)
+ return rv;
+
+ // chop off DEVICE_KEY_PREFIX
+ mDeviceKey = displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX)-1;
+
+ mDeviceID = displayDevice.DeviceID;
+ mDeviceString = 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).
+ if (mWindowsVersion == kWindows8 &&
+ mDeviceID.Length() == 0 &&
+ mDeviceString.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);
+ mDriverDate.AssignLiteral("01-01-1970");
+
+ // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code
+ mDeviceID.AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000");
+ }
+ }
+
+ /* create a device information set composed of the current display device */
+ HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, mDeviceID.get(), nullptr,
+ DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
+
+ 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);
+ NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\");
+ /* 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 = value;
+ } else {
+ // If the entry wasn't found, assume the worst (0.0.0.0).
+ mDriverVersion.AssignLiteral("0.0.0.0");
+ }
+ dwcbData = sizeof(value);
+ result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
+ (LPBYTE)value, &dwcbData);
+ if (result == ERROR_SUCCESS) {
+ mDriverDate = value;
+ } else {
+ // Again, assume the worst
+ mDriverDate.AssignLiteral("01-01-1970");
+ }
+ RegCloseKey(key);
+ break;
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+
+ mAdapterVendorID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "VEN_", 4));
+ mAdapterDeviceID.AppendPrintf("0x%04x", ParseIDFromDeviceID(mDeviceID, "&DEV_", 4));
+ mAdapterSubsysID.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID, "&SUBSYS_", 8));
+
+ // We now check for second display adapter.
+
+ // 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;
+ uint32_t adapterVendorID2;
+ uint32_t adapterDeviceID2;
+
+ NS_NAMED_LITERAL_STRING(driverKeyPre, "System\\CurrentControlSet\\Control\\Class\\");
+ /* 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;
+ nsAutoString adapterVendorID2String;
+ nsAutoString adapterDeviceID2String;
+ adapterVendorID2 = ParseIDFromDeviceID(deviceID2, "VEN_", 4);
+ adapterVendorID2String.AppendPrintf("0x%04x", adapterVendorID2);
+ adapterDeviceID2 = ParseIDFromDeviceID(deviceID2, "&DEV_", 4);
+ adapterDeviceID2String.AppendPrintf("0x%04x", adapterDeviceID2);
+ if (mAdapterVendorID == adapterVendorID2String &&
+ mAdapterDeviceID == adapterDeviceID2String) {
+ 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) {
+ mHasDualGPU = true;
+ mDeviceString2 = value;
+ mDeviceID2 = deviceID2;
+ mDeviceKey2 = driverKey2;
+ mDriverVersion2 = driverVersion2;
+ mDriverDate2 = driverDate2;
+ mAdapterVendorID2.AppendPrintf("0x%04x", adapterVendorID2);
+ mAdapterDeviceID2.AppendPrintf("0x%04x", adapterDeviceID2);
+ mAdapterSubsysID2.AppendPrintf("%08x", ParseIDFromDeviceID(mDeviceID2, "&SUBSYS_", 8));
+ break;
+ }
+ }
+ }
+ }
+
+ SetupDiDestroyDeviceInfoList(devinfo);
+ }
+ }
+
+ mHasDriverVersionMismatch = false;
+ if (mAdapterVendorID == GfxDriverInfo::GetDeviceVendor(VendorIntel)) {
+ // 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.
+ bool is64bitApp = sizeof(void*) == 8;
+ const char16_t *dllFileName = is64bitApp
+ ? u"igd10umd64.dll"
+ : u"igd10umd32.dll",
+ *dllFileName2 = is64bitApp
+ ? u"igd10iumd64.dll"
+ : u"igd10iumd32.dll";
+ nsString dllVersion, dllVersion2;
+ gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName, dllVersion);
+ gfxWindowsPlatform::GetDLLVersion((char16_t*)dllFileName2, dllVersion2);
+
+ uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0,
+ driverNumericVersion = 0, knownSafeMismatchVersion = 0;
+ ParseDriverVersion(dllVersion, &dllNumericVersion);
+ ParseDriverVersion(dllVersion2, &dllNumericVersion2);
+
+ ParseDriverVersion(mDriverVersion, &driverNumericVersion);
+ ParseDriverVersion(NS_LITERAL_STRING("9.17.10.0"), &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;
+ gfxWarningOnce() << "Mismatched driver versions between the registry " << mDriverVersion.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(dllVersion).get() << " and " << NS_ConvertUTF16toUTF8(dllVersion2).get();
+ }
+ }
+
+ const char *spoofedDriverVersionString = PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION");
+ if (spoofedDriverVersionString) {
+ mDriverVersion.AssignASCII(spoofedDriverVersionString);
+ }
+
+ const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID");
+ if (spoofedVendor) {
+ mAdapterVendorID.AssignASCII(spoofedVendor);
+ }
+
+ const char *spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID");
+ if (spoofedDevice) {
+ mAdapterDeviceID.AssignASCII(spoofedDevice);
+ }
+
+ AddCrashReportAnnotations();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ aAdapterDescription = mDeviceString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ aAdapterDescription = mDeviceString2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) {
+ aAdapterRAM = L"Unknown";
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ if (!mHasDualGPU) {
+ aAdapterRAM.Truncate();
+ } else if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.qwMemorySize", aAdapterRAM, REG_QWORD)) || aAdapterRAM.Length() == 0) {
+ if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"HardwareInformation.MemorySize", aAdapterRAM, REG_DWORD))) {
+ aAdapterRAM = L"Unknown";
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ if (NS_FAILED(GetKeyValue(mDeviceKey.get(), L"InstalledDisplayDrivers", aAdapterDriver, REG_MULTI_SZ)))
+ aAdapterDriver = L"Unknown";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ if (!mHasDualGPU) {
+ aAdapterDriver.Truncate();
+ } else if (NS_FAILED(GetKeyValue(mDeviceKey2.get(), L"InstalledDisplayDrivers", aAdapterDriver, REG_MULTI_SZ))) {
+ aAdapterDriver = L"Unknown";
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion = mDriverVersion;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate = mDriverDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion = mDriverVersion2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate = mDriverDate2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ aAdapterSubsysID = mAdapterSubsysID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ aAdapterSubsysID = mAdapterSubsysID2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ *aIsGPU2Active = mIsGPU2Active;
+ return NS_OK;
+}
+
+#if defined(MOZ_CRASHREPORTER)
+/* 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(NS_LITERAL_CSTRING("Cisco VPN\n"));
+ }
+}
+#endif
+
+void
+GfxInfo::AddCrashReportAnnotations()
+{
+#if defined(MOZ_CRASHREPORTER)
+ CheckForCiscoVPN();
+
+ if (mHasDriverVersionMismatch) {
+ CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DriverVersionMismatch\n"));
+ }
+
+ 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(NS_LITERAL_CSTRING("AdapterVendorID"),
+ narrowVendorID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDeviceID"),
+ narrowDeviceID);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterDriverVersion"),
+ narrowDriverVersion);
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AdapterSubsysID"),
+ narrowSubsysID);
+
+ /* Add an App Note for now so that we get the data immediately. These
+ * can go away after we store the above in the socorro db */
+ nsAutoCString note;
+ /* AppendPrintf only supports 32 character strings, mrghh. */
+ note.AppendLiteral("AdapterVendorID: ");
+ note.Append(narrowVendorID);
+ note.AppendLiteral(", AdapterDeviceID: ");
+ note.Append(narrowDeviceID);
+ note.AppendLiteral(", AdapterSubsysID: ");
+ note.Append(narrowSubsysID);
+ note.AppendLiteral(", AdapterDriverVersion: ");
+ note.Append(NS_LossyConvertUTF16toASCII(driverVersion));
+
+ if (vendorID == GfxDriverInfo::GetDeviceVendor(VendorAll)) {
+ /* if we didn't find a valid vendorID lets append the mDeviceID string to try to find out why */
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceID, note);
+ note.AppendLiteral(", ");
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ LossyAppendUTF16toASCII(mDeviceKeyDebug, note);
+ }
+ note.Append("\n");
+
+ if (mHasDualGPU) {
+ nsString deviceID2, vendorID2, subsysID2;
+ nsAutoString adapterDriverVersionString2;
+ nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2;
+
+ 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);
+
+#endif
+}
+
+static OperatingSystem
+WindowsVersionToOperatingSystem(int32_t aWindowsVersion)
+{
+ switch(aWindowsVersion) {
+ case kWindowsXP:
+ return OperatingSystem::WindowsXP;
+ case kWindowsServer2003:
+ return OperatingSystem::WindowsServer2003;
+ case kWindowsVista:
+ return OperatingSystem::WindowsVista;
+ 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;
+ }
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (!mDriverInfo->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
+ * blacklisting call.
+ */
+
+ /*
+ * NVIDIA entries
+ */
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(6,14,11,8745), "FEATURE_FAILURE_NV_XP", "nVidia driver > 187.45" );
+
+ /*
+ * 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8,15,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_15", "nVidia driver > 187.45" );
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8,16,10,0000), V(8,16,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_16", "nVidia driver > 187.45" );
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, 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::WindowsVista,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8,17,10,0000), V(8,17,11,8745),
+ "FEATURE_FAILURE_NV_VISTA_17", "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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD1", "8.56.1.15" );
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(8,56,1,15), "FEATURE_FAILURE_AMD2", "8.56.1.15" );
+
+ // Bug 1099252
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8,832,0,0), "FEATURE_FAILURE_BUG_1099252");
+
+ // Bug 1118695
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_EQUAL, V(8,783,2,2000), "FEATURE_FAILURE_BUG_1118695");
+
+ // Bug 1198815
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, 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.*.*" );
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows8,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_INCLUSIVE_START, V(8,982,0,0), V(8,983,0,0),
+ "FEATURE_FAILURE_BUG_783517_ATI", "!= 8.982.*.*" );
+
+ /* 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_ATI_DIS" );
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_OGL_AMD_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, \
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(devFamily), \
+ GfxDriverInfo::allFeatures, 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, \
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(devFamily), \
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \
+ DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId )
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA500, 1006, "FEATURE_FAILURE_594877_1");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_2");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA950, 1504, "FEATURE_FAILURE_594877_3");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMA3150, 2124, "FEATURE_FAILURE_594877_4");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelGMAX3000, 1666, "FEATURE_FAILURE_594877_5");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, 2202, "FEATURE_FAILURE_594877_6");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA500, 2026, "FEATURE_FAILURE_594877_7");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA950, 1930, "FEATURE_FAILURE_594877_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMA3150, 2117, "FEATURE_FAILURE_594877_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, IntelGMAX3000, 1930, "FEATURE_FAILURE_594877_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_1180379");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_1");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA900, V(6,14,10,4764), "FEATURE_FAILURE_INTEL_2");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA950, V(6,14,10,4926), "FEATURE_FAILURE_INTEL_3");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMA3150, V(6,14,10,5134), "FEATURE_FAILURE_INTEL_4");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX3000, V(6,14,10,5218), "FEATURE_FAILURE_INTEL_5");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelGMAX4500HD, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_6");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsXP, IntelHDGraphicsToSandyBridge, V(6,14,10,4969), "FEATURE_FAILURE_INTEL_7");
+
+ // StrechRect seems to suffer from precision issues which leads to artifacting
+ // during content drawing starting with at least version 6.14.10.5082
+ // and going until 6.14.10.5218. See bug 919454 and bug 949275 for more info.
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::WindowsXP,
+ const_cast<nsAString&>(GfxDriverInfo::GetDeviceVendor(VendorIntel)),
+ const_cast<GfxDeviceFamily*>(GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD)),
+ GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BETWEEN_EXCLUSIVE, V(6,14,10,5076), V(6,14,10,5218), "FEATURE_FAILURE_INTEL_8", "6.14.10.5218");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA500, V(3,0,20,3200), "FEATURE_FAILURE_INTEL_9");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_10");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA950, V(7,14,10,1504), "FEATURE_FAILURE_INTEL_11");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMA3150, V(7,14,10,1910), "FEATURE_FAILURE_INTEL_12");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_13");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_14");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::WindowsVista, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_15");
+
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA500, V(5,0,0,2026), "FEATURE_FAILURE_INTEL_16");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA900, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA950, V(8,15,10,1930), "FEATURE_FAILURE_INTEL_18");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMA3150, V(8,14,10,1972), "FEATURE_FAILURE_INTEL_19");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX3000, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_20");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelGMAX4500HD, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_21");
+ IMPLEMENT_INTEL_DRIVER_BLOCKLIST(OperatingSystem::Windows7, IntelHDGraphicsToSandyBridge, V(7,15,10,1666), "FEATURE_FAILURE_INTEL_22");
+
+ // Bug 1074378
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel),
+ (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ GfxDriverInfo::allFeatures, 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel),
+ (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelHDGraphicsToSandyBridge),
+ GfxDriverInfo::allFeatures, 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelHD3000),
+ GfxDriverInfo::allFeatures, 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 HD Graphics on driver <= 8.15.10.2302
+ * See bug 806786
+ */
+ APPEND_TO_DRIVER_BLOCKLIST2( OperatingSystem::Windows7,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(IntelMobileHDGraphics),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2302), "FEATURE_FAILURE_BUG_804144" );
+
+ /* 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665),
+ nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_2" );
+
+ /* Disable D2D on AMD Catalyst 14.4 until 14.6
+ * See bug 984488
+ */
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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+");
+ APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ 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_2", "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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), GfxDriverInfo::allDevices,
+ GfxDriverInfo::allFeatures, 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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(OperatingSystem::Windows,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2849, "FEATURE_FAILURE_BUG_1203199_1", "Intel driver > X.X.X.2849");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1137716),
+ GfxDriverInfo::allFeatures, 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 1153381: WebGL issues with D3D11 ANGLE on Intel. These may be fixed by an ANGLE update. */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1153381");
+
+ /* Bug 1336710: Crash in rx::Blit9::initialize. */
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(IntelGMAX4500HD),
+ nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1336710");
+
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::WindowsXP,
+ (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(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");
+
+ ////////////////////////////////////
+ // WebGL
+
+ // Older than 5-15-2016
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), GfxDriverInfo::allDevices,
+ 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,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAMD), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_DX_INTEROP2, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH");
+ }
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = WindowsVersionToOperatingSystem(mWindowsVersion);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS)
+ *aOS = os;
+
+ // 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)))
+ {
+ aFailureId = "FEATURE_FAILURE_GET_ADAPTER";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ if (!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorIntel), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorAMD), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) &&
+ !adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), 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"))
+ {
+ aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
+ uint64_t driverVersion;
+ if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) {
+ aFailureId = "FEATURE_FAILURE_PARSE_DRIVER";
+ *aStatus = FEATURE_BLOCKED_DRIVER_VERSION;
+ return NS_OK;
+ }
+
+ // special-case the WinXP test slaves: they have out-of-date drivers, but we still want to
+ // whitelist them, actually we do know that this combination of device and driver version
+ // works well.
+ if (mWindowsVersion == kWindowsXP &&
+ adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), nsCaseInsensitiveStringComparator()) &&
+ adapterDeviceID.LowerCaseEqualsLiteral("0x0861") && // GeForce 9400
+ driverVersion == V(6,14,11,7756))
+ {
+ *aStatus = FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ // Windows Server 2003 should be just like Windows XP for present purpose, but still has a different version number.
+ // OTOH Windows Server 2008 R1 and R2 already have the same version numbers as Vista and Seven respectively
+ if (os == OperatingSystem::WindowsServer2003)
+ os = OperatingSystem::WindowsXP;
+
+ if (mHasDriverVersionMismatch) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_MISMATCHED_VERSION;
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+nsresult
+GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray)
+{
+ int deviceCount = 0;
+ for (int deviceIndex = 0;; deviceIndex++) {
+ DISPLAY_DEVICEA device;
+ device.cb = sizeof(device);
+ if (!::EnumDisplayDevicesA(nullptr, deviceIndex, &device, 0)) {
+ break;
+ }
+
+ if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) {
+ continue;
+ }
+
+ DEVMODEA mode;
+ mode.dmSize = sizeof(mode);
+ mode.dmDriverExtra = 0;
+ if (!::EnumDisplaySettingsA(device.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) {
+ continue;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value(mode.dmPelsWidth));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value(mode.dmPelsHeight));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> refreshRate(aCx, JS::Int32Value(mode.dmDisplayFrequency));
+ JS_SetProperty(aCx, obj, "refreshRate", refreshRate);
+
+ JS::Rooted<JS::Value> pseudoDisplay(aCx,
+ JS::BooleanValue(!!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)));
+ JS_SetProperty(aCx, obj, "pseudoDisplay", pseudoDisplay);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, deviceCount++, element);
+ }
+ return NS_OK;
+}
+
+void
+GfxInfo::DescribeFeatures(JSContext* aCx, JS::Handle<JSObject*> aObj)
+{
+ // Add the platform neutral features
+ GfxInfoBase::DescribeFeatures(aCx, aObj);
+
+ JS::Rooted<JSObject*> obj(aCx);
+
+ gfx::FeatureStatus d3d11 = gfxConfig::GetValue(Feature::D3D11_COMPOSITING);
+ if (!InitFeatureObject(aCx, aObj, "d3d11", FEATURE_DIRECT3D_11_ANGLE,
+ Some(d3d11), &obj)) {
+ return;
+ }
+ if (d3d11 == 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 blacklisted = false;
+ if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status))) {
+ blacklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK);
+ }
+ }
+
+ val = JS::BooleanValue(blacklisted);
+ JS_SetProperty(aCx, obj, "blacklisted", val);
+ }
+
+ gfx::FeatureStatus d2d = gfxConfig::GetValue(Feature::DIRECT2D);
+ if (!InitFeatureObject(aCx, aObj, "d2d", nsIGfxInfo::FEATURE_DIRECT2D,
+ Some(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 = aVendorID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ mAdapterDeviceID = aDeviceID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ mDriverVersion = 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..c7aa859a6e
--- /dev/null
+++ b/widget/windows/GfxInfo.h
@@ -0,0 +1,102 @@
+/* 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_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+ ~GfxInfo() {}
+public:
+ GfxInfo();
+
+ // 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 GetCleartypeParameters(nsAString & aCleartypeParams) 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(nsAString & aAdapterRAM) 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(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+ virtual uint32_t OperatingSystemVersion() override { return mWindowsVersion; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+protected:
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ void DescribeFeatures(JSContext* cx, JS::Handle<JSObject*> aOut) override;
+
+private:
+
+ void AddCrashReportAnnotations();
+
+ nsString mDeviceString;
+ nsString mDeviceID;
+ nsString mDriverVersion;
+ nsString mDriverDate;
+ nsString mDeviceKey;
+ nsString mDeviceKeyDebug;
+ nsString mAdapterVendorID;
+ nsString mAdapterDeviceID;
+ nsString mAdapterSubsysID;
+ nsString mDeviceString2;
+ nsString mDriverVersion2;
+ nsString mDeviceID2;
+ nsString mDriverDate2;
+ nsString mDeviceKey2;
+ nsString mAdapterVendorID2;
+ nsString mAdapterDeviceID2;
+ nsString mAdapterSubsysID2;
+ uint32_t mWindowsVersion;
+ bool mHasDualGPU;
+ bool mIsGPU2Active;
+ bool mHasDriverVersionMismatch;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/windows/IEnumFE.cpp b/widget/windows/IEnumFE.cpp
new file mode 100644
index 0000000000..4fa76258ba
--- /dev/null
+++ b/widget/windows/IEnumFE.cpp
@@ -0,0 +1,173 @@
+/* -*- 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..d3875865a9
--- /dev/null
+++ b/widget/windows/IEnumFE.h
@@ -0,0 +1,90 @@
+/* -*- 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:
+ 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..9bd7d2e7a9
--- /dev/null
+++ b/widget/windows/IMMHandler.cpp
@@ -0,0 +1,2910 @@
+/* -*- 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 "WinUtils.h"
+#include "KeyboardLayout.h"
+#include <algorithm>
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.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
+
+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:
+ 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:
+ 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 GetWritingModeName : public nsAutoCString
+{
+public:
+ GetWritingModeName(const WritingMode& aWritingMode)
+ {
+ if (!aWritingMode.IsVertical()) {
+ Assign("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ Assign("Vertical (LR)");
+ return;
+ }
+ Assign("Vertical (RL)");
+ }
+ virtual ~GetWritingModeName() {}
+};
+
+class GetReconvertStringLog : public nsAutoCString
+{
+public:
+ 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;
+
+LazyLogModule gIMMLog("nsIMM32HandlerWidgets");
+
+/******************************************************************************
+ * IMEContext
+ ******************************************************************************/
+
+IMEContext::IMEContext(HWND aWnd)
+ : mWnd(aWnd)
+ , mIMC(::ImmGetContext(aWnd))
+{
+}
+
+IMEContext::IMEContext(nsWindowBase* aWindowBase)
+ : mWnd(aWindowBase->GetWindowHandle())
+ , mIMC(::ImmGetContext(aWindowBase->GetWindowHandle()))
+{
+}
+
+void
+IMEContext::Init(HWND aWnd)
+{
+ Clear();
+ mWnd = aWnd;
+ mIMC = ::ImmGetContext(mWnd);
+}
+
+void
+IMEContext::Init(nsWindowBase* 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;
+bool IMMHandler::sNativeCaretIsCreatedForPlugin = 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::IsComposingOnPlugin()
+{
+ return gIMMHandler && gIMMHandler->mIsComposingOnPlugin;
+}
+
+// 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::IsJapanist2003Active()
+{
+ return sIMEName.EqualsLiteral("Japanist 2003");
+}
+
+// static
+bool
+IMMHandler::IsGoogleJapaneseInputActive()
+{
+ // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
+ // written in Japanese.
+ return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B "
+ L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB");
+}
+
+// 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, wwc(sIMEName.BeginWriting()),
+ 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()) {
+ sIMEName =
+ Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as");
+ }
+
+ // 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(gIMMLog, LogLevel::Info,
+ ("InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
+ "sIMEProperty=%s, sIMEUIProperty=%s",
+ aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(),
+ sCodePage, GetIMEGeneralPropertyName(sIMEProperty).get(),
+ GetIMEUIPropertyName(sIMEUIProperty).get()));
+}
+
+// static
+UINT
+IMMHandler::GetKeyboardCodePage()
+{
+ return sCodePage;
+}
+
+// static
+nsIMEUpdatePreference
+IMMHandler::GetIMEUpdatePreference()
+{
+ return nsIMEUpdatePreference(
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
+ nsIMEUpdatePreference::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)
+ , mIsComposingOnPlugin(false)
+ , mNativeCaretIsCreated(false)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Debug, ("IMMHandler is created"));
+}
+
+IMMHandler::~IMMHandler()
+{
+ if (mIsComposing) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("~IMMHandler, ERROR, the instance is still composing"));
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Debug, ("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(gIMMLog, LogLevel::Info,
+ ("CommitComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
+ "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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("CancelComposition, aForce=%s, aWindow=%p, hWnd=%08x, "
+ "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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("OnFocusChange(aFocus=%s, aWindow=%p), sHasFocus=%s, "
+ "IsComposingWindow(aWindow)=%s, aWindow->Destroyed()=%s, "
+ "sNativeCaretIsCreatedForPlugin=%s",
+ GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus),
+ GetBoolName(IsComposingWindow(aWindow)),
+ GetBoolName(aWindow->Destroyed()),
+ GetBoolName(sNativeCaretIsCreatedForPlugin)));
+
+ if (!aFocus) {
+ if (sNativeCaretIsCreatedForPlugin) {
+ ::DestroyCaret();
+ sNativeCaretIsCreatedForPlugin = false;
+ }
+ if (IsComposingWindow(aWindow) && aWindow->Destroyed()) {
+ CancelComposition(aWindow);
+ }
+ }
+ if (gIMMHandler) {
+ gIMMHandler->mSelection.Clear();
+ }
+ sHasFocus = aFocus;
+}
+
+// static
+void
+IMMHandler::OnUpdateComposition(nsWindow* aWindow)
+{
+ if (!gIMMHandler) {
+ return;
+ }
+
+ if (aWindow->PluginHasFocus()) {
+ 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->mSelection.Update(aIMENotification);
+ }
+}
+
+// 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.
+
+ // When a plug-in has focus, we should dispatch the IME events to
+ // the plug-in at first.
+ if (aWindow->PluginHasFocus()) {
+ bool ret = false;
+ if (ProcessMessageForPlugin(aWindow, msg, wParam, lParam, ret, aResult)) {
+ return ret;
+ }
+ }
+
+ 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;
+ };
+}
+
+// static
+bool
+IMMHandler::ProcessMessageForPlugin(nsWindow* aWindow,
+ UINT msg,
+ WPARAM& wParam,
+ LPARAM& lParam,
+ bool& aRet,
+ MSGResult& aResult)
+{
+ aResult.mResult = 0;
+ aResult.mConsumed = false;
+ switch (msg) {
+ case WM_INPUTLANGCHANGEREQUEST:
+ case WM_INPUTLANGCHANGE:
+ aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
+ aRet = ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_CHAR:
+ EnsureHandlerInstance();
+ aRet = gIMMHandler->OnIMECharOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_SETCONTEXT:
+ aRet = OnIMESetContextOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_CHAR:
+ if (!gIMMHandler) {
+ return true;
+ }
+ aRet = gIMMHandler->OnCharOnPlugin(aWindow, wParam, lParam, aResult);
+ return true;
+ case WM_IME_COMPOSITIONFULL:
+ case WM_IME_CONTROL:
+ case WM_IME_KEYDOWN:
+ case WM_IME_KEYUP:
+ case WM_IME_SELECT:
+ aResult.mConsumed =
+ aWindow->DispatchPluginEvent(msg, wParam, lParam, false);
+ aRet = true;
+ return true;
+ case WM_IME_REQUEST:
+ // Our plugin implementation is alwasy OOP. So WM_IME_REQUEST doesn't
+ // allow that parameter is pointer and shouldn't handle into Gecko.
+ aRet = false;
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ * message handlers
+ ****************************************************************************/
+
+void
+IMMHandler::OnInputLangChange(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnInputLangChange, hWnd=%08x, wParam=%08x, lParam=%08x",
+ 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(gIMMLog, LogLevel::Info,
+ ("OnIMEStartComposition, hWnd=%08x, 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(gIMMLog, LogLevel::Info,
+ ("OnIMEComposition, hWnd=%08x, lParam=%08x, 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(gIMMLog, LogLevel::Info,
+ ("OnIMEEndComposition, hWnd=%08x, 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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("OnIMEChar, hWnd=%08x, char=%08x",
+ 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(gIMMLog, LogLevel::Info,
+ ("OnIMECompositionFull, hWnd=%08x",
+ 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(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CHANGECANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSECANDIDATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CLOSECANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_CLOSESTATUSWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_CLOSESTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_GUIDELINE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_GUIDELINE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_OPENCANDIDATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_OPENCANDIDATE, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_OPENSTATUSWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_OPENSTATUSWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCANDIDATEPOS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCANDIDATEPOS, lParam=%08x",
+ aWindow->GetWindowHandle(), lParam));
+ break;
+ case IMN_SETCOMPOSITIONFONT:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONFONT",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCOMPOSITIONWINDOW:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCOMPOSITIONWINDOW",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETCONVERSIONMODE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETCONVERSIONMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETOPENSTATUS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETOPENSTATUS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSENTENCEMODE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETSENTENCEMODE",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_SETSTATUSWINDOWPOS:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, IMN_SETSTATUSWINDOWPOS",
+ aWindow->GetWindowHandle()));
+ break;
+ case IMN_PRIVATE:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMENotify, hWnd=%08x, 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(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_RECONVERTSTRING",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleReconvert(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_QUERYCHARPOSITION:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_QUERYCHARPOSITION",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed =
+ HandleQueryCharPosition(aWindow, lParam, &aResult.mResult);
+ return true;
+ case IMR_DOCUMENTFEED:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, IMR_DOCUMENTFEED",
+ aWindow->GetWindowHandle()));
+ aResult.mConsumed = HandleDocumentFeed(aWindow, lParam, &aResult.mResult);
+ return true;
+ default:
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMERequest, hWnd=%08x, wParam=%08x",
+ aWindow->GetWindowHandle(), wParam));
+ aResult.mConsumed = false;
+ return true;
+ }
+}
+
+// static
+bool
+IMMHandler::OnIMESelect(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESelect, hWnd=%08x, wParam=%08x, lParam=%08x",
+ 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(gIMMLog, LogLevel::Info,
+ ("OnIMESetContext, hWnd=%08x, %s, lParam=%08x",
+ 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(gIMMLog, LogLevel::Info,
+ ("OnIMESetContext, hWnd=%08x is top level window"));
+ 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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("OnChar, aWindow=%p, wParam=%08x, lParam=%08x, "
+ "recorded: wParam=%08x, lParam=%08x",
+ 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;
+}
+
+/****************************************************************************
+ * message handlers for plug-in
+ ****************************************************************************/
+
+void
+IMMHandler::OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
+ mIsComposingOnPlugin = true;
+ mDispatcher = GetTextEventDispatcherFor(aWindow);
+ mComposingWindow = aWindow;
+ IMEContext context(aWindow);
+ SetIMERelatedWindowsPosOnPlugin(aWindow, context);
+ // On widnowless plugin, we should assume that the focused editor is always
+ // in horizontal writing mode.
+ AdjustCompositionFont(aWindow, context, WritingMode());
+}
+
+void
+IMMHandler::OnIMECompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMECompositionOnPlugin, hWnd=%08x, lParam=%08x, "
+ "mIsComposingOnPlugin=%s, GCS_RESULTSTR=%s, GCS_COMPSTR=%s, "
+ "GCS_COMPATTR=%s, GCS_COMPCLAUSE=%s, GCS_CURSORPOS=%s",
+ aWindow->GetWindowHandle(), lParam, GetBoolName(mIsComposingOnPlugin),
+ GetBoolName(lParam & GCS_RESULTSTR), GetBoolName(lParam & GCS_COMPSTR),
+ GetBoolName(lParam & GCS_COMPATTR), GetBoolName(lParam & GCS_COMPCLAUSE),
+ GetBoolName(lParam & GCS_CURSORPOS)));
+ // We should end composition if there is a committed string.
+ if (IS_COMMITTING_LPARAM(lParam)) {
+ mIsComposingOnPlugin = false;
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+ return;
+ }
+ // Continue composition if there is still a string being composed.
+ if (IS_COMPOSING_LPARAM(lParam)) {
+ mIsComposingOnPlugin = true;
+ mDispatcher = GetTextEventDispatcherFor(aWindow);
+ mComposingWindow = aWindow;
+ IMEContext context(aWindow);
+ SetIMERelatedWindowsPosOnPlugin(aWindow, context);
+ }
+}
+
+void
+IMMHandler::OnIMEEndCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMEEndCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s",
+ aWindow->GetWindowHandle(), GetBoolName(mIsComposingOnPlugin)));
+
+ mIsComposingOnPlugin = false;
+ mComposingWindow = nullptr;
+ mDispatcher = nullptr;
+
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+ }
+}
+
+bool
+IMMHandler::OnIMECharOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMECharOnPlugin, hWnd=%08x, char=%08x, scancode=%08x",
+ aWindow->GetWindowHandle(), wParam, lParam));
+
+ aResult.mConsumed =
+ aWindow->DispatchPluginEvent(WM_IME_CHAR, wParam, lParam, true);
+
+ if (!aResult.mConsumed) {
+ // Record the WM_CHAR messages which are going to be coming.
+ EnsureHandlerInstance();
+ EnqueueIMECharRecords(wParam, lParam);
+ }
+ return true;
+}
+
+// static
+bool
+IMMHandler::OnIMESetContextOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnIMESetContextOnPlugin, hWnd=%08x, %s, lParam=%08x",
+ aWindow->GetWindowHandle(), wParam ? "Active" : "Deactive", lParam));
+
+ // If the IME context becomes active on a plug-in, we should commit
+ // our composition. And also we should cancel the composition on new
+ // window. Note that if IsTopLevelWindowOfComposition(aWindow) returns
+ // true, we should ignore the message here, see the comment in
+ // OnIMESetContext() for the detail.
+ if (wParam && gIMMHandler && !IsTopLevelWindowOfComposition(aWindow)) {
+ if (gIMMHandler->CommitCompositionOnPreviousWindow(aWindow)) {
+ CancelComposition(aWindow);
+ }
+ }
+
+ // Dispatch message to the plug-in.
+ // XXX When a windowless plug-in gets focus, we should send
+ // WM_IME_SETCONTEXT
+ aWindow->DispatchPluginEvent(WM_IME_SETCONTEXT, wParam, lParam, false);
+
+ // We should send WM_IME_SETCONTEXT to the DefWndProc here. It shouldn't
+ // be received on ancestor windows, see OnIMESetContext() for the detail.
+ aResult.mResult = ::DefWindowProc(aWindow->GetWindowHandle(),
+ WM_IME_SETCONTEXT, wParam, lParam);
+
+ // Don't synchronously dispatch the pending events when we receive
+ // WM_IME_SETCONTEXT because we get it during plugin destruction.
+ // (bug 491848)
+ aResult.mConsumed = true;
+ return true;
+}
+
+bool
+IMMHandler::OnCharOnPlugin(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult)
+{
+ NS_WARNING("OnCharOnPlugin");
+ if (mIsComposing) {
+ aWindow->NotifyIME(REQUEST_TO_COMMIT_COMPOSITION);
+ return true;
+ }
+
+ // We should never consume char message on windowless plugin.
+ aResult.mConsumed = false;
+ if (IsIMECharRecordsEmpty()) {
+ return false;
+ }
+
+ WPARAM recWParam;
+ LPARAM recLParam;
+ DequeueIMECharRecords(recWParam, recLParam);
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("OnCharOnPlugin, aWindow=%p, wParam=%08x, lParam=%08x, "
+ "recorded: wParam=%08x, lParam=%08x",
+ 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();
+ }
+ // WM_CHAR on plug-in is always handled by nsWindow.
+ return false;
+}
+
+/****************************************************************************
+ * others
+ ****************************************************************************/
+
+TextEventDispatcher*
+IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow)
+{
+ return aWindow == mComposingWindow && mDispatcher ?
+ mDispatcher.get() : aWindow->GetTextEventDispatcher();
+}
+
+void
+IMMHandler::HandleStartComposition(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ NS_PRECONDITION(!mIsComposing,
+ "HandleStartComposition is called but mIsComposing is TRUE");
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return;
+ }
+
+ AdjustCompositionFont(aWindow, aContext, selection.mWritingMode);
+
+ mCompositionStart = selection.mOffset;
+ mCursorPosition = NO_IME_CARET;
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("HandleStartComposition, FAILED, due to "
+ "TextEventDispatcher::StartComposition() failure"));
+ return;
+ }
+
+ mIsComposing = true;
+ mComposingWindow = aWindow;
+ mDispatcher = dispatcher;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleStartComposition, START composition, mCompositionStart=%ld",
+ 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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPSTR"));
+
+ nsAutoString previousCompositionString(mCompositionString);
+ GetCompositionString(aContext, GCS_COMPSTR, mCompositionString);
+
+ if (!IS_COMPOSING_LPARAM(lParam)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPCLAUSE, mClauseLength=%ld",
+ 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(gIMMLog, LogLevel::Info,
+ ("HandleComposition, GCS_COMPATTR, mAttributeLength=%ld",
+ 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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("HandleEndComposition(aWindow=0x%p, aCommitString=0x%p (\"%s\"))",
+ aWindow, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcherFor(aWindow);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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);
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return false;
+ }
+
+ uint32_t len = selection.Length();
+ uint32_t needSize = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
+
+ if (!pReconv) {
+ // Return need size to reconvert.
+ if (len == 0) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, There are not selected text"));
+ return false;
+ }
+ *oResult = needSize;
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, succeeded, result=%ld",
+ *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, FAILED, pReconv->dwSize=%ld, needSize=%ld",
+ 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;
+
+ ::CopyMemory(reinterpret_cast<LPVOID>(lParam + sizeof(RECONVERTSTRING)),
+ selection.mString.get(), len * sizeof(WCHAR));
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("HandleReconvert, SUCCEEDED, pReconv=%s, result=%ld",
+ 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(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, FAILED, due to pCharPosition is null"));
+ return false;
+ }
+ if (pCharPosition->dwSize < sizeof(IMECHARPOSITION)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, pCharPosition->dwSize=%ld, "
+ "sizeof(IMECHARPOSITION)=%ld",
+ pCharPosition->dwSize, sizeof(IMECHARPOSITION)));
+ return false;
+ }
+ if (::GetFocus() != aWindow->GetWindowHandle()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleReconvert, FAILED, ::GetFocus()=%08x, OurWindowHandle=%08x",
+ ::GetFocus(), aWindow->GetWindowHandle()));
+ return false;
+ }
+ if (pCharPosition->dwCharPos > len) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, FAILED, pCharPosition->dwCharPos=%ld, "
+ "len=%ld",
+ 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 editorRect(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(editorRect);
+ DispatchEvent(aWindow, editorRect);
+ if (NS_WARN_IF(!editorRect.mSucceeded)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleQueryCharPosition, eQueryEditorRect failed"));
+ ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+ } else {
+ LayoutDeviceIntRect editorRectInWindow = editorRect.mReply.mRect;
+ nsWindow* window = editorRect.mReply.mFocusedWidget ?
+ static_cast<nsWindow*>(editorRect.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(gIMMLog, LogLevel::Info,
+ ("HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
+ "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
+ "bottom=%d } }",
+ 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) {
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ return false;
+ }
+ targetOffset = int32_t(selection.mOffset);
+ targetLength = int32_t(selection.Length());
+ } 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(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to the selection is out of "
+ "range"));
+ return false;
+ }
+
+ // Get all contents of the focused editor.
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, aWindow);
+ textContent.InitForQueryTextContent(0, UINT32_MAX);
+ aWindow->InitEvent(textContent, &point);
+ DispatchEvent(aWindow, textContent);
+ if (!textContent.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure"));
+ return false;
+ }
+
+ nsAutoString str(textContent.mReply.mString);
+ if (targetOffset > int32_t(str.Length())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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 = str.RFind("\n", false, targetOffset, -1) + 1;
+ int32_t paragraphEnd =
+ str.Find("\r", false, targetOffset + targetLength, -1);
+ 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(gIMMLog, LogLevel::Info,
+ ("HandleDocumentFeed, succeeded, result=%ld",
+ *oResult));
+ return true;
+ }
+
+ if (pReconv->dwSize < needSize) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, pReconv->dwSize=%ld, needSize=%ld",
+ 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(gIMMLog, LogLevel::Error,
+ ("HandleDocumentFeed, FAILED, due to 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(gIMMLog, LogLevel::Info,
+ ("HandleDocumentFeed, SUCCEEDED, pReconv=%s, result=%ld",
+ GetReconvertStringLog(pReconv).get(), *oResult));
+
+ return true;
+}
+
+bool
+IMMHandler::CommitCompositionOnPreviousWindow(nsWindow* aWindow)
+{
+ if (!mComposingWindow || mComposingWindow == aWindow) {
+ return false;
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, mClauseArray.Length()=0"));
+ rv =dispatcher->SetPendingComposition(mCompositionString, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, mClauseArray[%ld]=%lu. "
+ "This is larger than mCompositionString.Length()=%lu",
+ i + 1, current, mCompositionString.Length()));
+ current = int32_t(mCompositionString.Length());
+ }
+
+ uint32_t length = current - lastOffset;
+ if (NS_WARN_IF(lastOffset >= mAttributeArray.Length())) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("DispatchCompositionChangeEvent, FAILED due to"
+ "TextEventDispatcher::AppendClauseToPendingComposition() failure"));
+ return;
+ }
+
+ lastOffset = current;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, index=%ld, rangeType=%s, "
+ "range length=%lu",
+ i, ToChar(textRangeType), length));
+ }
+ }
+
+ if (mCursorPosition == NO_IME_CARET) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("DispatchCompositionChangeEvent, no caret"));
+ } else {
+ uint32_t cursor = static_cast<uint32_t>(mCursorPosition);
+ if (cursor > mCompositionString.Length()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("CreateTextRangeArray, mCursorPosition=%ld. "
+ "This is larger than mCompositionString.Length()=%lu",
+ 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(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Info,
+ ("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 nsAFlatString& 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(gIMMLog, LogLevel::Error,
+ ("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);
+
+ Selection& selection = GetSelection();
+ if (!selection.EnsureValidSelection(aWindow)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("GetCharacterRectOfSelectedTextAt, FAILED, due to "
+ "Selection::EnsureValidSelection() failure"));
+ 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.
+ uint32_t targetLength =
+ mIsComposing ? mCompositionString.Length() : selection.Length();
+ if (NS_WARN_IF(aOffset > targetLength)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("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 (selection.Collapsed()) {
+ 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 charRect(true, eQueryTextRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ charRect.InitForQueryTextRect(aOffset, 1, options);
+ aWindow->InitEvent(charRect, &point);
+ DispatchEvent(aWindow, charRect);
+ if (charRect.mSucceeded) {
+ aCharRect = charRect.mReply.mRect;
+ if (aWritingMode) {
+ *aWritingMode = charRect.GetWritingMode();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Debug,
+ ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, "
+ "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "charRect.GetWritingMode()=%s",
+ aOffset, aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height,
+ GetWritingModeName(charRect.GetWritingMode()).get()));
+ return true;
+ }
+ }
+
+ return GetCaretRect(aWindow, aCharRect, aWritingMode);
+}
+
+bool
+IMMHandler::GetCaretRect(nsWindow* aWindow,
+ LayoutDeviceIntRect& aCaretRect,
+ WritingMode* aWritingMode)
+{
+ LayoutDeviceIntPoint point(0, 0);
+
+ WidgetQueryContentEvent caretRect(true, eQueryCaretRect, aWindow);
+ WidgetQueryContentEvent::Options options;
+ options.mRelativeToInsertionPoint = true;
+ caretRect.InitForQueryCaretRect(0, options);
+ aWindow->InitEvent(caretRect, &point);
+ DispatchEvent(aWindow, caretRect);
+ if (!caretRect.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, FAILED, due to eQueryCaretRect failure"));
+ return false;
+ }
+ aCaretRect = caretRect.mReply.mRect;
+ if (aWritingMode) {
+ *aWritingMode = caretRect.GetWritingMode();
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("GetCaretRect, SUCCEEDED, "
+ "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+ "caretRect.GetWritingMode()=%s",
+ aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height,
+ GetWritingModeName(caretRect.GetWritingMode()).get()));
+ return true;
+}
+
+bool
+IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ LayoutDeviceIntRect r;
+ // Get first character rect of current a normal selected text or a composing
+ // string.
+ WritingMode writingMode;
+ bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode);
+ NS_ENSURE_TRUE(ret, false);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect firstSelectedCharRect;
+ ResolveIMECaretPos(toplevelWindow, r, 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.
+ LayoutDeviceIntRect caretRect(firstSelectedCharRect);
+ if (GetCaretRect(aWindow, r)) {
+ ResolveIMECaretPos(toplevelWindow, r, aWindow, caretRect);
+ } else {
+ NS_WARNING("failed to get caret rect");
+ caretRect.width = 1;
+ }
+ if (!mNativeCaretIsCreated) {
+ mNativeCaretIsCreated = ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
+ caretRect.width, caretRect.height);
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, mNativeCaretIsCreated=%s, "
+ "width=%ld, height=%ld",
+ GetBoolName(mNativeCaretIsCreated), caretRect.width, caretRect.height));
+ }
+ ::SetCaretPos(caretRect.x, caretRect.y);
+
+ if (ShouldDrawCompositionStringOurselves()) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("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(gIMMLog, LogLevel::Error,
+ ("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(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
+ "ptCurrentPos={ x=%d, y=%d }, "
+ "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
+ "writingMode=%s",
+ candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
+ candForm.rcArea.left, candForm.rcArea.top,
+ candForm.rcArea.right, candForm.rcArea.bottom,
+ GetWritingModeName(writingMode).get()));
+ ::ImmSetCandidateWindow(aContext.get(), &candForm);
+ } else {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("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::SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
+ const IMEContext& aContext)
+{
+ WidgetQueryContentEvent editorRectEvent(true, eQueryEditorRect, aWindow);
+ aWindow->InitEvent(editorRectEvent);
+ DispatchEvent(aWindow, editorRectEvent);
+ if (!editorRectEvent.mSucceeded) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPosOnPlugin, "
+ "FAILED, due to eQueryEditorRect failure"));
+ return;
+ }
+
+ // Clip the plugin rect by the client rect of the window because composition
+ // window needs to be specified the position in the client area.
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ LayoutDeviceIntRect pluginRectInScreen =
+ editorRectEvent.mReply.mRect + toplevelWindow->WidgetToScreenOffset();
+ LayoutDeviceIntRect winRectInScreen = aWindow->GetClientBounds();
+ // composition window cannot be positioned on the edge of client area.
+ winRectInScreen.width--;
+ winRectInScreen.height--;
+ LayoutDeviceIntRect clippedPluginRect;
+ clippedPluginRect.x =
+ std::min(std::max(pluginRectInScreen.x, winRectInScreen.x),
+ winRectInScreen.XMost());
+ clippedPluginRect.y =
+ std::min(std::max(pluginRectInScreen.y, winRectInScreen.y),
+ winRectInScreen.YMost());
+ int32_t xMost = std::min(pluginRectInScreen.XMost(), winRectInScreen.XMost());
+ int32_t yMost = std::min(pluginRectInScreen.YMost(), winRectInScreen.YMost());
+ clippedPluginRect.width = std::max(0, xMost - clippedPluginRect.x);
+ clippedPluginRect.height = std::max(0, yMost - clippedPluginRect.y);
+ clippedPluginRect -= aWindow->WidgetToScreenOffset();
+
+ // Cover the plugin with native caret. This prevents IME's window and plugin
+ // overlap.
+ if (mNativeCaretIsCreated) {
+ ::DestroyCaret();
+ }
+ mNativeCaretIsCreated =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr,
+ clippedPluginRect.width, clippedPluginRect.height);
+ ::SetCaretPos(clippedPluginRect.x, clippedPluginRect.y);
+
+ // Set the composition window to bottom-left of the clipped plugin.
+ // As far as we know, there is no IME for RTL language. Therefore, this code
+ // must not need to take care of RTL environment.
+ COMPOSITIONFORM compForm;
+ compForm.dwStyle = CFS_POINT;
+ compForm.ptCurrentPos.x = clippedPluginRect.BottomLeft().x;
+ compForm.ptCurrentPos.y = clippedPluginRect.BottomLeft().y;
+ if (!::ImmSetCompositionWindow(aContext.get(), &compForm)) {
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("SetIMERelatedWindowsPosOnPlugin, "
+ "FAILED, due to ::ImmSetCompositionWindow() failure"));
+ return;
+ }
+}
+
+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 =
+ Preferences::GetString("intl.imm.composition_font");
+
+ // 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(gIMMLog, LogLevel::Error,
+ ("AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
+ sCompositionFont.AssignLiteral("System");
+ } else {
+ // The font face is typically, "System".
+ sCompositionFont.Assign(defaultLogFont.lfFaceName);
+ }
+ }
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("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";
+ sCompositionFontForJapanist2003 =
+ Preferences::GetString(kCompositionFontForJapanist2003);
+ // 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(gIMMLog, LogLevel::Error,
+ ("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 (!aWindow->PluginHasFocus() &&
+ aWritingMode.IsVertical() && IsVerticalWritingSupported()) {
+ SetVerticalFontToLogFont(
+ IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
+ sCompositionFont, logFont);
+ } else {
+ SetHorizontalFontToLogFont(
+ IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
+ sCompositionFont, logFont);
+ }
+ MOZ_LOG(gIMMLog, LogLevel::Warning,
+ ("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 WidgetMouseEventBase::eLeftButton:
+ button = IMEMOUSE_LDOWN;
+ break;
+ case WidgetMouseEventBase::eMiddleButton:
+ button = IMEMOUSE_MDOWN;
+ break;
+ case WidgetMouseEventBase::eRightButton:
+ button = IMEMOUSE_RDOWN;
+ break;
+ default:
+ return NS_OK;
+ }
+
+ // calcurate positioning and offset
+ // char : JCH1|JCH2|JCH3
+ // offset: 0011 1122 2233
+ // positioning: 2301 2301 2301
+ nsIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
+ nsIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
+ 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(gIMMLog, LogLevel::Info,
+ ("OnMouseButtonEvent, x,y=%ld,%ld, offset=%ld, positioning=%ld",
+ cursorPos.x, cursorPos.y, 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(gIMMLog, LogLevel::Info,
+ ("OnKeyDownEvent, hWnd=%08x, wParam=%08x, lParam=%08x",
+ 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;
+ }
+}
+
+// static
+void
+IMMHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+ // Hack for ATOK 2011 - 2016 (Japanese IME). They refer native caret
+ // position at deciding candidate window position. Note that we cannot
+ // check active IME since TIPs are wrapped and hidden by CUAS.
+ if (aWindow->PluginHasFocus()) {
+ // We cannot retrieve proper character height from plugin. Therefore,
+ // we should assume that the caret height is always 20px since if less than
+ // this height, candidate window may overlap with composition string when
+ // there is no enough space under composition string to show candidate
+ // window.
+ static const int32_t kCaretHeight = 20;
+ if (sNativeCaretIsCreatedForPlugin) {
+ ::DestroyCaret();
+ }
+ sNativeCaretIsCreatedForPlugin =
+ ::CreateCaret(aWindow->GetWindowHandle(), nullptr, 0, kCaretHeight);
+ if (sNativeCaretIsCreatedForPlugin) {
+ LayoutDeviceIntPoint caretPosition(aForm->ptCurrentPos.x,
+ aForm->ptCurrentPos.y - kCaretHeight);
+ nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
+ if (toplevelWindow && toplevelWindow != aWindow) {
+ caretPosition += toplevelWindow->WidgetToScreenOffset();
+ caretPosition -= aWindow->WidgetToScreenOffset();
+ }
+ ::SetCaretPos(caretPosition.x, caretPosition.y);
+ }
+ }
+ IMEContext context(aWindow);
+ ::ImmSetCandidateWindow(context.get(), aForm);
+}
+
+// staitc
+void
+IMMHandler::DefaultProcOfPluginEvent(nsWindow* aWindow, const NPEvent* aEvent)
+{
+ switch (aEvent->event) {
+ case WM_IME_STARTCOMPOSITION:
+ EnsureHandlerInstance();
+ gIMMHandler->OnIMEStartCompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ break;
+
+ case WM_IME_COMPOSITION:
+ if (gIMMHandler) {
+ gIMMHandler->OnIMECompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ }
+ break;
+
+ case WM_IME_ENDCOMPOSITION:
+ if (gIMMHandler) {
+ gIMMHandler->OnIMEEndCompositionOnPlugin(aWindow, aEvent->wParam,
+ aEvent->lParam);
+ }
+ break;
+ }
+}
+
+/******************************************************************************
+ * IMMHandler::Selection
+ ******************************************************************************/
+
+bool
+IMMHandler::Selection::IsValid() const
+{
+ if (!mIsValid || NS_WARN_IF(mOffset == UINT32_MAX)) {
+ return false;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + Length();
+ return endOffset.isValid();
+}
+
+bool
+IMMHandler::Selection::Update(const IMENotification& aIMENotification)
+{
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mString = aIMENotification.mSelectionChangeData.String();
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+ mIsValid = true;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("Selection::Update, aIMENotification={ mSelectionChangeData={ "
+ "mOffset=%u, mLength=%u, GetWritingMode()=%s } }",
+ mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
+
+ if (!IsValid()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Update, FAILED, due to invalid range"));
+ Clear();
+ return false;
+ }
+ return true;
+}
+
+bool
+IMMHandler::Selection::Init(nsWindow* aWindow)
+{
+ Clear();
+
+ WidgetQueryContentEvent selection(true, eQuerySelectedText, aWindow);
+ LayoutDeviceIntPoint point(0, 0);
+ aWindow->InitEvent(selection, &point);
+ DispatchEvent(aWindow, selection);
+ if (NS_WARN_IF(!selection.mSucceeded)) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to eQuerySelectedText failure"));
+ return false;
+ }
+ // If the window is destroyed during querying selected text, we shouldn't
+ // do anymore.
+ if (aWindow->Destroyed()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to the widget destroyed"));
+ return false;
+ }
+
+ mOffset = selection.mReply.mOffset;
+ mString = selection.mReply.mString;
+ mWritingMode = selection.GetWritingMode();
+ mIsValid = true;
+
+ MOZ_LOG(gIMMLog, LogLevel::Info,
+ ("Selection::Init, selection={ mReply={ mOffset=%u, "
+ "mString.Length()=%u, mWritingMode=%s } }",
+ mOffset, mString.Length(), GetWritingModeName(mWritingMode).get()));
+
+ if (!IsValid()) {
+ MOZ_LOG(gIMMLog, LogLevel::Error,
+ ("Selection::Init, FAILED, due to invalid range"));
+ Clear();
+ return false;
+ }
+ return true;
+}
+
+bool
+IMMHandler::Selection::EnsureValidSelection(nsWindow* aWindow)
+{
+ if (IsValid()) {
+ return true;
+ }
+ return Init(aWindow);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/IMMHandler.h b/widget/windows/IMMHandler.h
new file mode 100644
index 0000000000..f3120cfec7
--- /dev/null
+++ b/widget/windows/IMMHandler.h
@@ -0,0 +1,495 @@
+/* -*- 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 "nscore.h"
+#include <windows.h>
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "nsRect.h"
+#include "WritingModes.h"
+#include "npapi.h"
+
+class nsWindow;
+class nsWindowBase;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class IMEContext final
+{
+public:
+ IMEContext()
+ : mWnd(nullptr)
+ , mIMC(nullptr)
+ {
+ }
+
+ explicit IMEContext(HWND aWnd);
+ explicit IMEContext(nsWindowBase* aWindowBase);
+
+ ~IMEContext()
+ {
+ Clear();
+ }
+
+ HIMC get() const
+ {
+ return mIMC;
+ }
+
+ void Init(HWND aWnd);
+ void Init(nsWindowBase* 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 nsIMEUpdatePreference GetIMEUpdatePreference();
+
+ // 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);
+ static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+ static void DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aEvent);
+
+protected:
+ static void EnsureHandlerInstance();
+
+ static bool IsComposingOnOurEditor();
+ static bool IsComposingOnPlugin();
+ static bool IsComposingWindow(nsWindow* aWindow);
+
+ static bool IsJapanist2003Active();
+ static bool IsGoogleJapaneseInputActive();
+
+ 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);
+ static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
+ WPARAM &wParam, LPARAM &lParam,
+ bool &aRet, 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);
+ void OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam, LPARAM lParam);
+ bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
+ void OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnCharOnPlugin(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 OnIMESetContextOnPlugin(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 nsAFlatString& aStr,
+ UINT aCodePage,
+ nsACString& aANSIStr);
+
+ bool SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext);
+ void SetIMERelatedWindowsPosOnPlugin(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;
+ InfallibleTArray<uint32_t> mClauseArray;
+ InfallibleTArray<uint8_t> mAttributeArray;
+
+ int32_t mCursorPosition;
+ uint32_t mCompositionStart;
+
+ struct Selection
+ {
+ nsString mString;
+ uint32_t mOffset;
+ mozilla::WritingMode mWritingMode;
+ bool mIsValid;
+
+ Selection()
+ : mOffset(UINT32_MAX)
+ , mIsValid(false)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mIsValid = false;
+ }
+ uint32_t Length() const { return mString.Length(); }
+ bool Collapsed() const { return !Length(); }
+
+ bool IsValid() const;
+ bool Update(const IMENotification& aIMENotification);
+ bool Init(nsWindow* aWindow);
+ bool EnsureValidSelection(nsWindow* aWindow);
+ private:
+ Selection(const Selection& aOther) = delete;
+ void operator =(const Selection& aOther) = delete;
+ };
+ // mSelection stores the latest selection data only when sHasFocus is true.
+ // Don't access mSelection directly. You should use GetSelection() for
+ // getting proper state.
+ Selection mSelection;
+
+ Selection& GetSelection()
+ {
+ // When IME has focus, mSelection is automatically updated by
+ // NOTIFY_IME_OF_SELECTION_CHANGE.
+ if (sHasFocus) {
+ return mSelection;
+ }
+ // 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 Selection sTempSelection;
+ sTempSelection.Clear();
+ return sTempSelection;
+ }
+
+ bool mIsComposing;
+ bool mIsComposingOnPlugin;
+ bool mNativeCaretIsCreated;
+
+ static mozilla::WritingMode sWritingModeOfCompositionFont;
+ static nsString sIMEName;
+ static UINT sCodePage;
+ static DWORD sIMEProperty;
+ static DWORD sIMEUIProperty;
+ static bool sAssumeVerticalWritingModeNotSupported;
+ static bool sHasFocus;
+ static bool sNativeCaretIsCreatedForPlugin;
+};
+
+} // 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..685eaf5ca6
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.cpp
@@ -0,0 +1,41 @@
+/* -*- 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 "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+/* static */ RefPtr<CompositorWidget>
+CompositorWidget::CreateLocal(const CompositorWidgetInitData& aInitData, nsIWidget* aWidget)
+{
+ return new InProcessWinCompositorWidget(aInitData, static_cast<nsWindow*>(aWidget));
+}
+
+InProcessWinCompositorWidget::InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData,
+ nsWindow* aWindow)
+ : WinCompositorWidget(aInitData),
+ mWindow(aWindow)
+{
+ MOZ_ASSERT(mWindow);
+}
+
+nsIWidget*
+InProcessWinCompositorWidget::RealWidget()
+{
+ return mWindow;
+}
+
+void
+InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver)
+{
+ if (RefPtr<CompositorVsyncDispatcher> cvd = mWindow->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/InProcessWinCompositorWidget.h b/widget/windows/InProcessWinCompositorWidget.h
new file mode 100644
index 0000000000..2ce6ba0be1
--- /dev/null
+++ b/widget/windows/InProcessWinCompositorWidget.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 widget_windows_InProcessCompositorWidgetParent_h
+#define widget_windows_InProcessCompositorWidgetParent_h
+
+#include "WinCompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace 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:
+ InProcessWinCompositorWidget(const CompositorWidgetInitData& aInitData, nsWindow* aWindow);
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ nsIWidget* RealWidget() override;
+
+private:
+ nsWindow* mWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_InProcessCompositorWidgetParent_h
diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp
new file mode 100644
index 0000000000..5383dda7c1
--- /dev/null
+++ b/widget/windows/InkCollector.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+* vim: set ts=2 sw=2 et tw=78:
+* 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 "InkCollector.h"
+
+// Msinkaut_i.c and Msinkaut.h should both be included
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms695519.aspx
+#include <msinkaut_i.c>
+
+StaticAutoPtr<InkCollector> InkCollector::sInkCollector;
+
+InkCollector::~InkCollector()
+{
+ Shutdown();
+ MOZ_ASSERT(!mCookie && !mEnabled && !mComInitialized
+ && !mMarshaller && !mInkCollector
+ && !mConnectionPoint && !mInkCollectorEvent);
+}
+
+void InkCollector::Initialize()
+{
+ // Possibly, we can use mConnectionPoint for checking,
+ // But if errors exist (perhaps COM object is unavailable),
+ // Initialize() will be called more times.
+ static bool sInkCollectorCreated = false;
+ if (sInkCollectorCreated) {
+ return;
+ }
+ sInkCollectorCreated = true;
+
+ // COM could get uninitialized due to previous initialization.
+ mComInitialized = SUCCEEDED(::CoInitialize(nullptr));
+
+ // Set up instance of InkCollectorEvent.
+ mInkCollectorEvent = new InkCollectorEvent();
+
+ // Set up a free threaded marshaler.
+ if (FAILED(::CoCreateFreeThreadedMarshaler(mInkCollectorEvent, getter_AddRefs(mMarshaller)))) {
+ return;
+ }
+
+ // Create the ink collector.
+ if (FAILED(::CoCreateInstance(CLSID_InkCollector, NULL, CLSCTX_INPROC_SERVER,
+ IID_IInkCollector, getter_AddRefs(mInkCollector)))) {
+ return;
+ }
+
+ // Set up connection between sink and InkCollector.
+ RefPtr<IConnectionPointContainer> connPointContainer;
+
+ // Get the connection point container.
+ if (SUCCEEDED(mInkCollector->QueryInterface(IID_IConnectionPointContainer,
+ getter_AddRefs(connPointContainer)))) {
+
+ // Find the connection point for Ink Collector events.
+ if (SUCCEEDED(connPointContainer->FindConnectionPoint(__uuidof(_IInkCollectorEvents),
+ getter_AddRefs(mConnectionPoint)))) {
+
+ // Hook up sink to connection point.
+ if (SUCCEEDED(mConnectionPoint->Advise(mInkCollectorEvent, &mCookie))) {
+ OnInitialize();
+ }
+ }
+ }
+}
+
+void InkCollector::Shutdown()
+{
+ Enable(false);
+ if (mConnectionPoint) {
+ // Remove the connection of the sink to the Ink Collector.
+ mConnectionPoint->Unadvise(mCookie);
+ mCookie = 0;
+ mConnectionPoint = nullptr;
+ }
+ mInkCollector = nullptr;
+ mMarshaller = nullptr;
+ mInkCollectorEvent = nullptr;
+
+ // Let uninitialization get handled in a place where it got inited.
+ if (mComInitialized) {
+ CoUninitialize();
+ mComInitialized = false;
+ }
+}
+
+void InkCollector::OnInitialize()
+{
+ // Suppress all events to do not allow performance decreasing.
+ // https://msdn.microsoft.com/en-us/library/ms820347.aspx
+ mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_AllEvents, VARIANT_FALSE);
+
+ // Sets a value that indicates whether an object or control has interest in a specified event.
+ mInkCollector->SetEventInterest(InkCollectorEventInterest::ICEI_CursorOutOfRange, VARIANT_TRUE);
+
+ // If the MousePointer property is set to IMP_Custom and the MouseIcon property is NULL,
+ // Then the ink collector no longer handles mouse cursor settings.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms700686.aspx
+ mInkCollector->put_MouseIcon(nullptr);
+ mInkCollector->put_MousePointer(InkMousePointer::IMP_Custom);
+
+ // This mode allows an ink collector to collect ink from any tablet attached to the Tablet PC.
+ // The Boolean value that indicates whether to use the mouse as an input device.
+ // If TRUE, the mouse is used for input.
+ // https://msdn.microsoft.com/en-us/library/ms820346.aspx
+ mInkCollector->SetAllTabletsMode(VARIANT_FALSE);
+
+ // Sets the value that specifies whether ink is rendered as it is drawn.
+ // VARIANT_TRUE to render ink as it is drawn on the display.
+ // VARIANT_FALSE to not have the ink appear on the display as strokes are made.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/dd314598.aspx
+ mInkCollector->put_DynamicRendering(VARIANT_FALSE);
+}
+
+// Sets a value that specifies whether the InkCollector object collects pen input.
+// This property must be set to FALSE before setting or
+// calling specific properties and methods of the object.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms701721.aspx
+void InkCollector::Enable(bool aNewState)
+{
+ if (aNewState != mEnabled) {
+ if (mInkCollector) {
+ if (SUCCEEDED(mInkCollector->put_Enabled(aNewState ? VARIANT_TRUE : VARIANT_FALSE))) {
+ mEnabled = aNewState;
+ } else {
+ NS_WARNING("InkCollector did not change status successfully");
+ }
+ } else {
+ NS_WARNING("InkCollector should be exist");
+ }
+ }
+}
+
+HWND InkCollector::GetTarget()
+{
+ return mTargetWindow;
+}
+
+void InkCollector::SetTarget(HWND aTargetWindow)
+{
+ NS_ASSERTION(aTargetWindow, "aTargetWindow should be exist");
+ if (aTargetWindow && (aTargetWindow != mTargetWindow)) {
+ Initialize();
+ if (mInkCollector) {
+ Enable(false);
+ if (SUCCEEDED(mInkCollector->put_hWnd((LONG_PTR)aTargetWindow))) {
+ mTargetWindow = aTargetWindow;
+ } else {
+ NS_WARNING("InkCollector did not change window property successfully");
+ }
+ Enable(true);
+ }
+ }
+}
+
+void InkCollector::ClearTarget()
+{
+ if (mTargetWindow && mInkCollector) {
+ Enable(false);
+ if (SUCCEEDED(mInkCollector->put_hWnd(0))) {
+ mTargetWindow = 0;
+ } else {
+ NS_WARNING("InkCollector did not clear window property successfully");
+ }
+ }
+}
+
+uint16_t InkCollector::GetPointerId()
+{
+ return mPointerId;
+}
+
+void InkCollector::SetPointerId(uint16_t aPointerId)
+{
+ mPointerId = aPointerId;
+}
+
+void InkCollector::ClearPointerId()
+{
+ mPointerId = 0;
+}
+
+// The display and the digitizer have quite different properties.
+// The display has CursorMustTouch, the mouse pointer alway touches the display surface.
+// The digitizer lists Integrated and HardProximity.
+// When the stylus is in the proximity of the tablet its movements are also detected.
+// An external tablet will only list HardProximity.
+bool InkCollectorEvent::IsHardProximityTablet(IInkTablet* aTablet) const
+{
+ if (aTablet) {
+ TabletHardwareCapabilities caps;
+ if (SUCCEEDED(aTablet->get_HardwareCapabilities(&caps))) {
+ return (TabletHardwareCapabilities::THWC_HardProximity & caps);
+ }
+ }
+ return false;
+}
+
+HRESULT __stdcall InkCollectorEvent::QueryInterface(REFIID aRiid, void **aObject)
+{
+ // Validate the input
+ if (!aObject) {
+ return E_POINTER;
+ }
+ HRESULT result = E_NOINTERFACE;
+ // This object supports IUnknown/IDispatch/IInkCollectorEvents
+ if ((IID_IUnknown == aRiid) ||
+ (IID_IDispatch == aRiid) ||
+ (DIID__IInkCollectorEvents == aRiid)) {
+ *aObject = this;
+ // AddRef should be called when we give info about interface
+ NS_ADDREF_THIS();
+ result = S_OK;
+ }
+ return result;
+}
+
+HRESULT InkCollectorEvent::Invoke(DISPID aDispIdMember, REFIID /*aRiid*/,
+ LCID /*aId*/, WORD /*wFlags*/,
+ DISPPARAMS* aDispParams, VARIANT* /*aVarResult*/,
+ EXCEPINFO* /*aExcepInfo*/, UINT* /*aArgErr*/)
+{
+ switch (aDispIdMember) {
+ case DISPID_ICECursorOutOfRange: {
+ if (aDispParams && aDispParams->cArgs) {
+ CursorOutOfRange(static_cast<IInkCursor*>(aDispParams->rgvarg[0].pdispVal));
+ }
+ break;
+ }
+ }
+ return S_OK;
+}
+
+void InkCollectorEvent::CursorOutOfRange(IInkCursor* aCursor) const
+{
+ IInkTablet* curTablet = nullptr;
+ if (FAILED(aCursor->get_Tablet(&curTablet))) {
+ return;
+ }
+ // All events should be suppressed except
+ // from tablets with hard proximity.
+ if (!IsHardProximityTablet(curTablet)) {
+ return;
+ }
+ // Notify current target window.
+ if (HWND targetWindow = InkCollector::sInkCollector->GetTarget()) {
+ ::SendMessage(targetWindow, MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER, 0, 0);
+ }
+}
diff --git a/widget/windows/InkCollector.h b/widget/windows/InkCollector.h
new file mode 100644
index 0000000000..9570eea2a1
--- /dev/null
+++ b/widget/windows/InkCollector.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+* vim: set ts=2 sw=2 et tw=78:
+* 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 InkCollector_h__
+#define InkCollector_h__
+
+#include <msinkaut.h>
+#include "mozilla/StaticPtr.h"
+
+#define MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER WM_USER + 0x83
+
+class InkCollectorEvent : public _IInkCollectorEvents
+{
+public:
+ // IUnknown
+ HRESULT __stdcall QueryInterface(REFIID aRiid, void **aObject);
+ virtual ULONG STDMETHODCALLTYPE AddRef() { return ++mRefCount; }
+ virtual ULONG STDMETHODCALLTYPE Release()
+ {
+ MOZ_ASSERT(mRefCount);
+ if (!--mRefCount) {
+ delete this;
+ return 0;
+ }
+ return mRefCount;
+ }
+
+protected:
+ // IDispatch
+ STDMETHOD(GetTypeInfoCount)(UINT* aInfo) { return E_NOTIMPL; }
+ STDMETHOD(GetTypeInfo)(UINT aInfo, LCID aId, ITypeInfo** aTInfo) { return E_NOTIMPL; }
+ STDMETHOD(GetIDsOfNames)(REFIID aRiid, LPOLESTR* aStrNames, UINT aNames,
+ LCID aId, DISPID* aDispId) { return E_NOTIMPL; }
+ STDMETHOD(Invoke)(DISPID aDispIdMember, REFIID aRiid,
+ LCID aId, WORD wFlags,
+ DISPPARAMS* aDispParams, VARIANT* aVarResult,
+ EXCEPINFO* aExcepInfo, UINT* aArgErr);
+
+ // InkCollectorEvent
+ void CursorOutOfRange(IInkCursor* aCursor) const;
+ bool IsHardProximityTablet(IInkTablet* aTablet) const;
+
+private:
+ uint32_t mRefCount = 0;
+};
+
+class InkCollector
+{
+public:
+ ~InkCollector();
+ void Shutdown();
+
+ HWND GetTarget();
+ void SetTarget(HWND aTargetWindow);
+ void ClearTarget();
+
+ uint16_t GetPointerId(); // 0 shows that there is no existing pen.
+ void SetPointerId(uint16_t aPointerId);
+ void ClearPointerId();
+
+ static StaticAutoPtr<InkCollector> sInkCollector;
+
+protected:
+ void Initialize();
+ void OnInitialize();
+ void Enable(bool aNewState);
+
+private:
+ RefPtr<IUnknown> mMarshaller;
+ RefPtr<IInkCollector> mInkCollector;
+ RefPtr<IConnectionPoint> mConnectionPoint;
+ RefPtr<InkCollectorEvent> mInkCollectorEvent;
+
+ HWND mTargetWindow = 0;
+ DWORD mCookie = 0;
+ bool mComInitialized = false;
+ bool mEnabled = false;
+
+ // This value holds the previous pointerId of the pen, and is used by the
+ // nsWindow when processing a MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER which
+ // indicates that a pen leaves the digitizer.
+
+ // TODO: If we move our implementation to window pointer input messages, then
+ // we no longer need this value, since the pointerId can be retrieved from the
+ // window message, please refer to
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/hh454916(v=vs.85).aspx
+
+ // NOTE: The pointerId of a pen shouldn't be 0 on a Windows platform, since 0
+ // is reserved of the mouse, please refer to
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
+ uint16_t mPointerId = 0;
+};
+
+#endif // InkCollector_h__
diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp
new file mode 100644
index 0000000000..566c41d4ab
--- /dev/null
+++ b/widget/windows/JumpListBuilder.cpp
@@ -0,0 +1,542 @@
+/* -*- 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 "JumpListBuilder.h"
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsWidgetsCID.h"
+#include "WinTaskbar.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/Preferences.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsIObserverService.h"
+
+#include "WinUtils.h"
+
+// 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 {
+
+static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID);
+static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID);
+static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID);
+
+// defined in WinTaskbar.cpp
+extern const wchar_t *gMozillaJumpListIDGeneric;
+
+bool JumpListBuilder::sBuildingList = false;
+const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
+
+NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
+#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
+
+JumpListBuilder::JumpListBuilder() :
+ mMaxItems(0),
+ mHasCommit(false)
+{
+ ::CoInitialize(nullptr);
+
+ CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr));
+
+ // Make a lazy thread for any IO
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ NS_LITERAL_CSTRING("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);
+ }
+}
+
+JumpListBuilder::~JumpListBuilder()
+{
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+ mJumpListMgr = nullptr;
+ ::CoUninitialize();
+}
+
+NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable)
+{
+ *aAvailable = false;
+
+ if (mJumpListMgr)
+ *aAvailable = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit)
+{
+ *aCommit = mHasCommit;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems)
+{
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aMaxItems = 0;
+
+ if (sBuildingList) {
+ *aMaxItems = mMaxItems;
+ return NS_OK;
+ }
+
+ IObjectArray *objArray;
+ if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ *aMaxItems = mMaxItems;
+
+ if (objArray)
+ objArray->Release();
+
+ mJumpListMgr->AbortList();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(removedItems);
+
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if(sBuildingList)
+ AbortListBuild();
+
+ IObjectArray *objArray;
+
+ // 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.
+ if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ if (objArray) {
+ TransferIObjectArrayToIMutableArray(objArray, removedItems);
+ objArray->Release();
+ }
+
+ RemoveIconCacheForItems(removedItems);
+
+ sBuildingList = true;
+ *_retval = true;
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// Ensures that we don't have old ICO files that aren't in our jump lists
+// anymore left over in the cache.
+nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items)
+{
+ NS_ENSURE_ARG_POINTER(items);
+
+ nsresult rv;
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+
+ //Obtain an IJumpListItem and get the type
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) {
+ continue;
+ }
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) {
+ continue;
+ }
+
+ // If the item is a shortcut, remove its associated icon if any
+ if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {
+ nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item);
+ if (shortcut) {
+ nsCOMPtr<nsIURI> uri;
+ rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv) && uri) {
+
+ // The local file path is stored inside the nsIURI
+ // Get the nsIURI spec which stores the local path for the icon to remove
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> event
+ = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec));
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ // The shortcut was generated from an IShellLinkW so IShellLinkW can
+ // only tell us what the original icon is and not the URI.
+ // So this field was used only temporarily as the actual icon file
+ // path. It should be cleared.
+ shortcut->SetFaviconPageUri(nullptr);
+ }
+ }
+ }
+
+ } // end for
+
+ return NS_OK;
+}
+
+// Ensures that we have no old ICO files left in the jump list cache
+nsresult JumpListBuilder::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<nsISimpleEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ bool hasMore = false;
+ if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> supp;
+ if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
+ break;
+
+ nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
+ 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 JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval)
+{
+ nsresult rv;
+
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ switch(aCatType) {
+ case nsIJumpListBuilder::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<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item)
+ continue;
+ // Check for separators
+ if (IsSeparator(item)) {
+ RefPtr<IShellLinkW> link;
+ rv = JumpListSeparator::GetSeparator(link);
+ if (NS_FAILED(rv))
+ return rv;
+ collection->AddObject(link);
+ continue;
+ }
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = JumpListShortcut::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 = mJumpListMgr->AddUserTasks(pArray);
+ if (SUCCEEDED(hr))
+ *_retval = true;
+ return NS_OK;
+ }
+ break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT:
+ {
+ if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT)))
+ *_retval = true;
+ return NS_OK;
+ }
+ break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT:
+ {
+ if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
+ *_retval = true;
+ return NS_OK;
+ }
+ break;
+ case nsIJumpListBuilder::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<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item)
+ continue;
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type)))
+ continue;
+ switch(type) {
+ case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR:
+ {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListSeparator::GetSeparator(shellItem);
+ if (NS_FAILED(rv))
+ return rv;
+ collection->AddObject(shellItem);
+ }
+ break;
+ case nsIJumpListItem::JUMPLIST_ITEM_LINK:
+ {
+ RefPtr<IShellItem2> shellItem;
+ rv = JumpListLink::GetShellItem(item, shellItem);
+ if (NS_FAILED(rv))
+ return rv;
+ collection->AddObject(shellItem);
+ }
+ break;
+ case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT:
+ {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListShortcut::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 = mJumpListMgr->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 JumpListBuilder::AbortListBuild()
+{
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mJumpListMgr->AbortList();
+ sBuildingList = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval)
+{
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HRESULT hr = mJumpListMgr->CommitList();
+ sBuildingList = false;
+
+ // XXX We might want some specific error data here.
+ if (SUCCEEDED(hr)) {
+ *_retval = true;
+ mHasCommit = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval)
+{
+ *_retval = false;
+
+ if (!mJumpListMgr)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if(sBuildingList)
+ AbortListBuild();
+
+ nsAutoString uid;
+ if (!WinTaskbar::GetAppUserModelID(uid))
+ return NS_OK;
+
+ if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get())))
+ *_retval = true;
+
+ return NS_OK;
+}
+
+/* internal */
+
+bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item)
+{
+ int16_t type;
+ item->GetType(&type);
+ if (NS_FAILED(item->GetType(&type)))
+ return false;
+
+ if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
+ return true;
+ return false;
+}
+
+// TransferIObjectArrayToIMutableArray - used in converting removed items
+// to our objects.
+nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems)
+{
+ NS_ENSURE_ARG_POINTER(objArray);
+ NS_ENSURE_ARG_POINTER(removedItems);
+
+ nsresult rv;
+
+ uint32_t count = 0;
+ objArray->GetCount(&count);
+
+ nsCOMPtr<nsIJumpListItem> item;
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ IShellLinkW * pLink = nullptr;
+ IShellItem * pItem = nullptr;
+
+ if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) {
+ nsCOMPtr<nsIJumpListShortcut> shortcut =
+ do_CreateInstance(kJumpListShortcutCID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+ rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut);
+ item = do_QueryInterface(shortcut);
+ }
+ else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) {
+ nsCOMPtr<nsIJumpListLink> link =
+ do_CreateInstance(kJumpListLinkCID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+ rv = JumpListLink::GetJumpListLink(pItem, link);
+ item = do_QueryInterface(link);
+ }
+
+ if (pLink)
+ pLink->Release();
+ if (pItem)
+ pItem->Release();
+
+ if (NS_SUCCEEDED(rv)) {
+ removedItems->AppendElement(item, false);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::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();
+ } 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..553ff765d0
--- /dev/null
+++ b/widget/windows/JumpListBuilder.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 __JumpListBuilder_h__
+#define __JumpListBuilder_h__
+
+#include <windows.h>
+
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN7
+// Needed for various com interfaces
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "nsString.h"
+#include "nsIMutableArray.h"
+
+#include "nsIJumpListBuilder.h"
+#include "nsIJumpListItem.h"
+#include "JumpListItem.h"
+#include "nsIObserver.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace widget {
+
+class JumpListBuilder : public nsIJumpListBuilder,
+ public nsIObserver
+{
+ virtual ~JumpListBuilder();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJUMPLISTBUILDER
+ NS_DECL_NSIOBSERVER
+
+ JumpListBuilder();
+
+protected:
+ static bool sBuildingList;
+
+private:
+ RefPtr<ICustomDestinationList> mJumpListMgr;
+ uint32_t mMaxItems;
+ bool mHasCommit;
+ nsCOMPtr<nsIThread> mIOThread;
+
+ bool IsSeparator(nsCOMPtr<nsIJumpListItem>& item);
+ nsresult TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems);
+ nsresult RemoveIconCacheForItems(nsIMutableArray *removedItems);
+ nsresult RemoveIconCacheForAllItems();
+
+ friend class WinTaskbar;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __JumpListBuilder_h__ */
+
diff --git a/widget/windows/JumpListItem.cpp b/widget/windows/JumpListItem.cpp
new file mode 100644
index 0000000000..57dff6466e
--- /dev/null
+++ b/widget/windows/JumpListItem.cpp
@@ -0,0 +1,630 @@
+/* -*- 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 "JumpListItem.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 "nsCycleCollectionParticipant.h"
+#include "mozilla/Preferences.h"
+#include "JumpListBuilder.h"
+#include "WinUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+// ISUPPORTS Impl's
+NS_IMPL_ISUPPORTS(JumpListItem,
+ nsIJumpListItem)
+
+NS_IMPL_ISUPPORTS_INHERITED(JumpListSeparator,
+ JumpListItem,
+ nsIJumpListSeparator)
+
+NS_IMPL_ISUPPORTS_INHERITED(JumpListLink,
+ JumpListItem,
+ nsIJumpListLink)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut)
+ NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut)
+NS_INTERFACE_MAP_END_INHERITING(JumpListItem)
+
+NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut)
+
+NS_IMETHODIMP JumpListItem::GetType(int16_t *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mItemType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListItem::Equals(nsIJumpListItem *aItem, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::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 JumpListLink::GetUri(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::SetUri(nsIURI *aURI)
+{
+ mURI = aURI;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::SetUriTitle(const nsAString &aUriTitle)
+{
+ mUriTitle.Assign(aUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::GetUriTitle(nsAString& aUriTitle)
+{
+ aUriTitle.Assign(mUriTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::GetUriHash(nsACString& aUriHash)
+{
+ if (!mURI)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, aUriHash);
+}
+
+NS_IMETHODIMP JumpListLink::CompareHash(nsIURI *aUri, bool *aResult)
+{
+ nsresult rv;
+
+ if (!mURI) {
+ *aResult = !aUri;
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG_POINTER(aUri);
+
+ nsAutoCString hash1, hash2;
+
+ rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, hash1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, aUri, hash2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = hash1.Equals(hash2);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListLink::Equals(nsIJumpListItem *aItem, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType)))
+ return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType)
+ return NS_OK;
+
+ nsCOMPtr<nsIJumpListLink> 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 JumpListShortcut::GetApp(nsILocalHandlerApp **aApp)
+{
+ NS_IF_ADDREF(*aApp = mHandlerApp);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetApp(nsILocalHandlerApp *aApp)
+{
+ mHandlerApp = aApp;
+
+ // Confirm the app is present on the system
+ if (!ExecutableExists(mHandlerApp))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::GetIconIndex(int32_t *aIconIndex)
+{
+ NS_ENSURE_ARG_POINTER(aIconIndex);
+
+ *aIconIndex = mIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetIconIndex(int32_t aIconIndex)
+{
+ mIconIndex = aIconIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::GetFaviconPageUri(nsIURI **aFaviconPageURI)
+{
+ NS_IF_ADDREF(*aFaviconPageURI = mFaviconPageURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::SetFaviconPageUri(nsIURI *aFaviconPageURI)
+{
+ mFaviconPageURI = aFaviconPageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListShortcut::Equals(nsIJumpListItem *aItem, bool *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ nsresult rv;
+
+ *aResult = false;
+
+ int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
+ if (NS_FAILED(aItem->GetType(&theType)))
+ return NS_OK;
+
+ // Make sure the types match.
+ if (Type() != theType)
+ return NS_OK;
+
+ nsCOMPtr<nsIJumpListShortcut> 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 JumpListSeparator::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 JumpListShortcut::GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellLinkW>& aShellLink,
+ nsCOMPtr<nsIThread> &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 != nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIJumpListShortcut> 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<nsIJumpListShortcut>& 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 nsIJumpListShortcut.
+nsresult JumpListShortcut::GetJumpListShortcut(IShellLinkW *pLink, nsCOMPtr<nsIJumpListShortcut>& 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 JumpListLink::GetShellItem(nsCOMPtr<nsIJumpListItem>& 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 != nsIJumpListItem::JUMPLIST_ITEM_LINK)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIJumpListLink> 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(WinUtils::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 nsIJumpListLink.
+nsresult JumpListLink::GetJumpListLink(IShellItem *pItem, nsCOMPtr<nsIJumpListLink>& 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;
+}
+
+// Confirm the app is on the system
+bool JumpListShortcut::ExecutableExists(nsCOMPtr<nsILocalHandlerApp>& handlerApp)
+{
+ nsresult rv;
+
+ if (!handlerApp)
+ return false;
+
+ nsCOMPtr<nsIFile> executable;
+ rv = handlerApp->GetExecutable(getter_AddRefs(executable));
+ if (NS_SUCCEEDED(rv) && executable) {
+ bool exists;
+ executable->Exists(&exists);
+ return exists;
+ }
+ return false;
+}
+
+} // namespace widget
+} // namespace mozilla
+
diff --git a/widget/windows/JumpListItem.h b/widget/windows/JumpListItem.h
new file mode 100644
index 0000000000..1e3152ae09
--- /dev/null
+++ b/widget/windows/JumpListItem.h
@@ -0,0 +1,132 @@
+/* -*- 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 __JumpListItem_h__
+#define __JumpListItem_h__
+
+#include <windows.h>
+#include <shobjidl.h>
+#undef LogSeverity // SetupAPI.h #defines this as DWORD
+
+#include "mozilla/RefPtr.h"
+#include "nsIJumpListItem.h" // defines nsIJumpListItem
+#include "nsIMIMEInfo.h" // defines nsILocalHandlerApp
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsICryptoHash.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace widget {
+
+class JumpListItem : public nsIJumpListItem
+{
+public:
+ JumpListItem() :
+ mItemType(nsIJumpListItem::JUMPLIST_ITEM_EMPTY)
+ {}
+
+ JumpListItem(int32_t type) :
+ mItemType(type)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJUMPLISTITEM
+
+ static const char kJumpListCacheDir[];
+
+protected:
+ virtual ~JumpListItem()
+ {}
+
+ short Type() { return mItemType; }
+ short mItemType;
+
+};
+
+class JumpListSeparator : public JumpListItem, public nsIJumpListSeparator
+{
+ ~JumpListSeparator() {}
+
+public:
+ JumpListSeparator() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); }
+ NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override { return JumpListItem::Equals(item, _retval); }
+
+ static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink);
+};
+
+class JumpListLink : public JumpListItem, public nsIJumpListLink
+{
+ ~JumpListLink() {}
+
+public:
+ JumpListLink() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_LINK)
+ {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); }
+ NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override;
+ NS_DECL_NSIJUMPLISTLINK
+
+ static nsresult GetShellItem(nsCOMPtr<nsIJumpListItem>& item, RefPtr<IShellItem2>& aShellItem);
+ static nsresult GetJumpListLink(IShellItem *pItem, nsCOMPtr<nsIJumpListLink>& aLink);
+
+protected:
+ nsString mUriTitle;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsICryptoHash> mCryptoHash;
+};
+
+class JumpListShortcut : public JumpListItem, public nsIJumpListShortcut
+{
+ ~JumpListShortcut() {}
+
+public:
+ JumpListShortcut() :
+ JumpListItem(nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT)
+ {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JumpListShortcut, JumpListItem)
+ NS_IMETHOD GetType(int16_t *aType) override { return JumpListItem::GetType(aType); }
+ NS_IMETHOD Equals(nsIJumpListItem *item, bool *_retval) override;
+ NS_DECL_NSIJUMPLISTSHORTCUT
+
+ static nsresult GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
+ RefPtr<IShellLinkW>& aShellLink,
+ nsCOMPtr<nsIThread> &aIOThread);
+ static nsresult GetJumpListShortcut(IShellLinkW *pLink, nsCOMPtr<nsIJumpListShortcut>& 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);
+ static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString &aICOFilePath,
+ nsCOMPtr<nsIThread> &aIOThread);
+ static nsresult CacheIconFileFromFaviconURIAsync(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile> aICOFile,
+ nsCOMPtr<nsIThread> &aIOThread);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __JumpListItem_h__ */
diff --git a/widget/windows/KeyboardLayout.cpp b/widget/windows/KeyboardLayout.cpp
new file mode 100644
index 0000000000..11f6578747
--- /dev/null
+++ b/widget/windows/KeyboardLayout.cpp
@@ -0,0 +1,5232 @@
+/* -*- 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/DebugOnly.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsAlgorithm.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsGkAtoms.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIIdleServiceInternal.h"
+#include "nsIWindowsRegKey.h"
+#include "nsMemory.h"
+#include "nsPrintfCString.h"
+#include "nsQuickSort.h"
+#include "nsServiceManagerUtils.h"
+#include "nsToolkit.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowDbg.h"
+
+#include "KeyboardLayout.h"
+#include "WidgetUtils.h"
+#include "WinUtils.h"
+
+#include "npapi.h"
+
+#include <windows.h>
+#include <winuser.h>
+#include <algorithm>
+
+#ifndef WINABLEAPI
+#include <winable.h>
+#endif
+
+// In WinUser.h, MAPVK_VK_TO_VSC_EX is defined only when WINVER >= 0x0600
+#ifndef MAPVK_VK_TO_VSC_EX
+#define MAPVK_VK_TO_VSC_EX (4)
+#endif
+
+namespace mozilla {
+namespace widget {
+
+static const char* 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 NS_LITERAL_CSTRING("NULL (0x0000)");
+ case 0x0008:
+ return NS_LITERAL_CSTRING("BACKSPACE (0x0008)");
+ case 0x0009:
+ return NS_LITERAL_CSTRING("CHARACTER TABULATION (0x0009)");
+ case 0x000A:
+ return NS_LITERAL_CSTRING("LINE FEED (0x000A)");
+ case 0x000B:
+ return NS_LITERAL_CSTRING("LINE TABULATION (0x000B)");
+ case 0x000C:
+ return NS_LITERAL_CSTRING("FORM FEED (0x000C)");
+ case 0x000D:
+ return NS_LITERAL_CSTRING("CARRIAGE RETURN (0x000D)");
+ case 0x0018:
+ return NS_LITERAL_CSTRING("CANCEL (0x0018)");
+ case 0x001B:
+ return NS_LITERAL_CSTRING("ESCAPE (0x001B)");
+ case 0x0020:
+ return NS_LITERAL_CSTRING("SPACE (0x0020)");
+ case 0x007F:
+ return NS_LITERAL_CSTRING("DELETE (0x007F)");
+ case 0x00A0:
+ return NS_LITERAL_CSTRING("NO-BREAK SPACE (0x00A0)");
+ case 0x00AD:
+ return NS_LITERAL_CSTRING("SOFT HYPHEN (0x00AD)");
+ case 0x2000:
+ return NS_LITERAL_CSTRING("EN QUAD (0x2000)");
+ case 0x2001:
+ return NS_LITERAL_CSTRING("EM QUAD (0x2001)");
+ case 0x2002:
+ return NS_LITERAL_CSTRING("EN SPACE (0x2002)");
+ case 0x2003:
+ return NS_LITERAL_CSTRING("EM SPACE (0x2003)");
+ case 0x2004:
+ return NS_LITERAL_CSTRING("THREE-PER-EM SPACE (0x2004)");
+ case 0x2005:
+ return NS_LITERAL_CSTRING("FOUR-PER-EM SPACE (0x2005)");
+ case 0x2006:
+ return NS_LITERAL_CSTRING("SIX-PER-EM SPACE (0x2006)");
+ case 0x2007:
+ return NS_LITERAL_CSTRING("FIGURE SPACE (0x2007)");
+ case 0x2008:
+ return NS_LITERAL_CSTRING("PUNCTUATION SPACE (0x2008)");
+ case 0x2009:
+ return NS_LITERAL_CSTRING("THIN SPACE (0x2009)");
+ case 0x200A:
+ return NS_LITERAL_CSTRING("HAIR SPACE (0x200A)");
+ case 0x200B:
+ return NS_LITERAL_CSTRING("ZERO WIDTH SPACE (0x200B)");
+ case 0x200C:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NON-JOINER (0x200C)");
+ case 0x200D:
+ return NS_LITERAL_CSTRING("ZERO WIDTH JOINER (0x200D)");
+ case 0x200E:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT MARK (0x200E)");
+ case 0x200F:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT MARK (0x200F)");
+ case 0x2029:
+ return NS_LITERAL_CSTRING("PARAGRAPH SEPARATOR (0x2029)");
+ case 0x202A:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT EMBEDDING (0x202A)");
+ case 0x202B:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT EMBEDDING (0x202B)");
+ case 0x202D:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT OVERRIDE (0x202D)");
+ case 0x202E:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT OVERRIDE (0x202E)");
+ case 0x202F:
+ return NS_LITERAL_CSTRING("NARROW NO-BREAK SPACE (0x202F)");
+ case 0x205F:
+ return NS_LITERAL_CSTRING("MEDIUM MATHEMATICAL SPACE (0x205F)");
+ case 0x2060:
+ return NS_LITERAL_CSTRING("WORD JOINER (0x2060)");
+ case 0x2066:
+ return NS_LITERAL_CSTRING("LEFT-TO-RIGHT ISOLATE (0x2066)");
+ case 0x2067:
+ return NS_LITERAL_CSTRING("RIGHT-TO-LEFT ISOLATE (0x2067)");
+ case 0x3000:
+ return NS_LITERAL_CSTRING("IDEOGRAPHIC SPACE (0x3000)");
+ case 0xFEFF:
+ return NS_LITERAL_CSTRING("ZERO WIDTH NO-BREAK SPACE (0xFEFF)");
+ default: {
+ if (aCharCode < ' ' ||
+ (aCharCode >= 0x80 && aCharCode < 0xA0)) {
+ return nsPrintfCString("control (0x%04X)", aCharCode);
+ }
+ if (NS_IS_HIGH_SURROGATE(aCharCode)) {
+ return nsPrintfCString("high surrogate (0x%04X)", aCharCode);
+ }
+ if (NS_IS_LOW_SURROGATE(aCharCode)) {
+ return nsPrintfCString("low surrogate (0x%04X)", aCharCode);
+ }
+ return IS_IN_BMP(aCharCode) ?
+ nsPrintfCString("'%s' (0x%04X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode) :
+ nsPrintfCString("'%s' (0x%08X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aCharCode)).get(), aCharCode);
+ }
+ }
+}
+
+static const nsCString
+GetKeyLocationName(uint32_t aLocation)
+{
+ switch (aLocation) {
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_LEFT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_RIGHT");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_STANDARD");
+ case nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD:
+ return NS_LITERAL_CSTRING("KEY_LOCATION_NUMPAD");
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString
+GetCharacterCodeName(char16_t* aChars, uint32_t aLength)
+{
+ if (!aLength) {
+ return NS_LITERAL_CSTRING("");
+ }
+ nsAutoCString result;
+ for (uint32_t i = 0; i < aLength; ++i) {
+ if (!result.IsEmpty()) {
+ result.AppendLiteral(", ");
+ } else {
+ result.AssignLiteral("\"");
+ }
+ result.Append(GetCharacterCodeName(aChars[i]));
+ }
+ 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 NS_LITERAL_CSTRING("WM_NULL");
+ case WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("WM_KEYDOWN");
+ case WM_KEYUP:
+ return NS_LITERAL_CSTRING("WM_KEYUP");
+ case WM_SYSKEYDOWN:
+ return NS_LITERAL_CSTRING("WM_SYSKEYDOWN");
+ case WM_SYSKEYUP:
+ return NS_LITERAL_CSTRING("WM_SYSKEYUP");
+ case WM_CHAR:
+ return NS_LITERAL_CSTRING("WM_CHAR");
+ case WM_UNICHAR:
+ return NS_LITERAL_CSTRING("WM_UNICHAR");
+ case WM_SYSCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSCHAR");
+ case WM_DEADCHAR:
+ return NS_LITERAL_CSTRING("WM_DEADCHAR");
+ case WM_SYSDEADCHAR:
+ return NS_LITERAL_CSTRING("WM_SYSDEADCHAR");
+ case MOZ_WM_KEYDOWN:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYDOWN");
+ case MOZ_WM_KEYUP:
+ return NS_LITERAL_CSTRING("MOZ_WM_KEYUP");
+ case WM_APPCOMMAND:
+ return NS_LITERAL_CSTRING("WM_APPCOMMAND");
+ case WM_QUIT:
+ return NS_LITERAL_CSTRING("WM_QUIT");
+ default:
+ return nsPrintfCString("Unknown Message (0x%04X)", aMessage);
+ }
+}
+
+static const nsCString
+GetVirtualKeyCodeName(WPARAM aVK)
+{
+ if (aVK >= ArrayLength(kVirtualKeyName)) {
+ return nsPrintfCString("Invalid (0x%08X)", aVK);
+ }
+ return nsCString(kVirtualKeyName[aVK]);
+}
+
+static const nsCString
+GetAppCommandName(WPARAM aCommand)
+{
+ switch (aCommand) {
+ case APPCOMMAND_BASS_BOOST:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_BOOST");
+ case APPCOMMAND_BASS_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_DOWN");
+ case APPCOMMAND_BASS_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BASS_UP");
+ case APPCOMMAND_BROWSER_BACKWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_BACKWARD");
+ case APPCOMMAND_BROWSER_FAVORITES:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FAVORITES");
+ case APPCOMMAND_BROWSER_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_FORWARD");
+ case APPCOMMAND_BROWSER_HOME:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_HOME");
+ case APPCOMMAND_BROWSER_REFRESH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_REFRESH");
+ case APPCOMMAND_BROWSER_SEARCH:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_SEARCH");
+ case APPCOMMAND_BROWSER_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_BROWSER_STOP");
+ case APPCOMMAND_CLOSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CLOSE");
+ case APPCOMMAND_COPY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_COPY");
+ case APPCOMMAND_CORRECTION_LIST:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CORRECTION_LIST");
+ case APPCOMMAND_CUT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_CUT");
+ case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE");
+ case APPCOMMAND_FIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FIND");
+ case APPCOMMAND_FORWARD_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_FORWARD_MAIL");
+ case APPCOMMAND_HELP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_HELP");
+ case APPCOMMAND_LAUNCH_APP1:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP1");
+ case APPCOMMAND_LAUNCH_APP2:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_APP2");
+ case APPCOMMAND_LAUNCH_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MAIL");
+ case APPCOMMAND_LAUNCH_MEDIA_SELECT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_LAUNCH_MEDIA_SELECT");
+ case APPCOMMAND_MEDIA_CHANNEL_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_DOWN");
+ case APPCOMMAND_MEDIA_CHANNEL_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_CHANNEL_UP");
+ case APPCOMMAND_MEDIA_FAST_FORWARD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_FAST_FORWARD");
+ case APPCOMMAND_MEDIA_NEXTTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_NEXTTRACK");
+ case APPCOMMAND_MEDIA_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PAUSE");
+ case APPCOMMAND_MEDIA_PLAY:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY");
+ case APPCOMMAND_MEDIA_PLAY_PAUSE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PLAY_PAUSE");
+ case APPCOMMAND_MEDIA_PREVIOUSTRACK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_PREVIOUSTRACK");
+ case APPCOMMAND_MEDIA_RECORD:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_RECORD");
+ case APPCOMMAND_MEDIA_REWIND:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_REWIND");
+ case APPCOMMAND_MEDIA_STOP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MEDIA_STOP");
+ case APPCOMMAND_MIC_ON_OFF_TOGGLE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MIC_ON_OFF_TOGGLE");
+ case APPCOMMAND_MICROPHONE_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_DOWN");
+ case APPCOMMAND_MICROPHONE_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_MUTE");
+ case APPCOMMAND_MICROPHONE_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_MICROPHONE_VOLUME_UP");
+ case APPCOMMAND_NEW:
+ return NS_LITERAL_CSTRING("APPCOMMAND_NEW");
+ case APPCOMMAND_OPEN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_OPEN");
+ case APPCOMMAND_PASTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PASTE");
+ case APPCOMMAND_PRINT:
+ return NS_LITERAL_CSTRING("APPCOMMAND_PRINT");
+ case APPCOMMAND_REDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REDO");
+ case APPCOMMAND_REPLY_TO_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_REPLY_TO_MAIL");
+ case APPCOMMAND_SAVE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SAVE");
+ case APPCOMMAND_SEND_MAIL:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SEND_MAIL");
+ case APPCOMMAND_SPELL_CHECK:
+ return NS_LITERAL_CSTRING("APPCOMMAND_SPELL_CHECK");
+ case APPCOMMAND_TREBLE_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_DOWN");
+ case APPCOMMAND_TREBLE_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_TREBLE_UP");
+ case APPCOMMAND_UNDO:
+ return NS_LITERAL_CSTRING("APPCOMMAND_UNDO");
+ case APPCOMMAND_VOLUME_DOWN:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_DOWN");
+ case APPCOMMAND_VOLUME_MUTE:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_MUTE");
+ case APPCOMMAND_VOLUME_UP:
+ return NS_LITERAL_CSTRING("APPCOMMAND_VOLUME_UP");
+ default:
+ return nsPrintfCString("Unknown app command (0x%08X)", aCommand);
+ }
+}
+
+static const nsCString
+GetAppCommandDeviceName(LPARAM aDevice)
+{
+ switch (aDevice) {
+ case FAPPCOMMAND_KEY:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_KEY");
+ case FAPPCOMMAND_MOUSE:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_MOUSE");
+ case FAPPCOMMAND_OEM:
+ return NS_LITERAL_CSTRING("FAPPCOMMAND_OEM");
+ default:
+ return nsPrintfCString("Unknown app command device (0x%04X)", aDevice);
+ }
+};
+
+class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString
+{
+public:
+ 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%04X)", aKeys);
+ }
+ if (IsEmpty()) {
+ AssignLiteral("none (0x0000)");
+ }
+ }
+
+private:
+ void MaybeAppendSeparator()
+ {
+ if (!IsEmpty()) {
+ AppendLiteral(" | ");
+ }
+ }
+};
+
+static const nsCString
+ToString(const MSG& aMSG)
+{
+ nsAutoCString 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:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP:
+ result.AppendPrintf(
+ "virtual keycode=%s, repeat count=%d, "
+ "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=%d, "
+ "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%p, 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=%u, lParam=%u", 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_LITERAL_CSTRING("{}");
+ }
+ nsAutoCString 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)
+{
+ nsAutoCString 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;
+
+struct DeadKeyEntry
+{
+ char16_t BaseChar;
+ char16_t CompositeChar;
+};
+
+
+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;
+};
+
+
+/*****************************************************************************
+ * mozilla::widget::ModifierKeyState
+ *****************************************************************************/
+
+ModifierKeyState::ModifierKeyState()
+{
+ Update();
+}
+
+ModifierKeyState::ModifierKeyState(bool aIsShiftDown,
+ bool aIsControlDown,
+ bool aIsAltDown)
+{
+ Update();
+ Unset(MODIFIER_SHIFT | MODIFIER_CONTROL | MODIFIER_ALT | MODIFIER_ALTGRAPH);
+ Modifiers modifiers = 0;
+ if (aIsShiftDown) {
+ modifiers |= MODIFIER_SHIFT;
+ }
+ if (aIsControlDown) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aIsAltDown) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (modifiers) {
+ Set(modifiers);
+ }
+}
+
+ModifierKeyState::ModifierKeyState(Modifiers aModifiers) :
+ mModifiers(aModifiers)
+{
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Update()
+{
+ mModifiers = 0;
+ if (IS_VK_DOWN(VK_SHIFT)) {
+ mModifiers |= MODIFIER_SHIFT;
+ }
+ 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_OS;
+ }
+ if (::GetKeyState(VK_CAPITAL) & 1) {
+ mModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (::GetKeyState(VK_NUMLOCK) & 1) {
+ mModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (::GetKeyState(VK_SCROLL) & 1) {
+ mModifiers |= MODIFIER_SCROLLLOCK;
+ }
+
+ EnsureAltGr();
+}
+
+void
+ModifierKeyState::Unset(Modifiers aRemovingModifiers)
+{
+ mModifiers &= ~aRemovingModifiers;
+ // Note that we don't need to unset AltGr flag here automatically.
+ // For EditorBase, we need to remove Alt and Control flags but AltGr isn't
+ // checked in EditorBase, so, it can be kept.
+}
+
+void
+ModifierKeyState::Set(Modifiers aAddingModifiers)
+{
+ mModifiers |= aAddingModifiers;
+ EnsureAltGr();
+}
+
+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.buttons = 0;
+ if (::GetKeyState(VK_LBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (::GetKeyState(VK_RBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (::GetKeyState(VK_MBUTTON) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON1) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e4thButtonFlag;
+ }
+ if (::GetKeyState(VK_XBUTTON2) < 0) {
+ mouseEvent.buttons |= WidgetMouseEvent::e5thButtonFlag;
+ }
+}
+
+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::IsAltGr() const
+{
+ return IsControl() && IsAlt();
+}
+
+bool
+ModifierKeyState::IsWin() const
+{
+ return (mModifiers & MODIFIER_OS) != 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;
+}
+
+void
+ModifierKeyState::EnsureAltGr()
+{
+ // If both Control key and Alt key are pressed, it means AltGr is pressed.
+ // Ideally, we should check whether the current keyboard layout has AltGr
+ // or not. However, setting AltGr flags for keyboard which doesn't have
+ // AltGr must not be serious bug. So, it should be OK for now.
+ if (IsAltGr()) {
+ mModifiers |= MODIFIER_ALTGRAPH;
+ }
+}
+
+/*****************************************************************************
+ * 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
+{
+ nsCaseInsensitiveStringComparator comp;
+ return mChars.Equals(aOther.mChars, comp);
+}
+
+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_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_CONTROL) {
+ modifiers |= MODIFIER_CONTROL;
+ }
+ if (aShiftState & STATE_ALT) {
+ modifiers |= MODIFIER_ALT;
+ }
+ if (aShiftState & STATE_CAPSLOCK) {
+ modifiers |= MODIFIER_CAPSLOCK;
+ }
+ if ((modifiers & (MODIFIER_ALT | MODIFIER_CONTROL)) ==
+ (MODIFIER_ALT | MODIFIER_CONTROL)) {
+ modifiers |= MODIFIER_ALTGRAPH;
+ }
+ return modifiers;
+}
+
+inline char16_t
+VirtualKey::GetCompositeChar(ShiftState aShiftState, char16_t aBaseChar) const
+{
+ return mShiftStates[aShiftState].DeadKey.Table->GetCompositeChar(aBaseChar);
+}
+
+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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ 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)
+{
+ NS_ASSERTION(aShiftState < ArrayLength(mShiftStates), "invalid index");
+
+ SetDeadKey(aShiftState, true);
+
+ mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar;
+ mShiftStates[aShiftState].DeadKey.Table = nullptr;
+}
+
+UniCharsAndModifiers
+VirtualKey::GetUniChars(ShiftState aShiftState) const
+{
+ UniCharsAndModifiers result = GetNativeUniChars(aShiftState);
+
+ const ShiftState STATE_ALT_CONTROL = (STATE_ALT | STATE_CONTROL);
+ if (!(aShiftState & STATE_ALT_CONTROL)) {
+ return result;
+ }
+
+ if (result.IsEmpty()) {
+ result = GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ result.FillModifiers(ShiftStateToModifiers(aShiftState));
+ return result;
+ }
+
+ if ((aShiftState & STATE_ALT_CONTROL) == STATE_ALT_CONTROL) {
+ // Even if the shifted chars and the unshifted chars are same, we
+ // should consume the Alt key state and the Ctrl key state when
+ // AltGr key is pressed. Because if we don't consume them, the input
+ // events are ignored on EditorBase. (I.e., Users cannot input the
+ // characters with this key combination.)
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ return result;
+ }
+
+ UniCharsAndModifiers unmodifiedReslt =
+ GetNativeUniChars(aShiftState & ~STATE_ALT_CONTROL);
+ if (!result.UniCharsEqual(unmodifiedReslt)) {
+ // Otherwise, we should consume the Alt key state and the Ctrl key state
+ // only when the shifted chars and unshifted chars are different.
+ Modifiers finalModifiers = ShiftStateToModifiers(aShiftState);
+ finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ result.FillModifiers(finalModifiers);
+ }
+ return result;
+}
+
+
+UniCharsAndModifiers
+VirtualKey::GetNativeUniChars(ShiftState aShiftState) const
+{
+#ifdef DEBUG
+ if (aShiftState >= ArrayLength(mShiftStates)) {
+ nsPrintfCString warning("Shift state is out of range: "
+ "aShiftState=%d, ArrayLength(mShiftState)=%d",
+ aShiftState, ArrayLength(mShiftStates));
+ NS_WARNING(warning.get());
+ }
+#endif
+
+ UniCharsAndModifiers result;
+ Modifiers modifiers = ShiftStateToModifiers(aShiftState);
+ if (IsDeadKey(aShiftState)) {
+ result.Append(mShiftStates[aShiftState].DeadKey.DeadChar, modifiers);
+ return result;
+ }
+
+ uint32_t index;
+ uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars);
+ for (index = 0;
+ index < len && mShiftStates[aShiftState].Normal.Chars[index]; index++) {
+ result.Append(mShiftStates[aShiftState].Normal.Chars[index], modifiers);
+ }
+ return result;
+}
+
+// static
+void
+VirtualKey::FillKbdState(PBYTE aKbdState,
+ const ShiftState aShiftState)
+{
+ NS_ASSERTION(aShiftState < 16, "aShiftState out of range");
+
+ if (aShiftState & STATE_SHIFT) {
+ aKbdState[VK_SHIFT] |= 0x80;
+ } else {
+ aKbdState[VK_SHIFT] &= ~0x80;
+ aKbdState[VK_LSHIFT] &= ~0x80;
+ aKbdState[VK_RSHIFT] &= ~0x80;
+ }
+
+ 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 = {};
+
+LazyLogModule sNativeKeyLogger("NativeKeyWidgets");
+
+NativeKey::NativeKey(nsWindowBase* 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)
+ , mIsDeadKey(false)
+ , mCharMessageHasGone(false)
+ , mCanIgnoreModifierStateAtKeyPress(true)
+ , mFakeCharMsgs(aFakeCharMsgs && aFakeCharMsgs->Length() ?
+ aFakeCharMsgs : nullptr)
+{
+ MOZ_LOG(sNativeKeyLogger, 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->GetLayout();
+ MOZ_ASSERT(mKeyboardLayout == aOverrideKeyboardLayout);
+ mIsOverridingKeyboardLayout = true;
+ } else {
+ mIsOverridingKeyboardLayout = false;
+ }
+
+ if (mMsg.message == WM_APPCOMMAND) {
+ InitWithAppCommand();
+ } else {
+ InitWithKeyChar();
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, "
+ "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=0x%04X, "
+ "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, "
+ "mIsDeadKey=%s, mIsPrintableKey=%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(mIsDeadKey),
+ GetBoolName(mIsPrintableKey), GetBoolName(mCharMessageHasGone),
+ GetBoolName(mIsOverridingKeyboardLayout)));
+}
+
+void
+NativeKey::InitWithKeyChar()
+{
+ mScanCode = WinUtils::GetScanCode(mMsg.lParam);
+ mIsExtended = WinUtils::IsExtendedScanCode(mMsg.lParam);
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ case MOZ_WM_KEYDOWN:
+ case MOZ_WM_KEYUP: {
+ // First, resolve the IME converted virtual keycode to its original
+ // keycode.
+ if (mMsg.wParam == VK_PROCESSKEY) {
+ mOriginalVirtualKeyCode =
+ static_cast<uint8_t>(::ImmGetVirtualKey(mMsg.hwnd));
+ } else {
+ 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;
+ }
+
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ // The right control key and the right alt key are extended keys.
+ // Therefore, we never get VK_RCONTRL and VK_RMENU for the result of
+ // MapVirtualKeyEx() on WinXP or WinServer2003.
+ //
+ // If VK_SHIFT, VK_CONTROL or VK_MENU key message is caused by well
+ // known scan code, we should decide it as Right key. Otherwise,
+ // decide it as Left key.
+ switch (mOriginalVirtualKeyCode) {
+ case VK_CONTROL:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x1D ? VK_RCONTROL : VK_LCONTROL;
+ break;
+ case VK_MENU:
+ mVirtualKeyCode =
+ mIsExtended && mScanCode == 0x38 ? VK_RMENU : VK_LMENU;
+ break;
+ case VK_SHIFT:
+ // 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;
+ }
+
+ 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:
+ // 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(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::InitWithKeyChar(), 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.
+
+ // We cannot compute the virtual key code from WM_CHAR message on WinXP
+ // if it's caused by an extended key.
+ if (!CanComputeVirtualKeyCodeFromScanCode()) {
+ break;
+ }
+ mVirtualKeyCode = mOriginalVirtualKeyCode =
+ ComputeVirtualKeyCodeFromScanCodeEx();
+ NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode");
+ break;
+ default:
+ MOZ_CRASH("Unsupported message");
+ }
+
+ if (!mVirtualKeyCode) {
+ mVirtualKeyCode = mOriginalVirtualKeyCode;
+ }
+
+ KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
+ mDOMKeyCode =
+ keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
+ // 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(mOriginalVirtualKeyCode);
+ 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::InitWithKeyChar(), removed char message, %s",
+ this, ToString(charMsg).get()));
+ NS_WARN_IF(charMsg.hwnd != mMsg.hwnd);
+ mFollowingCharMsgs.AppendElement(charMsg);
+ }
+ }
+
+ keyboardLayout->InitNativeKey(*this, mModKeyState);
+
+ mIsDeadKey =
+ (IsFollowedByDeadCharMessage() ||
+ keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState));
+ mIsPrintableKey =
+ mKeyNameIndex == KEY_NAME_INDEX_USE_STRING ||
+ KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode);
+
+ 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(
+ const ModifierKeyState& aModKeyState)
+{
+ 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 = aModKeyState.GetModifiers();
+ if (IsFollowedByPrintableCharMessage()) {
+ modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ // 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(sNativeKeyLogger, 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());
+}
+
+// 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(sNativeKeyLogger, 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+
+ case VK_RSHIFT:
+ case VK_RCONTROL:
+ case VK_RMENU:
+ case VK_RWIN:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+
+ case VK_RETURN:
+ // XXX This code assumes that all keyboard drivers use same mapping.
+ return !mIsExtended ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ 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 ? nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD :
+ nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ // 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 nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+
+ case VK_SHIFT:
+ case VK_CONTROL:
+ case VK_MENU:
+ NS_WARNING("Failed to decide the key location?");
+
+ default:
+ return nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ }
+}
+
+bool
+NativeKey::CanComputeVirtualKeyCodeFromScanCode() const
+{
+ // Vista or later supports ScanCodeEx.
+ if (IsVistaOrLater()) {
+ return true;
+ }
+ // Otherwise, MapVirtualKeyEx() can compute virtual keycode only with
+ // non-extended key.
+ return !mIsExtended;
+}
+
+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.
+ // On the other hand, neither WinXP nor WinServer2003 doesn't support 0xE000.
+ // Therefore, we have no way to get virtual keycode from scan code of
+ // extended keys.
+ if (NS_WARN_IF(!CanComputeVirtualKeyCodeFromScanCode())) {
+ return 0;
+ }
+ 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,
+ IsVistaOrLater() ? MAPVK_VK_TO_VSC_EX :
+ MAPVK_VK_TO_VSC,
+ mKeyboardLayout));
+}
+
+char16_t
+NativeKey::ComputeUnicharFromScanCode() const
+{
+ return static_cast<char16_t>(
+ ::MapVirtualKeyEx(ComputeVirtualKeyCodeFromScanCode(),
+ MAPVK_VK_TO_CHAR, mKeyboardLayout));
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG* aMsgSentToPlugin) const
+{
+ return InitKeyEvent(aKeyEvent, mModKeyState, aMsgSentToPlugin);
+}
+
+nsEventStatus
+NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const ModifierKeyState& aModKeyState,
+ const MSG* aMsgSentToPlugin) 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.
+ if (mCharMessageHasGone) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ MOZ_FALLTHROUGH;
+ case eKeyDownOnPlugin:
+ aKeyEvent.mKeyCode = mDOMKeyCode;
+ // Unique id for this keydown event and its associated keypress.
+ sUniqueKeyEventId++;
+ aKeyEvent.mUniqueId = sUniqueKeyEventId;
+ break;
+ case eKeyUp:
+ case eKeyUpOnPlugin:
+ 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.
+ if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) {
+ aKeyEvent.PreventDefaultBeforeDispatch();
+ }
+ 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 = IsRepeat();
+ 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);
+
+ if (aMsgSentToPlugin) {
+ MaybeInitPluginEventOfKeyEvent(aKeyEvent, *aMsgSentToPlugin);
+ }
+
+ KeyboardLayout::NotifyIdleServiceOfUserActivity();
+
+ MOZ_LOG(sNativeKeyLogger, 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;
+}
+
+void
+NativeKey::MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG& aMsgSentToPlugin) const
+{
+ if (mWidget->GetInputContext().mIMEState.mEnabled != IMEState::PLUGIN) {
+ return;
+ }
+ NPEvent pluginEvent;
+ pluginEvent.event = aMsgSentToPlugin.message;
+ pluginEvent.wParam = aMsgSentToPlugin.wParam;
+ pluginEvent.lParam = aMsgSentToPlugin.lParam;
+ aKeyEvent.mPluginEvent.Copy(pluginEvent);
+}
+
+bool
+NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const
+{
+ nsCOMPtr<nsIAtom> 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command "
+ "event", this));
+ return false;
+ }
+ WidgetCommandEvent commandEvent(true, nsGkAtoms::onAppCommand,
+ command, mWidget);
+
+ mWidget->InitEvent(commandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatching %s command event...",
+ this, nsAtomCString(command).get()));
+ bool ok = mWidget->DispatchWindowEvent(&commandEvent) || mWidget->Destroyed();
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchCommandEvent(), dispatched 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keydown "
+ "event...", this));
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), keydown event was "
+ "dispatched, consumed=%s",
+ this, GetBoolName(consumed)));
+ sDispatchedKeyOfAppCommand = mVirtualKeyCode;
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatching %s event...",
+ this, ToChar(contentCommandMessage)));
+ mWidget->DispatchWindowEvent(&contentCommandEvent);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched %s event",
+ this, ToChar(contentCommandMessage)));
+ consumed = true;
+
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s event caused "
+ "destroying the widget",
+ this, ToChar(contentCommandMessage)));
+ return true;
+ }
+ } else {
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleAppCommandMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), initializing keyup "
+ "event...", this));
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), dispatched keyup event",
+ this));
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleAppCommandMessage(), %s 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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 the widget has gone, we should do nothing.
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, 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 || IsKeyMessageOnPlugin() ||
+ !RedirectedKeyDownMessageManager::IsRedirectedMessage(mMsg)) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyDownMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ bool isIMEEnabled = WinUtils::IsIMEEnabled(mWidget->GetInputContext());
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyDownMessage(), initializing keydown "
+ "event...", this));
+
+ EventMessage keyDownMessage =
+ IsKeyMessageOnPlugin() ? eKeyDownOnPlugin : eKeyDown;
+ WidgetKeyboardEvent keydownEvent(true, keyDownMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keydownEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), dispatching keydown event...",
+ this));
+ bool dispatched =
+ mDispatcher->DispatchKeyboardEvent(keyDownMessage, 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(sNativeKeyLogger, 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;
+
+ // We don't need to handle key messages on plugin for eKeyPress since
+ // eKeyDownOnPlugin is handled as both eKeyDown and eKeyPress.
+ if (IsKeyMessageOnPlugin()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), doesn't dispatch keypress "
+ "event(s) because it's a keydown message on windowed plugin, "
+ "defaultPrevented=%s",
+ this, GetBoolName(defaultPrevented)));
+ return defaultPrevented;
+ }
+
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), keydown event caused "
+ "destroying the widget", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, 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 && !IsKeyMessageOnPlugin() &&
+ focusedWnd && !mWidget->PluginHasFocus() && !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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), redirecting %s...",
+ this, ToString(mMsg).get()));
+
+ ::SendInput(1, &keyinput, sizeof(keyinput));
+
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), not dispatching keypress "
+ "event because preceding keydown event was consumed",
+ this));
+ MaybeDispatchPluginEventsForRemovedCharMessages();
+ 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyDownMessage(), tries to be dispatching "
+ "keypress events...", this));
+ return (MaybeDispatchPluginEventsForRemovedCharMessages() ||
+ 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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) && IsAnotherInstanceRemovingCharMessage()) {
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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.
+ if (IsControlCharMessage(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(sNativeKeyLogger, 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);
+ keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleCharMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, 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, EditorBase 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.IsAltGr() && IsPrintableCharMessage(aCharMsg)) {
+ modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ }
+ nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleCharMessage(), keypress event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), doesn't dispatch keyup "
+ "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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::HandleKeyUpMessage(), FAILED due to "
+ "BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::HandleKeyUpMessage(), initializing keyup event...",
+ this));
+ EventMessage keyUpMessage = IsKeyMessageOnPlugin() ? eKeyUpOnPlugin : eKeyUp;
+ WidgetKeyboardEvent keyupEvent(true, keyUpMessage, mWidget);
+ nsEventStatus status = InitKeyEvent(keyupEvent, mModKeyState, &mMsg);
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), dispatching keyup event...",
+ this));
+ bool dispatched =
+ mDispatcher->DispatchKeyboardEvent(keyUpMessage, keyupEvent, status,
+ const_cast<NativeKey*>(this));
+ if (aEventDispatched) {
+ *aEventDispatched = dispatched;
+ }
+ if (mWidget->Destroyed()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::HandleKeyUpMessage(), keyup event caused "
+ "destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, 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());
+
+ // We cannot know following char messages of key messages in a plugin
+ // process. So, let's compute the character to be inputted with every
+ // printable key should be computed with the keyboard layout.
+ if (IsKeyMessageOnPlugin()) {
+ return true;
+ }
+
+ // 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.
+ 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;
+}
+
+#ifdef MOZ_CRASHREPORTER
+
+static nsCString
+GetResultOfInSendMessageEx()
+{
+ DWORD ret = ::InSendMessageEx(nullptr);
+ if (!ret) {
+ return NS_LITERAL_CSTRING("ISMEX_NOSEND");
+ }
+ nsAutoCString 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;
+}
+
+#endif // #ifdef MOZ_CRASHREPORTER
+
+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());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ 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(sNativeKeyLogger, LogLevel::Verbose,
+ ("%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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Verbose,
+ ("%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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, lost target "
+ "message to remove, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+ }
+
+ if (doCrash) {
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nPeekMessage() failed to remove char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nWM_NULL has been removed: %d, "
+ "\nNext key message in all windows: %s, "
+ "time=%d, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().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=%d",
+ ToString(nextMsg).get(), nextMsg.time);
+ CrashReporter::AppendAppNotesToCrashReport(info);
+ } else {
+ CrashReporter::AppendAppNotesToCrashReport(
+ NS_LITERAL_CSTRING("\nThere is no message in any window"));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Verbose,
+ ("%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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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()));
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nPeekMessage() removed unexpcted char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, "
+ "\nFound message: %s, "
+ "\nRemoved message: %s, ",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().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(
+ NS_LITERAL_CSTRING("\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(
+ NS_LITERAL_CSTRING("\nThere is no key message in any windows."));
+ }
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("PeekMessage() removed unexpected message");
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages "
+ "are all WM_NULL, nextKeyMsg=%s",
+ this, ToString(nextKeyMsg).get()));
+#ifdef MOZ_CRASHREPORTER
+ nsPrintfCString info("\nWe lost following char message! "
+ "\nActive keyboard layout=0x%08X (%s), "
+ "\nHandling message: %s, InSendMessageEx()=%s, \n"
+ "Found message: %s, removed a lot of WM_NULL",
+ KeyboardLayout::GetActiveLayout(),
+ KeyboardLayout::GetActiveLayoutName().get(),
+ ToString(mMsg).get(),
+ GetResultOfInSendMessageEx().get(),
+ ToString(kFoundCharMsg).get());
+ CrashReporter::AppendAppNotesToCrashReport(info);
+#endif // #ifdef MOZ_CRASHREPORTER
+ MOZ_CRASH("We lost the following char message");
+ return false;
+}
+
+bool
+NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages() const
+{
+ MOZ_ASSERT(IsKeyDownMessage());
+ MOZ_ASSERT(!IsKeyMessageOnPlugin());
+
+ for (size_t i = 0;
+ i < mFollowingCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mFollowingCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event",
+ this, i + 1));
+ }
+
+ // Dispatch odd char messages which are caused by ATOK or WXG (both of them
+ // are Japanese IME) and removed by RemoveFollowingOddCharMessages().
+ for (size_t i = 0;
+ i < mRemovedOddCharMsgs.Length() && mWidget->ShouldDispatchPluginEvent();
+ ++i) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatching %uth plugin event for odd char message, %s...",
+ this, i + 1, ToString(mFollowingCharMsgs[i]).get()));
+ MOZ_RELEASE_ASSERT(!mWidget->Destroyed(),
+ "NativeKey tries to dispatch a plugin event on destroyed widget");
+ mWidget->DispatchPluginEvent(mRemovedOddCharMsgs[i]);
+ if (mWidget->Destroyed() || IsFocusedWindowChanged()) {
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "%uth plugin event for odd char message caused %s",
+ this, i + 1, mWidget->Destroyed() ? "destroying the widget" :
+ "focus change"));
+ return true;
+ }
+ MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::MaybeDispatchPluginEventsForRemovedCharMessages(), "
+ "dispatched %uth plugin event for odd char message",
+ this, i + 1));
+ }
+
+ 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;
+ }
+
+ 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(sNativeKeyLogger, LogLevel::Error,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "FAILED due to BeginNativeInputTransaction() failure", this));
+ return true;
+ }
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+ MOZ_LOG(sNativeKeyLogger, 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,
+ !needsCallback ? &mFollowingCharMsgs[0] : nullptr);
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages(), "
+ "keypress event(s) caused destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Debug,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), initializing "
+ "keypress event...", this));
+ nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState);
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Info,
+ ("%p NativeKey::DispatchKeyPressEventsWithoutCharMessage(), "
+ "keypress event(s) caused destroying the widget", this));
+ return true;
+ }
+ bool consumed = status == nsEventStatus_eConsumeNoDefault;
+ MOZ_LOG(sNativeKeyLogger, 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(sNativeKeyLogger, LogLevel::Warning,
+ ("%p NativeKey::WillDispatchKeyboardEvent(), WARNING, "
+ "ignoring %uth 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. Let's set the
+ // message for plugin if it's necessary.
+ MaybeInitPluginEventOfKeyEvent(aKeyboardEvent, mFollowingCharMsgs[i]);
+ 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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(sNativeKeyLogger, 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;
+nsIIdleServiceInternal* KeyboardLayout::sIdleService = nullptr;
+
+// This log is very noisy if you don't want to retrieve the mapping table
+// of specific keyboard layout. LogLevel::Debug and LogLevel::Verbose are
+// used to log the layout mapping. If you need to log some behavior of
+// KeyboardLayout class, you should use LogLevel::Info or lower level.
+LazyLogModule sKeyboardLayoutLogger("KeyboardLayoutWidgets");
+
+// static
+KeyboardLayout*
+KeyboardLayout::GetInstance()
+{
+ if (!sInstance) {
+ sInstance = new KeyboardLayout();
+ nsCOMPtr<nsIIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ // The refcount will be decreased at shut down.
+ sIdleService = idleService.forget().take();
+ }
+ return sInstance;
+}
+
+// static
+void
+KeyboardLayout::Shutdown()
+{
+ delete sInstance;
+ sInstance = nullptr;
+ NS_IF_RELEASE(sIdleService);
+}
+
+// static
+void
+KeyboardLayout::NotifyIdleServiceOfUserActivity()
+{
+ sIdleService->ResetIdleTimeOut(0);
+}
+
+KeyboardLayout::KeyboardLayout()
+ : mKeyboardLayout(0)
+ , mIsOverridden(false)
+ , mIsPendingToRestoreKeyboardLayout(false)
+{
+ 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);
+
+ // NOTE: LoadLayout() should be called via OnLayoutChange().
+}
+
+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, 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,
+ const ModifierKeyState& aModKeyState)
+{
+ 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 =
+ aModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+ aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
+ 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(aModKeyState);
+ 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) &&
+ !aModKeyState.IsAltGr() &&
+ (aModKeyState.IsControl() || aModKeyState.IsAlt())) {
+ ModifierKeyState state = aModKeyState;
+ state.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
+ UniCharsAndModifiers charsWithoutModifier =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, 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, aModKeyState)) {
+ 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, aModKeyState)) {
+ return;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+
+ // 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,
+ const ModifierKeyState& aModKeyState)
+{
+ // Only when it's not in dead key sequence, we can trust IsDeadKey() result.
+ if (!IsInDeadKeySequence() &&
+ !IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
+ 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.mOriginalVirtualKeyCode);
+
+ if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
+ ActivateDeadKeyState(aNativeKey, aModKeyState);
+ // 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.mOriginalVirtualKeyCode, aModKeyState);
+ 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.mOriginalVirtualKeyCode, aModKeyState)) {
+ 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, aModKeyState)) {
+ return true;
+ }
+
+ // Otherwise, dead key followed by another dead key causes inputting both
+ // character.
+ UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
+ UniCharsAndModifiers newChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ // 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,
+ const ModifierKeyState& aModKeyState)
+{
+ if (!IsInDeadKeySequence()) {
+ return false;
+ }
+
+ if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
+ return false;
+ }
+
+ UniCharsAndModifiers baseChars =
+ GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
+ 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::GetNativeUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const
+{
+ int32_t key = GetKeyIndex(aVirtualKey);
+ if (key < 0) {
+ return UniCharsAndModifiers();
+ }
+ VirtualKey::ShiftState shiftState =
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
+ return mVirtualKeys[key].GetNativeUniChars(shiftState);
+}
+
+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
+HKL
+KeyboardLayout::GetActiveLayout()
+{
+ return GetInstance()->mKeyboardLayout;
+}
+
+// static
+nsCString
+KeyboardLayout::GetActiveLayoutName()
+{
+ return GetInstance()->GetLayoutName(GetActiveLayout());
+}
+
+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;
+}
+
+nsCString
+KeyboardLayout::GetLayoutName(HKL aLayout) const
+{
+ const wchar_t kKeyboardLayouts[] =
+ L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\";
+ 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("%08X", layout < 0xA000 ?
+ layout : reinterpret_cast<uintptr_t>(aLayout));
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Text",
+ buf, sizeof(buf)))) {
+ return NS_LITERAL_CSTRING("No name or too long name");
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+
+ if (NS_WARN_IF((layout & 0xF000) != 0xF000)) {
+ nsAutoCString result;
+ result.AppendPrintf("Odd HKL: 0x%08X",
+ reinterpret_cast<uintptr_t>(aLayout));
+ return result;
+ }
+
+ // Otherwise, we need to walk the registry under "Keyboard Layouts".
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1");
+ if (NS_WARN_IF(!regKey)) {
+ return EmptyCString();
+ }
+ nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+ nsString(kKeyboardLayouts),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EmptyCString();
+ }
+ uint32_t childCount = 0;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) ||
+ NS_WARN_IF(!childCount)) {
+ return EmptyCString();
+ }
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString childName;
+ if (NS_WARN_IF(NS_FAILED(regKey->GetChildName(i, childName))) ||
+ !IsValidKeyboardLayoutsChild(childName)) {
+ continue;
+ }
+ 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;
+ wchar_t buf[256];
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Id",
+ buf, sizeof(buf)))) {
+ continue;
+ }
+ uint16_t layoutId = wcstol(buf, nullptr, 16);
+ if (layoutId != (layout & 0x0FFF)) {
+ continue;
+ }
+ if (NS_WARN_IF(!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ key.get(), L"Layout Text",
+ buf, sizeof(buf)))) {
+ continue;
+ }
+ return NS_ConvertUTF16toUTF8(buf);
+ }
+ return EmptyCString();
+}
+
+void
+KeyboardLayout::LoadLayout(HKL aLayout)
+{
+ mIsPendingToRestoreKeyboardLayout = false;
+
+ if (mKeyboardLayout == aLayout) {
+ return;
+ }
+
+ mKeyboardLayout = aLayout;
+
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info,
+ ("KeyboardLayout::LoadLayout(aLayout=0x%08X (%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);
+ 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(sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s (%d): DeadChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeName(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(sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s (%d): NormalChar(%s, %s) (ret=%d)",
+ kVirtualKeyName[virtualKey], vki,
+ GetShiftStateName(shiftState).get(),
+ GetCharacterCodeName(uniChars, ret).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)) {
+ DeadKeyEntry deadKeyArray[256];
+ int32_t n = GetDeadKeyCombinations(virtualKey, kbdState,
+ shiftStatesWithBaseChars,
+ deadKeyArray,
+ ArrayLength(deadKeyArray));
+ const DeadKeyTable* dkt =
+ mVirtualKeys[vki].MatchingDeadKeyTable(deadKeyArray, n);
+ if (!dkt) {
+ dkt = AddDeadKeyTable(deadKeyArray, n);
+ }
+ mVirtualKeys[vki].AttachDeadKeyTable(shiftState, dkt);
+ }
+ }
+ }
+
+ ::SetKeyboardState(originalKbdState);
+
+ if (MOZ_LOG_TEST(sKeyboardLayoutLogger, LogLevel::Verbose)) {
+ static const UINT kExtendedScanCode[] = { 0x0000, 0xE000 };
+ static const UINT kMapType =
+ IsVistaOrLater() ? MAPVK_VSC_TO_VK_EX : MAPVK_VSC_TO_VK;
+ MOZ_LOG(sKeyboardLayoutLogger, 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(sKeyboardLayoutLogger, LogLevel::Verbose,
+ ("0x%04X, %s", scanCode, kVirtualKeyName[virtualKeyCode]));
+ }
+ // XP and Server 2003 don't support 0xE0 prefix of the scancode.
+ // Therefore, we don't need to continue on them.
+ if (!IsVistaOrLater()) {
+ break;
+ }
+ }
+ }
+}
+
+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];
+}
+
+int
+KeyboardLayout::CompareDeadKeyEntries(const void* aArg1,
+ const void* aArg2,
+ void*)
+{
+ const DeadKeyEntry* arg1 = static_cast<const DeadKeyEntry*>(aArg1);
+ const DeadKeyEntry* arg2 = static_cast<const DeadKeyEntry*>(aArg2);
+
+ return arg1->BaseChar - arg2->BaseChar;
+}
+
+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,
+ const ModifierKeyState& aModKeyState)
+{
+ // Dead-key state should be activated at keydown.
+ if (!aNativeKey.IsKeyDownMessage()) {
+ return;
+ }
+
+ mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
+ mDeadKeyShiftStates.AppendElement(
+ VirtualKey::ModifierKeyStateToShiftState(aModKeyState));
+}
+
+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,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aEntries)
+{
+ for (uint32_t index = 0; index < aEntries; index++) {
+ if (aDeadKeyArray[index].BaseChar == aBaseChar) {
+ return false;
+ }
+ }
+
+ aDeadKeyArray[aEntries].BaseChar = aBaseChar;
+ aDeadKeyArray[aEntries].CompositeChar = aCompositeChar;
+
+ return true;
+}
+
+uint32_t
+KeyboardLayout::GetDeadKeyCombinations(uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aMaxEntries)
+{
+ 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 < aMaxEntries) {
+ 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)) {
+ 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(sKeyboardLayoutLogger, 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)) {
+ entries++;
+ }
+ // Inactivate dead-key state for current virtual keycode.
+ EnsureDeadKeyActive(false, virtualKey, kbdState);
+ break;
+ }
+ default:
+ NS_WARN_IF("File a bug for this dead-key handling!");
+ deadKeyActive = false;
+ break;
+ }
+ }
+ MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug,
+ (" %s -> %s (%d): DeadKeyEntry(%s, %s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ GetCharacterCodeName(compositeChars, 1).get(),
+ ret <= 0 ? "''" :
+ GetCharacterCodeName(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(sKeyboardLayoutLogger, LogLevel::Verbose,
+ (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)",
+ kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki,
+ ret <= 0 ? "''" :
+ GetCharacterCodeName(compositeChars,
+ std::min(ret, 5)).get(), ret));
+ break;
+ }
+ }
+ }
+ }
+
+ if (deadKeyActive) {
+ deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState);
+ }
+
+ NS_QuickSort(aDeadKeyArray, entries, sizeof(DeadKeyEntry),
+ CompareDeadKeyEntries, nullptr);
+ 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;
+
+ // 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) {
+ return 0;
+ }
+ }
+ 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 0;
+ // 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;
+ }
+
+ 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 = 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(nsWindowBase* 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);
+
+ 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;
+ argumentKeySpecific = aNativeKeyCode & 0xFF;
+ aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU;
+ 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);
+ 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;
+ }
+ bool makeSysKeyMsg = 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();
+ }
+ }
+ for (uint32_t i = keySequence.Length(); i > 0; --i) {
+ uint8_t key = keySequence[i - 1].mGeneral;
+ uint8_t keySpecific = keySequence[i - 1].mSpecific;
+ uint16_t scanCode = keySequence[i - 1].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.
+ 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..dd2ac0bfce
--- /dev/null
+++ b/widget/windows/KeyboardLayout.h
@@ -0,0 +1,1012 @@
+/* -*- 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 "nsWindowBase.h"
+#include "nsWindowDefs.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.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 nsIIdleServiceInternal;
+
+namespace mozilla {
+namespace widget {
+
+static const uint32_t sModifierKeyMap[][3] = {
+ { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 },
+ { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 },
+ { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT },
+ { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT },
+ { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL },
+ { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL },
+ { nsIWidget::ALT_L, VK_MENU, VK_LMENU },
+ { nsIWidget::ALT_R, VK_MENU, VK_RMENU }
+};
+
+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();
+ }
+
+ 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.
+ AutoTArray<Modifiers, 5> mModifiers;
+};
+
+struct DeadKeyEntry;
+class DeadKeyTable;
+
+
+class VirtualKey
+{
+public:
+ // 0 - Normal
+ // 1 - Shift
+ // 2 - Control
+ // 3 - Control + Shift
+ // 4 - Alt
+ // 5 - Alt + Shift
+ // 6 - Alt + Control (AltGr)
+ // 7 - Alt + Control + Shift (AltGr + Shift)
+ // 8 - CapsLock
+ // 9 - CapsLock + Shift
+ // 10 - CapsLock + Control
+ // 11 - CapsLock + Control + Shift
+ // 12 - CapsLock + Alt
+ // 13 - CapsLock + Alt + Shift
+ // 14 - CapsLock + Alt + Control (CapsLock + AltGr)
+ // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift)
+
+ enum ShiftStateFlag
+ {
+ STATE_SHIFT = 0x01,
+ STATE_CONTROL = 0x02,
+ STATE_ALT = 0x04,
+ STATE_CAPSLOCK = 0x08
+ };
+
+ typedef uint8_t ShiftState;
+
+ static ShiftState ModifiersToShiftState(Modifiers aModifiers);
+ static ShiftState ModifierKeyStateToShiftState(
+ const ModifierKeyState& aModKeyState)
+ {
+ return ModifiersToShiftState(aModKeyState.GetModifiers());
+ }
+ static Modifiers ShiftStateToModifiers(ShiftState aShiftState);
+
+private:
+ union KeyShiftState
+ {
+ struct
+ {
+ char16_t Chars[4];
+ } Normal;
+ struct
+ {
+ const DeadKeyTable* Table;
+ char16_t DeadChar;
+ } DeadKey;
+ };
+
+ KeyShiftState mShiftStates[16];
+ uint16_t mIsDeadKey;
+
+ void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey)
+ {
+ if (aIsDeadKey) {
+ mIsDeadKey |= 1 << aShiftState;
+ } else {
+ mIsDeadKey &= ~(1 << aShiftState);
+ }
+ }
+
+public:
+ static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
+
+ bool IsDeadKey(ShiftState aShiftState) const
+ {
+ return (mIsDeadKey & (1 << aShiftState)) != 0;
+ }
+
+ void AttachDeadKeyTable(ShiftState aShiftState,
+ const DeadKeyTable* aDeadKeyTable)
+ {
+ 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;
+ char16_t GetCompositeChar(const ModifierKeyState& aModKeyState,
+ char16_t aBaseChar) const
+ {
+ return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState),
+ aBaseChar);
+ }
+ UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
+ UniCharsAndModifiers GetNativeUniChars(
+ const ModifierKeyState& aModKeyState) const
+ {
+ return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState));
+ }
+ 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(nsWindowBase* 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);
+
+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<nsWindowBase> 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;
+ 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;
+ // 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 InitWithKeyChar();
+
+ /**
+ * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes
+ * mCommittedCharsAndModifiers with mFollowingCharMsgs and aModKeyState.
+ * If mFollowingCharMsgs includes non-printable char messages, they are
+ * ignored (skipped).
+ */
+ void InitCommittedCharsAndModifiersWithFollowingCharMessages(
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * Returns true if the key event is caused by auto repeat.
+ */
+ bool IsRepeat() const
+ {
+ switch (mMsg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+ case MOZ_WM_KEYDOWN:
+ return ((mMsg.lParam & (1 << 30)) != 0);
+ case WM_APPCOMMAND:
+ if (mVirtualKeyCode) {
+ // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust
+ // the result of GetKeyboardState().
+ BYTE kbdState[256];
+ memset(kbdState, 0, sizeof(kbdState));
+ ::GetKeyboardState(kbdState);
+ return !!kbdState[mVirtualKeyCode];
+ }
+ // If there is no virtual keycode for the command, 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.
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ UINT GetScanCodeWithExtendedFlag() const;
+
+ // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*.
+ 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;
+
+ bool IsKeyDownMessage() const
+ {
+ return (mMsg.message == WM_KEYDOWN ||
+ mMsg.message == WM_SYSKEYDOWN ||
+ mMsg.message == MOZ_WM_KEYDOWN);
+ }
+ bool IsKeyUpMessage() const
+ {
+ return (mMsg.message == WM_KEYUP ||
+ mMsg.message == WM_SYSKEYUP ||
+ mMsg.message == MOZ_WM_KEYUP);
+ }
+ 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 IsKeyMessageOnPlugin() const
+ {
+ return (mMsg.message == MOZ_WM_KEYDOWN ||
+ mMsg.message == MOZ_WM_KEYUP);
+ }
+ bool IsPrintableCharMessage(const MSG& aMSG) const
+ {
+ return aMSG.message == WM_CHAR &&
+ !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+ }
+ 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);
+
+ /**
+ * Whether the key event can compute virtual keycode from the scancode value.
+ */
+ bool CanComputeVirtualKeyCodeFromScanCode() const;
+
+ /**
+ * 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 MSG* aMsgSentToPlugin = nullptr) const;
+ nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG* aMsgSentToPlugin = nullptr) const;
+
+ /**
+ * MaybeInitPluginEventOfKeyEvent() may initialize aKeyEvent::mPluginEvent
+ * with aMsgSentToPlugin if it's necessary.
+ */
+ void MaybeInitPluginEventOfKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ const MSG& aMsgSentToPlugin) 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;
+
+ /**
+ * MaybeDispatchPluginEventsForRemovedCharMessages() dispatches plugin events
+ * for removed char messages when a windowless plugin has focus.
+ * Returns true if the widget is destroyed or blurred during dispatching a
+ * plugin event.
+ */
+ bool MaybeDispatchPluginEventsForRemovedCharMessages() 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 bool IsEmptyMSG(const MSG& aMSG)
+ {
+ return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG));
+ }
+
+ bool IsAnotherInstanceRemovingCharMessage() const
+ {
+ return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg);
+ }
+};
+
+class KeyboardLayout
+{
+public:
+ static KeyboardLayout* GetInstance();
+ static void Shutdown();
+ static HKL GetActiveLayout();
+ static nsCString GetActiveLayoutName();
+ static void NotifyIdleServiceOfUserActivity();
+
+ static bool IsPrintableCharKey(uint8_t aVirtualKey);
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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);
+ }
+
+ /**
+ * GetNativeUniCharsAndModifiers() returns characters which are inputted by
+ * aVirtualKey with aModKeyState. The method isn't stateful.
+ * Note that different from GetUniCharsAndModifiers(), this returns
+ * actual modifier state of Ctrl and Alt.
+ */
+ UniCharsAndModifiers GetNativeUniCharsAndModifiers(
+ uint8_t aVirtualKey,
+ const ModifierKeyState& aModKeyState) const;
+
+ /**
+ * 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);
+
+ HKL GetLayout() const
+ {
+ return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0) :
+ mKeyboardLayout;
+ }
+
+ /**
+ * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC.
+ */
+ WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const;
+
+ /**
+ * Implementation of nsIWidget::SynthesizeNativeKeyEvent().
+ */
+ nsresult SynthesizeNativeKeyEvent(nsWindowBase* aWidget,
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+private:
+ KeyboardLayout();
+ ~KeyboardLayout();
+
+ static KeyboardLayout* sInstance;
+ static nsIIdleServiceInternal* sIdleService;
+
+ struct DeadKeyTableListEntry
+ {
+ DeadKeyTableListEntry* next;
+ uint8_t data[1];
+ };
+
+ HKL mKeyboardLayout;
+
+ VirtualKey mVirtualKeys[NS_NUM_OF_KEYS];
+ DeadKeyTableListEntry* mDeadKeyTableListHead;
+ // 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;
+ bool mIsPendingToRestoreKeyboardLayout;
+
+ static inline int32_t GetKeyIndex(uint8_t aVirtualKey);
+ static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2,
+ void* aData);
+ static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar,
+ DeadKeyEntry* aDeadKeyArray, uint32_t aEntries);
+ bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState);
+ uint32_t GetDeadKeyCombinations(uint8_t aDeadKey,
+ const PBYTE aDeadKeyKbdState,
+ uint16_t aShiftStatesWithBaseChars,
+ DeadKeyEntry* aDeadKeyArray,
+ uint32_t aMaxEntries);
+ /**
+ * Activates or deactivates dead key state.
+ */
+ void ActivateDeadKeyState(const NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState);
+ 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.
+ */
+ nsCString GetLayoutName(HKL aLayout) const;
+
+ /**
+ * 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,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * 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,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with
+ * proper composite character when dead key produces a composite character.
+ * Otherwise, just returns false.
+ */
+ bool MaybeInitNativeKeyWithCompositeChar(
+ NativeKey& aNativeKey,
+ const ModifierKeyState& aModKeyState);
+
+ /**
+ * 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(nsWindowBase* 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<nsWindowBase> 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..de4a40d2ac
--- /dev/null
+++ b/widget/windows/LSPAnnotator.cpp
@@ -0,0 +1,161 @@
+/* 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.
+ */
+
+#if _WIN32_WINNT < 0x0600
+// Redefining _WIN32_WINNT for some Vista APIs that we call
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#endif
+#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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ void Annotate();
+ nsCString mString;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS(LSPAnnotationGatherer, nsIRunnable)
+
+void
+LSPAnnotationGatherer::Annotate()
+{
+ nsCOMPtr<nsICrashReporter> cr =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ bool enabled;
+ if (cr && NS_SUCCEEDED(cr->GetEnabled(&enabled)) && enabled) {
+ cr->AnnotateCrashReport(NS_LITERAL_CSTRING("Winsock_LSP"), mString);
+ }
+ mThread->AsyncShutdown();
+}
+
+NS_IMETHODIMP
+LSPAnnotationGatherer::Run()
+{
+ PR_SetCurrentThreadName("LSP Annotator");
+
+ mThread = NS_GetCurrentThread();
+
+ 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?
+ NS_NOTREACHED("WSCEnumProtocols suceeded 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%x", providers[i].dwServiceFlags1);
+ str.AppendLiteral(" : ");
+ str.AppendPrintf("0x%x", 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(" : ");
+ // If WSCGetProviderInfo is available, we should call it to obtain the
+ // category flags for this provider. When present, these flags inform
+ // Windows as to which order to chain the providers.
+ nsModuleHandle ws2_32(LoadLibraryW(L"ws2_32.dll"));
+ if (ws2_32) {
+ decltype(WSCGetProviderInfo)* pWSCGetProviderInfo =
+ reinterpret_cast<decltype(WSCGetProviderInfo)*>(
+ GetProcAddress(ws2_32, "WSCGetProviderInfo"));
+ if (pWSCGetProviderInfo) {
+ DWORD categoryInfo;
+ size_t categoryInfoSize = sizeof(categoryInfo);
+ if (!pWSCGetProviderInfo(&providers[i].ProviderId,
+ ProviderInfoLspCategories,
+ (PBYTE)&categoryInfo, &categoryInfoSize, 0,
+ &err)) {
+ str.AppendPrintf("0x%x", 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(this, &LSPAnnotationGatherer::Annotate));
+ return NS_OK;
+}
+
+void LSPAnnotate()
+{
+ nsCOMPtr<nsIThread> thread;
+ nsCOMPtr<nsIRunnable> runnable =
+ do_QueryObject(new LSPAnnotationGatherer());
+ NS_NewThread(getter_AddRefs(thread), runnable);
+}
+
+} // namespace crashreporter
+} // namespace mozilla
diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..4805c95a41
--- /dev/null
+++ b/widget/windows/PCompositorWidget.ipdl
@@ -0,0 +1,31 @@
+/* -*- 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;
+
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ sync EnterPresentLock();
+ sync LeavePresentLock();
+ async UpdateTransparency(int32_t aMode);
+ sync ClearTransparentWindow();
+ async __delete__();
+
+child:
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..aad9cf3959
--- /dev/null
+++ b/widget/windows/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+
+// This file is a stub, for platforms that do not yet support out-of-process
+// compositing or do not need specialized types to do so.
+
+using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace widget {
+
+struct CompositorWidgetInitData
+{
+ WindowsHandle hWnd;
+ uintptr_t widgetKey;
+ int32_t transparencyMode;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp
new file mode 100644
index 0000000000..fb0505aa37
--- /dev/null
+++ b/widget/windows/TSFTextStore.cpp
@@ -0,0 +1,6423 @@
+/* -*- 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 <olectl.h>
+#include <algorithm>
+
+#include "nscore.h"
+#include "nsWindow.h"
+#include "nsPrintfCString.h"
+#include "WinIMEHandler.h"
+#include "WinUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsIXULRuntime.h"
+
+namespace mozilla {
+namespace widget {
+
+static const char* kPrefNameEnableTSF = "intl.tsf.enable";
+
+/**
+ * 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("
+ */
+
+LazyLogModule sTextStoreLog("nsTextStoreWidgets");
+
+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)
+{
+ nsAutoCString 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:
+ 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*
+GetIMEEnabledName(IMEState::Enabled aIMEEnabled)
+{
+ switch (aIMEEnabled) {
+ case IMEState::DISABLED:
+ return "DISABLED";
+ case IMEState::ENABLED:
+ return "ENABLED";
+ case IMEState::PASSWORD:
+ return "PASSWORD";
+ case IMEState::PLUGIN:
+ return "PLUGIN";
+ default:
+ return "Invalid";
+ }
+}
+
+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";
+ default:
+ return "Unknown";
+ }
+}
+
+static nsCString
+GetCLSIDNameStr(REFCLSID aCLSID)
+{
+ LPOLESTR str = nullptr;
+ HRESULT hr = ::StringFromCLSID(aCLSID, &str);
+ if (FAILED(hr) || !str || !str[0]) {
+ return EmptyCString();
+ }
+
+ nsAutoCString 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 EmptyCString();
+ }
+
+ return NS_ConvertUTF16toUTF8(str);
+}
+
+static nsCString
+GetGUIDNameStrWithTable(REFGUID aGUID)
+{
+#define RETURN_GUID_NAME(aNamedGUID) \
+ if (IsEqualGUID(aGUID, aNamedGUID)) { \
+ return NS_LITERAL_CSTRING(#aNamedGUID); \
+ }
+
+ RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE)
+ 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 EmptyCString();
+ }
+
+ nsAutoString key(L"Interface\\");
+ key += str;
+
+ nsAutoCString result;
+ wchar_t buf[256];
+ if (WinUtils::GetRegistryKey(HKEY_CLASSES_ROOT, key.get(), nullptr,
+ buf, sizeof(buf))) {
+ 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)
+{
+ nsAutoCString 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 char*
+GetActiveSelEndName(TsActiveSelEnd aSelEnd)
+{
+ return aSelEnd == TS_AE_NONE ? "TS_AE_NONE" :
+ aSelEnd == TS_AE_START ? "TS_AE_START" :
+ aSelEnd == TS_AE_END ? "TS_AE_END" : "Unknown";
+}
+
+static const nsCString
+GetLockFlagNameStr(DWORD aLockFlags)
+{
+ nsAutoCString 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 NS_LITERAL_CSTRING("TF_CT_NONE");
+ 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 NS_LITERAL_CSTRING("TF_LS_NONE");
+ case TF_LS_SOLID:
+ return NS_LITERAL_CSTRING("TF_LS_SOLID");
+ case TF_LS_DOT:
+ return NS_LITERAL_CSTRING("TF_LS_DOT");
+ case TF_LS_DASH:
+ return NS_LITERAL_CSTRING("TF_LS_DASH");
+ case TF_LS_SQUIGGLE:
+ return NS_LITERAL_CSTRING("TF_LS_SQUIGGLE");
+ 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 NS_LITERAL_CSTRING("TF_ATTR_INPUT");
+ case TF_ATTR_TARGET_CONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_TARGET_CONVERTED");
+ case TF_ATTR_CONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_CONVERTED");
+ case TF_ATTR_TARGET_NOTCONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_TARGET_NOTCONVERTED");
+ case TF_ATTR_INPUT_ERROR:
+ return NS_LITERAL_CSTRING("TF_ATTR_INPUT_ERROR");
+ case TF_ATTR_FIXEDCONVERTED:
+ return NS_LITERAL_CSTRING("TF_ATTR_FIXEDCONVERTED");
+ case TF_ATTR_OTHER:
+ return NS_LITERAL_CSTRING("TF_ATTR_OTHER");
+ default: {
+ return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr));
+ }
+ }
+}
+
+static nsCString
+GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr)
+{
+ nsAutoCString 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 WidgetMouseEventBase::eLeftButton:
+ return "LeftButton";
+ case WidgetMouseEventBase::eMiddleButton:
+ return "MiddleButton";
+ case WidgetMouseEventBase::eRightButton:
+ 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 NS_LITERAL_CSTRING("no buttons");
+ }
+ nsAutoCString names;
+ if (aButtons & WidgetMouseEventBase::eLeftButtonFlag) {
+ names = "LeftButton";
+ }
+ if (aButtons & WidgetMouseEventBase::eRightButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "RightButton";
+ }
+ if (aButtons & WidgetMouseEventBase::eMiddleButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "MiddleButton";
+ }
+ if (aButtons & WidgetMouseEventBase::e4thButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "4thButton";
+ }
+ if (aButtons & WidgetMouseEventBase::e5thButtonFlag) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += "5thButton";
+ }
+ return names;
+}
+
+static nsCString
+GetModifiersName(Modifiers aModifiers)
+{
+ if (aModifiers == MODIFIER_NONE) {
+ return NS_LITERAL_CSTRING("no modifiers");
+ }
+ nsAutoCString 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;
+ }
+ if (aModifiers & MODIFIER_OS) {
+ ADD_SEPARATOR_IF_NECESSARY(names);
+ names += NS_DOM_KEYNAME_OS;
+ }
+ return names;
+}
+
+class GetWritingModeName : public nsAutoCString
+{
+public:
+ 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");
+ }
+};
+
+/******************************************************************/
+/* InputScopeImpl */
+/******************************************************************/
+
+class InputScopeImpl final : public ITfInputScope
+{
+ ~InputScopeImpl() {}
+
+public:
+ InputScopeImpl(const nsTArray<InputScope>& aList)
+ : mInputScopes(aList)
+ {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p InputScopeImpl()", this));
+ }
+
+ 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) {
+ sInstance = new TSFStaticSink();
+ }
+ 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()
+ {
+ 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);
+ }
+
+ bool EnsureInitActiveTIPKeyboard();
+
+ /****************************************************************************
+ * Japanese TIP
+ ****************************************************************************/
+
+ // Note that TIP name may depend on the language of the environment.
+ // For example, some TIP may use localized name for its target language
+ // environment but English name for the others.
+
+ bool IsMSJapaneseIMEActive() const
+ {
+ // FYI: Name of MS-IME for Japanese is same as MS-IME for Korean.
+ // Therefore, we need to check the langid too.
+ return mLangID == 0x411 &&
+ (mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft IME") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"Microsoft \xC785\xB825\xAE30")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x8F93\x5165\x6CD5")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x8F38\x5165\x6CD5")));
+ }
+
+ bool IsMSOfficeJapaneseIME2010Active() const
+ {
+ // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64}
+ static const GUID kGUID = {
+ 0x54EDCC94, 0x1524, 0x4BB1,
+ { 0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOKActive() const
+ {
+ // FYI: Name of ATOK includes the release year like "ATOK 2015".
+ return StringBeginsWith(mActiveTIPKeyboardDescription,
+ NS_LITERAL_STRING("ATOK "));
+ }
+
+ bool IsATOK2011Active() const
+ {
+ // {F9C24A5C-8A53-499D-9572-93B2FF582115}
+ static const GUID kGUID = {
+ 0xF9C24A5C, 0x8A53, 0x499D,
+ { 0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2012Active() const
+ {
+ // {1DE01562-F445-401B-B6C3-E5B18DB79461}
+ static const GUID kGUID = {
+ 0x1DE01562, 0xF445, 0x401B,
+ { 0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2013Active() const
+ {
+ // {3C4DB511-189A-4168-B6EA-BFD0B4C85615}
+ static const GUID kGUID = {
+ 0x3C4DB511, 0x189A, 0x4168,
+ { 0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15 }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2014Active() const
+ {
+ // {4EF33B79-6AA9-4271-B4BF-9321C279381B}
+ static const GUID kGUID = {
+ 0x4EF33B79, 0x6AA9, 0x4271,
+ { 0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2015Active() const
+ {
+ // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A}
+ static const GUID kGUID = {
+ 0xEAB4DC00, 0xCE2E, 0x483D,
+ { 0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ bool IsATOK2016Active() const
+ {
+ // {0B557B4C-5740-4110-A60A-1493FA10BF2B}
+ static const GUID kGUID = {
+ 0x0B557B4C, 0x5740, 0x4110,
+ { 0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B }
+ };
+ return mActiveTIPGUID == kGUID;
+ }
+
+ // Note that ATOK 2011 - 2016 refers native caret position for deciding its
+ // popup window position.
+ bool IsATOKReferringNativeCaretActive() const
+ {
+ return IsATOKActive() &&
+ (IsATOK2011Active() || IsATOK2012Active() || IsATOK2013Active() ||
+ IsATOK2014Active() || IsATOK2015Active() || IsATOK2016Active());
+ }
+
+ /****************************************************************************
+ * Traditional Chinese TIP
+ ****************************************************************************/
+
+ bool IsMSChangJieActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft ChangJie") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4ED3\x9889")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x5009\x9821"));
+ }
+
+ bool IsMSQuickQuickActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Quick") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x901F\x6210")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x901F\x6210"));
+ }
+
+ bool IsFreeChangJieActive() const
+ {
+ // FYI: The TIP name is misspelled...
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Free CangJie IME 10");
+ }
+
+ bool IsEasyChangjeiActive() const
+ {
+ return
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(
+ u"\x4E2D\x6587 (\x7E41\x9AD4) - \x6613\x9821\x8F38\x5165\x6CD5"));
+ }
+
+ /****************************************************************************
+ * Simplified Chinese TIP
+ ****************************************************************************/
+
+ bool IsMSPinyinActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Pinyin") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x62FC\x97F3")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x62FC\x97F3"));
+ }
+
+ bool IsMSWubiActive() const
+ {
+ return mActiveTIPKeyboardDescription.EqualsLiteral("Microsoft Wubi") ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8F6F\x4E94\x7B14")) ||
+ mActiveTIPKeyboardDescription.Equals(
+ NS_LITERAL_STRING(u"\x5FAE\x8EDF\x4E94\x7B46"));
+ }
+
+public: // ITfInputProcessorProfileActivationSink
+ STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID,
+ HKL, DWORD);
+
+private:
+ TSFStaticSink();
+ virtual ~TSFStaticSink() {}
+
+ void Destroy();
+
+ void GetTIPDescription(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile, nsAString& aDescription);
+ bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID,
+ REFGUID aProfile);
+
+ // 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
+ GUID mActiveTIPGUID;
+
+ static StaticRefPtr<TSFStaticSink> sInstance;
+};
+
+StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance;
+
+TSFStaticSink::TSFStaticSink()
+ : 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to get ITfSource "
+ "instance (0x%08X)", 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Init() FAILED to install "
+ "ITfInputProcessorProfileActivationSink (0x%08X)", this, hr));
+ return false;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Init(), "
+ "mIPProfileCookie=0x%08X",
+ this, mIPProfileCookie));
+ return true;
+}
+
+void
+TSFStaticSink::Destroy()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::Shutdown() "
+ "mIPProfileCookie=0x%08X",
+ this, mIPProfileCookie));
+
+ if (mIPProfileCookie != TF_INVALID_COOKIE) {
+ RefPtr<ITfSource> source;
+ HRESULT hr =
+ mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::Shutdown() FAILED to get "
+ "ITfSource instance (0x%08X)", this, hr));
+ } else {
+ hr = source->UnadviseSink(mIPProfileCookie);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Shutdown() FAILED to uninstall "
+ "ITfInputProcessorProfileActivationSink (0x%08X)",
+ 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;
+ mActiveTIPGUID = guidProfile;
+ mLangID = langid;
+ mIsIMM_IME = IsIMM_IME(hkl);
+ GetTIPDescription(rclsid, mLangID, guidProfile,
+ mActiveTIPKeyboardDescription);
+ // Notify IMEHandler of changing active keyboard layout.
+ IMEHandler::OnKeyboardLayoutChanged();
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::OnActivated(dwProfileType=%s (0x%08X), "
+ "langid=0x%08X, rclsid=%s, catid=%s, guidProfile=%s, hkl=0x%08X, "
+ "dwFlags=0x%08X (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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get input processor profile manager, hr=0x%08X", this, hr));
+ return false;
+ }
+
+ TF_INPUTPROCESSORPROFILE profile;
+ hr = profileMgr->GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD, &profile);
+ if (hr == S_FALSE) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active keyboard layout profile due to no active profile, "
+ "hr=0x%08X", this, hr));
+ // XXX Should we call OnActivated() with arguments like non-TIP in this
+ // case?
+ return false;
+ }
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::EnsureInitActiveLanguageProfile(), FAILED "
+ "to get active TIP keyboard, hr=0x%08X", this, hr));
+ return false;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::InitActiveTIPDescription() FAILED "
+ "due to GetLanguageProfileDescription() failure, hr=0x%08X",
+ 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFStaticSink::IsTIPCategoryKeyboard(), FAILED "
+ "to get language profiles enumerator, hr=0x%08X", 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<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr;
+StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext;
+StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles;
+StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore;
+DWORD TSFTextStore::sClientId = 0;
+
+bool TSFTextStore::sCreateNativeCaretForLegacyATOK = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToATOKOfCompositionString = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSSimplifiedTIP = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSTraditionalTIP = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToFreeChangJie = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToEasyChangjei = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar = false;
+bool TSFTextStore::sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret = false;
+bool TSFTextStore::sHackQueryInsertForMSSimplifiedTIP = false;
+bool TSFTextStore::sHackQueryInsertForMSTraditionalTIP = false;
+
+#define TEXTSTORE_DEFAULT_VIEW (1)
+
+TSFTextStore::TSFTextStore()
+ : mEditCookie(0)
+ , mSinkMask(0)
+ , mLock(0)
+ , mLockQueued(0)
+ , mHandlingKeyMessage(0)
+ , mContentForTSF(mComposition, mSelectionForTSF)
+ , mRequestedAttrValues(false)
+ , mIsRecordingActionsWithoutLock(false)
+ , mHasReturnedNoLayoutError(false)
+ , mWaitingQueryLayout(false)
+ , mPendingDestroy(false)
+ , mDeferClearingContentForTSF(false)
+ , mNativeCaretIsCreated(false)
+ , mDeferNotifyingTSF(false)
+ , mDeferCommittingComposition(false)
+ , mDeferCancellingComposition(false)
+ , mDestroyed(false)
+ , mBeingDestroyed(false)
+{
+ for (int32_t i = 0; i < NUM_OF_SUPPORTED_ATTRS; i++) {
+ mRequestedAttrs[i] = false;
+ }
+
+ // We hope that 5 or more actions don't occur at once.
+ mPendingActions.SetCapacity(5);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::TSFTextStore() SUCCEEDED", this));
+}
+
+TSFTextStore::~TSFTextStore()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore instance is destroyed", this));
+}
+
+bool
+TSFTextStore::Init(nsWindowBase* aWidget,
+ const InputContext& aContext)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init(aWidget=0x%p)",
+ this, aWidget));
+
+ if (NS_WARN_IF(!aWidget) || NS_WARN_IF(aWidget->Destroyed())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to being initialized with "
+ "destroyed widget",
+ this));
+ return false;
+ }
+
+ TSFStaticSink::GetInstance()->EnsureInitActiveTIPKeyboard();
+
+ if (mDocumentMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED due to already initialized",
+ this));
+ return false;
+ }
+
+ mWidget = aWidget;
+ if (NS_WARN_IF(!mWidget)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED "
+ "due to aWidget->GetTextEventDispatcher() failure", this));
+ return false;
+ }
+
+ SetInputScope(aContext.mHTMLInputType, aContext.mHTMLInputInputmode);
+
+ // 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create ITfDocumentMgr "
+ "(0x%08X)", this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to create the context "
+ "(0x%08X)", this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::Init() FAILED to push the context (0x%08X)",
+ this, hr));
+ return false;
+ }
+ if (NS_WARN_IF(mDestroyed)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Init() succeeded: "
+ "mDocumentMgr=0x%p, mContext=0x%p, mEditCookie=0x%08X",
+ this, mDocumentMgr.get(), mContext.get(), mEditCookie));
+
+ return true;
+}
+
+void
+TSFTextStore::Destroy()
+{
+ if (mBeingDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy(), mLock=%s, "
+ "mComposition.IsComposing()=%s, mHandlingKeyMessage=%u",
+ this, GetLockFlagNameStr(mLock).get(),
+ GetBoolName(mComposition.IsComposing()),
+ 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.
+ 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.IsComposing()) {
+ CommitCompositionInternal(false);
+ }
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::Destroy() succeeded", this));
+}
+
+void
+TSFTextStore::ReleaseTSFObjects()
+{
+ MOZ_ASSERT(!mHandlingKeyMessage);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::ReleaseTSFObjects()", this));
+
+ 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ReleaseTSFObjects(), "
+ "removing a mouse tracker...",
+ this));
+ mMouseTrackers.Clear();
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseSink() FAILED due to the null punk",
+ this));
+ return E_UNEXPECTED;
+ }
+
+ if (IID_ITextStoreACPSink != riid) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseSink(punk=0x%p), mSink=0x%p",
+ this, punk, mSink.get()));
+
+ if (!punk) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseSink() FAILED due to the null punk",
+ this));
+ return E_INVALIDARG;
+ }
+ if (!mSink) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RequestLock() FAILED due to "
+ "any sink not stored", this));
+ return E_FAIL;
+ }
+ if (mDestroyed &&
+ (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty())) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ while (mLockQueued) {
+ mLock = mLockQueued;
+ mLockQueued = 0;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p Locking for the request in the queue (%s) >>>>>>>>>>>>>>"
+ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>",
+ this, GetLockFlagNameStr(mLock).get()));
+ sink->OnLockGranted(mLock);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p Unlocked (%s) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+ this, GetLockFlagNameStr(mLock).get()));
+ DidLockGranted();
+ }
+
+ // The document is now completely unlocked.
+ mLock = 0;
+
+ MaybeFlushPendingNotifications();
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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.Clear();
+ 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()) {
+ mDeferNotifyingTSF = 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.Clear();
+ mHasReturnedNoLayoutError = false;
+ return;
+ }
+
+ RefPtr<nsWindowBase> kungFuDeathGrip(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, 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::COMPOSITION_START: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_START={ mSelectionStart=%d, "
+ "mSelectionLength=%d }, mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, 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, mWidget);
+ mWidget->InitEvent(selectionSet);
+ selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = false;
+ DispatchEvent(selectionSet);
+ if (!selectionSet.mSucceeded) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionstart event...", this));
+ WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionstart event, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(!IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ if (!mWidget || mWidget->Destroyed()) {
+ break;
+ }
+ break;
+ }
+ case PendingAction::COMPOSITION_UPDATE: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_UPDATE={ mData=\"%s\", "
+ "mRanges=0x%p, mRanges->Length()=%d }",
+ 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to setting pending composition... "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "dispatching compositionchange event...", this));
+ WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositionchange event, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ // Be aware, the mWidget might already have been destroyed.
+ }
+ break;
+ }
+ case PendingAction::COMPOSITION_END: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing COMPOSITION_END={ 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.
+ // In this case, we should not clear mContentForTSFuntil we notify
+ // the IME of the composition update.
+ mDeferClearingContentForTSF = true;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "dispatching compositioncommit event...", this));
+ WidgetEventTime eventTime = mWidget->CurrentMessageWidgetEventTime();
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "FAILED to dispatch compositioncommit event, "
+ "IsComposingInContent()=%s",
+ this, GetBoolName(IsComposingInContent())));
+ mDeferClearingContentForTSF = !IsComposingInContent();
+ }
+ break;
+ }
+ case PendingAction::SET_SELECTION: {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "flushing SET_SELECTION={ mSelectionStart=%d, "
+ "mSelectionLength=%d, mSelectionReversed=%s }, "
+ "mDestroyed=%s",
+ this, action.mSelectionStart, action.mSelectionLength,
+ GetBoolName(action.mSelectionReversed),
+ GetBoolName(mDestroyed)));
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::FlushPendingActions() "
+ "IGNORED pending selectionset due to already destroyed",
+ this));
+ break;
+ }
+
+ WidgetSelectionEvent selectionSet(true, eSetSelection, mWidget);
+ selectionSet.mOffset =
+ static_cast<uint32_t>(action.mSelectionStart);
+ selectionSet.mLength =
+ static_cast<uint32_t>(action.mSelectionLength);
+ selectionSet.mReversed = action.mSelectionReversed;
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected action type");
+ }
+
+ if (mWidget && !mWidget->Destroyed()) {
+ continue;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::FlushPendingActions(), "
+ "qutting since the mWidget has gone", this));
+ break;
+ }
+ mPendingActions.Clear();
+}
+
+void
+TSFTextStore::MaybeFlushPendingNotifications()
+{
+ if (IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "putting off flushing pending notifications due to being the "
+ "document locked...", this));
+ return;
+ }
+
+ if (mDeferCommittingComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(false)...", this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(false);
+ } else if (mDeferCancellingComposition) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::CommitCompositionInternal(true)...", this));
+ mDeferCommittingComposition = mDeferCancellingComposition = false;
+ CommitCompositionInternal(true);
+ }
+
+ if (mDeferNotifyingTSF) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "does nothing because this has already destroyed completely...", this));
+ return;
+ }
+
+ if (!mDeferClearingContentForTSF && mContentForTSF.IsInitialized()) {
+ mContentForTSF.Clear();
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "mContentForTSF is cleared", 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.IsInitialized()) {
+ if (mPendingTextChangeData.IsValid()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfTextChange()...", this));
+ NotifyTSFOfTextChange();
+ }
+ if (mPendingSelectionChangeData.IsValid()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfSelectionChange()...", this));
+ NotifyTSFOfSelectionChange();
+ }
+ }
+
+ if (mHasReturnedNoLayoutError) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), "
+ "calling TSFTextStore::NotifyTSFOfLayoutChange()...", this));
+ NotifyTSFOfLayoutChange();
+ }
+}
+
+STDMETHODIMP
+TSFTextStore::GetStatus(TS_STATUS* pdcs)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetStatus(pdcs=0x%p)", this, pdcs));
+
+ if (!pdcs) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetStatus() FAILED due to null pdcs", this));
+ return E_INVALIDARG;
+ }
+ pdcs->dwDynamicFlags = 0;
+ // 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::QueryInsert(acpTestStart=%ld, "
+ "acpTestEnd=%ld, cch=%lu, pacpResultStart=0x%p, pacpResultEnd=0x%p)",
+ this, acpTestStart, acpTestEnd, cch, acpTestStart, acpTestEnd));
+
+ if (!pacpResultStart || !pacpResultEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::QueryInsert() FAILED due to "
+ "the null argument", this));
+ return E_INVALIDARG;
+ }
+
+ if (acpTestStart < 0 || acpTestStart > acpTestEnd) {
+ MOZ_LOG(sTextStoreLog, 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
+ const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
+ if (IsWin8OrLater() && !mComposition.IsComposing() &&
+ ((sHackQueryInsertForMSTraditionalTIP &&
+ (kSink->IsMSChangJieActive() || kSink->IsMSQuickQuickActive())) ||
+ (sHackQueryInsertForMSSimplifiedTIP &&
+ (kSink->IsMSPinyinActive() || kSink->IsMSWubiActive())))) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to not locked",
+ this));
+ return TS_E_NOLOCK;
+ }
+ if (!ulCount || !pSelection || !pcFetched) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "unsupported selection", this));
+ return TS_E_NOSELECTION;
+ }
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetSelection() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ *pSelection = selectionForTSF.ACP();
+ *pcFetched = 1;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetSelection() succeeded", this));
+ return S_OK;
+}
+
+bool
+TSFTextStore::IsComposingInContent() const
+{
+ if (!mDispatcher) {
+ return false;
+ }
+ if (!mDispatcher->IsInNativeInputTransaction()) {
+ return false;
+ }
+ return mDispatcher->IsComposing();
+}
+
+TSFTextStore::Content&
+TSFTextStore::ContentForTSFRef()
+{
+ // This should be called when the document is locked or the content hasn't
+ // been abandoned yet.
+ if (NS_WARN_IF(!IsReadLocked() && !mContentForTSF.IsInitialized())) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "called wrong timing, IsReadLocked()=%s, "
+ "mContentForTSF.IsInitialized()=%s",
+ this, GetBoolName(IsReadLocked()),
+ GetBoolName(mContentForTSF.IsInitialized())));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "SelectionForTSFRef() failure", this));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ if (!mContentForTSF.IsInitialized()) {
+ nsAutoString text;
+ if (NS_WARN_IF(!GetCurrentText(text))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::ContentForTSFRef(), FAILED, due to "
+ "GetCurrentText() failure", this));
+ mContentForTSF.Clear();
+ return mContentForTSF;
+ }
+
+ mContentForTSF.Init(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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::ContentForTSFRef(): "
+ "mContentForTSF={ mText=\"%s\" (Length()=%u), "
+ "mLastCompositionString=\"%s\" (Length()=%u), "
+ "mMinTextModifiedOffset=%u }",
+ this, mContentForTSF.Text().Length() <= 40 ?
+ GetEscapedUTF8String(mContentForTSF.Text()).get() : "<omitted>",
+ mContentForTSF.Text().Length(),
+ GetEscapedUTF8String(mContentForTSF.LastCompositionString()).get(),
+ mContentForTSF.LastCompositionString().Length(),
+ mContentForTSF.MinTextModifiedOffset()));
+
+ return mContentForTSF;
+}
+
+bool
+TSFTextStore::CanAccessActualContentDirectly() const
+{
+ if (!mContentForTSF.IsInitialized() || mSelectionForTSF.IsDirty()) {
+ 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.IsValid()) {
+ return true;
+ }
+
+ return mSelectionForTSF.EqualsExceptDirection(mPendingSelectionChangeData);
+}
+
+bool
+TSFTextStore::GetCurrentText(nsAString& aTextContent)
+{
+ if (mContentForTSF.IsInitialized()) {
+ aTextContent = mContentForTSF.Text();
+ return true;
+ }
+
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(mWidget && !mWidget->Destroyed());
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetCurrentText(): "
+ "retrieving text from the content...", this));
+
+ WidgetQueryContentEvent queryText(true, eQueryTextContent, mWidget);
+ queryText.InitForQueryTextContent(0, UINT32_MAX);
+ mWidget->InitEvent(queryText);
+ DispatchEvent(queryText);
+ if (NS_WARN_IF(!queryText.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to "
+ "eQueryTextContent failure", this));
+ aTextContent.Truncate();
+ return false;
+ }
+
+ aTextContent = queryText.mReply.mString;
+ return true;
+}
+
+TSFTextStore::Selection&
+TSFTextStore::SelectionForTSFRef()
+{
+ if (mSelectionForTSF.IsDirty()) {
+ 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_CRASH();
+ }
+
+ WidgetQueryContentEvent querySelection(true, eQuerySelectedText, mWidget);
+ mWidget->InitEvent(querySelection);
+ DispatchEvent(querySelection);
+ if (NS_WARN_IF(!querySelection.mSucceeded)) {
+ return mSelectionForTSF;
+ }
+
+ mSelectionForTSF.SetSelection(querySelection.mReply.mOffset,
+ querySelection.mReply.mString.Length(),
+ querySelection.mReply.mReversed,
+ querySelection.GetWritingMode());
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SelectionForTSFRef(): "
+ "acpStart=%d, acpEnd=%d (length=%d), reverted=%s",
+ this, mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ mSelectionForTSF.Length(),
+ GetBoolName(mSelectionForTSF.IsReversed())));
+
+ 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(sTextStoreLog, LogLevel::Debug)) {
+ LONG start = 0, length = 0;
+ hr = GetRangeExtent(aRange, &start, &length);
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute(): "
+ "GetDisplayAttribute range=%ld-%ld (hr=%s)",
+ this, start - mComposition.mStart,
+ start - mComposition.mStart + length,
+ GetCommonReturnValueName(hr)));
+ }
+
+ VARIANT propValue;
+ ::VariantInit(&propValue);
+ hr = aAttrProperty->GetValue(TfEditCookie(mEditCookie), aRange, &propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() failed", this));
+ return hr;
+ }
+ if (VT_I4 != propValue.vt) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfProperty::GetValue() returns non-VT_I4 value", this));
+ ::VariantClear(&propValue);
+ return E_FAIL;
+ }
+
+ NS_ENSURE_TRUE(sCategoryMgr, E_FAIL);
+ GUID guid;
+ hr = sCategoryMgr->GetGUID(DWORD(propValue.lVal), &guid);
+ ::VariantClear(&propValue);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfCategoryMgr::GetGUID() failed", this));
+ return hr;
+ }
+
+ NS_ENSURE_TRUE(sDisplayAttrMgr, E_FAIL);
+ RefPtr<ITfDisplayAttributeInfo> info;
+ hr = sDisplayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info),
+ nullptr);
+ if (FAILED(hr) || !info) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeMgr::GetDisplayAttributeInfo() failed", this));
+ return hr;
+ }
+
+ hr = info->GetAttributeInfo(aResult);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to "
+ "ITfDisplayAttributeInfo::GetAttributeInfo() failed", this));
+ return hr;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetDisplayAttribute() succeeded: "
+ "Result={ %s }", this, GetDisplayAttrStr(*aResult).get()));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RestartCompositionIfNecessary(ITfRange* aRangeNew)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary("
+ "aRangeNew=0x%p), mComposition.mView=0x%p",
+ this, aRangeNew, mComposition.mView.get()));
+
+ if (!mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to no composition view", this));
+ return E_FAIL;
+ }
+
+ HRESULT hr;
+ RefPtr<ITfCompositionView> pComposition(mComposition.mView);
+ RefPtr<ITfRange> composingRange(aRangeNew);
+ if (!composingRange) {
+ hr = pComposition->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() FAILED "
+ "due to GetRangeExtent() failure", this));
+ return hr;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary(), "
+ "range=%ld-%ld, mComposition={ mStart=%ld, mString.Length()=%lu }",
+ this, compStart, compStart + compLength, mComposition.mStart,
+ mComposition.mString.Length()));
+
+ if (mComposition.mStart != compStart ||
+ mComposition.mString.Length() != (ULONG)compLength) {
+ // 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(pComposition, composingRange);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() "
+ "FAILED due to RestartComposition() failure", this));
+ return hr;
+ }
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartCompositionIfNecessary() succeeded",
+ this));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RestartComposition(ITfCompositionView* aCompositionView,
+ ITfRange* aNewRange)
+{
+ Selection& selectionForTSF = SelectionForTSFRef();
+
+ LONG newStart, newLength;
+ HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength);
+ LONG newEnd = newStart + newLength;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, "
+ "aNewRange=0x%p { newStart=%d, newLength=%d }), "
+ "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
+ "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+ this, aCompositionView, aNewRange, newStart, newLength,
+ mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(selectionForTSF.IsDirty()),
+ selectionForTSF.StartOffset(), selectionForTSF.Length()));
+
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, 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 >= mComposition.EndOffset() || newEnd <= mComposition.mStart) {
+ RecordCompositionEndAction();
+ RecordCompositionStartAction(aCompositionView, newStart, newLength, true);
+ return S_OK;
+ }
+
+ // 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 = mComposition;
+ Selection oldSelection = selectionForTSF;
+
+ // Commit only the part of composition.
+ LONG keepComposingStartOffset = std::max(mComposition.mStart, newStart);
+ LONG keepComposingEndOffset = std::min(mComposition.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(mComposition.mString);
+ commitString.Cut(keepComposingStartOffset - mComposition.mStart,
+ keepComposingLength);
+ // Update the composition string.
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RestartComposition() FAILED "
+ "due to ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ contentForTSF.ReplaceTextWith(mComposition.mStart,
+ mComposition.mString.Length(),
+ commitString);
+ // Record a compositionupdate action for commit the part of composing string.
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition.mString;
+ 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 =
+ uint32_t(oldComposition.mStart + 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.mString,
+ keepComposingStartOffset - oldComposition.mStart,
+ keepComposingLength));
+ selectionForTSF = oldSelection;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RestartComposition() succeeded, "
+ "mComposition={ mStart=%d, mCompositionString.Length()=%d }, "
+ "selectionForTSF={ IsDirty()=%s, StartOffset()=%d, Length()=%d }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(selectionForTSF.IsDirty()),
+ selectionForTSF.StartOffset(), selectionForTSF.Length()));
+
+ 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, uint8_t& 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction(), "
+ "mComposition={ mView=0x%p, mStart=%d, mString=\"%s\" "
+ "(Length()=%d) }",
+ this, mComposition.mView.get(), mComposition.mStart,
+ GetEscapedUTF8String(mComposition.mString).get(),
+ mComposition.mString.Length()));
+
+ if (!mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to mContext->GetProperty() failure", this));
+ return FAILED(hr) ? hr : E_FAIL;
+ }
+
+ RefPtr<ITfRange> composingRange;
+ hr = mComposition.mView->GetRange(getter_AddRefs(composingRange));
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "FAILED due to mComposition.mView->GetRange() failure", this));
+ return hr;
+ }
+
+ RefPtr<IEnumTfRanges> enumRanges;
+ hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie),
+ getter_AddRefs(enumRanges), composingRange);
+ if (FAILED(hr) || !enumRanges) {
+ MOZ_LOG(sTextStoreLog, 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.
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED "
+ "due to SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mData = mComposition.mString;
+ // 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.mStart),
+ mComposition.EndOffset());
+ LONG end = std::max(std::min(rangeStart + rangeLength,
+ mComposition.EndOffset()),
+ mComposition.mStart);
+ LONG length = end - start;
+ if (length < 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores invalid range (%d-%d)",
+ this, rangeStart - mComposition.mStart,
+ rangeStart - mComposition.mStart + rangeLength));
+ continue;
+ }
+ if (!length) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "ignores a range due to outside of the composition or empty "
+ "(%d-%d)",
+ this, rangeStart - mComposition.mStart,
+ rangeStart - mComposition.mStart + rangeLength));
+ continue;
+ }
+
+ TextRange newRange;
+ newRange.mStartOffset = uint32_t(start - mComposition.mStart);
+ // The end of the last range in the array is
+ // always kept at the end of composition
+ newRange.mEndOffset = mComposition.mString.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.IsCollapsed() && action->mRanges->Length() == 1) {
+ TextRange& range = action->mRanges->ElementAt(0);
+ LONG start = selectionForTSF.MinOffset();
+ LONG end = selectionForTSF.MaxOffset();
+ if ((LONG)range.mStartOffset == start - mComposition.mStart &&
+ (LONG)range.mEndOffset == end - mComposition.mStart &&
+ 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.MaxOffset() - mComposition.mStart);
+
+ // 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionUpdateAction() "
+ "succeeded", this));
+
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::SetSelectionInternal(const TS_SELECTION_ACP* pSelection,
+ bool aDispatchCompositionChangeEvent)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::SetSelectionInternal(pSelection={ "
+ "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s} }, "
+ "aDispatchCompositionChangeEvent=%s), mComposition.IsComposing()=%s",
+ this, pSelection->acpStart, pSelection->acpEnd,
+ GetActiveSelEndName(pSelection->style.ase),
+ GetBoolName(pSelection->style.fInterimChar),
+ GetBoolName(aDispatchCompositionChangeEvent),
+ GetBoolName(mComposition.IsComposing())));
+
+ MOZ_ASSERT(IsReadWriteLocked());
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "SelectionForTSFRef() failure", 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but "
+ "did nothing because the selection range isn't changing", this));
+ selectionForTSF.SetSelection(*pSelection);
+ return S_OK;
+ }
+
+ if (mComposition.IsComposing()) {
+ if (aDispatchCompositionChangeEvent) {
+ HRESULT hr = RestartCompositionIfNecessary();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to "
+ "RestartCompositionIfNecessary() failure", this));
+ return hr;
+ }
+ }
+ if (pSelection->acpStart < mComposition.mStart ||
+ pSelection->acpEnd > mComposition.EndOffset()) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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.IsInitialized() &&
+ 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(sTextStoreLog, 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::SET_SELECTION;
+ 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%p { "
+ "acpStart=%ld, acpEnd=%ld, style={ ase=%s, fInterimChar=%s } }), "
+ "mComposition.IsComposing()=%s",
+ this, ulCount, pSelection,
+ pSelection ? pSelection->acpStart : 0,
+ pSelection ? pSelection->acpEnd : 0,
+ pSelection ? GetActiveSelEndName(pSelection->style.ase) : "",
+ pSelection ? GetBoolName(pSelection->style.fInterimChar) : "",
+ GetBoolName(mComposition.IsComposing())));
+
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "not locked (read-write)", this));
+ return TS_E_NOLOCK;
+ }
+ if (ulCount != 1) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "trying setting multiple selection", this));
+ return E_INVALIDARG;
+ }
+ if (!pSelection) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetSelection() FAILED due to "
+ "SetSelectionInternal() failure", this));
+ } else {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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={ mStart=%ld, "
+ "mString.Length()=%lu, IsComposing()=%s }",
+ this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut,
+ prgRunInfo, ulRunInfoReq, pulRunInfoOut, pacpNext,
+ mComposition.mStart, mComposition.mString.Length(),
+ GetBoolName(mComposition.IsComposing())));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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;
+ }
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetText() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ if (contentForTSF.Text().Length() < static_cast<uint32_t>(acpStart)) {
+ MOZ_LOG(sTextStoreLog, 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.Text().Length() < static_cast<uint32_t>(acpEnd)) {
+ MOZ_LOG(sTextStoreLog, 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.Text().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.Text().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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::SetText(dwFlags=%s, acpStart=%ld, "
+ "acpEnd=%ld, pchText=0x%p \"%s\", cch=%lu, pChange=0x%p), "
+ "mComposition.IsComposing()=%s",
+ this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" :
+ "not-specified",
+ acpStart, acpEnd, pchText,
+ pchText && cch ?
+ GetEscapedUTF8String(pchText, cch).get() : "",
+ cch, pChange, GetBoolName(mComposition.IsComposing())));
+
+ // 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::SetText() FAILED due to "
+ "InsertTextAtSelectionInternal() failure", this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertEmbedded() called "
+ "but not supported (E_NOTIMPL)", this));
+
+ // embedded objects are not supported
+ return E_NOTIMPL;
+}
+
+void
+TSFTextStore::SetInputScope(const nsString& aHTMLInputType,
+ const nsString& aHTMLInputInputMode)
+{
+ mInputScopes.Clear();
+ if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
+ if (aHTMLInputInputMode.EqualsLiteral("url")) {
+ mInputScopes.AppendElement(IS_URL);
+ } else if (aHTMLInputInputMode.EqualsLiteral("email")) {
+ mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ } else if (aHTMLInputType.EqualsLiteral("numeric")) {
+ mInputScopes.AppendElement(IS_NUMBER);
+ }
+ return;
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.EqualsLiteral("url")) {
+ mInputScopes.AppendElement(IS_URL);
+ } else if (aHTMLInputType.EqualsLiteral("search")) {
+ mInputScopes.AppendElement(IS_SEARCH);
+ } else if (aHTMLInputType.EqualsLiteral("email")) {
+ mInputScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS);
+ } else if (aHTMLInputType.EqualsLiteral("password")) {
+ mInputScopes.AppendElement(IS_PASSWORD);
+ } else if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ mInputScopes.AppendElement(IS_DATE_FULLDATE);
+ mInputScopes.AppendElement(IS_TIME_FULLTIME);
+ } else if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ mInputScopes.AppendElement(IS_DATE_FULLDATE);
+ } else if (aHTMLInputType.EqualsLiteral("time")) {
+ mInputScopes.AppendElement(IS_TIME_FULLTIME);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ mInputScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER);
+ mInputScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER);
+ } else if (aHTMLInputType.EqualsLiteral("number")) {
+ mInputScopes.AppendElement(IS_NUMBER);
+ }
+}
+
+int32_t
+TSFTextStore::GetRequestedAttrIndex(const TS_ATTRID& aAttrID)
+{
+ if (IsEqualGUID(aAttrID, GUID_PROP_INPUTSCOPE)) {
+ return eInputScope;
+ }
+ 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 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::HandleRequestAttrs(aFlags=%s, "
+ "aFilterCount=%u)",
+ 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ (" 0x%p TSFTextStore::FindNextAttrTransition() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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;
+}
+
+STDMETHODIMP
+TSFTextStore::RetrieveRequestedAttrs(ULONG ulCount,
+ TS_ATTRVAL* paAttrVals,
+ ULONG* pcFetched)
+{
+ if (!pcFetched || !paAttrVals) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() FAILED due to "
+ "not enough count ulCount=%u, expectedCount=%u",
+ this, ulCount, expectedCount));
+ return E_INVALIDARG;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RetrieveRequestedAttrs() called "
+ "ulCount=%d, mRequestedAttrValues=%s",
+ this, ulCount, GetBoolName(mRequestedAttrValues)));
+
+ 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(sTextStoreLog, 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 eTextVerticalWriting: {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ paAttrVals[count].varValue.vt = VT_BOOL;
+ paAttrVals[count].varValue.boolVal =
+ selectionForTSF.GetWritingMode().IsVertical() ? VARIANT_TRUE :
+ VARIANT_FALSE;
+ break;
+ }
+ case eTextOrientation: {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ paAttrVals[count].varValue.vt = VT_I4;
+ paAttrVals[count].varValue.lVal =
+ selectionForTSF.GetWritingMode().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(sTextStoreLog, 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;
+}
+
+STDMETHODIMP
+TSFTextStore::GetEndACP(LONG* pacp)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetEndACP(pacp=0x%p)", this, pacp));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "not locked (read)", this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetEndACP() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ *pacp = static_cast<LONG>(contentForTSF.Text().Length());
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetActiveView(TsViewCookie* pvcView)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetActiveView(pvcView=0x%p)",
+ this, pvcView));
+
+ if (!pvcView) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetActiveView() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ *pvcView = TEXTSTORE_DEFAULT_VIEW;
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint(pvcView=%d, pt=%p (x=%d, "
+ "y=%d), dwFlags=%s, pacp=%p, mDeferNotifyingTSF=%s, "
+ "mWaitingQueryLayout=%s",
+ this, vcView, pt, pt ? pt->x : 0, pt ? pt->y : 0,
+ GetACPFromPointFlagName(dwFlags).get(), pacp,
+ GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "not locked (read)", this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "called with invalid view", this));
+ return E_INVALIDARG;
+ }
+
+ if (!pt) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pt", this));
+ return E_INVALIDARG;
+ }
+
+ if (!pacp) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "null pacp", this));
+ return E_INVALIDARG;
+ }
+
+ mWaitingQueryLayout = false;
+
+ if (mDestroyed || mContentForTSF.IsLayoutChanged()) {
+ MOZ_LOG(sTextStoreLog, 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 charAtPt(true, eQueryCharacterAtPoint, mWidget);
+ mWidget->InitEvent(charAtPt, &ourPt);
+
+ // FYI: WidgetQueryContentEvent may cause flushing pending layout and it
+ // may cause focus change or something.
+ RefPtr<TSFTextStore> kungFuDeathGrip(this);
+ DispatchEvent(charAtPt);
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "mWidget was destroyed during eQueryCharacterAtPoint", this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetACPFromPoint(), charAtPt={ "
+ "mSucceeded=%s, mReply={ mOffset=%u, mTentativeCaretOffset=%u }}",
+ this, GetBoolName(charAtPt.mSucceeded), charAtPt.mReply.mOffset,
+ charAtPt.mReply.mTentativeCaretOffset));
+
+ if (NS_WARN_IF(!charAtPt.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, 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) &&
+ charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
+ MOZ_LOG(sTextStoreLog, 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.
+ if (NS_WARN_IF(charAtPt.mReply.mTentativeCaretOffset ==
+ WidgetQueryContentEvent::NOT_FOUND)) {
+ charAtPt.mReply.mTentativeCaretOffset = 0;
+ }
+
+ 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 = charAtPt.mReply.mTentativeCaretOffset;
+ } else if (charAtPt.mReply.mOffset != WidgetQueryContentEvent::NOT_FOUND) {
+ // Otherwise, we should return character offset whose bounding box contains
+ // the point.
+ offset = charAtPt.mReply.mOffset;
+ } 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 = charAtPt.mReply.mTentativeCaretOffset;
+
+ // However, if it's after the last character, we need to decrement the
+ // offset.
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to "
+ "ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ if (contentForTSF.Text().Length() <= offset) {
+ // If the tentative caret is after the last character, let's return
+ // the last character's offset.
+ offset = contentForTSF.Text().Length() - 1;
+ }
+ }
+
+ if (NS_WARN_IF(offset > LONG_MAX)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetACPFromPoint() succeeded: *pacp=%d",
+ this, *pacp));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::GetTextExt(TsViewCookie vcView,
+ LONG acpStart,
+ LONG acpEnd,
+ RECT* prc,
+ BOOL* pfClipped)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetTextExt(vcView=%ld, "
+ "acpStart=%ld, acpEnd=%ld, prc=0x%p, pfClipped=0x%p), "
+ "mDeferNotifyingTSF=%s, mWaitingQueryLayout=%s",
+ this, vcView, acpStart, acpEnd, prc, pfClipped,
+ GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout)));
+
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "not locked (read)", this));
+ return TS_E_NOLOCK;
+ }
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "called with invalid view", this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc || !pfClipped) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ if (acpStart < 0 || acpEnd < acpStart) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "invalid position", this));
+ return TS_E_INVALIDPOS;
+ }
+
+ mWaitingQueryLayout = false;
+
+ // NOTE: TSF (at least on Win 8.1) doesn't return TS_E_NOLAYOUT to the
+ // caller even if we return it. It's converted to just E_FAIL.
+ // However, this is fixed on Win 10.
+
+ bool dontReturnNoLayoutError = false;
+
+ const TSFStaticSink* kSink = TSFStaticSink::GetInstance();
+ if (mComposition.IsComposing() && mComposition.mStart < acpEnd &&
+ mContentForTSF.IsLayoutChangedAt(acpEnd)) {
+ const Selection& selectionForTSF = SelectionForTSFRef();
+ // 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.
+ const bool kIsMSOfficeJapaneseIME2010 =
+ kSink->IsMSOfficeJapaneseIME2010Active();
+ // 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.
+ if (kIsMSOfficeJapaneseIME2010 ||
+ ((sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
+ kSink->IsMSJapaneseIMEActive())) {
+ // 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 ((kIsMSOfficeJapaneseIME2010 ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar) &&
+ acpStart < acpEnd) {
+ acpEnd = acpStart;
+ dontReturnNoLayoutError = true;
+ }
+ // 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.
+ else if ((kIsMSOfficeJapaneseIME2010 ||
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret) &&
+ acpStart == acpEnd &&
+ selectionForTSF.IsCollapsed() &&
+ selectionForTSF.EndOffset() == acpEnd) {
+ if (mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
+ "is too big for TSF (cannot treat modified offset as LONG), "
+ "mContentForTSF.MinOffsetOfLayoutChanged()=%u",
+ this, mContentForTSF.MinOffsetOfLayoutChanged()));
+ return E_FAIL;
+ }
+ int32_t minOffsetOfLayoutChanged =
+ static_cast<int32_t>(mContentForTSF.MinOffsetOfLayoutChanged());
+ acpEnd = acpStart = std::max(minOffsetOfLayoutChanged - 1, 0);
+ dontReturnNoLayoutError = true;
+ }
+ }
+ // 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
+ // composition string.
+ // XXX 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.
+ else if (sDoNotReturnNoLayoutErrorToATOKOfCompositionString &&
+ kSink->IsATOKActive() &&
+ (!kSink->IsATOKReferringNativeCaretActive() ||
+ !sCreateNativeCaretForLegacyATOK) &&
+ mComposition.mStart == acpStart &&
+ mComposition.EndOffset() == acpEnd) {
+ dontReturnNoLayoutError = true;
+ }
+ // Free ChangJie 2010 and Easy Changjei 1.0.12.0 doesn't handle
+ // ITfContextView::GetTextExt() properly. Prehaps, it's due to the bug of
+ // TSF. We need to check if this is necessary on Windows 10 before
+ // disabling this on Windows 10.
+ else if ((sDoNotReturnNoLayoutErrorToFreeChangJie &&
+ kSink->IsFreeChangJieActive()) ||
+ (sDoNotReturnNoLayoutErrorToEasyChangjei &&
+ kSink->IsEasyChangjeiActive())) {
+ acpEnd = mComposition.mStart;
+ acpStart = std::min(acpStart, acpEnd);
+ dontReturnNoLayoutError = true;
+ }
+ // Some Chinese TIPs of Microsoft doesn't show candidate window in e10s
+ // mode on Win8 or later.
+ else if (IsWin8OrLater() &&
+ ((sDoNotReturnNoLayoutErrorToMSTraditionalTIP &&
+ (kSink->IsMSChangJieActive() ||
+ kSink->IsMSQuickQuickActive())) ||
+ (sDoNotReturnNoLayoutErrorToMSSimplifiedTIP &&
+ (kSink->IsMSPinyinActive() ||
+ kSink->IsMSWubiActive())))) {
+ acpEnd = mComposition.mStart;
+ acpStart = std::min(acpStart, acpEnd);
+ dontReturnNoLayoutError = true;
+ }
+
+ // 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.
+ if (dontReturnNoLayoutError) {
+ MOZ_ASSERT(mContentForTSF.IsLayoutChanged());
+ if (mContentForTSF.MinOffsetOfLayoutChanged() > LONG_MAX) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text "
+ "is too big for TSF (cannot treat modified offset as LONG), "
+ "mContentForTSF.MinOffsetOfLayoutChanged()=%u",
+ this, mContentForTSF.MinOffsetOfLayoutChanged()));
+ return E_FAIL;
+ }
+ // 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.MinOffsetOfLayoutChanged());
+ LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0);
+ if (mContentForTSF.IsLayoutChangedAt(acpStart)) {
+ // If TSF queries text rect in composition string, we should return
+ // rect at start of the composition even if its layout is changed.
+ if (acpStart >= mComposition.mStart) {
+ acpStart = mComposition.mStart;
+ }
+ // Otherwise, use first character's rect. Even if there is no
+ // characters, the query event will return caret rect instead.
+ else {
+ acpStart = lastUnmodifiedOffset;
+ }
+ MOZ_ASSERT(acpStart <= acpEnd);
+ }
+ if (mContentForTSF.IsLayoutChangedAt(acpEnd)) {
+ // Use max larger offset of last unmodified offset or acpStart which
+ // may be the first character offset of the composition string.
+ acpEnd = std::max(acpStart, lastUnmodifiedOffset);
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetTextExt() hacked the queried range "
+ "for not returning TS_E_NOLAYOUT, new values are: "
+ "acpStart=%d, acpEnd=%d", this, acpStart, acpEnd));
+ }
+ }
+
+ if (!dontReturnNoLayoutError && mContentForTSF.IsLayoutChangedAt(acpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%d)", this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() returned TS_E_NOLAYOUT "
+ "(acpEnd=%d) because this has already been destroyed",
+ this, acpEnd));
+ mHasReturnedNoLayoutError = true;
+ return TS_E_NOLAYOUT;
+ }
+
+ // use eQueryTextRect to get rect in system, screen coordinates
+ WidgetQueryContentEvent event(true, eQueryTextRect, mWidget);
+ mWidget->InitEvent(event);
+
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = acpStart;
+ if (mComposition.IsComposing()) {
+ // 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.mStart;
+ } 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 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);
+ event.InitForQueryTextRect(startOffset, length, options);
+
+ DispatchEvent(event);
+ if (NS_WARN_IF(!event.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, 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 (event.mReply.mRect.width <= 0)
+ event.mReply.mRect.width = 1;
+ if (event.mReply.mRect.height <= 0)
+ event.mReply.mRect.height = 1;
+
+ // convert to unclipped screen rect
+ nsWindow* refWindow = static_cast<nsWindow*>(
+ event.mReply.mFocusedWidget ? event.mReply.mFocusedWidget : mWidget);
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetTextExt() FAILED due to "
+ "no top level window", this));
+ return E_FAIL;
+ }
+
+ event.mReply.mRect.MoveBy(refWindow->WidgetToScreenOffset());
+
+ // get bounding screen rect to test for clipping
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(sTextStoreLog, 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, event.mReply.mRect.x, event.mReply.mRect.y,
+ event.mReply.mRect.XMost(), event.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. Therefore, we need to create native caret only when ATOK 2011 -
+ // 2016 is active.
+ if (sCreateNativeCaretForLegacyATOK &&
+ kSink->IsATOKReferringNativeCaretActive() &&
+ mComposition.IsComposing() &&
+ mComposition.mStart <= acpStart && mComposition.EndOffset() >= acpStart &&
+ mComposition.mStart <= acpEnd && mComposition.EndOffset() >= acpEnd) {
+ CreateNativeCaret();
+ }
+
+ MOZ_LOG(sTextStoreLog, 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;
+}
+
+STDMETHODIMP
+TSFTextStore::GetScreenExt(TsViewCookie vcView,
+ RECT* prc)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::GetScreenExt(vcView=%ld, prc=0x%p)",
+ this, vcView, prc));
+
+ if (vcView != TEXTSTORE_DEFAULT_VIEW) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "called with invalid view", this));
+ return E_INVALIDARG;
+ }
+
+ if (!prc) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() returns empty rect "
+ "due to already destroyed", this));
+ prc->left = prc->top = prc->right = prc->left = 0;
+ return S_OK;
+ }
+
+ if (!GetScreenExtInternal(*prc)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "GetScreenExtInternal() failure", this));
+ return E_FAIL;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::GetScreenExtInternal()", this));
+
+ MOZ_ASSERT(!mDestroyed);
+
+ // use NS_QUERY_EDITOR_RECT to get rect in system, screen coordinates
+ WidgetQueryContentEvent event(true, eQueryEditorRect, mWidget);
+ mWidget->InitEvent(event);
+ DispatchEvent(event);
+ if (!event.mSucceeded) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to "
+ "eQueryEditorRect failure", this));
+ return false;
+ }
+
+ nsWindow* refWindow = static_cast<nsWindow*>(
+ event.mReply.mFocusedWidget ?
+ event.mReply.mFocusedWidget : mWidget);
+ // Result rect is in top level widget coordinates
+ refWindow = refWindow->GetTopLevelWindow(false);
+ if (!refWindow) {
+ MOZ_LOG(sTextStoreLog, 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(event.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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetWnd() FAILED due to "
+ "called with invalid view", this));
+ return E_INVALIDARG;
+ }
+
+ if (!phwnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::GetScreenExt() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ *phwnd = mWidget ? mWidget->GetWindowHandle() : nullptr;
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::InsertTextAtSelection(dwFlags=%s, "
+ "pchText=0x%p \"%s\", cch=%lu, pacpStart=0x%p, pacpEnd=0x%p, "
+ "pChange=0x%p), IsComposing()=%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,
+ GetBoolName(mComposition.IsComposing())));
+
+ if (cch && !pchText) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pchText", this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_QUERYONLY == dwFlags) {
+ if (!IsReadLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read)", this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pacpStart || !pacpEnd) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ // Get selection first
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ // Simulate text insertion
+ *pacpStart = selectionForTSF.StartOffset();
+ *pacpEnd = selectionForTSF.EndOffset();
+ if (pChange) {
+ pChange->acpStart = selectionForTSF.StartOffset();
+ pChange->acpOldEnd = selectionForTSF.EndOffset();
+ pChange->acpNewEnd =
+ selectionForTSF.StartOffset() + static_cast<LONG>(cch);
+ }
+ } else {
+ if (!IsReadWriteLocked()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "not locked (read-write)", this));
+ return TS_E_NOLOCK;
+ }
+
+ if (!pChange) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null pChange", this));
+ return E_INVALIDARG;
+ }
+
+ if (TS_IAS_NOQUERY != dwFlags && (!pacpStart || !pacpEnd)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to "
+ "null argument", this));
+ return E_INVALIDARG;
+ }
+
+ if (!InsertTextAtSelectionInternal(nsDependentSubstring(pchText, cch),
+ pChange)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal("
+ "aInsertStr=\"%s\", aTextChange=0x%p), IsComposing=%s",
+ this, GetEscapedUTF8String(aInsertStr).get(), aTextChange,
+ GetBoolName(mComposition.IsComposing())));
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed "
+ "due to ContentForTSFRef() failure()", this));
+ return false;
+ }
+
+ TS_SELECTION_ACP oldSelection = contentForTSF.Selection().ACP();
+ if (!mComposition.IsComposing()) {
+ // Use a temporary composition to contain the text
+ PendingAction* compositionStart = mPendingActions.AppendElement();
+ compositionStart->mType = PendingAction::COMPOSITION_START;
+ compositionStart->mSelectionStart = oldSelection.acpStart;
+ compositionStart->mSelectionLength =
+ oldSelection.acpEnd - oldSelection.acpStart;
+ compositionStart->mAdjustSelection = false;
+
+ PendingAction* compositionEnd = mPendingActions.AppendElement();
+ compositionEnd->mType = PendingAction::COMPOSITION_END;
+ compositionEnd->mData = aInsertStr;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::InsertTextAtSelectionInternal() "
+ "appending pending compositionstart and compositionend... "
+ "PendingCompositionStart={ mSelectionStart=%d, "
+ "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
+ "(Length()=%u) }",
+ this, compositionStart->mSelectionStart,
+ compositionStart->mSelectionLength,
+ GetEscapedUTF8String(compositionEnd->mData).get(),
+ compositionEnd->mData.Length()));
+ }
+
+ contentForTSF.ReplaceSelectedTextWith(aInsertStr);
+
+ if (aTextChange) {
+ aTextChange->acpStart = oldSelection.acpStart;
+ aTextChange->acpOldEnd = oldSelection.acpEnd;
+ aTextChange->acpNewEnd = contentForTSF.Selection().EndOffset();
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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* aComposition,
+ ITfRange* aRange,
+ bool aPreserveSelection)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aComposition=0x%p, aRange=0x%p, aPreserveSelection=%s), "
+ "mComposition.mView=0x%p",
+ this, aComposition, aRange, GetBoolName(aPreserveSelection),
+ mComposition.mView.get()));
+
+ LONG start = 0, length = 0;
+ HRESULT hr = GetRangeExtent(aRange, &start, &length);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to GetRangeExtent() failure", this));
+ return hr;
+ }
+
+ return RecordCompositionStartAction(aComposition, start, length,
+ aPreserveSelection);
+}
+
+HRESULT
+TSFTextStore::RecordCompositionStartAction(ITfCompositionView* aComposition,
+ LONG aStart,
+ LONG aLength,
+ bool aPreserveSelection)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionStartAction("
+ "aComposition=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), "
+ "mComposition.mView=0x%p",
+ this, aComposition, aStart, aLength, GetBoolName(aPreserveSelection),
+ mComposition.mView.get()));
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to ContentForTSFRef() failure", this));
+ return E_FAIL;
+ }
+
+ CompleteLastActionIfStillIncomplete();
+
+ // TIP may have inserted text at selection before calling
+ // OnStartComposition(). In this case, we've already created a pair of
+ // pending compositionstart and pending compositionend. If the pending
+ // compositionstart occurred same range as this composition, it was the
+ // start of this composition. In such case, we should cancel the pending
+ // compositionend and start composition normally.
+ if (!aPreserveSelection &&
+ WasTextInsertedWithoutCompositionAt(aStart, aLength)) {
+ const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+ const PendingAction& pendingCompositionStart =
+ mPendingActions[mPendingActions.Length() - 2];
+ contentForTSF.RestoreCommittedComposition(
+ aComposition, pendingCompositionStart, pendingCompositionEnd);
+ mPendingActions.RemoveElementAt(mPendingActions.Length() - 1);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() "
+ "succeeded: restoring the committed string as composing string, "
+ "mComposition={ mStart=%ld, mString.Length()=%ld, "
+ "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+ "style.fInterimChar=%s } }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+ GetBoolName(mSelectionForTSF.IsInterimChar())));
+ return S_OK;
+ }
+
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::COMPOSITION_START;
+ action->mSelectionStart = aStart;
+ action->mSelectionLength = aLength;
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED "
+ "due to SelectionForTSFRef() failure", this));
+ 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(aComposition, *action, aPreserveSelection);
+ action->mData = mComposition.mString;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: "
+ "mComposition={ mStart=%ld, mString.Length()=%ld, "
+ "mSelectionForTSF={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+ "style.fInterimChar=%s } }",
+ this, mComposition.mStart, mComposition.mString.Length(),
+ mSelectionForTSF.StartOffset(), mSelectionForTSF.EndOffset(),
+ GetActiveSelEndName(mSelectionForTSF.ActiveSelEnd()),
+ GetBoolName(mSelectionForTSF.IsInterimChar())));
+ return S_OK;
+}
+
+HRESULT
+TSFTextStore::RecordCompositionEndAction()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "mComposition={ mView=0x%p, mString=\"%s\" }",
+ this, mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ MOZ_ASSERT(mComposition.IsComposing());
+
+ CompleteLastActionIfStillIncomplete();
+ PendingAction* action = mPendingActions.AppendElement();
+ action->mType = PendingAction::COMPOSITION_END;
+ action->mData = mComposition.mString;
+
+ Content& contentForTSF = ContentForTSFRef();
+ if (!contentForTSF.IsInitialized()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due "
+ "to ContentForTSFRef() 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::COMPOSITION_START) {
+ if (pendingAction.mData != action->mData) {
+ break;
+ }
+ // When only setting selection is necessary, we should append it.
+ if (pendingAction.mAdjustSelection) {
+ PendingAction* setSelection = mPendingActions.AppendElement();
+ setSelection->mType = PendingAction::SET_SELECTION;
+ setSelection->mSelectionStart = pendingAction.mSelectionStart;
+ setSelection->mSelectionLength = pendingAction.mSelectionLength;
+ setSelection->mSelectionReversed = false;
+ }
+ // Remove the redundant pending composition.
+ mPendingActions.RemoveElementsAt(i - 1, j);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), "
+ "succeeded, but the composition was canceled due to redundant",
+ this));
+ return S_OK;
+ }
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::RecordCompositionEndAction(), succeeded",
+ this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnStartComposition(ITfCompositionView* pComposition,
+ BOOL* pfOk)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition(pComposition=0x%p, "
+ "pfOk=0x%p), mComposition.mView=0x%p",
+ this, pComposition, pfOk, mComposition.mView.get()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ *pfOk = FALSE;
+
+ // Only one composition at a time
+ if (mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnStartComposition() FAILED due to "
+ "RecordCompositionStartAction() failure", this));
+ return hr;
+ }
+
+ *pfOk = TRUE;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnStartComposition() succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnUpdateComposition(ITfCompositionView* pComposition,
+ ITfRange* pRangeNew)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition(pComposition=0x%p, "
+ "pRangeNew=0x%p), mComposition.mView=0x%p",
+ this, pComposition, pRangeNew, mComposition.mView.get()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mDocumentMgr || !mContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "not ready for the composition", this));
+ return E_UNEXPECTED;
+ }
+ if (!mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "no active composition", this));
+ return E_UNEXPECTED;
+ }
+ if (mComposition.mView != pComposition) {
+ MOZ_LOG(sTextStoreLog, 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) {
+ PendingAction* action = LastOrNewPendingCompositionUpdate();
+ action->mIncomplete = true;
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded but "
+ "not complete", this));
+ return S_OK;
+ }
+
+ HRESULT hr = RestartCompositionIfNecessary(pRangeNew);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RestartCompositionIfNecessary() failure", this));
+ return hr;
+ }
+
+ hr = RecordCompositionUpdateAction();
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "RecordCompositionUpdateAction() failure", this));
+ return hr;
+ }
+
+ if (MOZ_LOG_TEST(sTextStoreLog, LogLevel::Info)) {
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return E_FAIL;
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnUpdateComposition() succeeded: "
+ "mComposition={ mStart=%ld, mString=\"%s\" }, "
+ "SelectionForTSFRef()={ acpStart=%ld, acpEnd=%ld, style.ase=%s }",
+ this, mComposition.mStart,
+ GetEscapedUTF8String(mComposition.mString).get(),
+ selectionForTSF.StartOffset(), selectionForTSF.EndOffset(),
+ GetActiveSelEndName(selectionForTSF.ActiveSelEnd())));
+ }
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::OnEndComposition(ITfCompositionView* pComposition)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), "
+ "mComposition={ mView=0x%p, mString=\"%s\" }",
+ this, pComposition, mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ AutoPendingActionAndContentFlusher flusher(this);
+
+ if (!mComposition.IsComposing()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "no active composition", this));
+ return E_UNEXPECTED;
+ }
+
+ if (mComposition.mView != pComposition) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::OnEndComposition() FAILED due to "
+ "RecordCompositionEndAction() failure", this));
+ return hr;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnEndComposition(), succeeded", this));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::AdviseMouseSink(ITfRangeACP* range,
+ ITfMouseSink* pSink,
+ DWORD* pdwCookie)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(range=0x%p, pSink=0x%p, "
+ "pdwCookie=0x%p)", this, range, pSink, pdwCookie));
+
+ if (!pdwCookie) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to the "
+ "range is null", this));
+ return E_INVALIDARG;
+ }
+ if (!pSink) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::AdviseMouseSink() FAILED due to failure "
+ "of MouseTracker::Init()", this));
+ return hr;
+ }
+ *pdwCookie = tracker->Cookie();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::AdviseMouseSink(), succeeded, "
+ "*pdwCookie=%d", this, *pdwCookie));
+ return S_OK;
+}
+
+STDMETHODIMP
+TSFTextStore::UnadviseMouseSink(DWORD dwCookie)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(dwCookie=%d)",
+ this, dwCookie));
+ if (dwCookie == MouseTracker::kInvalidCookie) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::UnadviseMouseSink() FAILED due to "
+ "the found tracker uninstalled already", this));
+ return E_INVALIDARG;
+ }
+ tracker.UnadviseSink();
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::UnadviseMouseSink(), succeeded", this));
+ return S_OK;
+}
+
+// static
+nsresult
+TSFTextStore::OnFocusChange(bool aGotFocus,
+ nsWindowBase* aFocusedWidget,
+ const InputContext& aContext)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::OnFocusChange(aGotFocus=%s, "
+ "aFocusedWidget=0x%p, aContext={ mIMEState={ mEnabled=%s }, "
+ "mHTMLInputType=\"%s\" }), "
+ "sThreadMgr=0x%p, sEnabledTextStore=0x%p",
+ GetBoolName(aGotFocus), aFocusedWidget,
+ GetIMEEnabledName(aContext.mIMEState.mEnabled),
+ NS_ConvertUTF16toUTF8(aContext.mHTMLInputType).get(),
+ sThreadMgr.get(), sEnabledTextStore.get()));
+
+ if (NS_WARN_IF(!IsInTSFMode())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr;
+ RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget();
+
+ // If currently sEnableTextStore has focus, notifies TSF of losing focus.
+ if (ThinksHavingFocus()) {
+ 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");
+ }
+
+ // If there is sEnabledTextStore, we don't use it in the new focused editor.
+ // Release it now.
+ if (oldTextStore) {
+ oldTextStore->Destroy();
+ }
+
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(nsWindowBase* 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "invalid TSFTextStore::mDocumentMgr"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (aContext.mIMEState.mEnabled == IMEState::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;
+ {
+ // Windows 10's softwware keyboard requires that SetSelection must be
+ // always successful into SetFocus. If returning error, it might crash
+ // into TextInputFramework.dll.
+ AutoSetTemporarySelection setSelection(textStore->SelectionForTSFRef());
+
+ hr = threadMgr->SetFocus(newDocMgr);
+ }
+
+ if (NS_WARN_IF(FAILED(hr))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::SetFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CreateAndSetFocus() FAILED due to "
+ "ITfTheadMgr::AssociateFocus() failure"));
+ EnsureToDestroyAndReleaseEnabledTextStoreIf(textStore);
+ return false;
+ }
+ if (NS_WARN_IF(!sThreadMgr)) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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
+nsIMEUpdatePreference
+TSFTextStore::GetIMEUpdatePreference()
+{
+ if (sThreadMgr && sEnabledTextStore && sEnabledTextStore->mDocumentMgr) {
+ RefPtr<ITfDocumentMgr> docMgr;
+ sThreadMgr->GetFocus(getter_AddRefs(docMgr));
+ if (docMgr == sEnabledTextStore->mDocumentMgr) {
+ return nsIMEUpdatePreference(
+ nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE |
+ nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
+ nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR |
+ nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE);
+ }
+ }
+ return nsIMEUpdatePreference();
+}
+
+nsresult
+TSFTextStore::OnTextChangeInternal(const IMENotification& aIMENotification)
+{
+ const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ "
+ "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, "
+ "mRemovedEndOffset=%lu, mAddedEndOffset=%lu, "
+ "mCausedOnlyByComposition=%s, "
+ "mIncludingChangesDuringComposition=%s, "
+ "mIncludingChangesWithoutComposition=%s }), "
+ "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, "
+ "mComposition.IsComposing()=%s",
+ this, aIMENotification.mMessage,
+ textChangeData.mStartOffset,
+ textChangeData.mRemovedEndOffset,
+ textChangeData.mAddedEndOffset,
+ GetBoolName(textChangeData.mCausedOnlyByComposition),
+ GetBoolName(textChangeData.mIncludingChangesDuringComposition),
+ GetBoolName(textChangeData.mIncludingChangesWithoutComposition),
+ GetBoolName(mDestroyed),
+ mSink.get(),
+ GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mComposition.IsComposing())));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSF = 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.IsComposing());
+ 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.MarkDirty();
+
+ // 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnSelectionChangeInternal("
+ "aIMENotification={ mSelectionChangeData={ mOffset=%lu, "
+ "Length()=%lu, mReversed=%s, mWritingMode=%s, "
+ "mCausedByComposition=%s, mCausedBySelectionEvent=%s, "
+ "mOccurredDuringComposition=%s } }), mDestroyed=%s, "
+ "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, "
+ "mComposition.IsComposing()=%s",
+ this, selectionChangeData.mOffset, selectionChangeData.Length(),
+ GetBoolName(selectionChangeData.mReversed),
+ GetWritingModeName(selectionChangeData.GetWritingMode()).get(),
+ GetBoolName(selectionChangeData.mCausedByComposition),
+ GetBoolName(selectionChangeData.mCausedBySelectionEvent),
+ GetBoolName(selectionChangeData.mOccurredDuringComposition),
+ GetBoolName(mDestroyed),
+ mSink.get(), GetSinkMaskNameStr(mSinkMask).get(),
+ GetBoolName(mIsRecordingActionsWithoutLock),
+ GetBoolName(mComposition.IsComposing())));
+
+ if (mDestroyed) {
+ // If this instance is already destroyed, we shouldn't notify TSF of any
+ // changes.
+ return NS_OK;
+ }
+
+ mDeferNotifyingTSF = 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.Assign(selectionChangeData);
+
+ // Flush remaining pending notifications here if it's possible.
+ MaybeFlushPendingNotifications();
+
+ return NS_OK;
+}
+
+void
+TSFTextStore::NotifyTSFOfSelectionChange()
+{
+ MOZ_ASSERT(!mDestroyed);
+ MOZ_ASSERT(!IsReadLocked());
+ MOZ_ASSERT(!mComposition.IsComposing());
+ MOZ_ASSERT(mPendingSelectionChangeData.IsValid());
+
+ // If selection range isn't actually changed, we don't need to notify TSF
+ // of this selection change.
+ if (!mSelectionForTSF.SetSelection(
+ mPendingSelectionChangeData.mOffset,
+ mPendingSelectionChangeData.Length(),
+ mPendingSelectionChangeData.mReversed,
+ mPendingSelectionChangeData.GetWritingMode())) {
+ mPendingSelectionChangeData.Clear();
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::NotifyTSFOfSelectionChange(), "
+ "selection isn't actually changed.", this));
+ return;
+ }
+
+ mPendingSelectionChangeData.Clear();
+
+ if (!mSink || !(mSinkMask & TS_AS_SEL_CHANGE)) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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);
+
+ mDeferNotifyingTSF = 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::OnLayoutChangeInternal(), calling "
+ "NotifyTSFOfLayoutChange()...", this));
+ if (NS_WARN_IF(!NotifyTSFOfLayoutChange())) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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.IsInitialized()) {
+ mContentForTSF.OnLayoutChanged();
+ }
+
+ // Now, the caret position is different from ours. Destroy the native caret
+ // if there is.
+ MaybeDestroyNativeCaret();
+
+ // This method should return true if either way succeeds.
+ bool ret = true;
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "calling ITfContextOwnerServices::OnLayoutChange()...",
+ this));
+ HRESULT hr = service->OnLayoutChange();
+ ret = ret && SUCCEEDED(hr);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "called ITfContextOwnerServices::OnLayoutChange()",
+ this));
+ }
+ }
+
+ if (!mWidget || mWidget->Destroyed()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChange(), "
+ "the widget is destroyed during calling OnLayoutChange()",
+ this));
+ return ret;
+ }
+
+ if (mDestroyed) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Warning,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange() but TIP didn't retry to "
+ "retrieve the layout information", this));
+ } else {
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("0x%p TSFTextStore::NotifyTSFOfLayoutChangeAgain(), "
+ "called NotifyTSFOfLayoutChange()", this));
+ }
+}
+
+nsresult
+TSFTextStore::OnUpdateCompositionInternal()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnUpdateCompositionInternal(), "
+ "mDestroyed=%s, mDeferNotifyingTSF=%s",
+ this, GetBoolName(mDestroyed), GetBoolName(mDeferNotifyingTSF)));
+
+ // There are nothing to do after destroyed.
+ if (mDestroyed) {
+ return NS_OK;
+ }
+
+ // 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 until
+ // starting next composition.
+ if (!mComposition.IsComposing() && !IsComposingInContent()) {
+ mDeferClearingContentForTSF = false;
+ }
+ mDeferNotifyingTSF = false;
+ MaybeFlushPendingNotifications();
+ 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::OnMouseButtonEventInternal("
+ "aIMENotification={ mEventMessage=%s, mOffset=%u, mCursorPos={ "
+ "mX=%d, mY=%d }, mCharRect={ mX=%d, mY=%d, mWidth=%d, mHeight=%d }, "
+ "mButton=%s, mButtons=%s, mModifiers=%s })",
+ this, ToChar(aIMENotification.mMouseButtonEventData.mEventMessage),
+ aIMENotification.mMouseButtonEventData.mOffset,
+ aIMENotification.mMouseButtonEventData.mCursorPos.mX,
+ aIMENotification.mMouseButtonEventData.mCursorPos.mY,
+ aIMENotification.mMouseButtonEventData.mCharRect.mX,
+ aIMENotification.mMouseButtonEventData.mCharRect.mY,
+ aIMENotification.mMouseButtonEventData.mCharRect.mWidth,
+ aIMENotification.mMouseButtonEventData.mCharRect.mHeight,
+ GetMouseButtonName(aIMENotification.mMouseButtonEventData.mButton),
+ GetMouseButtonsName(
+ aIMENotification.mMouseButtonEventData.mButtons).get(),
+ GetModifiersName(
+ aIMENotification.mMouseButtonEventData.mModifiers).get()));
+
+ uint32_t offset = aIMENotification.mMouseButtonEventData.mOffset;
+ nsIntRect charRect =
+ aIMENotification.mMouseButtonEventData.mCharRect.AsIntRect();
+ nsIntPoint cursorPos =
+ aIMENotification.mMouseButtonEventData.mCursorPos.AsIntPoint();
+ 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 WidgetMouseEventBase::eLeftButton:
+ buttonStatus = MK_LBUTTON;
+ break;
+ case WidgetMouseEventBase::eMiddleButton:
+ buttonStatus = MK_MBUTTON;
+ break;
+ case WidgetMouseEventBase::eRightButton:
+ 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.InRange(offset)) {
+ continue;
+ }
+ if (tracker.OnMouseButtonEvent(edge - tracker.RangeStart(),
+ quadrant, buttonStatus)) {
+ return NS_SUCCESS_EVENT_CONSUMED;
+ }
+ }
+ return NS_OK;
+}
+
+void
+TSFTextStore::CreateNativeCaret()
+{
+ MaybeDestroyNativeCaret();
+
+ // Don't create native caret after destroyed.
+ if (mDestroyed) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CreateNativeCaret(), "
+ "mComposition.IsComposing()=%s",
+ this, GetBoolName(mComposition.IsComposing())));
+
+ Selection& selectionForTSF = SelectionForTSFRef();
+ if (selectionForTSF.IsDirty()) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "SelectionForTSFRef() failure", this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretRect(true, eQueryCaretRect, mWidget);
+ mWidget->InitEvent(queryCaretRect);
+
+ WidgetQueryContentEvent::Options options;
+ // XXX If this is called without composition and the selection isn't
+ // collapsed, is it OK?
+ int64_t caretOffset = selectionForTSF.MaxOffset();
+ if (mComposition.IsComposing()) {
+ // If there is a composition, use insertion point relative query for
+ // deciding caret position because composition might be at different
+ // position where TSFTextStore believes it at.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mComposition.mStart;
+ } 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 relative to insertion point query since
+ // TSF/TIP computes the offset from the cached selection.
+ options.mRelativeToInsertionPoint = true;
+ caretOffset -= mSelectionForTSF.StartOffset();
+ }
+ queryCaretRect.InitForQueryCaretRect(caretOffset, options);
+
+ DispatchEvent(queryCaretRect);
+ if (NS_WARN_IF(!queryCaretRect.mSucceeded)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "eQueryCaretRect failure (offset=%d)", this, caretOffset));
+ return;
+ }
+
+ LayoutDeviceIntRect& caretRect = queryCaretRect.mReply.mRect;
+ mNativeCaretIsCreated = ::CreateCaret(mWidget->GetWindowHandle(), nullptr,
+ caretRect.width, caretRect.height);
+ if (!mNativeCaretIsCreated) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "CreateCaret() failure", this));
+ return;
+ }
+
+ nsWindow* window = static_cast<nsWindow*>(mWidget.get());
+ nsWindow* toplevelWindow = window->GetTopLevelWindow(false);
+ if (!toplevelWindow) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to "
+ "no top level window", this));
+ return;
+ }
+
+ if (toplevelWindow != window) {
+ caretRect.MoveBy(toplevelWindow->WidgetToScreenOffset());
+ caretRect.MoveBy(-window->WidgetToScreenOffset());
+ }
+
+ ::SetCaretPos(caretRect.x, caretRect.y);
+}
+
+void
+TSFTextStore::MaybeDestroyNativeCaret()
+{
+ if (!mNativeCaretIsCreated) {
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MaybeDestroyNativeCaret(), "
+ "destroying native caret", this));
+
+ ::DestroyCaret();
+ mNativeCaretIsCreated = false;
+}
+
+void
+TSFTextStore::CommitCompositionInternal(bool aDiscard)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), "
+ "mSink=0x%p, mContext=0x%p, mComposition.mView=0x%p, "
+ "mComposition.mString=\"%s\"",
+ this, GetBoolName(aDiscard), mSink.get(), mContext.get(),
+ mComposition.mView.get(),
+ GetEscapedUTF8String(mComposition.mString).get()));
+
+ // 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::CommitCompositionInternal(), "
+ "putting off to request to %s composition after unlocking the document",
+ this, aDiscard ? "cancel" : "commit"));
+ return;
+ }
+
+ if (mComposition.IsComposing() && aDiscard) {
+ LONG endOffset = mComposition.EndOffset();
+ mComposition.mString.Truncate(0);
+ // Note that don't notify TSF of text change after this is destroyed.
+ if (mSink && !mDestroyed) {
+ TS_TEXTCHANGE textChange;
+ textChange.acpStart = mComposition.mStart;
+ textChange.acpOldEnd = endOffset;
+ textChange.acpNewEnd = mComposition.mStart;
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::SetIMEOpenState(aState=%s)",
+ GetBoolName(aState)));
+
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(sThreadMgr,
+ GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(comp))) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState() FAILED due to"
+ "no compartment available"));
+ return;
+ }
+
+ VARIANT variant;
+ variant.vt = VT_I4;
+ variant.lVal = aState;
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ (" TSFTextStore::SetIMEOpenState(), setting "
+ "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...",
+ variant.lVal));
+ comp->SetValue(sClientId, &variant);
+}
+
+// static
+bool
+TSFTextStore::GetIMEOpenState()
+{
+ RefPtr<ITfCompartment> comp;
+ if (!GetCompartment(sThreadMgr,
+ GUID_COMPARTMENT_KEYBOARD_OPENCLOSE,
+ getter_AddRefs(comp)))
+ return false;
+
+ VARIANT variant;
+ ::VariantInit(&variant);
+ if (SUCCEEDED(comp->GetValue(&variant)) && variant.vt == VT_I4)
+ return variant.lVal != 0;
+
+ ::VariantClear(&variant); // clear up in case variant.vt != VT_I4
+ return false;
+}
+
+// static
+void
+TSFTextStore::SetInputContext(nsWindowBase* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::SetInputContext(aWidget=%p, "
+ "aContext.mIMEState.mEnabled=%s, aAction.mFocusChange=%s), "
+ "sEnabledTextStore=0x%p, ThinksHavingFocus()=%s",
+ aWidget, GetIMEEnabledName(aContext.mIMEState.mEnabled),
+ GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(),
+ GetBoolName(ThinksHavingFocus())));
+
+ NS_ENSURE_TRUE_VOID(IsInTSFMode());
+
+ if (aAction.mFocusChange != InputContextAction::FOCUS_NOT_CHANGED) {
+ if (sEnabledTextStore) {
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ textStore->SetInputScope(aContext.mHTMLInputType,
+ aContext.mHTMLInputInputmode);
+ }
+ return;
+ }
+
+ // If focus isn't actually changed but the enabled state is changed,
+ // emulate the focus move.
+ if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) {
+ OnFocusChange(true, aWidget, aContext);
+ } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) {
+ 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(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsKeyboardDisabled() failed"
+ "aContext=0x%p...", aContext));
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::MarkContextAsEmpty() failed"
+ "aContext=0x%p...", aContext));
+ return;
+ }
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("TSFTextStore::MarkContextAsEmpty(), setting "
+ "to mark empty context 0x%p...", aContext));
+ comp->SetValue(sClientId, &variant_int4_value1);
+}
+
+// static
+void
+TSFTextStore::Initialize()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ ("TSFTextStore::Initialize() is called..."));
+
+ if (sThreadMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED due to already initialized"));
+ return;
+ }
+
+ bool enableTsf =
+ IsVistaOrLater() && Preferences::GetBool(kPrefNameEnableTSF, false);
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), TSF is %s",
+ enableTsf ? "enabled" : "disabled"));
+ if (!enableTsf) {
+ return;
+ }
+
+ // XXX MSDN documents that ITfInputProcessorProfiles is available only on
+ // desktop apps. However, there is no known way to obtain
+ // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles
+ // instance.
+ RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles;
+ HRESULT hr =
+ ::CoCreateInstance(CLSID_TF_InputProcessorProfiles, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_ITfInputProcessorProfiles,
+ getter_AddRefs(inputProcessorProfiles));
+ if (FAILED(hr) || !inputProcessorProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create input processor "
+ "profiles, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfThreadMgr> threadMgr;
+ hr = ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr,
+ CLSCTX_INPROC_SERVER, IID_ITfThreadMgr,
+ getter_AddRefs(threadMgr));
+ if (FAILED(hr) || !threadMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "create the thread manager, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfMessagePump> messagePump;
+ hr = threadMgr->QueryInterface(IID_ITfMessagePump,
+ getter_AddRefs(messagePump));
+ if (FAILED(hr) || !messagePump) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "QI message pump from the thread manager, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr;
+ hr = threadMgr->QueryInterface(IID_ITfKeystrokeMgr,
+ getter_AddRefs(keystrokeMgr));
+ if (FAILED(hr) || !keystrokeMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to "
+ "QI keystroke manager from the thread manager, hr=0x%08X", hr));
+ return;
+ }
+
+ hr = threadMgr->Activate(&sClientId);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to activate, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr;
+ hr = ::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr,
+ CLSCTX_INPROC_SERVER, IID_ITfDisplayAttributeMgr,
+ getter_AddRefs(displayAttributeMgr));
+ if (FAILED(hr) || !displayAttributeMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a display attribute manager instance, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfCategoryMgr> categoryMgr;
+ hr = ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr,
+ CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr,
+ getter_AddRefs(categoryMgr));
+ if (FAILED(hr) || !categoryMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a category manager instance, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfDocumentMgr> disabledDocumentMgr;
+ hr = threadMgr->CreateDocumentMgr(getter_AddRefs(disabledDocumentMgr));
+ if (FAILED(hr) || !disabledDocumentMgr) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a document manager for disabled mode, hr=0x%08X", hr));
+ return;
+ }
+
+ RefPtr<ITfContext> disabledContext;
+ DWORD editCookie = 0;
+ hr = disabledDocumentMgr->CreateContext(sClientId, 0, nullptr,
+ getter_AddRefs(disabledContext),
+ &editCookie);
+ if (FAILED(hr) || !disabledContext) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to create "
+ "a context for disabled mode, hr=0x%08X", hr));
+ return;
+ }
+
+ MarkContextAsKeyboardDisabled(disabledContext);
+ MarkContextAsEmpty(disabledContext);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize() is creating "
+ "a TSFStaticSink instance..."));
+ TSFStaticSink* staticSink = TSFStaticSink::GetInstance();
+ if (!staticSink->Init(threadMgr, inputProcessorProfiles)) {
+ TSFStaticSink::Shutdown();
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::Initialize() FAILED to initialize TSFStaticSink "
+ "instance"));
+ return;
+ }
+
+ sInputProcessorProfiles = inputProcessorProfiles;
+ sThreadMgr = threadMgr;
+ sMessagePump = messagePump;
+ sKeystrokeMgr = keystrokeMgr;
+ sDisplayAttrMgr = displayAttributeMgr;
+ sCategoryMgr = categoryMgr;
+ sDisabledDocumentMgr = disabledDocumentMgr;
+ sDisabledContext = disabledContext;
+
+ sCreateNativeCaretForLegacyATOK =
+ Preferences::GetBool("intl.tsf.hack.atok.create_native_caret", true);
+ sDoNotReturnNoLayoutErrorToATOKOfCompositionString =
+ Preferences::GetBool(
+ "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string",
+ true);
+ sDoNotReturnNoLayoutErrorToMSSimplifiedTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error",
+ true);
+ sDoNotReturnNoLayoutErrorToMSTraditionalTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error",
+ true);
+ sDoNotReturnNoLayoutErrorToFreeChangJie =
+ Preferences::GetBool(
+ "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error", true);
+ sDoNotReturnNoLayoutErrorToEasyChangjei =
+ Preferences::GetBool(
+ "intl.tsf.hack.easy_changjei.do_not_return_no_layout_error", true);
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime."
+ "do_not_return_no_layout_error_at_first_char", true);
+ sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret",
+ true);
+ sHackQueryInsertForMSSimplifiedTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_simplified_chinese.query_insert_result", true);
+ sHackQueryInsertForMSTraditionalTIP =
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_traditional_chinese.query_insert_result", true);
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Info,
+ (" TSFTextStore::Initialize(), sThreadMgr=0x%p, "
+ "sClientId=0x%08X, sDisplayAttrMgr=0x%p, "
+ "sCategoryMgr=0x%p, sDisabledDocumentMgr=0x%p, sDisabledContext=%p, "
+ "sCreateNativeCaretForLegacyATOK=%s, "
+ "sDoNotReturnNoLayoutErrorToATOKOfCompositionString=%s, "
+ "sDoNotReturnNoLayoutErrorToFreeChangJie=%s, "
+ "sDoNotReturnNoLayoutErrorToEasyChangjei=%s, "
+ "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar=%s, "
+ "sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret=%s",
+ sThreadMgr.get(), sClientId, sDisplayAttrMgr.get(),
+ sCategoryMgr.get(), sDisabledDocumentMgr.get(), sDisabledContext.get(),
+ GetBoolName(sCreateNativeCaretForLegacyATOK),
+ GetBoolName(sDoNotReturnNoLayoutErrorToATOKOfCompositionString),
+ GetBoolName(sDoNotReturnNoLayoutErrorToFreeChangJie),
+ GetBoolName(sDoNotReturnNoLayoutErrorToEasyChangjei),
+ GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar),
+ GetBoolName(sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret)));
+}
+
+// static
+void
+TSFTextStore::Terminate()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()"));
+
+ TSFStaticSink::Shutdown();
+
+ sDisplayAttrMgr = nullptr;
+ sCategoryMgr = nullptr;
+ sEnabledTextStore = nullptr;
+ sDisabledDocumentMgr = nullptr;
+ sDisabledContext = nullptr;
+ sInputProcessorProfiles = nullptr;
+ sClientId = 0;
+ if (sThreadMgr) {
+ sThreadMgr->Deactivate();
+ sThreadMgr = nullptr;
+ sMessagePump = nullptr;
+ sKeystrokeMgr = nullptr;
+ }
+}
+
+// static
+bool
+TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg)
+{
+ if (!sKeystrokeMgr) {
+ return false; // not in TSF mode
+ }
+
+ if (aMsg.message == WM_KEYDOWN) {
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ }
+ hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage();
+ }
+ return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
+ }
+ if (aMsg.message == WM_KEYUP) {
+ BOOL eaten;
+ RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr;
+ HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (FAILED(hr) || !sKeystrokeMgr || !eaten) {
+ return false;
+ }
+ RefPtr<TSFTextStore> textStore(sEnabledTextStore);
+ if (textStore) {
+ textStore->OnStartToHandleKeyMessage();
+ }
+ hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten);
+ if (textStore) {
+ textStore->OnEndHandlingKeyMessage();
+ }
+ return SUCCEEDED(hr) && (eaten || !sKeystrokeMgr);
+ }
+ return false;
+}
+
+// static
+void
+TSFTextStore::ProcessMessage(nsWindowBase* 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::GetInstance()->IsMSJapaneseIMEActive();
+}
+
+/******************************************************************/
+/* TSFTextStore::Composition */
+/******************************************************************/
+
+void
+TSFTextStore::Composition::Start(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString)
+{
+ mView = aCompositionView;
+ mString = aCompositionString;
+ mStart = aCompositionStartOffset;
+}
+
+void
+TSFTextStore::Composition::End()
+{
+ mView = nullptr;
+ mString.Truncate();
+}
+
+/******************************************************************************
+ * TSFTextStore::Content
+ *****************************************************************************/
+
+const nsDependentSubstring
+TSFTextStore::Content::GetSelectedText() const
+{
+ MOZ_ASSERT(mInitialized);
+ 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
+{
+ MOZ_ASSERT(mInitialized);
+ return nsDependentSubstring(mText, aStart, aLength);
+}
+
+void
+TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString)
+{
+ MOZ_ASSERT(mInitialized);
+ 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(mInitialized);
+ const nsDependentSubstring replacedString =
+ GetSubstring(static_cast<uint32_t>(aStart),
+ static_cast<uint32_t>(aLength));
+ if (aReplaceString != replacedString) {
+ uint32_t firstDifferentOffset = mMinTextModifiedOffset;
+ if (mComposition.IsComposing()) {
+ // 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.mStart);
+ MOZ_ASSERT(aStart + aLength <= mComposition.EndOffset());
+ mComposition.mString.Replace(
+ static_cast<uint32_t>(aStart - mComposition.mStart),
+ 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
+ // mLastCompositionString.
+ if (mComposition.mString != mLastCompositionString) {
+ firstDifferentOffset =
+ mComposition.mStart +
+ FirstDifferentCharOffset(mComposition.mString,
+ mLastCompositionString);
+ // The previous change to the composition string is canceled.
+ if (mMinTextModifiedOffset >=
+ static_cast<uint32_t>(mComposition.mStart) &&
+ mMinTextModifiedOffset < firstDifferentOffset) {
+ mMinTextModifiedOffset = firstDifferentOffset;
+ }
+ } else if (mMinTextModifiedOffset >=
+ static_cast<uint32_t>(mComposition.mStart) &&
+ mMinTextModifiedOffset <
+ static_cast<uint32_t>(mComposition.EndOffset())) {
+ // The previous change to the composition string is canceled.
+ mMinTextModifiedOffset = firstDifferentOffset =
+ mComposition.EndOffset();
+ }
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, "
+ "aLength=%d, aReplaceString=\"%s\"), mComposition={ mStart=%d, "
+ "mString=\"%s\" }, mLastCompositionString=\"%s\", "
+ "mMinTextModifiedOffset=%u, firstDifferentOffset=%u",
+ this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(),
+ mComposition.mStart, GetEscapedUTF8String(mComposition.mString).get(),
+ GetEscapedUTF8String(mLastCompositionString).get(),
+ mMinTextModifiedOffset, firstDifferentOffset));
+ } else {
+ firstDifferentOffset =
+ static_cast<uint32_t>(aStart) +
+ FirstDifferentCharOffset(aReplaceString, replacedString);
+ }
+ mMinTextModifiedOffset =
+ std::min(mMinTextModifiedOffset, 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.CollapseAt(
+ static_cast<uint32_t>(aStart) + aReplaceString.Length());
+}
+
+void
+TSFTextStore::Content::StartComposition(ITfCompositionView* aCompositionView,
+ const PendingAction& aCompStart,
+ bool aPreserveSelection)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(!mComposition.mView);
+ MOZ_ASSERT(aCompStart.mType == PendingAction::COMPOSITION_START);
+
+ mComposition.Start(aCompositionView, aCompStart.mSelectionStart,
+ GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart),
+ static_cast<uint32_t>(aCompStart.mSelectionLength)));
+ 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.
+ mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
+ false, mSelection.GetWritingMode());
+ }
+}
+
+void
+TSFTextStore::Content::RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aPendingCompositionStart,
+ const PendingAction& aCanceledCompositionEnd)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(aCompositionView);
+ MOZ_ASSERT(!mComposition.mView);
+ MOZ_ASSERT(aPendingCompositionStart.mType ==
+ PendingAction::COMPOSITION_START);
+ MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+ PendingAction::COMPOSITION_END);
+ MOZ_ASSERT(GetSubstring(
+ static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
+ static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+ aCanceledCompositionEnd.mData);
+
+ // Restore the committed string as composing string.
+ mComposition.Start(aCompositionView,
+ aPendingCompositionStart.mSelectionStart,
+ aCanceledCompositionEnd.mData);
+}
+
+void
+TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
+{
+ MOZ_ASSERT(mInitialized);
+ MOZ_ASSERT(mComposition.mView);
+ MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
+
+ mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
+ mComposition.End();
+}
+
+/******************************************************************************
+ * TSFTextStore::MouseTracker
+ *****************************************************************************/
+
+TSFTextStore::MouseTracker::MouseTracker()
+ : mStart(-1)
+ , mLength(-1)
+ , mCookie(kInvalidCookie)
+{
+}
+
+HRESULT
+TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore)
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::Init(aTextStore=0x%p), "
+ "aTextStore->mMouseTrackers.Length()=%d",
+ this, aTextStore->mMouseTrackers.Length()));
+
+ if (&aTextStore->mMouseTrackers.LastElement() != this) {
+ MOZ_LOG(sTextStoreLog, 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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseSink(aTextStore=0x%p, "
+ "aTextRange=0x%p, aMouseSink=0x%p), mCookie=%d, mSink=0x%p",
+ this, aTextStore, aTextRange, aMouseSink, mCookie, mSink.get()));
+ MOZ_ASSERT(mCookie != kInvalidCookie, "This hasn't been initalized?");
+
+ if (mSink) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to already being used", this));
+ return E_FAIL;
+ }
+
+ HRESULT hr = aTextRange->GetExtent(&mStart, &mLength);
+ if (FAILED(hr)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to failure of ITfRangeACP::GetExtent()", this));
+ return hr;
+ }
+
+ if (mStart < 0 || mLength <= 0) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to odd result of ITfRangeACP::GetExtent(), "
+ "mStart=%d, mLength=%d", this, mStart, mLength));
+ return E_INVALIDARG;
+ }
+
+ nsAutoString textContent;
+ if (NS_WARN_IF(!aTextStore->GetCurrentText(textContent))) {
+ MOZ_LOG(sTextStoreLog, 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>(mStart) ||
+ textContent.Length() < static_cast<uint32_t>(mStart + mLength)) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED "
+ "due to out of range, mStart=%d, mLength=%d, "
+ "textContent.Length()=%d",
+ this, mStart, mLength, textContent.Length()));
+ return E_INVALIDARG;
+ }
+
+ mSink = aMouseSink;
+
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), "
+ "succeeded, mStart=%d, mLength=%d, textContent.Length()=%d",
+ this, mStart, mLength, textContent.Length()));
+ return S_OK;
+}
+
+void
+TSFTextStore::MouseTracker::UnadviseSink()
+{
+ MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), "
+ "mCookie=%d, mSink=0x%p, mStart=%d, mLength=%d",
+ this, mCookie, mSink.get(), mStart, mLength));
+ mSink = nullptr;
+ mStart = mLength = -1;
+}
+
+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(sTextStoreLog, LogLevel::Debug,
+ ("0x%p TSFTextStore::MouseTracker::OnMouseEvent(aEdge=%d, "
+ "aQuadrant=%d, aButtonStatus=0x%08X), hr=0x%08X, eaten=%s",
+ this, aEdge, aQuadrant, aButtonStatus, hr, GetBoolName(!!eaten)));
+
+ return SUCCEEDED(hr) && eaten;
+}
+
+#ifdef DEBUG
+// static
+bool
+TSFTextStore::CurrentKeyboardLayoutHasIME()
+{
+ if (!sInputProcessorProfiles) {
+ MOZ_LOG(sTextStoreLog, LogLevel::Error,
+ ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to "
+ "there is no input processor profiles instance"));
+ return false;
+ }
+ RefPtr<ITfInputProcessorProfileMgr> profileMgr;
+ HRESULT hr =
+ sInputProcessorProfiles->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(sTextStoreLog, 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(sTextStoreLog, LogLevel::Error,
+ (" TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED to retreive "
+ "active profile"));
+ return false;
+ }
+ return (profile.dwProfileType == TF_PROFILETYPE_INPUTPROCESSOR);
+}
+#endif // #ifdef DEBUG
+
+} // name widget
+} // name mozilla
diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h
new file mode 100644
index 0000000000..9596510d51
--- /dev/null
+++ b/widget/windows/TSFTextStore.h
@@ -0,0 +1,1045 @@
+/* -*- 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 "nsWindowBase.h"
+
+#include "WinUtils.h"
+#include "WritingModes.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextRange.h"
+#include "mozilla/WindowsVersion.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;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+/*
+ * Text Services Framework text store
+ */
+
+class TSFTextStore final : public ITextStoreACP
+ , public ITfContextOwnerCompositionSink
+ , public ITfMouseTrackerACP
+{
+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(nsWindowBase* 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(nsWindowBase* aWidget,
+ const InputContext& aContext,
+ const InputContextAction& aAction);
+
+ static nsresult OnFocusChange(bool aGotFocus,
+ nsWindowBase* 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 nsIMEUpdatePreference GetIMEUpdatePreference();
+
+ // 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 ITfMessagePump* GetMessagePump()
+ {
+ return sMessagePump;
+ }
+
+ 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.IsComposing());
+ }
+
+ static bool IsComposingOn(nsWindowBase* aWidget)
+ {
+ return (IsComposing() && sEnabledTextStore->mWidget == aWidget);
+ }
+
+ static nsWindowBase* 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();
+
+#ifdef DEBUG
+ // Returns true when keyboard layout has IME (TIP).
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+protected:
+ TSFTextStore();
+ ~TSFTextStore();
+
+ static bool CreateAndSetFocus(nsWindowBase* aFocusedWidget,
+ const InputContext& aContext);
+ static void EnsureToDestroyAndReleaseEnabledTextStoreIf(
+ RefPtr<TSFTextStore>& aTextStore);
+ static void MarkContextAsKeyboardDisabled(ITfContext* aContext);
+ static void MarkContextAsEmpty(ITfContext* aContext);
+
+ bool Init(nsWindowBase* 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);
+ HRESULT RestartComposition(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* aComposition,
+ 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.
+ 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& aHTMLInputInputmode);
+
+ // 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();
+
+ // Holds the pointer to our current win32 widget
+ RefPtr<nsWindowBase> 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() { ++mHandlingKeyMessage; }
+ void OnEndHandlingKeyMessage()
+ {
+ 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();
+ }
+ }
+
+ class Composition final
+ {
+ public:
+ // nullptr if no composition is active, otherwise the current composition
+ RefPtr<ITfCompositionView> mView;
+
+ // Current copy of the active composition string. Only mString is
+ // changed during a InsertTextAtSelection call if we have a composition.
+ // mString acts as a buffer until OnUpdateComposition is called
+ // and mString is flushed to editor through eCompositionChange.
+ // This way all changes are updated in batches to avoid
+ // inconsistencies/artifacts.
+ nsString mString;
+
+ // The start of the current active composition, in ACP offsets
+ LONG mStart;
+
+ bool IsComposing() const
+ {
+ return (mView != nullptr);
+ }
+
+ LONG EndOffset() const
+ {
+ return mStart + static_cast<LONG>(mString.Length());
+ }
+
+ // Start() and End() updates the members for emulating the latest state.
+ // Unless flush the pending actions, this data never matches the actual
+ // content.
+ void Start(ITfCompositionView* aCompositionView,
+ LONG aCompositionStartOffset,
+ const nsAString& aCompositionString);
+ void End();
+ };
+ // 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.
+ Composition mComposition;
+
+ /**
+ * IsComposingInContent() returns true if there is a composition in the
+ * focused editor and it's caused by native IME (either TIP of TSF or IME of
+ * IMM). I.e., returns true between eCompositionStart and
+ * eCompositionCommit(AsIs).
+ */
+ bool IsComposingInContent() const;
+
+ class Selection
+ {
+ public:
+ Selection() : mDirty(true) {}
+
+ bool IsDirty() const { return mDirty; };
+ void MarkDirty() { mDirty = true; }
+
+ TS_SELECTION_ACP& ACP()
+ {
+ MOZ_ASSERT(!mDirty);
+ return mACP;
+ }
+
+ void SetSelection(const TS_SELECTION_ACP& aSelection)
+ {
+ mDirty = false;
+ mACP = 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(uint32_t aStart,
+ uint32_t aLength,
+ bool aReversed,
+ WritingMode aWritingMode)
+ {
+ bool changed = mDirty ||
+ mACP.acpStart != static_cast<LONG>(aStart) ||
+ mACP.acpEnd != static_cast<LONG>(aStart + aLength);
+
+ mDirty = false;
+ mACP.acpStart = static_cast<LONG>(aStart);
+ mACP.acpEnd = static_cast<LONG>(aStart + aLength);
+ mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END;
+ mACP.style.fInterimChar = FALSE;
+ mWritingMode = aWritingMode;
+
+ return changed;
+ }
+
+ bool IsCollapsed() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.acpStart == mACP.acpEnd);
+ }
+
+ void CollapseAt(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.
+ mDirty = false;
+ mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset);
+ mACP.style.ase = TS_AE_END;
+ mACP.style.fInterimChar = FALSE;
+ }
+
+ LONG MinOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ LONG min = std::min(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(min >= 0);
+ return min;
+ }
+
+ LONG MaxOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ LONG max = std::max(mACP.acpStart, mACP.acpEnd);
+ MOZ_ASSERT(max >= 0);
+ return max;
+ }
+
+ LONG StartOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpStart >= 0);
+ return mACP.acpStart;
+ }
+
+ LONG EndOffset() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpEnd >= 0);
+ return mACP.acpEnd;
+ }
+
+ LONG Length() const
+ {
+ MOZ_ASSERT(!mDirty);
+ MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart);
+ return std::abs(mACP.acpEnd - mACP.acpStart);
+ }
+
+ bool IsReversed() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.style.ase == TS_AE_START);
+ }
+
+ TsActiveSelEnd ActiveSelEnd() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return mACP.style.ase;
+ }
+
+ bool IsInterimChar() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return (mACP.style.fInterimChar != FALSE);
+ }
+
+ WritingMode GetWritingMode() const
+ {
+ MOZ_ASSERT(!mDirty);
+ return mWritingMode;
+ }
+
+ bool EqualsExceptDirection(const TS_SELECTION_ACP& aACP) const
+ {
+ 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(!mDirty);
+ MOZ_ASSERT(aChangedSelection.IsValid());
+ return aChangedSelection.Length() == static_cast<uint32_t>(Length()) &&
+ aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset());
+ }
+
+ private:
+ TS_SELECTION_ACP mACP;
+ WritingMode mWritingMode;
+ bool mDirty;
+ };
+ // Don't access mSelection directly except at calling MarkDirty().
+ // Use SelectionForTSFRef() instead. This is modified immediately when
+ // TSF requests to set selection and not updated by selection change in
+ // content until mContentForTSF is cleared.
+ 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 ContentForTSFRef().
+ */
+ Selection& SelectionForTSFRef();
+
+ class MOZ_STACK_CLASS AutoSetTemporarySelection final
+ {
+ public:
+ AutoSetTemporarySelection(Selection& aSelection)
+ : mSelection(aSelection)
+ {
+ mDirty = mSelection.IsDirty();
+ if (mDirty) {
+ mSelection.CollapseAt(0);
+ }
+ }
+
+ ~AutoSetTemporarySelection()
+ {
+ if (mDirty) {
+ mSelection.MarkDirty();
+ }
+ }
+
+ private:
+ Selection& mSelection;
+ bool mDirty;
+ };
+
+ struct PendingAction final
+ {
+ enum ActionType : uint8_t
+ {
+ COMPOSITION_START,
+ COMPOSITION_UPDATE,
+ COMPOSITION_END,
+ SET_SELECTION
+ };
+ ActionType mType;
+ // For compositionstart and selectionset
+ LONG mSelectionStart;
+ LONG mSelectionLength;
+ // For compositionstart, compositionupdate and compositionend
+ nsString mData;
+ // For compositionupdate
+ RefPtr<TextRangeArray> mRanges;
+ // For selectionset
+ bool mSelectionReversed;
+ // For compositionupdate
+ bool mIncomplete;
+ // For compositionstart
+ 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::COMPOSITION_UPDATE) {
+ return &lastAction;
+ }
+ }
+ PendingAction* newAction = mPendingActions.AppendElement();
+ newAction->mType = PendingAction::COMPOSITION_UPDATE;
+ newAction->mRanges = new TextRangeArray();
+ newAction->mIncomplete = true;
+ return newAction;
+ }
+
+ /**
+ * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
+ * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
+ *
+ * @param aStart The inserted offset you expected.
+ * @param aLength The inserted text length you expected.
+ * @return true if the last pending actions are
+ * COMPOSITION_START and COMPOSITION_END and
+ * aStart and aLength match their information.
+ */
+ bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
+ {
+ if (mPendingActions.Length() < 2) {
+ return false;
+ }
+ const PendingAction& pendingLastAction = mPendingActions.LastElement();
+ if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
+ pendingLastAction.mData.Length() != aLength) {
+ return false;
+ }
+ const PendingAction& pendingPreLastAction =
+ mPendingActions[mPendingActions.Length() - 2];
+ return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
+ pendingPreLastAction.mSelectionStart == aStart;
+ }
+
+ bool IsPendingCompositionUpdateIncomplete() const
+ {
+ if (mPendingActions.IsEmpty()) {
+ return false;
+ }
+ const PendingAction& lastAction = mPendingActions.LastElement();
+ return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
+ lastAction.mIncomplete;
+ }
+
+ void CompleteLastActionIfStillIncomplete()
+ {
+ if (!IsPendingCompositionUpdateIncomplete()) {
+ return;
+ }
+ RecordCompositionUpdateAction();
+ }
+
+ // 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:
+ 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::Composition& aComposition,
+ TSFTextStore::Selection& aSelection) :
+ mComposition(aComposition), mSelection(aSelection)
+ {
+ Clear();
+ }
+
+ void Clear()
+ {
+ mText.Truncate();
+ mLastCompositionString.Truncate();
+ mInitialized = false;
+ }
+
+ bool IsInitialized() const { return mInitialized; }
+
+ void Init(const nsAString& aText)
+ {
+ mText = aText;
+ if (mComposition.IsComposing()) {
+ mLastCompositionString = mComposition.mString;
+ } else {
+ mLastCompositionString.Truncate();
+ }
+ mMinTextModifiedOffset = NOT_MODIFIED;
+ mInitialized = true;
+ }
+
+ void OnLayoutChanged()
+ {
+ mMinTextModifiedOffset = NOT_MODIFIED;
+ }
+
+ 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(), there is a pending
+ * compositionstart and a pending compositionend. In this case, we
+ * need to cancel the pending compositionend and continue the composition.
+ *
+ * @param aCompositionView The composition view.
+ * @param aPendingCompositionStart The pending compositionstart which
+ * started the committed composition.
+ * @param aCanceledCompositionEnd The pending compositionend which is
+ * canceled for restarting the composition.
+ */
+ void RestoreCommittedComposition(
+ ITfCompositionView* aCompositionView,
+ const PendingAction& aPendingCompositionStart,
+ const PendingAction& aCanceledCompositionEnd);
+ void EndComposition(const PendingAction& aCompEnd);
+
+ const nsString& Text() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mText;
+ }
+ const nsString& LastCompositionString() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mLastCompositionString;
+ }
+ uint32_t MinTextModifiedOffset() const
+ {
+ MOZ_ASSERT(mInitialized);
+ return mMinTextModifiedOffset;
+ }
+
+ // Returns true if layout of the character at the aOffset has not been
+ // calculated.
+ bool IsLayoutChangedAt(uint32_t aOffset) const
+ {
+ return IsLayoutChanged() && (mMinTextModifiedOffset <= aOffset);
+ }
+ // Returns true if layout of the content has been changed, i.e., the new
+ // layout has not been calculated.
+ bool IsLayoutChanged() const
+ {
+ return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED);
+ }
+ // Returns minimum offset of modified text range.
+ uint32_t MinOffsetOfLayoutChanged() const
+ {
+ return mInitialized ? mMinTextModifiedOffset : NOT_MODIFIED;
+ }
+
+ TSFTextStore::Composition& Composition() { return mComposition; }
+ TSFTextStore::Selection& Selection() { return mSelection; }
+
+ private:
+ nsString mText;
+ // mLastCompositionString stores the composition string when the document
+ // is locked. This is necessary to compute mMinTextModifiedOffset.
+ nsString mLastCompositionString;
+ TSFTextStore::Composition& mComposition;
+ TSFTextStore::Selection& mSelection;
+
+ // The minimum offset of modified part of the text.
+ enum : uint32_t
+ {
+ NOT_MODIFIED = UINT32_MAX
+ };
+ uint32_t mMinTextModifiedOffset;
+
+ bool mInitialized;
+ };
+ // 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 ContentForTSFRef() 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.
+ Content mContentForTSF;
+
+ Content& ContentForTSFRef();
+
+ // 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; }
+ bool InRange(uint32_t aOffset) const
+ {
+ if (NS_WARN_IF(mStart < 0) ||
+ NS_WARN_IF(mLength <= 0)) {
+ return false;
+ }
+ return aOffset >= static_cast<uint32_t>(mStart) &&
+ aOffset < static_cast<uint32_t>(mStart + mLength);
+ }
+ DWORD Cookie() const { return mCookie; }
+ bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus);
+ LONG RangeStart() const { return mStart; }
+
+ private:
+ RefPtr<ITfMouseSink> mSink;
+ LONG mStart;
+ LONG mLength;
+ 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;
+
+ // Support retrieving attributes.
+ // TODO: We should support RightToLeft, perhaps.
+ enum
+ {
+ // Used for result of GetRequestedAttrIndex()
+ eNotSupported = -1,
+
+ // Supported attributes
+ eInputScope = 0,
+ eTextVerticalWriting,
+ eTextOrientation,
+
+ // Count of the supported attributes
+ NUM_OF_SUPPORTED_ATTRS
+ };
+ bool mRequestedAttrs[NUM_OF_SUPPORTED_ATTRS];
+
+ int32_t GetRequestedAttrIndex(const TS_ATTRID& aAttrID);
+ TS_ATTRID GetAttrID(int32_t aIndex);
+
+ bool mRequestedAttrValues;
+
+ // If edit actions are being recorded without document lock, this is true.
+ // Otherwise, false.
+ bool mIsRecordingActionsWithoutLock;
+ // 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;
+ // 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;
+ // During the documet is locked, we shouldn't destroy the instance.
+ // If this is true, the instance will be destroyed after unlocked.
+ bool mPendingDestroy;
+ // If this is false, MaybeFlushPendingNotifications() will clear the
+ // mContentForTSF.
+ bool mDeferClearingContentForTSF;
+ // While there is native caret, this is true. Otherwise, false.
+ bool mNativeCaretIsCreated;
+ // While the instance is dispatching events, the event may not be handled
+ // synchronously in e10s mode. So, in such case, in strictly speaking,
+ // we shouldn't query layout information. However, TS_E_NOLAYOUT bugs of
+ // ITextStoreAPC::GetTextExt() blocks us to behave ideally.
+ // For preventing it to be called, we should put off notifying TSF of
+ // anything until layout information becomes available.
+ bool mDeferNotifyingTSF;
+ // 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;
+ bool mDeferCancellingComposition;
+ // 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;
+ // While the instance is being destroyed, this is set to true for avoiding
+ // recursive Destroy() calls.
+ bool mBeingDestroyed;
+
+
+ // TSF thread manager object for the current application
+ static StaticRefPtr<ITfThreadMgr> sThreadMgr;
+ // sMessagePump is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfMessagePump> sMessagePump;
+ // sKeystrokeMgr is QI'ed from sThreadMgr
+ static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr;
+ // TSF display attribute manager
+ static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr;
+ // TSF category manager
+ static StaticRefPtr<ITfCategoryMgr> sCategoryMgr;
+
+ // 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;
+
+ // TSF client ID for the current application
+ static DWORD sClientId;
+
+ // Enables/Disables hack for specific TIP.
+ static bool sCreateNativeCaretForLegacyATOK;
+ static bool sDoNotReturnNoLayoutErrorToATOKOfCompositionString;
+ static bool sDoNotReturnNoLayoutErrorToMSSimplifiedTIP;
+ static bool sDoNotReturnNoLayoutErrorToMSTraditionalTIP;
+ static bool sDoNotReturnNoLayoutErrorToFreeChangJie;
+ static bool sDoNotReturnNoLayoutErrorToEasyChangjei;
+ static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar;
+ static bool sDoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret;
+ static bool sHackQueryInsertForMSSimplifiedTIP;
+ static bool sHackQueryInsertForMSTraditionalTIP;
+};
+
+} // 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..c897af0212
--- /dev/null
+++ b/widget/windows/TaskbarPreview.cpp
@@ -0,0 +1,431 @@
+/* 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 <nsIBaseWindow.h>
+#include <nsIObserverService.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/Telemetry.h"
+
+// Defined in dwmapi in a header that needs a higher numbered _WINNT #define
+#define DWM_SIT_DISPLAYFRAME 0x1
+
+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 may outlive the WinTaskbar that created it
+ ::CoInitialize(nullptr);
+
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(WM_DESTROY, MainWindowHook, this);
+}
+
+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();
+}
+
+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 (!mWnd)
+ 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;
+
+ // DWM Composition is required for previews
+ if (!nsUXThemeData::CheckForCompositor())
+ return NS_OK;
+
+ HWND previewWindow = PreviewWindow();
+ return FAILED(WinUtils::dwmInvalidateIconicBitmapsPtr(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 {
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), MainWindowHook, this);
+ }
+ return rv;
+}
+
+nsresult
+TaskbarPreview::Disable() {
+ if (!IsWindowAvailable()) {
+ // Window is already destroyed
+ return NS_OK;
+ }
+
+ WindowHook &hook = GetWindowHook();
+ (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() {
+ 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 = nsIWidget::DefaultScaleOverride();
+ 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->HasTaskbarIconBeenCreated();
+ }
+ return false;
+}
+
+WindowHook&
+TaskbarPreview::GetWindowHook() {
+ nsWindow *window = WinUtils::GetNSWindowPtr(mWnd);
+ NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!");
+
+ return window->GetWindowHook();
+}
+
+void
+TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) {
+ BOOL enabled = aEnable;
+ WinUtils::dwmSetWindowAttributePtr(
+ aHWND,
+ DWMWA_FORCE_ICONIC_REPRESENTATION,
+ &enabled,
+ sizeof(enabled));
+
+ WinUtils::dwmSetWindowAttributePtr(
+ 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<nsIDOMHTMLCanvasElement> domcanvas(do_QueryInterface(aCanvas));
+ dom::HTMLCanvasElement * canvas = ((dom::HTMLCanvasElement*)domcanvas.get());
+ 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) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfx::DataSourceSurface> srcSurface = source->GetDataSurface();
+ RefPtr<gfxImageSurface> imageSurface = target->GetAsImageSurface();
+ if (!srcSurface || !imageSurface) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfx::DataSourceSurface::MappedSurface sourceMap;
+ srcSurface->Map(gfx::DataSourceSurface::READ, &sourceMap);
+ mozilla::gfx::CopySurfaceDataToPackedArray(sourceMap.mData,
+ imageSurface->Data(),
+ srcSurface->GetSize(),
+ sourceMap.mStride,
+ BytesPerPixel(srcSurface->GetFormat()));
+ srcSurface->Unmap();
+
+ HDC hDC = target->GetDC();
+ HBITMAP hBitmap = (HBITMAP)GetCurrentObject(hDC, OBJ_BITMAP);
+
+ DWORD flags = aDrawBorder ? DWM_SIT_DISPLAYFRAME : 0;
+ POINT pptClient = { 0, 0 };
+ HRESULT hr;
+ if (!mIsThumbnail) {
+ hr = WinUtils::dwmSetIconicLivePreviewBitmapPtr(mPreview->PreviewWindow(),
+ hBitmap, &pptClient, flags);
+ } else {
+ hr = WinUtils::dwmSetIconicThumbnailPtr(mPreview->PreviewWindow(),
+ hBitmap, flags);
+ }
+ MOZ_ASSERT(SUCCEEDED(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..d5f6b86f19
--- /dev/null
+++ b/widget/windows/TaskbarPreview.h
@@ -0,0 +1,140 @@
+/* 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 <nsWeakPtr.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);
+
+ 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..69cea38092
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.cpp
@@ -0,0 +1,145 @@
+/* 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, 0, 0,
+ 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..2b4342d157
--- /dev/null
+++ b/widget/windows/TaskbarPreviewButton.h
@@ -0,0 +1,48 @@
+/* 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..c89fecb32e
--- /dev/null
+++ b/widget/windows/TaskbarTabPreview.cpp
@@ -0,0 +1,357 @@
+/* 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)
+{
+ WindowHook &hook = GetWindowHook();
+ hook.AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this);
+}
+
+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::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, 0, 0,
+ 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 0;
+ }
+ 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);
+ 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 {
+ NS_NOTREACHED("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..3d043a1414
--- /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);
+
+ 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..c3d005252d
--- /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;
+}
+}
+
+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;
+ }
+
+ WindowHook &hook = GetWindowHook();
+ if (!CanMakeTaskbarCalls())
+ hook.AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(),
+ TaskbarWindowHook, this);
+}
+
+TaskbarWindowPreview::~TaskbarWindowPreview() {
+ if (mOverlayIcon) {
+ ::DestroyIcon(mOverlayIcon);
+ mOverlayIcon = nullptr;
+ }
+
+ if (IsWindowAvailable()) {
+ DetachFromNSWindow();
+ } else {
+ mWnd = nullptr;
+ }
+}
+
+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();
+ (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;
+ mCustomDrawing = aEnable;
+ TaskbarPreview::EnableCustomDrawing(mWnd, aEnable);
+
+ WindowHook &hook = GetWindowHook();
+ 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, 0, 0,
+ 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);
+
+ 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..d0719f739a
--- /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);
+
+ 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/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..589449af8b
--- /dev/null
+++ b/widget/windows/WidgetTraceEvent.cpp
@@ -0,0 +1,132 @@
+/* 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 "nsIXULWindow.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() : hidden_window_hwnd(nullptr) {
+ MOZ_COUNT_CTOR(HWNDGetter);
+ }
+ ~HWNDGetter() {
+ MOZ_COUNT_DTOR(HWNDGetter);
+ }
+
+ 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<nsIXULWindow> 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_DispatchToMainThread(getter, NS_DISPATCH_SYNC);
+ 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..f660bd0192
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -0,0 +1,329 @@
+/* -*- 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 "gfxPrefs.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/Compositor.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+#include "VsyncDispatcher.h"
+
+#include <ddraw.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+
+WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData)
+ : mWidgetKey(aInitData.widgetKey()),
+ mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
+ mTransparencyMode(static_cast<nsTransparencyMode>(aInitData.transparencyMode())),
+ mMemoryDC(nullptr),
+ mCompositeDC(nullptr),
+ mLockedBackBufferData(nullptr)
+{
+ MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
+
+ // mNotDeferEndRemoteDrawing is set on the main thread during init,
+ // but is only accessed after on the compositor thread.
+ mNotDeferEndRemoteDrawing = gfxPrefs::LayersCompositionFrameRate() == 0 ||
+ gfxPlatform::IsInLayoutAsapMode() ||
+ gfxPlatform::ForceSoftwareVsync();
+}
+
+void
+WinCompositorWidget::OnDestroyWindow()
+{
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+}
+
+bool
+WinCompositorWidget::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
+WinCompositorWidget::PostRender(WidgetRenderingContext* aContext)
+{
+ mPresentLock.Leave();
+}
+
+LayoutDeviceIntSize
+WinCompositorWidget::GetClientSize()
+{
+ RECT r;
+ if (!::GetClientRect(mWnd, &r)) {
+ return LayoutDeviceIntSize();
+ }
+ return LayoutDeviceIntSize(
+ r.right - r.left,
+ r.bottom - r.top);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WinCompositorWidget::StartRemoteDrawing()
+{
+ MOZ_ASSERT(!mCompositeDC);
+
+ RefPtr<gfxASurface> surf;
+ if (mTransparencyMode == eTransparencyTransparent) {
+ 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 = (mTransparencyMode == eTransparencyOpaque) ? 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;
+ }
+
+ MOZ_ASSERT(!mCompositeDC);
+ mCompositeDC = dc;
+
+ return mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(surf->CairoSurface(), size);
+}
+
+void
+WinCompositorWidget::EndRemoteDrawing()
+{
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ MOZ_ASSERT(mTransparentSurface);
+ RedrawTransparentWindow();
+ }
+ if (mCompositeDC) {
+ FreeWindowSurface(mCompositeDC);
+ }
+ mCompositeDC = nullptr;
+}
+
+bool
+WinCompositorWidget::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>
+WinCompositorWidget::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget,
+ const LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect)
+{
+ MOZ_ASSERT(!mLockedBackBufferData);
+
+ RefPtr<gfx::DrawTarget> target =
+ CompositorWidget::GetBackBufferDrawTarget(aScreenTarget, aRect, aClearRect);
+ 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>
+WinCompositorWidget::EndBackBufferDrawing()
+{
+ if (mLockedBackBufferData) {
+ MOZ_ASSERT(mLastBackBuffer);
+ mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
+ mLockedBackBufferData = nullptr;
+ }
+ return CompositorWidget::EndBackBufferDrawing();
+}
+
+bool
+WinCompositorWidget::InitCompositor(layers::Compositor* aCompositor)
+{
+ if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) {
+ DeviceManagerDx::Get()->InitializeDirectDraw();
+ }
+ return true;
+}
+
+uintptr_t
+WinCompositorWidget::GetWidgetKey()
+{
+ return mWidgetKey;
+}
+
+void
+WinCompositorWidget::EnterPresentLock()
+{
+ mPresentLock.Enter();
+}
+
+void
+WinCompositorWidget::LeavePresentLock()
+{
+ mPresentLock.Leave();
+}
+
+RefPtr<gfxASurface>
+WinCompositorWidget::EnsureTransparentSurface()
+{
+ MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
+
+ IntSize size = GetClientSize().ToUnknownSize();
+ if (!mTransparentSurface || mTransparentSurface->GetSize() != size) {
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+ CreateTransparentSurface(size);
+ }
+
+ RefPtr<gfxASurface> surface = mTransparentSurface;
+ return surface.forget();
+}
+
+void
+WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize)
+{
+ MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
+ RefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
+ mTransparentSurface = surface;
+ mMemoryDC = surface->GetDC();
+}
+
+void
+WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode)
+{
+ if (mTransparencyMode == aMode) {
+ return;
+ }
+
+ mTransparencyMode = aMode;
+ mTransparentSurface = nullptr;
+ mMemoryDC = nullptr;
+
+ if (mTransparencyMode == eTransparencyTransparent) {
+ EnsureTransparentSurface();
+ }
+}
+
+void
+WinCompositorWidget::ClearTransparentWindow()
+{
+ 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
+WinCompositorWidget::RedrawTransparentWindow()
+{
+ MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
+
+ 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
+WinCompositorWidget::GetWindowSurface()
+{
+ return eTransparencyTransparent == mTransparencyMode
+ ? mMemoryDC
+ : ::GetDC(mWnd);
+}
+
+void
+WinCompositorWidget::FreeWindowSurface(HDC dc)
+{
+ if (eTransparencyTransparent != mTransparencyMode)
+ ::ReleaseDC(mWnd, dc);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h
new file mode 100644
index 0000000000..9661cab458
--- /dev/null
+++ b/widget/windows/WinCompositorWidget.h
@@ -0,0 +1,112 @@
+/* -*- 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 "CompositorWidget.h"
+#include "gfxASurface.h"
+#include "mozilla/gfx/CriticalSection.h"
+#include "mozilla/gfx/Point.h"
+#include "nsIWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetDelegate
+{
+public:
+ // Callbacks for nsWindow.
+ virtual void EnterPresentLock() = 0;
+ virtual void LeavePresentLock() = 0;
+ virtual void OnDestroyWindow() = 0;
+
+ // Transparency handling.
+ virtual void UpdateTransparency(nsTransparencyMode aMode) = 0;
+ virtual void ClearTransparentWindow() = 0;
+
+ // If in-process and using software rendering, return the backing transparent
+ // DC.
+ virtual HDC GetTransparentDC() const = 0;
+};
+
+// 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 CompositorWidgetDelegate
+{
+public:
+ WinCompositorWidget(const CompositorWidgetInitData& aInitData);
+
+ 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 LayoutDeviceIntRect& aRect,
+ const LayoutDeviceIntRect& aClearRect) override;
+ already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override;
+ bool InitCompositor(layers::Compositor* aCompositor) override;
+ uintptr_t GetWidgetKey() override;
+ WinCompositorWidget* AsWindows() override {
+ return this;
+ }
+ CompositorWidgetDelegate* AsDelegate() override {
+ return this;
+ }
+
+ // CompositorWidgetDelegate overrides.
+ void EnterPresentLock() override;
+ void LeavePresentLock() override;
+ void OnDestroyWindow() override;
+ void UpdateTransparency(nsTransparencyMode aMode) override;
+ void ClearTransparentWindow() override;
+
+ bool RedrawTransparentWindow();
+
+ // Ensure that a transparent surface exists, then return it.
+ RefPtr<gfxASurface> EnsureTransparentSurface();
+
+ HDC GetTransparentDC() const override {
+ return mMemoryDC;
+ }
+ HWND GetHwnd() const {
+ return mWnd;
+ }
+
+private:
+ HDC GetWindowSurface();
+ void FreeWindowSurface(HDC dc);
+
+ void CreateTransparentSurface(const gfx::IntSize& aSize);
+
+private:
+ uintptr_t mWidgetKey;
+ HWND mWnd;
+ gfx::CriticalSection mPresentLock;
+
+ // Transparency handling.
+ nsTransparencyMode mTransparencyMode;
+ RefPtr<gfxASurface> mTransparentSurface;
+ HDC mMemoryDC;
+ HDC mCompositeDC;
+
+ // Locked back buffer of BasicCompositor
+ uint8_t* mLockedBackBufferData;
+
+ bool mNotDeferEndRemoteDrawing;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_windows_CompositorWidgetParent_h
diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp
new file mode 100644
index 0000000000..d44f729c48
--- /dev/null
+++ b/widget/windows/WinIMEHandler.cpp
@@ -0,0 +1,1055 @@
+/* -*- 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 "mozilla/Preferences.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowDefs.h"
+#include "WinTextEventDispatcherListener.h"
+
+#ifdef NS_ENABLE_TSF
+#include "TSFTextStore.h"
+#endif // #ifdef NS_ENABLE_TSF
+
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "nsIWindowsRegKey.h"
+#include "nsIWindowsUIUtils.h"
+
+#include "shellapi.h"
+#include "shlobj.h"
+#include "powrprof.h"
+#include "setupapi.h"
+#include "cfgmgr32.h"
+
+const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
+const char* kOskEnabled = "ui.osk.enabled";
+const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
+const char* kOskRequireWin10 = "ui.osk.require_win10";
+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::sPluginHasFocus = false;
+
+#ifdef NS_ENABLE_TSF
+bool IMEHandler::sIsInTSFMode = false;
+bool IMEHandler::sIsIMMEnabled = true;
+bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false;
+decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr;
+#endif // #ifdef NS_ENABLE_TSF
+
+static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified;
+static bool sDeterminedPowerPlatformRole = false;
+
+// static
+void
+IMEHandler::Initialize()
+{
+#ifdef NS_ENABLE_TSF
+ TSFTextStore::Initialize();
+ sIsInTSFMode = TSFTextStore::IsInTSFMode();
+ sIsIMMEnabled =
+ !sIsInTSFMode || Preferences::GetBool("intl.tsf.support_imm", true);
+ sAssociateIMCOnlyWhenIMM_IMEActive =
+ sIsIMMEnabled &&
+ Preferences::GetBool("intl.tsf.associate_imc_only_when_imm_ime_is_active",
+ false);
+ 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"));
+ }
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ IMMHandler::Initialize();
+}
+
+// static
+void
+IMEHandler::Terminate()
+{
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ TSFTextStore::Terminate();
+ sIsInTSFMode = false;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ IMMHandler::Terminate();
+ WinTextEventDispatcherListener::Shutdown();
+}
+
+// static
+void*
+IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType)
+{
+ if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) {
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::GetThreadManager();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ 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;
+ }
+
+#ifdef NS_ENABLE_TSF
+ 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;
+#else // #ifdef NS_ENABLE_TSF
+ return nullptr;
+#endif // #ifdef NS_ENABLE_TSF #else
+}
+
+// static
+bool
+IMEHandler::ProcessRawKeyMessage(const MSG& aMsg)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::ProcessRawKeyMessage(aMsg);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return false; // noting to do in IMM mode.
+}
+
+// static
+bool
+IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage,
+ WPARAM& aWParam, LPARAM& aLParam,
+ MSGResult& aResult)
+{
+ if (aMessage == MOZ_WM_DISMISS_ONSCREEN_KEYBOARD) {
+ if (!sFocusedWindow) {
+ DismissOnScreenKeyboard();
+ }
+ return true;
+ }
+
+#ifdef NS_ENABLE_TSF
+ 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 (!TSFTextStore::IsIMM_IMEActive()) {
+ return false;
+ }
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam,
+ aResult);
+}
+
+#ifdef NS_ENABLE_TSF
+// static
+bool
+IMEHandler::IsIMMActive()
+{
+ return TSFTextStore::IsIMM_IMEActive();
+}
+
+#endif // #ifdef NS_ENABLE_TSF
+
+// static
+bool
+IMEHandler::IsComposing()
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposing() || IMMHandler::IsComposing();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsComposing();
+}
+
+// static
+bool
+IMEHandler::IsComposingOn(nsWindow* aWindow)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ return TSFTextStore::IsComposingOn(aWindow) ||
+ IMMHandler::IsComposingOn(aWindow);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsComposingOn(aWindow);
+}
+
+// static
+nsresult
+IMEHandler::NotifyIME(nsWindow* aWindow,
+ const IMENotification& aIMENotification)
+{
+#ifdef NS_ENABLE_TSF
+ 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());
+ IMEHandler::MaybeShowOnScreenKeyboard();
+ 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;
+ }
+ }
+#endif //NS_ENABLE_TSF
+
+ 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);
+ 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();
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ sFocusedWindow = nullptr;
+ IMEHandler::MaybeDismissOnScreenKeyboard(aWindow);
+ IMMHandler::OnFocusChange(false, aWindow);
+#ifdef NS_ENABLE_TSF
+ // 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());
+ }
+#endif //NS_ENABLE_TSF
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+// static
+nsIMEUpdatePreference
+IMEHandler::GetUpdatePreference()
+{
+ // While a plugin has focus, neither TSFTextStore nor IMMHandler needs
+ // notifications.
+ if (sPluginHasFocus) {
+ return nsIMEUpdatePreference();
+ }
+
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable()) {
+ if (!sIsIMMEnabled) {
+ return TSFTextStore::GetIMEUpdatePreference();
+ }
+ // Even if TSF is available, the active IME may be an IMM-IME.
+ // Unfortunately, changing the result of GetUpdatePreference() 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::GetIMEUpdatePreference() |
+ TSFTextStore::GetIMEUpdatePreference();
+ }
+#endif //NS_ENABLE_TSF
+
+ return IMMHandler::GetIMEUpdatePreference();
+}
+
+// static
+TextEventDispatcherListener*
+IMEHandler::GetNativeTextEventDispatcherListener()
+{
+ return WinTextEventDispatcherListener::GetInstance();
+}
+
+// static
+bool
+IMEHandler::GetOpenState(nsWindow* aWindow)
+{
+#ifdef NS_ENABLE_TSF
+ if (IsTSFAvailable() && !IsIMMActive()) {
+ return TSFTextStore::GetIMEOpenState();
+ }
+#endif //NS_ENABLE_TSF
+
+ 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 TabParent already lost the reference to the nsWindow when
+ // it receives from the remote process.
+ if (sFocusedWindow == aWindow) {
+ NS_ASSERTION(aWindow->GetInputContext().IsOriginContentProcess(),
+ "input context of focused widget should be set from a remote process");
+ NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR));
+ }
+
+#ifdef NS_ENABLE_TSF
+ // 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, EmptyString(), EmptyString());
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ AssociateIMEContext(aWindow, true);
+}
+
+#ifdef NS_ENABLE_TSF
+// static
+bool
+IMEHandler::NeedsToAssociateIMC()
+{
+ if (sAssociateIMCOnlyWhenIMM_IMEActive) {
+ return TSFTextStore::IsIMM_IMEActive();
+ }
+
+ // Even if IMC should be associated with focused widget with non-IMM-IME,
+ // we need to avoid crash bug of MS-IME for Japanese on Win10. It crashes
+ // while we're associating default IME to a window when it's active.
+ static const bool sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 =
+ IsWin10OrLater() &&
+ Preferences::GetBool(
+ "intl.tsf.hack.ms_japanese_ime.do_not_associate_imc_on_win10", true);
+ return !sDoNotAssociateIMCWhenMSJapaneseIMEActiveOnWin10 ||
+ !TSFTextStore::IsMSJapaneseIMEActive();
+}
+#endif // #ifdef NS_ENABLE_TSF
+
+// 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));
+
+ const InputContext& oldInputContext = aWindow->GetInputContext();
+
+ // Assume that SetInputContext() is called only when aWindow has focus.
+ sPluginHasFocus = (aInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
+
+ if (aAction.UserMightRequestOpenVKB()) {
+ IMEHandler::MaybeShowOnScreenKeyboard();
+ }
+
+ bool enable = WinUtils::IsIMEEnabled(aInputContext);
+ bool adjustOpenState = (enable &&
+ aInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE);
+ bool open = (adjustOpenState &&
+ aInputContext.mIMEState.mOpen == IMEState::OPEN);
+
+#ifdef NS_ENABLE_TSF
+ // 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());
+ } else if (oldInputContext.mIMEState.mEnabled == IMEState::PLUGIN) {
+ // Disassociate the IME context from the window when plugin loses focus
+ // in pure TSF mode.
+ AssociateIMEContext(aWindow, false);
+ }
+ if (adjustOpenState) {
+ TSFTextStore::SetIMEOpenState(open);
+ }
+ return;
+ }
+ } else {
+ // Set at least InputScope even when TextStore is not available.
+ SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType,
+ aInputContext.mHTMLInputInputmode);
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ AssociateIMEContext(aWindow, enable);
+
+ IMEContext context(aWindow);
+ if (adjustOpenState) {
+ context.SetOpenState(open);
+ }
+}
+
+// static
+void
+IMEHandler::AssociateIMEContext(nsWindowBase* 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)
+{
+ // For a11y, the default enabled state should be 'enabled'.
+ aInputContext.mIMEState.mEnabled = IMEState::ENABLED;
+
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ TSFTextStore::SetInputContext(aWindow, aInputContext,
+ InputContextAction(InputContextAction::CAUSE_UNKNOWN,
+ InputContextAction::GOT_FOCUS));
+ // IME context isn't necessary in pure TSF mode.
+ if (!sIsIMMEnabled) {
+ AssociateIMEContext(aWindow, false);
+ }
+ return;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+#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()
+{
+#ifdef NS_ENABLE_TSF
+ if (sIsInTSFMode) {
+ return TSFTextStore::CurrentKeyboardLayoutHasIME();
+ }
+#endif // #ifdef NS_ENABLE_TSF
+
+ return IMMHandler::IsIMEAvailable();
+}
+#endif // #ifdef DEBUG
+
+// static
+void
+IMEHandler::OnKeyboardLayoutChanged()
+{
+ if (!sIsIMMEnabled || !IsTSFAvailable()) {
+ return;
+ }
+
+ // If there is no TSFTextStore which has focus, i.e., no editor has focus,
+ // nothing to do here.
+ nsWindowBase* windowBase = TSFTextStore::GetEnabledWindowBase();
+ if (!windowBase) {
+ return;
+ }
+
+ // If IME isn't available, nothing to do here.
+ InputContext inputContext = windowBase->GetInputContext();
+ if (!WinUtils::IsIMEEnabled(inputContext)) {
+ return;
+ }
+
+ // Associate or Disassociate IMC if it's necessary.
+ // Note that this does nothing if the window has already associated with or
+ // disassociated from the window.
+ AssociateIMEContext(windowBase, NeedsToAssociateIMC());
+}
+
+// static
+void
+IMEHandler::SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputInputmode)
+{
+ if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) {
+ return;
+ }
+ UINT arraySize = 0;
+ const InputScope* scopes = nullptr;
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html
+ if (aHTMLInputType.IsEmpty() || aHTMLInputType.EqualsLiteral("text")) {
+ if (aHTMLInputInputmode.EqualsLiteral("url")) {
+ static const InputScope inputScopes[] = { IS_URL };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("email")) {
+ static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("tel")) {
+ static const InputScope inputScopes[] =
+ {IS_TELEPHONE_LOCALNUMBER, IS_TELEPHONE_FULLTELEPHONENUMBER};
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputInputmode.EqualsLiteral("numeric")) {
+ static const InputScope inputScopes[] = { IS_NUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else {
+ static const InputScope inputScopes[] = { IS_DEFAULT };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ }
+ } else if (aHTMLInputType.EqualsLiteral("url")) {
+ static const InputScope inputScopes[] = { IS_URL };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("search")) {
+ static const InputScope inputScopes[] = { IS_SEARCH };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("email")) {
+ static const InputScope inputScopes[] = { IS_EMAIL_SMTPEMAILADDRESS };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("password")) {
+ static const InputScope inputScopes[] = { IS_PASSWORD };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("datetime") ||
+ aHTMLInputType.EqualsLiteral("datetime-local")) {
+ static const InputScope inputScopes[] = {
+ IS_DATE_FULLDATE, IS_TIME_FULLTIME };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("date") ||
+ aHTMLInputType.EqualsLiteral("month") ||
+ aHTMLInputType.EqualsLiteral("week")) {
+ static const InputScope inputScopes[] = { IS_DATE_FULLDATE };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("time")) {
+ static const InputScope inputScopes[] = { IS_TIME_FULLTIME };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("tel")) {
+ static const InputScope inputScopes[] = {
+ IS_TELEPHONE_FULLTELEPHONENUMBER, IS_TELEPHONE_LOCALNUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ } else if (aHTMLInputType.EqualsLiteral("number")) {
+ static const InputScope inputScopes[] = { IS_NUMBER };
+ scopes = &inputScopes[0];
+ arraySize = ArrayLength(inputScopes);
+ }
+ if (scopes && arraySize > 0) {
+ sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
+ nullptr, nullptr);
+ }
+}
+
+// static
+void
+IMEHandler::MaybeShowOnScreenKeyboard()
+{
+ if (sPluginHasFocus ||
+ !IsWin8OrLater() ||
+ !Preferences::GetBool(kOskEnabled, true) ||
+ GetOnScreenKeyboardWindow() ||
+ !IMEHandler::NeedOnScreenKeyboard()) {
+ return;
+ }
+
+ // On Windows 10 we require tablet mode, unless the user has set the relevant
+ // Windows setting to enable the on-screen keyboard in desktop mode.
+ // We might be disabled specifically on Win8(.1), so we check that afterwards.
+ if (IsWin10OrLater()) {
+ if (!IsInTabletMode() && !AutoInvokeOnScreenKeyboardInDesktopMode()) {
+ return;
+ }
+ }
+ else if (Preferences::GetBool(kOskRequireWin10, true)) {
+ return;
+ }
+
+ IMEHandler::ShowOnScreenKeyboard();
+}
+
+// static
+void
+IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow)
+{
+ if (sPluginHasFocus ||
+ !IsWin8OrLater()) {
+ return;
+ }
+
+ ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD,
+ 0, 0);
+}
+
+// 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()
+{
+ // This function is only supported for Windows 8 and up.
+ if (!IsWin8OrLater()) {
+ Preferences::SetString(kOskDebugReason, L"IKPOS: Requires Win8+.");
+ return false;
+ }
+
+ 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::IsUserAction(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.
+ typedef POWER_PLATFORM_ROLE (WINAPI* PowerDeterminePlatformRoleEx)(ULONG Version);
+ if (!sDeterminedPowerPlatformRole) {
+ sDeterminedPowerPlatformRole = true;
+ PowerDeterminePlatformRoleEx power_determine_platform_role =
+ reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress(
+ ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx"));
+ if (power_determine_platform_role) {
+ sPowerPlatformRole = power_determine_platform_role(POWER_PLATFORM_ROLE_V2);
+ } else {
+ sPowerPlatformRole = PlatformRoleUnspecified;
+ }
+ }
+
+ // 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()
+{
+ nsCOMPtr<nsIWindowsUIUtils>
+ uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (NS_WARN_IF(!uiUtils)) {
+ Preferences::SetString(kOskDebugReason,
+ L"IITM: nsIWindowsUIUtils not available.");
+ return false;
+ }
+ bool isInTabletMode = false;
+ uiUtils->GetInTabletMode(&isInTabletMode);
+ if (isInTabletMode) {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=true.");
+ } else {
+ Preferences::SetString(kOskDebugReason, L"IITM: GetInTabletMode=false.");
+ }
+ return isInTabletMode;
+}
+
+// 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;
+ }
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("SOFTWARE\\Microsoft\\TabletTip\\1.7"),
+ 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.
+ uint32_t value;
+ rv = regKey->ReadIntValue(NS_LITERAL_STRING("EnableDesktopModeAutoInvoke"),
+ &value);
+ if (NS_FAILED(rv)) {
+ Preferences::SetString(kOskDebugReason,
+ L"AIOSKIDM: failed reading value of regkey.");
+ 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()
+{
+ 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.
+ const wchar_t kRegKeyName[] =
+ L"Software\\Classes\\CLSID\\"
+ L"{054AAE20-4BEA-4347-8A35-64A533254A9D}\\LocalServer32";
+ if (!WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ kRegKeyName,
+ nullptr,
+ path,
+ sizeof path)) {
+ 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 =
+ WinUtils::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);
+}
+
+// Based on DismissVirtualKeyboard() in Chromium's base/win/win_util.cc.
+// static
+void
+IMEHandler::DismissOnScreenKeyboard()
+{
+ // Dismiss the virtual keyboard if it's open
+ HWND osk = GetOnScreenKeyboardWindow();
+ if (osk) {
+ ::PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0);
+ }
+}
+
+// static
+HWND
+IMEHandler::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
+IMEHandler::SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm)
+{
+ if (!sPluginHasFocus) {
+ return;
+ }
+
+ IMMHandler::SetCandidateWindow(aWindow, aForm);
+}
+
+// static
+void
+IMEHandler::DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aPluginEvent)
+{
+ if (!sPluginHasFocus) {
+ return;
+ }
+ IMMHandler::DefaultProcOfPluginEvent(aWindow, aPluginEvent);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h
new file mode 100644
index 0000000000..c18a4437e8
--- /dev/null
+++ b/widget/windows/WinIMEHandler.h
@@ -0,0 +1,190 @@
+/* -*- 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 "nsWindowBase.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
+{
+public:
+ static void Initialize();
+ 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);
+
+ /**
+ * 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 update preferences.
+ */
+ static nsIMEUpdatePreference GetUpdatePreference();
+
+ /**
+ * 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(nsWindowBase* aWindowBase, bool aEnable);
+
+ /**
+ * Called when the window is created.
+ */
+ static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext);
+
+ /*
+ * For windowless plugin helper.
+ */
+ static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+
+ /*
+ * For WM_IME_*COMPOSITION messages and e10s with windowless plugin
+ */
+ static void DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aPluginEvent);
+
+#ifdef NS_ENABLE_TSF
+ /**
+ * This is called by TSFStaticSink when active IME is changed.
+ */
+ static void OnKeyboardLayoutChanged();
+#endif // #ifdef NS_ENABLE_TSF
+
+#ifdef DEBUG
+ /**
+ * Returns true when current keyboard layout has IME. Otherwise, false.
+ */
+ static bool CurrentKeyboardLayoutHasIME();
+#endif // #ifdef DEBUG
+
+private:
+ static nsWindow* sFocusedWindow;
+ static InputContextAction::Cause sLastContextActionCause;
+
+ static bool sPluginHasFocus;
+
+#ifdef NS_ENABLE_TSF
+ static decltype(SetInputScopes)* sSetInputScopes;
+ static void SetInputScopeForIMM32(nsWindow* aWindow,
+ const nsAString& aHTMLInputType,
+ const nsAString& aHTMLInputInputmode);
+ 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 sAssociateIMCOnlyWhenIMM_IMEActive;
+
+ static bool IsTSFAvailable() { return (sIsInTSFMode && !sPluginHasFocus); }
+ static bool IsIMMActive();
+
+ static void MaybeShowOnScreenKeyboard();
+ static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow);
+ 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();
+
+ /**
+ * Show the Windows on-screen keyboard. Only allowed for
+ * chrome documents and Windows 8 and higher.
+ */
+ static void ShowOnScreenKeyboard();
+
+ /**
+ * Dismiss the Windows on-screen keyboard. Only allowed for
+ * Windows 8 and higher.
+ */
+ static void DismissOnScreenKeyboard();
+
+ /**
+ * Get the HWND for the on-screen keyboard, if it's up. Only
+ * allowed for Windows 8 and higher.
+ */
+ static HWND GetOnScreenKeyboardWindow();
+#endif // #ifdef NS_ENABLE_TSF
+};
+
+} // 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..e722a38360
--- /dev/null
+++ b/widget/windows/WinMessages.h
@@ -0,0 +1,187 @@
+/* -*- 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)
+// 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 hiding the on-screen keyboard
+#define MOZ_WM_DISMISS_ONSCREEN_KEYBOARD (WM_APP+0x0317)
+
+// Following MOZ_WM_*KEY* messages are used by PluginInstanceChild and
+// NativeKey internally. (never posted to the queue)
+#define MOZ_WM_KEYDOWN (WM_APP+0x0318)
+#define MOZ_WM_KEYUP (WM_APP+0x0319)
+#define MOZ_WM_SYSKEYDOWN (WM_APP+0x031A)
+#define MOZ_WM_SYSKEYUP (WM_APP+0x031B)
+#define MOZ_WM_CHAR (WM_APP+0x031C)
+#define MOZ_WM_SYSCHAR (WM_APP+0x031D)
+#define MOZ_WM_DEADCHAR (WM_APP+0x031E)
+#define MOZ_WM_SYSDEADCHAR (WM_APP+0x031F)
+
+// Internal message for ensuring the file picker is visible on multi monitor
+// systems, and when the screen resolution changes.
+#define MOZ_WM_ENSUREVISIBLE (WM_APP+0x374F)
+
+// 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)
+
+/*****************************************************************************
+ * WM_* messages and related constants which may not be defined by
+ * old Windows SDK
+ ****************************************************************************/
+
+#ifndef SM_CXPADDEDBORDER
+#define SM_CXPADDEDBORDER 92
+#endif
+
+// require WINVER >= 0x601
+#ifndef SM_MAXIMUMTOUCHES
+#define SM_MAXIMUMTOUCHES 95
+#endif
+
+#ifndef WM_THEMECHANGED
+#define WM_THEMECHANGED 0x031A
+#endif
+
+#ifndef WM_GETOBJECT
+#define WM_GETOBJECT 0x03d
+#endif
+
+#ifndef PBT_APMRESUMEAUTOMATIC
+#define PBT_APMRESUMEAUTOMATIC 0x0012
+#endif
+
+#ifndef WM_MOUSEHWHEEL
+#define WM_MOUSEHWHEEL 0x020E
+#endif
+
+#ifndef MOUSEEVENTF_HWHEEL
+#define MOUSEEVENTF_HWHEEL 0x01000
+#endif
+
+#ifndef WM_MOUSELEAVE
+#define WM_MOUSELEAVE 0x02A3
+#endif
+
+#ifndef SPI_GETWHEELSCROLLCHARS
+#define SPI_GETWHEELSCROLLCHARS 0x006C
+#endif
+
+#ifndef SPI_SETWHEELSCROLLCHARS
+#define SPI_SETWHEELSCROLLCHARS 0x006D
+#endif
+
+#ifndef MAPVK_VSC_TO_VK
+#define MAPVK_VK_TO_VSC 0
+#define MAPVK_VSC_TO_VK 1
+#define MAPVK_VK_TO_CHAR 2
+#define MAPVK_VSC_TO_VK_EX 3
+#define MAPVK_VK_TO_VSC_EX 4
+#endif
+
+#ifndef WM_DWMCOMPOSITIONCHANGED
+#define WM_DWMCOMPOSITIONCHANGED 0x031E
+#endif
+#ifndef WM_DWMNCRENDERINGCHANGED
+#define WM_DWMNCRENDERINGCHANGED 0x031F
+#endif
+#ifndef WM_DWMCOLORIZATIONCOLORCHANGED
+#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320
+#endif
+#ifndef WM_DWMWINDOWMAXIMIZEDCHANGE
+#define WM_DWMWINDOWMAXIMIZEDCHANGE 0x0321
+#endif
+
+// Drop shadow window style
+#define CS_XP_DROPSHADOW 0x00020000
+
+// App Command messages for IntelliMouse and Natural Keyboard Pro
+// These messages are not included in Visual C++ 6.0, but are in 7.0+
+#ifndef WM_APPCOMMAND
+#define WM_APPCOMMAND 0x0319
+#endif
+
+#define FAPPCOMMAND_MASK 0xF000
+
+#ifndef WM_GETTITLEBARINFOEX
+#define WM_GETTITLEBARINFOEX 0x033F
+#endif
+
+#ifndef CCHILDREN_TITLEBAR
+#define CCHILDREN_TITLEBAR 5
+#endif
+
+#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..f2c1704b96
--- /dev/null
+++ b/widget/windows/WinModifierKeyState.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 mozilla_widget_WinModifierKeyState_h_
+#define mozilla_widget_WinModifierKeyState_h_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/EventForwards.h"
+#include <windows.h>
+
+class nsCString;
+
+namespace mozilla {
+namespace widget {
+
+class MOZ_STACK_CLASS ModifierKeyState final
+{
+public:
+ ModifierKeyState();
+ ModifierKeyState(bool aIsShiftDown, bool aIsControlDown, bool aIsAltDown);
+ ModifierKeyState(Modifiers aModifiers);
+
+ void Update();
+
+ void Unset(Modifiers aRemovingModifiers);
+ void Set(Modifiers aAddingModifiers);
+
+ void InitInputEvent(WidgetInputEvent& aInputEvent) const;
+
+ bool IsShift() const;
+ bool IsControl() const;
+ bool IsAlt() const;
+ bool IsAltGr() 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;
+
+ MOZ_ALWAYS_INLINE void EnsureAltGr();
+
+ 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..10937ba519
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.cpp
@@ -0,0 +1,1799 @@
+/* -*- 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 "nsIDOMWheelEvent.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/WindowsVersion.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%02X]=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(nsWindowBase* 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%02X, ::GetMessageTime()=%d",
+ 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(nsWindowBase* 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);
+
+ 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(nsWindowBase* aWidget,
+ WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint)
+{
+ NS_ENSURE_TRUE_VOID(aWidget);
+ LayoutDeviceIntPoint point;
+ if (aPoint) {
+ point = *aPoint;
+ } else {
+ POINTS pts = GetCurrentMessagePos();
+ POINT pt;
+ pt.x = pts.x;
+ pt.y = pts.y;
+ ::ScreenToClient(aWidget->GetWindowHandle(), &pt);
+ point.x = pt.x;
+ point.y = pt.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()) {
+ 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(nsWindowBase* 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%08X, lParam=0x%08X, point: { x=%d, y=%d }",
+ 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)) {
+ nsWindowBase* destWindow = WinUtils::GetNSWindowBasePtr(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::GetNSWindowBasePtr(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");
+
+ // If the found window is our plugin window, it means that the message
+ // has been handled by the plugin but not consumed. We should handle the
+ // message on its parent window. However, note that the DOM event may
+ // cause accessing the plugin. Therefore, we should unlock the plugin
+ // process by using PostMessage().
+ if (destWindow->IsPlugin()) {
+ destWindow = destWindow->GetParentWindowBase(false);
+ if (!destWindow) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: "
+ "Our window which is a parent of a plugin window is not found"));
+ return;
+ }
+ }
+ 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 we're a plugin window (MozillaWindowClass) and cursor in this window,
+ // the message shouldn't go to plugin's wndproc again. So, we should handle
+ // it on parent window. However, note that the DOM event may cause accessing
+ // the plugin. Therefore, we should unlock the plugin process by using
+ // PostMessage().
+ if (aWidget->IsPlugin() &&
+ aWidget->GetWindowHandle() == pluginWnd) {
+ nsWindowBase* destWindow = aWidget->GetParentWindowBase(false);
+ if (!destWindow) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Our normal window which "
+ "is a parent of this plugin window is not found"));
+ return;
+ }
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::ProcessNativeMouseWheelMessage: Succeeded, "
+ "Posting internal message to an nsWindow (%p) which is parent of this "
+ "plugin window...",
+ destWindow));
+ mIsWaitingInternalMessage = true;
+ UINT internalMessage = WinUtils::GetInternalMessage(aMessage);
+ ::PostMessage(destWindow->GetWindowHandle(), internalMessage,
+ aWParam, aLParam);
+ 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(nsWindowBase* 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%08X, lParam=0x%08X",
+ 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(nsWindowBase* 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%08X, aLParam=0x%08X",
+ 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<nsWindowBase> kungFuDethGrip(aWidget);
+
+ WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
+ if (mLastEventInfo.InitWheelEvent(aWidget, wheelEvent, modKeyState)) {
+ 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(nsWindowBase* 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;
+ case SB_PAGEDOWN:
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_PAGE;
+ break;
+
+ case SB_LINEUP:
+ delta = -1.0;
+ lineOrPageDelta = -1;
+ case SB_LINEDOWN:
+ wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE;
+ break;
+
+ default:
+ return;
+ }
+ modKeyState.InitInputEvent(wheelEvent);
+ // XXX Current mouse position may not be same as when the original message
+ // is received. We need to know the actual mouse cursor position when
+ // the original message was received.
+ InitEvent(aWidget, wheelEvent);
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, "
+ "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%08X, aLParam=0x%08X, "
+ "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, wheelEvent.mRefPoint.y,
+ 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(nsWindowBase* 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(
+ nsWindowBase* aWidget,
+ WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState)
+{
+ MOZ_ASSERT(aWheelEvent.mMessage == eWheel);
+
+ // XXX Why don't we use lParam value? We should use lParam value because
+ // our internal message is always posted by original message handler.
+ // So, GetMessagePos() may return different cursor position.
+ InitEvent(aWidget, aWheelEvent);
+
+ 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 ? nsIDOMWheelEvent::DOM_DELTA_PAGE :
+ nsIDOMWheelEvent::DOM_DELTA_LINE;
+
+ double& delta = mIsVertical ? aWheelEvent.mDeltaY : aWheelEvent.mDeltaX;
+ int32_t& lineOrPageDelta = mIsVertical ? aWheelEvent.mLineOrPageDeltaY :
+ aWheelEvent.mLineOrPageDeltaX;
+
+ double nativeDeltaPerUnit =
+ mIsPage ? static_cast<double>(WHEEL_DELTA) :
+ static_cast<double>(WHEEL_DELTA) / GetScrollAmount();
+
+ delta = static_cast<double>(mDelta) * orienter / nativeDeltaPerUnit;
+ mAccumulatedDelta += mDelta;
+ lineOrPageDelta =
+ mAccumulatedDelta * orienter / RoundDelta(nativeDeltaPerUnit);
+ mAccumulatedDelta -=
+ lineOrPageDelta * orienter * RoundDelta(nativeDeltaPerUnit);
+
+ if (aWheelEvent.mDeltaMode != nsIDOMWheelEvent::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;
+ } else if (!MouseScrollHandler::sInstance->
+ mSystemSettings.IsOverridingSystemScrollSpeedAllowed()) {
+ // If the system settings are customized by either the user or
+ // the mouse utility, we shouldn't allow to override the system scroll
+ // speed setting.
+ aWheelEvent.mAllowToOverrideSystemScrollSpeed = false;
+ } else {
+ // For suppressing too fast scroll, we should ensure that the maximum
+ // overridden delta value should be less than overridden scroll speed
+ // with default scroll amount.
+ double defaultScrollAmount =
+ mIsVertical ? SystemSettings::DefaultScrollLines() :
+ SystemSettings::DefaultScrollChars();
+ double maxDelta =
+ WidgetWheelEvent::ComputeOverriddenDelta(defaultScrollAmount,
+ mIsVertical);
+ if (maxDelta != defaultScrollAmount) {
+ double overriddenDelta =
+ WidgetWheelEvent::ComputeOverriddenDelta(Abs(delta), mIsVertical);
+ if (overriddenDelta > maxDelta) {
+ // Suppress to fast scroll since overriding system scroll speed with
+ // current delta value causes too big delta value.
+ 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, aWheelEvent.mRefPoint.y,
+ 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, %s",
+ IsVistaOrLater() ?
+ "this is unexpected on Vista or later" :
+ "but on XP or earlier, this is not a problem"));
+ // 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...
+}
+
+bool
+MouseScrollHandler::SystemSettings::IsOverridingSystemScrollSpeedAllowed()
+{
+ return mScrollLines == DefaultScrollLines() &&
+ (!IsVistaOrLater() || mScrollChars == DefaultScrollChars());
+}
+
+/******************************************************************************
+ *
+ * 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::RegisterCallback(OnChange, "mousewheel.", this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Failed to register callback for mousewheel.");
+}
+
+MouseScrollHandler::UserPrefs::~UserPrefs()
+{
+ DebugOnly<nsresult> rv =
+ Preferences::UnregisterCallback(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);
+ 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, "
+ "mOverriddenVerticalScrollAmount=%d, "
+ "mOverriddenHorizontalScrollAmount=%d, "
+ "mMouseScrollTransactionTimeout=%d",
+ GetBoolName(mScrollMessageHandledAsWheelMessage),
+ GetBoolName(mEnableSystemSettingCache),
+ GetBoolName(mForceEnableSystemSettingCache),
+ 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];
+ bool foundKey =
+ WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ L"Software\\Synaptics\\SynTP\\Install",
+ L"DriverVersion",
+ buf, sizeof buf);
+ if (!foundKey) {
+ 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.
+ bool foundKey =
+ WinUtils::GetRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Elantech\\MainOption",
+ L"DriverVersion",
+ buf, sizeof buf);
+ if (!foundKey) {
+ foundKey =
+ WinUtils::GetRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Elantech",
+ L"DriverVersion",
+ buf, sizeof buf);
+ }
+
+ if (!foundKey) {
+ 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(nsWindowBase* 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 commandEvent(true, nsGkAtoms::onAppCommand,
+ (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back, aWidget);
+ InitEvent(aWidget, commandEvent);
+ aWidget->DispatchWindowEvent(&commandEvent);
+ }
+ 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=%d",
+ 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];
+ bool foundKey =
+ WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ L"Software\\Alps\\Apoint",
+ L"ProductVer",
+ buf, sizeof buf);
+ if (!foundKey) {
+ 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 (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Lenovo\\TrackPoint")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Lenovo's TrackPoint driver is found"));
+ return true;
+ }
+
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Alps\\Apoint\\TrackPoint")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::TrackPoint::IsDriverInstalled(): "
+ "Alps's TrackPoint driver is found"));
+ }
+
+ return false;
+}
+
+/******************************************************************************
+ *
+ * Device::UltraNav
+ *
+ ******************************************************************************/
+
+/* static */
+bool
+MouseScrollHandler::Device::UltraNav::IsObsoleteDriverInstalled()
+{
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Lenovo\\UltraNav")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Lenovo's UltraNav driver is found"));
+ return true;
+ }
+
+ bool installed = false;
+ if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Synaptics\\SynTPEnh\\UltraNavUSB")) {
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScroll::Device::UltraNav::IsObsoleteDriverInstalled(): "
+ "Synaptics's UltraNav (USB) driver is found"));
+ installed = true;
+ } else if (WinUtils::HasRegistryKey(HKEY_CURRENT_USER,
+ L"Software\\Synaptics\\SynTPEnh\\UltraNavPS2")) {
+ 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%X, aMessage=0x%04X, aWParam=0x%08X, "
+ "aLParam=0x%08X, 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(nsWindowBase* 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;
+ }
+ // If the target window is not ours and received window is our plugin
+ // window, it comes from child window of the plugin.
+ if (aWidget && aWidget->IsPlugin() &&
+ !WinUtils::GetNSWindowBasePtr(mWnd)) {
+ return;
+ }
+ // Otherwise, the message may not be sent by us.
+ }
+
+ MOZ_LOG(gMouseScrollLog, LogLevel::Info,
+ ("MouseScrollHandler::SynthesizingEvent::NativeMessageReceived(): "
+ "aWidget=%p, aWidget->GetWindowHandle()=0x%X, mWnd=0x%X, "
+ "aMessage=0x%04X, aWParam=0x%08X, aLParam=0x%08X, mStatus=%s",
+ aWidget, aWidget ? aWidget->GetWindowHandle() : 0, 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..5d8c58ba0e
--- /dev/null
+++ b/widget/windows/WinMouseScrollHandler.h
@@ -0,0 +1,625 @@
+/* -*- 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 nsWindowBase;
+
+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(nsWindowBase* aWidget,
+ UINT msg,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult);
+
+ /**
+ * See nsIWidget::SynthesizeNativeMouseScrollEvent() for the detail about
+ * this method.
+ */
+ static nsresult SynthesizeNativeMouseScrollEvent(nsWindowBase* 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(nsWindowBase* aWidget,
+ WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr);
+
+ /**
+ * 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(nsWindowBase* 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(nsWindowBase* 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(nsWindowBase* 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(nsWindowBase* 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(nsWindowBase* 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(nsWindowBase* aWidget,
+ WidgetWheelEvent& aWheelEvent,
+ const ModifierKeyState& aModKeyState);
+
+ 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();
+
+ // Returns true if the system scroll may be overridden for faster scroll.
+ // Otherwise, false. For example, if the user maybe uses an expensive
+ // mouse which supports acceleration of scroll speed, faster scroll makes
+ // the user inconvenient.
+ bool IsOverridingSystemScrollSpeedAllowed();
+
+ 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; }
+ static int32_t DefaultScrollChars() { 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;
+ }
+
+ 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* aClosure)
+ {
+ static_cast<UserPrefs*>(aClosure)->MarkDirty();
+ }
+
+ bool mInitialized;
+ bool mScrollMessageHandledAsWheelMessage;
+ bool mEnableSystemSettingCache;
+ bool mForceEnableSystemSettingCache;
+ 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(nsWindowBase* 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(nsWindowBase* 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/WinNativeEventData.h b/widget/windows/WinNativeEventData.h
new file mode 100644
index 0000000000..5ed5ca44a4
--- /dev/null
+++ b/widget/windows/WinNativeEventData.h
@@ -0,0 +1,56 @@
+/* -*- 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_WinNativeEventData_h_
+#define mozilla_widget_WinNativeEventData_h_
+
+#include <windows.h>
+
+#include "mozilla/EventForwards.h"
+#include "mozilla/widget/WinModifierKeyState.h"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * WinNativeKeyEventData is used by nsIWidget::OnWindowedPluginKeyEvent() and
+ * related IPC methods. This class cannot hold any pointers and references
+ * since which are not available in different process.
+ */
+
+class WinNativeKeyEventData final
+{
+public:
+ UINT mMessage;
+ WPARAM mWParam;
+ LPARAM mLParam;
+ Modifiers mModifiers;
+
+private:
+ uintptr_t mKeyboardLayout;
+
+public:
+ WinNativeKeyEventData(UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam,
+ const ModifierKeyState& aModifierKeyState)
+ : mMessage(aMessage)
+ , mWParam(aWParam)
+ , mLParam(aLParam)
+ , mModifiers(aModifierKeyState.GetModifiers())
+ , mKeyboardLayout(reinterpret_cast<uintptr_t>(::GetKeyboardLayout(0)))
+ {
+ }
+
+ HKL GetKeyboardLayout() const
+ {
+ return reinterpret_cast<HKL>(mKeyboardLayout);
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef mozilla_widget_WinNativeEventData_h_
diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp
new file mode 100644
index 0000000000..698b7ec0e5
--- /dev/null
+++ b/widget/windows/WinTaskbar.cpp
@@ -0,0 +1,506 @@
+/* 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 "WinTaskbar.h"
+#include "TaskbarPreview.h"
+#include <nsITaskbarPreviewController.h>
+
+#include "mozilla/RefPtr.h"
+#include <nsError.h>
+#include <nsCOMPtr.h>
+#include <nsIWidget.h>
+#include <nsIBaseWindow.h>
+#include <nsIObserverService.h>
+#include <nsServiceManagerUtils.h>
+#include "nsIXULAppInfo.h"
+#include "nsIJumpListBuilder.h"
+#include "nsUXThemeData.h"
+#include "nsWindow.h"
+#include "WinUtils.h"
+#include "TaskbarTabPreview.h"
+#include "TaskbarWindowPreview.h"
+#include "JumpListBuilder.h"
+#include "nsWidgetsCID.h"
+#include "nsPIDOMWindow.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/WindowsVersion.h"
+#include <io.h>
+#include <propvarutil.h>
+#include <propkey.h>
+#include <shellapi.h>
+
+const wchar_t kShellLibraryName[] = L"shell32.dll";
+
+static NS_DEFINE_CID(kJumpListBuilderCID, NS_WIN_JUMPLISTBUILDER_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;
+
+ typedef HRESULT (WINAPI * SHGetPropertyStoreForWindowPtr)
+ (HWND hwnd, REFIID riid, void** ppv);
+ SHGetPropertyStoreForWindowPtr funcGetProStore = nullptr;
+
+ HMODULE hDLL = ::LoadLibraryW(kShellLibraryName);
+ funcGetProStore = (SHGetPropertyStoreForWindowPtr)
+ GetProcAddress(hDLL, "SHGetPropertyStoreForWindow");
+
+ if (!funcGetProStore) {
+ FreeLibrary(hDLL);
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ IPropertyStore* pPropStore;
+ if (FAILED(funcGetProStore(toplevelHWND,
+ IID_PPV_ARGS(&pPropStore)))) {
+ FreeLibrary(hDLL);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PROPVARIANT pv;
+ if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) {
+ pPropStore->Release();
+ FreeLibrary(hDLL);
+ 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);
+ pPropStore->Release();
+ FreeLibrary(hDLL);
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// default nsITaskbarPreviewController
+
+class DefaultController final : public nsITaskbarPreviewController
+{
+ ~DefaultController() {}
+ HWND mWnd;
+public:
+ 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;
+}
+
+// deprecated
+NS_IMETHODIMP
+DefaultController::DrawPreview(nsISupports *ctx, bool *rDrawFrame) {
+ *rDrawFrame = true;
+ return NS_ERROR_UNEXPECTED;
+}
+
+// deprecated
+NS_IMETHODIMP
+DefaultController::DrawThumbnail(nsISupports *ctx, uint32_t width, uint32_t height, bool *rDrawFrame) {
+ *rDrawFrame = false;
+ return NS_ERROR_UNEXPECTED;
+}
+
+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) {
+ NS_NOTREACHED("OnClose should not be called for TaskbarWindowPreviews");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DefaultController::OnActivate(bool *rAcceptActivation) {
+ *rAcceptActivation = true;
+ NS_NOTREACHED("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 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::GetAppUserModelID(nsAString & aDefaultGroupId) {
+ // 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.
+ bool useProfile =
+ Preferences::GetBool("taskbar.grouping.useprofile", false);
+ if (useProfile) {
+ 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->GetNativePath(path))) {
+ nsAutoString id;
+ id.AppendInt(HashString(path));
+ if (!id.IsEmpty()) {
+ aDefaultGroupId.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. We skip setting the global id in this case as well.
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (!appInfo)
+ return false;
+
+ nsCString appName;
+ if (NS_FAILED(appInfo->GetName(appName))) {
+ // We just won't register then, let Windows handle it.
+ return false;
+ }
+
+ 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
+
+ // The hash is short, but users may customize this, so use a respectable
+ // string buffer.
+ wchar_t buf[256];
+ if (WinUtils::GetRegistryKey(HKEY_LOCAL_MACHINE,
+ regKey.get(),
+ path,
+ buf,
+ sizeof buf)) {
+ aDefaultGroupId.Assign(buf);
+ } else if (WinUtils::GetRegistryKey(HKEY_CURRENT_USER,
+ regKey.get(),
+ path,
+ buf,
+ sizeof buf)) {
+ aDefaultGroupId.Assign(buf);
+ }
+ }
+
+ return !aDefaultGroupId.IsEmpty();
+
+ return true;
+}
+
+NS_IMETHODIMP
+WinTaskbar::GetDefaultGroupId(nsAString & aDefaultGroupId) {
+ if (!GetAppUserModelID(aDefaultGroupId))
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+// (static) Called from AppShell
+bool
+WinTaskbar::RegisterAppUserModelID() {
+ if (!IsWin7OrLater())
+ return false;
+
+ SetCurrentProcessExplicitAppUserModelIDPtr funcAppUserModelID = nullptr;
+ bool retVal = false;
+
+ nsAutoString uid;
+ if (!GetAppUserModelID(uid))
+ return false;
+
+ HMODULE hDLL = ::LoadLibraryW(kShellLibraryName);
+
+ funcAppUserModelID = (SetCurrentProcessExplicitAppUserModelIDPtr)
+ GetProcAddress(hDLL, "SetCurrentProcessExplicitAppUserModelID");
+
+ if (!funcAppUserModelID) {
+ ::FreeLibrary(hDLL);
+ return false;
+ }
+
+ if (SUCCEEDED(funcAppUserModelID(uid.get())))
+ retVal = true;
+
+ if (hDLL)
+ ::FreeLibrary(hDLL);
+
+ return retVal;
+}
+
+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 = IsWin7OrLater() && 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;
+
+ 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);
+ preview = new TaskbarWindowPreview(mTaskbar, defaultController, toplevelHWND, shell);
+ if (!preview)
+ return NS_ERROR_OUT_OF_MEMORY;
+ 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::CreateJumpListBuilder(nsIJumpListBuilder * *aJumpListBuilder) {
+ nsresult rv;
+
+ if (JumpListBuilder::sBuildingList)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ nsCOMPtr<nsIJumpListBuilder> builder =
+ do_CreateInstance(kJumpListBuilderCID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_IF_ADDREF(*aJumpListBuilder = builder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WinTaskbar::SetGroupIdForWindow(mozIDOMWindow *aParent,
+ const nsAString & aIdentifier) {
+ return SetWindowAppUserModelProp(aParent, nsString(aIdentifier));
+}
+
+NS_IMETHODIMP
+WinTaskbar::PrepareFullScreen(mozIDOMWindow *aWindow, bool aFullScreen) {
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ HWND toplevelHWND = ::GetAncestor(GetHWNDFromDOMWindow(aWindow), GA_ROOT);
+ if (!toplevelHWND)
+ return NS_ERROR_INVALID_ARG;
+
+ return PrepareFullScreenHWND(toplevelHWND, aFullScreen);
+}
+
+NS_IMETHODIMP
+WinTaskbar::PrepareFullScreenHWND(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..46968737d9
--- /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
+
+ // 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);
+
+private:
+ bool Initialize();
+
+ typedef HRESULT (WINAPI * SetCurrentProcessExplicitAppUserModelIDPtr)(PCWSTR AppID);
+ 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..156c7a79b1
--- /dev/null
+++ b/widget/windows/WinTextEventDispatcherListener.cpp
@@ -0,0 +1,78 @@
+/* -*- 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_(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..d4e7965d95
--- /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_(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..149513b2fa
--- /dev/null
+++ b/widget/windows/WinUtils.cpp
@@ -0,0 +1,2110 @@
+/* -*- 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 "KeyboardLayout.h"
+#include "nsIDOMMouseEvent.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/HangMonitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/Unused.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+#ifdef MOZ_PLACES
+#include "mozIAsyncFavicons.h"
+#endif
+#include "nsIIconURI.h"
+#include "nsIDownloader.h"
+#include "nsINetUtil.h"
+#include "nsIChannel.h"
+#include "nsIObserver.h"
+#include "imgIEncoder.h"
+#include "nsIThread.h"
+#include "MainThreadUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsUnicharUtils.h"
+#include "nsWindowsHelpers.h"
+
+#ifdef NS_ENABLE_TSF
+#include <textstor.h>
+#include "TSFTextStore.h"
+#endif // #ifdef NS_ENABLE_TSF
+
+#include <shlwapi.h>
+
+mozilla::LazyLogModule gWindowsLog("Widget");
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace widget {
+
+#define ENTRY(_msg) { #_msg, _msg }
+EventMsgInfo gAllEvents[] = {
+ ENTRY(WM_NULL),
+ ENTRY(WM_CREATE),
+ ENTRY(WM_DESTROY),
+ ENTRY(WM_MOVE),
+ ENTRY(WM_SIZE),
+ ENTRY(WM_ACTIVATE),
+ ENTRY(WM_SETFOCUS),
+ ENTRY(WM_KILLFOCUS),
+ ENTRY(WM_ENABLE),
+ ENTRY(WM_SETREDRAW),
+ ENTRY(WM_SETTEXT),
+ ENTRY(WM_GETTEXT),
+ ENTRY(WM_GETTEXTLENGTH),
+ ENTRY(WM_PAINT),
+ ENTRY(WM_CLOSE),
+ ENTRY(WM_QUERYENDSESSION),
+ ENTRY(WM_QUIT),
+ ENTRY(WM_QUERYOPEN),
+ ENTRY(WM_ERASEBKGND),
+ ENTRY(WM_SYSCOLORCHANGE),
+ ENTRY(WM_ENDSESSION),
+ ENTRY(WM_SHOWWINDOW),
+ ENTRY(WM_SETTINGCHANGE),
+ ENTRY(WM_DEVMODECHANGE),
+ ENTRY(WM_ACTIVATEAPP),
+ ENTRY(WM_FONTCHANGE),
+ ENTRY(WM_TIMECHANGE),
+ ENTRY(WM_CANCELMODE),
+ ENTRY(WM_SETCURSOR),
+ ENTRY(WM_MOUSEACTIVATE),
+ ENTRY(WM_CHILDACTIVATE),
+ ENTRY(WM_QUEUESYNC),
+ ENTRY(WM_GETMINMAXINFO),
+ 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(WM_WINDOWPOSCHANGING),
+ ENTRY(WM_WINDOWPOSCHANGED),
+ 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(WM_STYLECHANGING),
+ ENTRY(WM_STYLECHANGED),
+ ENTRY(WM_DISPLAYCHANGE),
+ ENTRY(WM_GETICON),
+ ENTRY(WM_SETICON),
+ ENTRY(WM_NCCREATE),
+ ENTRY(WM_NCDESTROY),
+ ENTRY(WM_NCCALCSIZE),
+ ENTRY(WM_NCHITTEST),
+ ENTRY(WM_NCPAINT),
+ ENTRY(WM_NCACTIVATE),
+ ENTRY(WM_GETDLGCODE),
+ ENTRY(WM_SYNCPAINT),
+ ENTRY(WM_NCMOUSEMOVE),
+ ENTRY(WM_NCLBUTTONDOWN),
+ ENTRY(WM_NCLBUTTONUP),
+ ENTRY(WM_NCLBUTTONDBLCLK),
+ ENTRY(WM_NCRBUTTONDOWN),
+ ENTRY(WM_NCRBUTTONUP),
+ ENTRY(WM_NCRBUTTONDBLCLK),
+ ENTRY(WM_NCMBUTTONDOWN),
+ ENTRY(WM_NCMBUTTONUP),
+ ENTRY(WM_NCMBUTTONDBLCLK),
+ 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(WM_KEYDOWN),
+ ENTRY(WM_KEYUP),
+ ENTRY(WM_CHAR),
+ ENTRY(WM_DEADCHAR),
+ ENTRY(WM_SYSKEYDOWN),
+ ENTRY(WM_SYSKEYUP),
+ ENTRY(WM_SYSCHAR),
+ ENTRY(WM_SYSDEADCHAR),
+ 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_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(WM_MOUSEMOVE),
+ ENTRY(WM_LBUTTONDOWN),
+ ENTRY(WM_LBUTTONUP),
+ ENTRY(WM_LBUTTONDBLCLK),
+ ENTRY(WM_RBUTTONDOWN),
+ ENTRY(WM_RBUTTONUP),
+ ENTRY(WM_RBUTTONDBLCLK),
+ ENTRY(WM_MBUTTONDOWN),
+ ENTRY(WM_MBUTTONUP),
+ ENTRY(WM_MBUTTONDBLCLK),
+ ENTRY(WM_MOUSEWHEEL),
+ ENTRY(WM_MOUSEHWHEEL),
+ ENTRY(WM_PARENTNOTIFY),
+ ENTRY(WM_ENTERMENULOOP),
+ ENTRY(WM_EXITMENULOOP),
+ ENTRY(WM_NEXTMENU),
+ ENTRY(WM_SIZING),
+ ENTRY(WM_CAPTURECHANGED),
+ ENTRY(WM_MOVING),
+ ENTRY(WM_POWERBROADCAST),
+ ENTRY(WM_DEVICECHANGE),
+ 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(WM_NCMOUSEHOVER),
+ ENTRY(WM_MOUSEHOVER),
+ ENTRY(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(WM_DWMCOMPOSITIONCHANGED),
+ ENTRY(WM_DWMNCRENDERINGCHANGED),
+ ENTRY(WM_DWMCOLORIZATIONCOLORCHANGED),
+ ENTRY(WM_DWMWINDOWMAXIMIZEDCHANGE),
+ ENTRY(WM_DWMSENDICONICTHUMBNAIL),
+ ENTRY(WM_DWMSENDICONICLIVEPREVIEWBITMAP),
+ ENTRY(WM_TABLET_QUERYSYSTEMGESTURESTATUS),
+ ENTRY(WM_GESTURE),
+ ENTRY(WM_GESTURENOTIFY),
+ ENTRY(WM_GETTITLEBARINFOEX),
+ {nullptr, 0x0}
+};
+#undef ENTRY
+
+#ifdef MOZ_PLACES
+NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver)
+NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback)
+#endif
+NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable)
+NS_IMPL_ISUPPORTS(AsyncDeleteIconFromDisk, nsIRunnable)
+NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable)
+
+
+const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache";
+const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache";
+
+// apis available on vista and up.
+WinUtils::SHCreateItemFromParsingNamePtr WinUtils::sCreateItemFromParsingName = nullptr;
+WinUtils::SHGetKnownFolderPathPtr WinUtils::sGetKnownFolderPath = nullptr;
+
+// We just leak these DLL HMODULEs. There's no point in calling FreeLibrary
+// on them during shutdown anyway.
+static const wchar_t kShellLibraryName[] = L"shell32.dll";
+static HMODULE sShellDll = nullptr;
+static const wchar_t kDwmLibraryName[] = L"dwmapi.dll";
+static HMODULE sDwmDll = nullptr;
+
+WinUtils::DwmExtendFrameIntoClientAreaProc WinUtils::dwmExtendFrameIntoClientAreaPtr = nullptr;
+WinUtils::DwmIsCompositionEnabledProc WinUtils::dwmIsCompositionEnabledPtr = nullptr;
+WinUtils::DwmSetIconicThumbnailProc WinUtils::dwmSetIconicThumbnailPtr = nullptr;
+WinUtils::DwmSetIconicLivePreviewBitmapProc WinUtils::dwmSetIconicLivePreviewBitmapPtr = nullptr;
+WinUtils::DwmGetWindowAttributeProc WinUtils::dwmGetWindowAttributePtr = nullptr;
+WinUtils::DwmSetWindowAttributeProc WinUtils::dwmSetWindowAttributePtr = nullptr;
+WinUtils::DwmInvalidateIconicBitmapsProc WinUtils::dwmInvalidateIconicBitmapsPtr = nullptr;
+WinUtils::DwmDefWindowProcProc WinUtils::dwmDwmDefWindowProcPtr = nullptr;
+WinUtils::DwmGetCompositionTimingInfoProc WinUtils::dwmGetCompositionTimingInfoPtr = nullptr;
+WinUtils::DwmFlushProc WinUtils::dwmFlushProcPtr = nullptr;
+
+// Prefix for path used by NT calls.
+const wchar_t kNTPrefix[] = L"\\??\\";
+const size_t kNTPrefixLen = ArrayLength(kNTPrefix) - 1;
+
+struct CoTaskMemFreePolicy
+{
+ void operator()(void* aPtr) {
+ ::CoTaskMemFree(aPtr);
+ }
+};
+
+SetThreadDpiAwarenessContextProc WinUtils::sSetThreadDpiAwarenessContext = NULL;
+EnableNonClientDpiScalingProc WinUtils::sEnableNonClientDpiScaling = NULL;
+#ifdef ACCESSIBILITY
+typedef NTSTATUS (NTAPI* NtTestAlertPtr)(VOID);
+static NtTestAlertPtr sNtTestAlert = nullptr;
+#endif
+
+
+/* static */
+void
+WinUtils::Initialize()
+{
+ if (!sDwmDll && IsVistaOrLater()) {
+ sDwmDll = ::LoadLibraryW(kDwmLibraryName);
+
+ if (sDwmDll) {
+ dwmExtendFrameIntoClientAreaPtr = (DwmExtendFrameIntoClientAreaProc)::GetProcAddress(sDwmDll, "DwmExtendFrameIntoClientArea");
+ dwmIsCompositionEnabledPtr = (DwmIsCompositionEnabledProc)::GetProcAddress(sDwmDll, "DwmIsCompositionEnabled");
+ dwmSetIconicThumbnailPtr = (DwmSetIconicThumbnailProc)::GetProcAddress(sDwmDll, "DwmSetIconicThumbnail");
+ dwmSetIconicLivePreviewBitmapPtr = (DwmSetIconicLivePreviewBitmapProc)::GetProcAddress(sDwmDll, "DwmSetIconicLivePreviewBitmap");
+ dwmGetWindowAttributePtr = (DwmGetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmGetWindowAttribute");
+ dwmSetWindowAttributePtr = (DwmSetWindowAttributeProc)::GetProcAddress(sDwmDll, "DwmSetWindowAttribute");
+ dwmInvalidateIconicBitmapsPtr = (DwmInvalidateIconicBitmapsProc)::GetProcAddress(sDwmDll, "DwmInvalidateIconicBitmaps");
+ dwmDwmDefWindowProcPtr = (DwmDefWindowProcProc)::GetProcAddress(sDwmDll, "DwmDefWindowProc");
+ dwmGetCompositionTimingInfoPtr = (DwmGetCompositionTimingInfoProc)::GetProcAddress(sDwmDll, "DwmGetCompositionTimingInfo");
+ dwmFlushProcPtr = (DwmFlushProc)::GetProcAddress(sDwmDll, "DwmFlush");
+ }
+ }
+
+ if (IsWin10OrLater()) {
+ HMODULE user32Dll = ::GetModuleHandleW(L"user32");
+ if (user32Dll) {
+ sEnableNonClientDpiScaling = (EnableNonClientDpiScalingProc)
+ ::GetProcAddress(user32Dll, "EnableNonClientDpiScaling");
+ sSetThreadDpiAwarenessContext = (SetThreadDpiAwarenessContextProc)
+ ::GetProcAddress(user32Dll, "SetThreadDpiAwarenessContext");
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ sNtTestAlert = reinterpret_cast<NtTestAlertPtr>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtTestAlert"));
+ MOZ_ASSERT(sNtTestAlert);
+#endif
+}
+
+// static
+LRESULT WINAPI
+WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ 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, (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, (buffer));
+ delete[] buffer;
+}
+
+// static
+double
+WinUtils::SystemScaleFactor()
+{
+ // 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 double systemScale = 0;
+ if (systemScale == 0) {
+ HDC screenDC = GetDC(nullptr);
+ systemScale = GetDeviceCaps(screenDC, LOGPIXELSY) / 96.0;
+ ReleaseDC(nullptr, screenDC);
+
+ if (systemScale == 0) {
+ // Bug 1012487 - This can occur 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 1.0;
+ }
+ }
+ return systemScale;
+}
+
+#ifndef WM_DPICHANGED
+typedef enum {
+ MDT_EFFECTIVE_DPI = 0,
+ MDT_ANGULAR_DPI = 1,
+ MDT_RAW_DPI = 2,
+ MDT_DEFAULT = MDT_EFFECTIVE_DPI
+} MONITOR_DPI_TYPE;
+
+typedef enum {
+ PROCESS_DPI_UNAWARE = 0,
+ PROCESS_SYSTEM_DPI_AWARE = 1,
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+} PROCESS_DPI_AWARENESS;
+#endif
+
+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()
+{
+ if (IsVistaOrLater()) {
+ // 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 */
+double
+WinUtils::LogToPhysFactor(HMONITOR aMonitor)
+{
+ if (IsPerMonitorDPIAware()) {
+ UINT dpiX, dpiY = 96;
+ sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(),
+ MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+ return dpiY / 96.0;
+ }
+
+ return SystemScaleFactor();
+}
+
+/* static */
+int32_t
+WinUtils::LogToPhys(HMONITOR aMonitor, double aValue)
+{
+ return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor)));
+}
+
+/* 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.x + rect.width)),
+ NSToIntRound(dpiScale * (rect.y + rect.height))
+ };
+
+ return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST);
+}
+
+#ifdef ACCESSIBILITY
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif
+
+static Atomic<bool> sAPCPending;
+
+/* static */
+void
+WinUtils::SetAPCPending()
+{
+ sAPCPending = true;
+}
+#endif // ACCESSIBILITY
+
+/* static */
+bool
+WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage, UINT aOption)
+{
+#ifdef ACCESSIBILITY
+ if (NS_IsMainThread() && sAPCPending.exchange(false)) {
+ while (sNtTestAlert() != STATUS_SUCCESS) ;
+ }
+#endif
+#ifdef NS_ENABLE_TSF
+ 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;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption);
+}
+
+/* static */
+bool
+WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
+ UINT aLastMessage)
+{
+#ifdef NS_ENABLE_TSF
+ 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;
+ }
+#endif // #ifdef NS_ENABLE_TSF
+ return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
+}
+
+#if defined(ACCESSIBILITY)
+static DWORD
+GetWaitFlags()
+{
+ DWORD result = MWMO_INPUTAVAILABLE;
+ if (IsVistaOrLater() && 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 = ::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()) {
+ if (sAPCPending.exchange(false)) {
+ // Clear out any pending APCs
+ while (sNtTestAlert() != STATUS_SUCCESS) ;
+ }
+ // 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::HangMonitor::Suspend();
+ }
+ 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 */
+bool
+WinUtils::GetRegistryKey(HKEY aRoot,
+ char16ptr_t aKeyName,
+ char16ptr_t aValueName,
+ wchar_t* aBuffer,
+ DWORD aBufferLength)
+{
+ NS_PRECONDITION(aKeyName, "The key name is NULL");
+
+ HKEY key;
+ LONG result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ }
+
+ DWORD type;
+ result =
+ ::RegQueryValueExW(key, aValueName, nullptr, &type, (BYTE*) aBuffer,
+ &aBufferLength);
+ ::RegCloseKey(key);
+ if (result != ERROR_SUCCESS ||
+ (type != REG_SZ && type != REG_EXPAND_SZ)) {
+ return false;
+ }
+ if (aBuffer) {
+ aBuffer[aBufferLength / sizeof(*aBuffer) - 1] = 0;
+ }
+ return true;
+}
+
+/* static */
+bool
+WinUtils::HasRegistryKey(HKEY aRoot, char16ptr_t aKeyName)
+{
+ MOZ_ASSERT(aRoot, "aRoot must not be NULL");
+ MOZ_ASSERT(aKeyName, "aKeyName must not be NULL");
+ HKEY key;
+ LONG result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ result =
+ ::RegOpenKeyExW(aRoot, aKeyName, 0, KEY_READ | KEY_WOW64_64KEY, &key);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ }
+ ::RegCloseKey(key);
+ return true;
+}
+
+/* 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;
+}
+
+static const wchar_t*
+GetNSWindowPropName()
+{
+ static wchar_t sPropName[40] = L"";
+ if (!*sPropName) {
+ _snwprintf(sPropName, 39, L"MozillansIWidgetPtr%u",
+ ::GetCurrentProcessId());
+ sPropName[39] = '\0';
+ }
+ return sPropName;
+}
+
+/* static */
+bool
+WinUtils::SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget)
+{
+ if (!aWidget) {
+ ::RemovePropW(aWnd, GetNSWindowPropName());
+ return true;
+ }
+ return ::SetPropW(aWnd, GetNSWindowPropName(), (HANDLE)aWidget);
+}
+
+/* static */
+nsWindowBase*
+WinUtils::GetNSWindowBasePtr(HWND aWnd)
+{
+ return static_cast<nsWindowBase*>(::GetPropW(aWnd, GetNSWindowPropName()));
+}
+
+/* static */
+nsWindow*
+WinUtils::GetNSWindowPtr(HWND aWnd)
+{
+ return static_cast<nsWindow*>(::GetPropW(aWnd, GetNSWindowPropName()));
+}
+
+static BOOL CALLBACK
+AddMonitor(HMONITOR, HDC, LPRECT, LPARAM aParam)
+{
+ (*(int32_t*)aParam)++;
+ return TRUE;
+}
+
+/* static */
+int32_t
+WinUtils::GetMonitorCount()
+{
+ int32_t monitorCount = 0;
+ EnumDisplayMonitors(nullptr, nullptr, AddMonitor, (LPARAM)&monitorCount);
+ return monitorCount;
+}
+
+/* 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 = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
+ LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
+ if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
+ inputSource = (lParamExtraInfo & TABLET_INK_TOUCH) ?
+ nsIDOMMouseEvent::MOZ_SOURCE_TOUCH : nsIDOMMouseEvent::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 == 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;
+}
+
+/* static */
+HRESULT
+WinUtils::SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc,
+ REFIID riid, void **ppv)
+{
+ if (sCreateItemFromParsingName) {
+ return sCreateItemFromParsingName(pszPath, pbc, riid, ppv);
+ }
+
+ if (!sShellDll) {
+ sShellDll = ::LoadLibraryW(kShellLibraryName);
+ if (!sShellDll) {
+ return false;
+ }
+ }
+
+ sCreateItemFromParsingName = (SHCreateItemFromParsingNamePtr)
+ GetProcAddress(sShellDll, "SHCreateItemFromParsingName");
+ if (!sCreateItemFromParsingName)
+ return E_FAIL;
+
+ return sCreateItemFromParsingName(pszPath, pbc, riid, ppv);
+}
+
+/* static */
+HRESULT
+WinUtils::SHGetKnownFolderPath(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath)
+{
+ if (sGetKnownFolderPath) {
+ return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath);
+ }
+
+ if (!sShellDll) {
+ sShellDll = ::LoadLibraryW(kShellLibraryName);
+ if (!sShellDll) {
+ return false;
+ }
+ }
+
+ sGetKnownFolderPath = (SHGetKnownFolderPathPtr)
+ GetProcAddress(sShellDll, "SHGetKnownFolderPath");
+ if (!sGetKnownFolderPath)
+ return E_FAIL;
+
+ return sGetKnownFolderPath(rfid, dwFlags, hToken, ppszPath);
+}
+
+static BOOL
+WINAPI EnumFirstChild(HWND hwnd, LPARAM lParam)
+{
+ *((HWND*)lParam) = hwnd;
+ return FALSE;
+}
+
+/* static */
+void
+WinUtils::InvalidatePluginAsWorkaround(nsIWidget* aWidget,
+ const LayoutDeviceIntRect& aRect)
+{
+ aWidget->Invalidate(aRect);
+
+ // XXX - Even more evil workaround!! See bug 762948, flash's bottom
+ // level sandboxed window doesn't seem to get our invalidate. We send
+ // an invalidate to it manually. This is totally specialized for this
+ // bug, for other child window structures this will just be a more or
+ // less bogus invalidate but since that should not have any bad
+ // side-effects this will have to do for now.
+ HWND current = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ RECT windowRect;
+ RECT parentRect;
+
+ ::GetWindowRect(current, &parentRect);
+
+ HWND next = current;
+ do {
+ current = next;
+ ::EnumChildWindows(current, &EnumFirstChild, (LPARAM)&next);
+ ::GetWindowRect(next, &windowRect);
+ // This is relative to the screen, adjust it to be relative to the
+ // window we're reconfiguring.
+ windowRect.left -= parentRect.left;
+ windowRect.top -= parentRect.top;
+ } while (next != current && windowRect.top == 0 && windowRect.left == 0);
+
+ if (windowRect.top == 0 && windowRect.left == 0) {
+ RECT rect;
+ rect.left = aRect.x;
+ rect.top = aRect.y;
+ rect.right = aRect.XMost();
+ rect.bottom = aRect.YMost();
+
+ ::InvalidateRect(next, &rect, FALSE);
+ }
+}
+
+#ifdef MOZ_PLACES
+/************************************************************************
+ * Constructs as AsyncFaviconDataReady Object
+ * @param aIOThread : the thread which performs the action
+ * @param aURLShortcut : Differentiates between (false)Jumplistcache and (true)Shortcutcache
+ ************************************************************************/
+
+AsyncFaviconDataReady::AsyncFaviconDataReady(nsIURI *aNewURI,
+ nsCOMPtr<nsIThread> &aIOThread,
+ const bool aURLShortcut):
+ mNewURI(aNewURI),
+ mIOThread(aIOThread),
+ mURLShortcut(aURLShortcut)
+{
+}
+
+NS_IMETHODIMP
+myDownloadObserver::OnDownloadComplete(nsIDownloader *downloader,
+ nsIRequest *request,
+ nsISupports *ctxt,
+ 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_DATA_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->AsyncOpen2(listener);
+}
+
+NS_IMETHODIMP
+AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI,
+ uint32_t aDataLen,
+ const uint8_t *aData,
+ const nsACString &aMimeType)
+{
+ 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);
+
+ // Convert the obtained favicon data to an input stream
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ reinterpret_cast<const char*>(aData),
+ aDataLen,
+ NS_ASSIGNMENT_DEPEND);
+ 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->DecodeImageData(stream, 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) {
+ // Create a 48x48 surface and paint the icon into the central 16x16 rect.
+ size.width = 48;
+ size.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(Color(1.0f, 1.0f, 1.0f, 1.0f)));
+ dt->DrawSurface(surface,
+ Rect(16, 16, 16, 16),
+ 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, Move(data),
+ stride,
+ size.width,
+ size.height,
+ mURLShortcut);
+ 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,
+ const bool aURLShortcut) :
+ mURLShortcut(aURLShortcut),
+ mIconPath(aIconPath),
+ mBuffer(Move(aBuffer)),
+ mStride(aStride),
+ mWidth(aWidth),
+ mHeight(aHeight)
+{
+}
+
+NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run()
+{
+ NS_PRECONDITION(!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 = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "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) {
+ //NS_ConvertUTF8toUTF16 utf16path(mIconPath);
+ 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 = fopen(NS_ConvertUTF16toUTF8(mIconPath).get(), "wb");
+ if (!file) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ if (!file) {
+ return rv;
+ }
+ }
+ nsresult rv =
+ gfxUtils::EncodeSourceSurface(surface,
+ NS_LITERAL_CSTRING("image/vnd.microsoft.icon"),
+ EmptyString(),
+ gfxUtils::eBinaryEncode,
+ file);
+ fclose(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mURLShortcut) {
+ SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, 0);
+ }
+ return rv;
+}
+
+AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon()
+{
+}
+
+AsyncDeleteIconFromDisk::AsyncDeleteIconFromDisk(const nsAString &aIconPath)
+ : mIconPath(aIconPath)
+{
+}
+
+NS_IMETHODIMP AsyncDeleteIconFromDisk::Run()
+{
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(icoFile, NS_ERROR_FAILURE);
+ nsresult rv = icoFile->InitWithPath(mIconPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the cached ICO file exists
+ bool exists;
+ rv = icoFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(mIconPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(icoFile->Exists(&exists)) || !exists)
+ return NS_ERROR_FAILURE;
+
+ // We found an ICO file that exists, so we should remove it
+ icoFile->Remove(false);
+ }
+
+ return NS_OK;
+}
+
+AsyncDeleteIconFromDisk::~AsyncDeleteIconFromDisk()
+{
+}
+
+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;
+}
+
+NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run()
+{
+ // 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(FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ bool hasMore = false;
+ if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> supp;
+ if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
+ break;
+
+ nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
+ 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)
+ */
+nsresult FaviconHelper::ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString &aICOFilePath,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut)
+{
+ // 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);
+ 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);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The icoFile is filled with a path that exists, get its path
+ rv = icoFile->GetPath(aICOFilePath);
+ return rv;
+}
+
+nsresult FaviconHelper::HashURI(nsCOMPtr<nsICryptoHash> &aCryptoHash,
+ nsIURI *aUri,
+ nsACString& aUriHash)
+{
+ if (!aUri)
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoCString spec;
+ nsresult rv = aUri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aCryptoHash) {
+ aCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aCryptoHash->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCryptoHash->Update(reinterpret_cast<const uint8_t*>(spec.BeginReading()),
+ spec.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCryptoHash->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
+nsresult FaviconHelper::GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile> &aICOFile,
+ bool aURLShortcut)
+{
+ // Hash the input URI and replace any / with _
+ nsAutoCString inputURIHash;
+ nsCOMPtr<nsICryptoHash> cryptoHash;
+ nsresult rv = HashURI(cryptoHash, 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,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut)
+{
+#ifdef MOZ_PLACES
+ // Obtain the favicon service and get the favicon for the specified page
+ nsCOMPtr<mozIAsyncFavicons> 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);
+
+ favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback);
+#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 */
+bool
+WinUtils::GetShellItemPath(IShellItem* aItem,
+ nsString& aResultString)
+{
+ NS_ENSURE_TRUE(aItem, false);
+ LPWSTR str = nullptr;
+ if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str)))
+ return false;
+ aResultString.Assign(str);
+ CoTaskMemFree(str);
+ return !aResultString.IsEmpty();
+}
+
+/* static */
+nsIntRegion
+WinUtils::ConvertHRGNToRegion(HRGN aRgn)
+{
+ NS_ASSERTION(aRgn, "Don't pass NULL region here");
+
+ nsIntRegion 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;
+}
+
+nsIntRect
+WinUtils::ToIntRect(const RECT& aRect)
+{
+ return nsIntRect(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(IMEState::Enabled aIMEState)
+{
+ return (aIMEState == IMEState::ENABLED ||
+ aIMEState == IMEState::PLUGIN);
+}
+
+/* static */
+void
+WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers)
+{
+ 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]));
+ }
+ }
+}
+
+/* static */
+bool
+WinUtils::ShouldHideScrollbars()
+{
+ return false;
+}
+
+// This is in use here and in dom/events/TouchEvent.cpp
+/* static */
+uint32_t
+WinUtils::IsTouchDeviceSupportPresent()
+{
+ int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER);
+ return (touchCapabilities & NID_READY) &&
+ (touchCapabilities & (NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH));
+}
+
+/* static */
+uint32_t
+WinUtils::GetMaxTouchPoints()
+{
+ if (IsWin7OrLater() && IsTouchDeviceSupportPresent()) {
+ return GetSystemMetrics(SM_MAXIMUMTOUCHES);
+ }
+ return 0;
+}
+
+typedef DWORD (WINAPI * GetFinalPathNameByHandlePtr)(HANDLE hFile,
+ LPTSTR lpszFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+
+/* static */
+bool
+WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath)
+{
+ // Users folder was introduced with Vista.
+ if (!IsVistaOrLater()) {
+ return true;
+ }
+
+ 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) {
+ return false;
+ }
+
+ // GetFinalPathNameByHandleW is a Vista and later API. Since ESR builds with
+ // XP support still, we need to load the function manually.
+ GetFinalPathNameByHandlePtr getFinalPathNameFnPtr = nullptr;
+ HMODULE kernel32Dll = ::GetModuleHandleW(L"Kernel32");
+ if (kernel32Dll) {
+ getFinalPathNameFnPtr = (GetFinalPathNameByHandlePtr)
+ ::GetProcAddress(kernel32Dll, "GetFinalPathNameByHandleW");
+ }
+
+ if (!getFinalPathNameFnPtr) {
+ return false;
+ }
+
+ DWORD pathLen = getFinalPathNameFnPtr(
+ handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
+ if (pathLen == 0 || pathLen >= MAX_PATH) {
+ 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);
+ }
+
+ return true;
+}
+
+/* static */
+bool
+WinUtils::SanitizePath(const wchar_t* aInputPath, nsAString& aOutput)
+{
+ aOutput.Truncate();
+ wchar_t buffer[MAX_PATH + 1] = {0};
+ if (!PathCanonicalizeW(buffer, aInputPath)) {
+ return false;
+ }
+ wchar_t longBuffer[MAX_PATH + 1] = {0};
+ DWORD longResult = GetLongPathNameW(buffer, longBuffer, MAX_PATH);
+ if (longResult == 0 || longResult > MAX_PATH - 1) {
+ return false;
+ }
+ aOutput.SetLength(MAX_PATH + 1);
+ wchar_t* output = reinterpret_cast<wchar_t*>(aOutput.BeginWriting());
+ if (!PathUnExpandEnvStringsW(longBuffer, output, MAX_PATH)) {
+ return false;
+ }
+ // Truncate to correct length
+ aOutput.Truncate(wcslen(char16ptr_t(aOutput.BeginReading())));
+ MOZ_ASSERT(aOutput.Length() <= MAX_PATH);
+ return true;
+}
+
+/**
+ * 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 GetAppInitDLLs for an example of its usage.
+ */
+/* static */
+void
+WinUtils::GetWhitelistedPaths(
+ nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput)
+{
+ aOutput.Clear();
+ aOutput.AppendElement(mozilla::MakePair(
+ nsString(NS_LITERAL_STRING("%ProgramFiles%")),
+ nsDependentString()));
+ // When no substitution is required, set the void flag
+ aOutput.LastElement().second().SetIsVoid(true);
+ wchar_t tmpPath[MAX_PATH + 1] = {0};
+ 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;
+ if (SanitizePath(tmpPath, cleanTmpPath)) {
+ aOutput.AppendElement(mozilla::MakePair(nsString(cleanTmpPath),
+ nsDependentString(L"%TEMP%")));
+ }
+ }
+}
+
+/**
+ * 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;
+ if (IsVistaOrLater()) {
+ 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;
+ }
+ nsTArray<mozilla::Pair<nsString,nsDependentString>> whitelistedPaths;
+ GetWhitelistedPaths(whitelistedPaths);
+ // 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;
+ // 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 (SanitizePath(token, cleanPath)) {
+ bool needsStrip = true;
+ for (uint32_t i = 0; i < whitelistedPaths.Length(); ++i) {
+ const nsString& testPath = whitelistedPaths[i].first();
+ const nsDependentString& substitution = whitelistedPaths[i].second();
+ if (StringBeginsWith(cleanPath, testPath,
+ nsCaseInsensitiveStringComparator())) {
+ if (!substitution.IsVoid()) {
+ cleanPath.Replace(0, testPath.Length(), substitution);
+ }
+ // Whitelisted paths may be used as-is provided that they have been
+ // previously sanitized.
+ needsStrip = false;
+ break;
+ }
+ }
+ if (!aOutput.IsEmpty()) {
+ aOutput += L";";
+ }
+ // For non-whitelisted paths, we strip the path component and just leave
+ // the filename.
+ if (needsStrip) {
+ // nsLocalFile doesn't like non-absolute paths. Since these paths might
+ // contain environment variables instead of roots, we can't use it.
+ wchar_t tmpPath[MAX_PATH + 1] = {0};
+ wcsncpy(tmpPath, cleanPath.get(), cleanPath.Length());
+ PathStripPath(tmpPath);
+ aOutput += tmpPath;
+ } else {
+ aOutput += cleanPath;
+ }
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h
new file mode 100644
index 0000000000..37469ce079
--- /dev/null
+++ b/widget/windows/WinUtils.h
@@ -0,0 +1,648 @@
+/* -*- 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>
+
+// 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/UniquePtr.h"
+
+/**
+ * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and
+ * implementing AddRef() and Release() of IUnknown interface.
+ * This depends on xpcom/glue/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;
+class nsWindowBase;
+struct KeyPair;
+
+#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && !defined(DPI_AWARENESS_CONTEXT_UNAWARE)
+
+DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
+
+typedef enum DPI_AWARENESS {
+ DPI_AWARENESS_INVALID = -1,
+ DPI_AWARENESS_UNAWARE = 0,
+ DPI_AWARENESS_SYSTEM_AWARE = 1,
+ DPI_AWARENESS_PER_MONITOR_AWARE = 2
+} DPI_AWARENESS;
+
+#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
+#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
+#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
+
+#define DPI_AWARENESS_CONTEXT_DECLARED
+#endif // (DPI_AWARENESS_CONTEXT_DECLARED)
+
+typedef DPI_AWARENESS_CONTEXT(WINAPI * SetThreadDpiAwarenessContextProc)(DPI_AWARENESS_CONTEXT);
+typedef BOOL(WINAPI * EnableNonClientDpiScalingProc)(HWND);
+
+namespace mozilla {
+namespace widget {
+
+// Windows message debugging data
+typedef struct {
+ const char * mStr;
+ UINT mId;
+} EventMsgInfo;
+extern EventMsgInfo gAllEvents[];
+
+// 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;
+
+public:
+ class AutoSystemDpiAware
+ {
+ public:
+ AutoSystemDpiAware()
+ {
+ 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();
+ /**
+ * 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) {
+ // if there's an ancestor window, we want to share its DPI setting
+ HWND ancestor = ::GetAncestor(aWnd, GA_ROOTOWNER);
+ return LogToPhysFactor(::MonitorFromWindow(ancestor ? ancestor : aWnd,
+ MONITOR_DEFAULTTOPRIMARY));
+ }
+ 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);
+
+ /**
+ * 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);
+
+ /**
+ * Gets the value of a string-typed registry value.
+ *
+ * @param aRoot The registry root to search in.
+ * @param aKeyName The name of the registry key to open.
+ * @param aValueName The name of the registry value in the specified key whose
+ * value is to be retrieved. Can be null, to retrieve the key's unnamed/
+ * default value.
+ * @param aBuffer The buffer into which to store the string value. Can be
+ * null, in which case the return value indicates just whether the value
+ * exists.
+ * @param aBufferLength The size of aBuffer, in bytes.
+ * @return Whether the value exists and is a string.
+ */
+ static bool GetRegistryKey(HKEY aRoot,
+ char16ptr_t aKeyName,
+ char16ptr_t aValueName,
+ wchar_t* aBuffer,
+ DWORD aBufferLength);
+
+ /**
+ * Checks whether the registry key exists in either 32bit or 64bit branch on
+ * the environment.
+ *
+ * @param aRoot The registry root of aName.
+ * @param aKeyName The name of the registry key to check.
+ * @return TRUE if it exists and is readable. Otherwise, FALSE.
+ */
+ static bool HasRegistryKey(HKEY aRoot,
+ char16ptr_t aKeyName);
+
+ /**
+ * 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);
+
+ /**
+ * SetNSWindowBasePtr() associates an nsWindowBase to aWnd. If aWidget is
+ * nullptr, it dissociate any nsBaseWidget pointer from aWnd.
+ * GetNSWindowBasePtr() returns an nsWindowBase pointer which was associated by
+ * SetNSWindowBasePtr().
+ * GetNSWindowPtr() is a legacy api for win32 nsWindow and should be avoided
+ * outside of nsWindow src.
+ */
+ static bool SetNSWindowBasePtr(HWND aWnd, nsWindowBase* aWidget);
+ static nsWindowBase* GetNSWindowBasePtr(HWND aWnd);
+ static nsWindow* GetNSWindowPtr(HWND aWnd);
+
+ /**
+ * GetMonitorCount() returns count of monitors on the environment.
+ */
+ static int32_t GetMonitorCount();
+
+ /**
+ * 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 nsIDOMMouseEvent::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);
+
+ /**
+ * SHCreateItemFromParsingName() calls native SHCreateItemFromParsingName()
+ * API which is available on Vista and up.
+ */
+ static HRESULT SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc,
+ REFIID riid, void **ppv);
+
+ /**
+ * SHGetKnownFolderPath() calls native SHGetKnownFolderPath()
+ * API which is available on Vista and up.
+ */
+ static HRESULT SHGetKnownFolderPath(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath);
+ /**
+ * GetShellItemPath return the file or directory path of a shell item.
+ * Internally calls IShellItem's GetDisplayName.
+ *
+ * aItem the shell item containing the path.
+ * aResultString the resulting string path.
+ * returns true if a path was retreived.
+ */
+ static bool GetShellItemPath(IShellItem* aItem,
+ nsString& aResultString);
+
+ /**
+ * ConvertHRGNToRegion converts a Windows HRGN to an nsIntRegion.
+ *
+ * aRgn the HRGN to convert.
+ * returns the nsIntRegion.
+ */
+ static nsIntRegion ConvertHRGNToRegion(HRGN aRgn);
+
+ /**
+ * ToIntRect converts a Windows RECT to a nsIntRect.
+ *
+ * aRect the RECT to convert.
+ * returns the nsIntRect.
+ */
+ static nsIntRect ToIntRect(const RECT& aRect);
+
+ /**
+ * Helper used in invalidating flash plugin windows owned
+ * by low rights flash containers.
+ */
+ static void InvalidatePluginAsWorkaround(nsIWidget* aWidget,
+ const LayoutDeviceIntRect& aRect);
+
+ /**
+ * Returns true if the context or IME state is enabled. Otherwise, false.
+ */
+ static bool IsIMEEnabled(const InputContext& aInputContext);
+ static bool IsIMEEnabled(IMEState::Enabled aIMEState);
+
+ /**
+ * Returns modifier key array for aModifiers. This is for
+ * nsIWidget::SynthethizeNative*Event().
+ */
+ static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray,
+ uint32_t aModifiers);
+
+ /**
+ * 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();
+
+ /**
+ * 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);
+
+ /**
+ * dwmapi.dll function typedefs and declarations
+ */
+ typedef HRESULT (WINAPI*DwmExtendFrameIntoClientAreaProc)(HWND hWnd, const MARGINS *pMarInset);
+ typedef HRESULT (WINAPI*DwmIsCompositionEnabledProc)(BOOL *pfEnabled);
+ typedef HRESULT (WINAPI*DwmSetIconicThumbnailProc)(HWND hWnd, HBITMAP hBitmap, DWORD dwSITFlags);
+ typedef HRESULT (WINAPI*DwmSetIconicLivePreviewBitmapProc)(HWND hWnd, HBITMAP hBitmap, POINT *pptClient, DWORD dwSITFlags);
+ typedef HRESULT (WINAPI*DwmGetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
+ typedef HRESULT (WINAPI*DwmSetWindowAttributeProc)(HWND hWnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
+ typedef HRESULT (WINAPI*DwmInvalidateIconicBitmapsProc)(HWND hWnd);
+ typedef HRESULT (WINAPI*DwmDefWindowProcProc)(HWND hWnd, UINT msg, LPARAM lParam, WPARAM wParam, LRESULT *aRetValue);
+ typedef HRESULT (WINAPI*DwmGetCompositionTimingInfoProc)(HWND hWnd, DWM_TIMING_INFO *info);
+ typedef HRESULT (WINAPI*DwmFlushProc)(void);
+
+ static DwmExtendFrameIntoClientAreaProc dwmExtendFrameIntoClientAreaPtr;
+ static DwmIsCompositionEnabledProc dwmIsCompositionEnabledPtr;
+ static DwmSetIconicThumbnailProc dwmSetIconicThumbnailPtr;
+ static DwmSetIconicLivePreviewBitmapProc dwmSetIconicLivePreviewBitmapPtr;
+ static DwmGetWindowAttributeProc dwmGetWindowAttributePtr;
+ static DwmSetWindowAttributeProc dwmSetWindowAttributePtr;
+ static DwmInvalidateIconicBitmapsProc dwmInvalidateIconicBitmapsPtr;
+ static DwmDefWindowProcProc dwmDwmDefWindowProcPtr;
+ static DwmGetCompositionTimingInfoProc dwmGetCompositionTimingInfoPtr;
+ static DwmFlushProc dwmFlushProcPtr;
+
+ static void Initialize();
+
+ static bool ShouldHideScrollbars();
+
+ /**
+ * This function normalizes the input path, converts short filenames to long
+ * filenames, and substitutes environment variables for system paths.
+ * The resulting output string length is guaranteed to be <= MAX_PATH.
+ */
+ static bool SanitizePath(const wchar_t* aInputPath, nsAString& aOutput);
+
+ /**
+ * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs
+ */
+ static bool GetAppInitDLLs(nsAString& aOutput);
+
+#ifdef ACCESSIBILITY
+ static void SetAPCPending();
+#endif
+
+private:
+ typedef HRESULT (WINAPI * SHCreateItemFromParsingNamePtr)(PCWSTR pszPath,
+ IBindCtx *pbc,
+ REFIID riid,
+ void **ppv);
+ static SHCreateItemFromParsingNamePtr sCreateItemFromParsingName;
+ typedef HRESULT (WINAPI * SHGetKnownFolderPathPtr)(REFKNOWNFOLDERID rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR *ppszPath);
+ static SHGetKnownFolderPathPtr sGetKnownFolderPath;
+
+ static void GetWhitelistedPaths(
+ nsTArray<mozilla::Pair<nsString,nsDependentString>>& aOutput);
+};
+
+#ifdef MOZ_PLACES
+class AsyncFaviconDataReady final : public nsIFaviconDataCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONDATACALLBACK
+
+ AsyncFaviconDataReady(nsIURI *aNewURI,
+ nsCOMPtr<nsIThread> &aIOThread,
+ const bool aURLShortcut);
+ nsresult OnFaviconDataNotAvailable(void);
+private:
+ ~AsyncFaviconDataReady() {}
+
+ nsCOMPtr<nsIURI> mNewURI;
+ nsCOMPtr<nsIThread> mIOThread;
+ const bool mURLShortcut;
+};
+#endif
+
+/**
+ * Asynchronously tries add the list to the build
+ */
+class AsyncEncodeAndWriteIcon : public nsIRunnable
+{
+public:
+ const bool mURLShortcut;
+ 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,
+ const bool aURLShortcut);
+
+private:
+ virtual ~AsyncEncodeAndWriteIcon();
+
+ nsAutoString mIconPath;
+ UniquePtr<uint8_t[]> mBuffer;
+ uint32_t mStride;
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+
+class AsyncDeleteIconFromDisk : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ AsyncDeleteIconFromDisk(const nsAString &aIconPath);
+
+private:
+ virtual ~AsyncDeleteIconFromDisk();
+
+ nsAutoString mIconPath;
+};
+
+class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false);
+
+private:
+ virtual ~AsyncDeleteAllFaviconsFromDisk();
+
+ int32_t mIcoNoDeleteSeconds;
+ bool mIgnoreRecent;
+};
+
+class FaviconHelper
+{
+public:
+ static const char kJumpListCacheDir[];
+ static const char kShortcutCacheDir[];
+ static nsresult ObtainCachedIconFile(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsString &aICOFilePath,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut);
+
+ static nsresult HashURI(nsCOMPtr<nsICryptoHash> &aCryptoHash,
+ nsIURI *aUri,
+ nsACString& aUriHash);
+
+ static nsresult GetOutputIconPath(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile> &aICOFile,
+ bool aURLShortcut);
+
+ static nsresult
+ CacheIconFileFromFaviconURIAsync(nsCOMPtr<nsIURI> aFaviconPageURI,
+ nsCOMPtr<nsIFile> aICOFile,
+ nsCOMPtr<nsIThread> &aIOThread,
+ bool aURLShortcut);
+
+ static int32_t GetICOCacheSecondsTimeout();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_WinUtils_h__
diff --git a/widget/windows/WindowHook.cpp b/widget/windows/WindowHook.cpp
new file mode 100644
index 0000000000..b2a3167d31
--- /dev/null
+++ b/widget/windows/WindowHook.cpp
@@ -0,0 +1,129 @@
+/* -*- 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)))
+ ? 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..69df4eb2fe
--- /dev/null
+++ b/widget/windows/WindowHook.h
@@ -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/. */
+
+#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);
+ }
+ 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;
+};
+
+}
+}
+
+#endif // __mozilla_WindowHook_h__
diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
new file mode 100644
index 0000000000..2235118597
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 <winsdkver.h>
+#include "mozwrlbase.h"
+#include "nsServiceManagerUtils.h"
+
+#include "WindowsUIUtils.h"
+
+#include "nsIObserverService.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsIXULWindow.h"
+#include "mozilla/Services.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsString.h"
+#include "nsIWidget.h"
+
+/* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it until it's fixed. */
+#ifndef __MINGW32__
+
+#include <windows.ui.viewmanagement.h>
+
+#pragma comment(lib, "runtimeobject.lib")
+
+using namespace mozilla;
+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;
+
+/* All of this is win10 stuff and we're compiling against win81 headers
+ * for now, so we may need to do some legwork: */
+#if WINVER_MAXVER < 0x0A00
+namespace ABI {
+ namespace Windows {
+ namespace UI {
+ namespace ViewManagement {
+ enum UserInteractionMode {
+ UserInteractionMode_Mouse = 0,
+ UserInteractionMode_Touch = 1
+ };
+ }
+ }
+ }
+}
+
+#endif
+
+#ifndef RuntimeClass_Windows_UI_ViewManagement_UIViewSettings
+#define RuntimeClass_Windows_UI_ViewManagement_UIViewSettings L"Windows.UI.ViewManagement.UIViewSettings"
+#endif
+
+#if WINVER_MAXVER < 0x0A00
+namespace ABI {
+ namespace Windows {
+ namespace UI {
+ namespace ViewManagement {
+ interface IUIViewSettings;
+ MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26")
+ IUIViewSettings : public IInspectable
+ {
+ public:
+ virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(UserInteractionMode *value) = 0;
+ };
+
+ extern const __declspec(selectany) IID & IID_IUIViewSettings = __uuidof(IUIViewSettings);
+ }
+ }
+ }
+}
+#endif
+
+#ifndef IUIViewSettingsInterop
+
+typedef interface IUIViewSettingsInterop IUIViewSettingsInterop;
+
+MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
+IUIViewSettingsInterop : public IInspectable
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid, void **ppv) = 0;
+};
+#endif
+
+#endif
+
+WindowsUIUtils::WindowsUIUtils() :
+ mInTabletMode(eTabletModeUnknown)
+{
+}
+
+WindowsUIUtils::~WindowsUIUtils()
+{
+}
+
+/*
+ * Implement the nsISupports methods...
+ */
+NS_IMPL_ISUPPORTS(WindowsUIUtils,
+ nsIWindowsUIUtils)
+
+NS_IMETHODIMP
+WindowsUIUtils::GetInTabletMode(bool* aResult)
+{
+ if (mInTabletMode == eTabletModeUnknown) {
+ UpdateTabletModeState();
+ }
+ *aResult = mInTabletMode == eTabletModeOn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WindowsUIUtils::UpdateTabletModeState()
+{
+#ifndef __MINGW32__
+ if (!IsWin10OrLater()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ nsCOMPtr<nsIXULWindow> 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;
+
+ HWND winPtr = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ ComPtr<IUIViewSettingsInterop> uiViewSettingsInterop;
+
+ HRESULT hr = GetActivationFactory(
+ HStringReference(RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(),
+ &uiViewSettingsInterop);
+ if (SUCCEEDED(hr)) {
+ ComPtr<IUIViewSettings> uiViewSettings;
+ hr = uiViewSettingsInterop->GetForWindow(winPtr, IID_PPV_ARGS(&uiViewSettings));
+ if (SUCCEEDED(hr)) {
+ UserInteractionMode mode;
+ hr = uiViewSettings->get_UserInteractionMode(&mode);
+ if (SUCCEEDED(hr)) {
+ TabletModeState oldTabletModeState = mInTabletMode;
+ mInTabletMode = (mode == UserInteractionMode_Touch) ? eTabletModeOn : eTabletModeOff;
+ if (mInTabletMode != oldTabletModeState) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_NAMED_LITERAL_STRING(tabletMode, "tablet-mode");
+ NS_NAMED_LITERAL_STRING(normalMode, "normal-mode");
+ observerService->NotifyObservers(nullptr, "tablet-mode-change",
+ ((mInTabletMode == eTabletModeOn) ? tabletMode.get() : normalMode.get()));
+ }
+ }
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h
new file mode 100644
index 0000000000..a33c93a94f
--- /dev/null
+++ b/widget/windows/WindowsUIUtils.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 mozilla_widget_WindowsUIUtils_h__
+#define mozilla_widget_WindowsUIUtils_h__
+
+#include "nsIWindowsUIUtils.h"
+
+enum TabletModeState {
+ eTabletModeUnknown = 0,
+ eTabletModeOff,
+ eTabletModeOn
+};
+
+class WindowsUIUtils final : public nsIWindowsUIUtils {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWSUIUTILS
+
+ WindowsUIUtils();
+protected:
+ ~WindowsUIUtils();
+
+ TabletModeState mInTabletMode;
+};
+
+#endif // mozilla_widget_WindowsUIUtils_h__
diff --git a/widget/windows/moz.build b/widget/windows/moz.build
new file mode 100644
index 0000000000..d4f089eeac
--- /dev/null
+++ b/widget/windows/moz.build
@@ -0,0 +1,123 @@
+# -*- 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/.
+
+TEST_DIRS += ['tests']
+
+EXPORTS += [
+ 'nsdefs.h',
+ 'WindowHook.h',
+ 'WinUtils.h',
+]
+
+EXPORTS.mozilla.widget += [
+ 'AudioSession.h',
+ 'CompositorWidgetChild.h',
+ 'CompositorWidgetParent.h',
+ 'InProcessWinCompositorWidget.h',
+ 'WinCompositorWidget.h',
+ 'WinMessages.h',
+ 'WinModifierKeyState.h',
+ 'WinNativeEventData.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AudioSession.cpp',
+ 'CompositorWidgetChild.cpp',
+ 'CompositorWidgetParent.cpp',
+ 'GfxInfo.cpp',
+ 'IEnumFE.cpp',
+ 'IMMHandler.cpp',
+ 'InkCollector.cpp',
+ 'InProcessWinCompositorWidget.cpp',
+ 'JumpListItem.cpp',
+ 'KeyboardLayout.cpp',
+ 'nsAppShell.cpp',
+ 'nsClipboard.cpp',
+ 'nsColorPicker.cpp',
+ 'nsDataObj.cpp',
+ 'nsDataObjCollection.cpp',
+ 'nsDragService.cpp',
+ 'nsIdleServiceWin.cpp',
+ 'nsImageClipboard.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsNativeDragSource.cpp',
+ 'nsNativeDragTarget.cpp',
+ 'nsNativeThemeWin.cpp',
+ 'nsScreenManagerWin.cpp',
+ 'nsScreenWin.cpp',
+ 'nsSound.cpp',
+ 'nsToolkit.cpp',
+ 'nsUXThemeData.cpp',
+ 'nsWindow.cpp',
+ 'nsWindowBase.cpp',
+ 'nsWindowDbg.cpp',
+ 'nsWindowGfx.cpp',
+ 'nsWinGesture.cpp',
+ 'TaskbarPreview.cpp',
+ 'TaskbarPreviewButton.cpp',
+ 'TaskbarTabPreview.cpp',
+ 'TaskbarWindowPreview.cpp',
+ 'WidgetTraceEvent.cpp',
+ 'WindowHook.cpp',
+ 'WinIMEHandler.cpp',
+ 'WinTaskbar.cpp',
+ 'WinTextEventDispatcherListener.cpp',
+ 'WinUtils.cpp',
+]
+
+# The following files cannot be built in unified mode because of name clashes.
+SOURCES += [
+ 'JumpListBuilder.cpp',
+ 'nsBidiKeyboard.cpp',
+ 'nsFilePicker.cpp',
+ 'nsWidgetFactory.cpp',
+ 'WinCompositorWidget.cpp',
+ 'WindowsUIUtils.cpp',
+ 'WinMouseScrollHandler.cpp',
+]
+
+if CONFIG['MOZ_CRASHREPORTER']:
+ UNIFIED_SOURCES += [
+ 'LSPAnnotator.cpp',
+ ]
+
+if CONFIG['NS_PRINTING']:
+ UNIFIED_SOURCES += [
+ 'nsDeviceContextSpecWin.cpp',
+ 'nsPrintOptionsWin.cpp',
+ 'nsPrintSettingsWin.cpp',
+ ]
+
+if CONFIG['NS_ENABLE_TSF']:
+ SOURCES += [
+ 'TSFTextStore.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/layout/generic',
+ '/layout/xul',
+ '/toolkit/xre',
+ '/widget',
+ '/xpcom/base',
+]
+
+DEFINES['MOZ_UNICODE'] = True
+
+for var in ('MOZ_ENABLE_D3D10_LAYER'):
+ if CONFIG[var]:
+ DEFINES[var] = True
+
+RESFILE = 'widget.res'
+
+CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+OS_LIBS += [
+ 'rpcrt4',
+]
diff --git a/widget/windows/mozwrlbase.h b/widget/windows/mozwrlbase.h
new file mode 100644
index 0000000000..d82be8f043
--- /dev/null
+++ b/widget/windows/mozwrlbase.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#pragma once
+
+/*
+ * Includes <wrl.h> and it's children. Defines imports needed by
+ * corewrappers.h in the case where windows.h has already been
+ * included w/WINVER < 0x600. Also ups WINVER/_WIN32_WINNT prior
+ * to including wrl.h. Mozilla's build currently has WINVER set to
+ * 0x502 for XP support.
+ */
+
+#if _WIN32_WINNT < 0x600
+
+#include <windows.h>
+
+VOID
+WINAPI
+ReleaseSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+ReleaseSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOL
+WINAPI
+InitializeCriticalSectionEx(
+ _Out_ LPCRITICAL_SECTION lpCriticalSection,
+ _In_ DWORD dwSpinCount,
+ _In_ DWORD Flags
+ );
+
+VOID
+WINAPI
+InitializeSRWLock(
+ _Out_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+AcquireSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOLEAN
+WINAPI
+TryAcquireSRWLockExclusive(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+BOOLEAN
+WINAPI
+TryAcquireSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+VOID
+WINAPI
+AcquireSRWLockShared(
+ _Inout_ PSRWLOCK SRWLock
+ );
+
+#undef WINVER
+#undef _WIN32_WINNT
+#define WINVER 0x600
+#define _WIN32_WINNT 0x600
+
+#endif // _WIN32_WINNT < 0x600
+
+#include <wrl.h>
diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp
new file mode 100644
index 0000000000..c8820e7d17
--- /dev/null
+++ b/widget/windows/nsAppShell.cpp
@@ -0,0 +1,436 @@
+/* -*- 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/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 "nsString.h"
+#include "WinIMEHandler.h"
+#include "mozilla/widget/AudioSession.h"
+#include "mozilla/HangMonitor.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+#include "mozilla/StaticPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "GeckoProfiler.h"
+#include "nsComponentManagerUtils.h"
+#include "nsITimer.h"
+
+// These are two messages that the code in winspool.drv on Windows 7 explicitly
+// waits for while it is pumping other Windows messages, during display of the
+// Printer Properties dialog.
+#define MOZ_WM_PRINTER_PROPERTIES_COMPLETION 0x5b7a
+#define MOZ_WM_PRINTER_PROPERTIES_FAILURE 0x5b7f
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define WAKE_LOCK_LOG(...) MOZ_LOG(GetWinWakeLockLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
+PRLogModuleInfo* GetWinWakeLockLog() {
+ static PRLogModuleInfo* log = nullptr;
+ if (!log) {
+ log = PR_NewLogModule("WinWakeLock");
+ }
+ return log;
+}
+
+// A wake lock listener that disables screen saver when requested by
+// Gecko. For example when we're playing video in a foreground tab we
+// don't want the screen saver to turn on.
+class WinWakeLockListener final : public nsIDOMMozWakeLockListener
+ , public nsITimerCallback {
+public:
+ NS_DECL_ISUPPORTS;
+
+ NS_IMETHOD Notify(nsITimer *timer) override {
+ WAKE_LOCK_LOG("WinWakeLock: periodic timer fired");
+ ResetScreenSaverTimeout();
+ return NS_OK;
+ }
+private:
+ ~WinWakeLockListener() {}
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) {
+ if (!aTopic.EqualsASCII("screen")) {
+ return NS_OK;
+ }
+ // Note the wake lock code ensures that we're not sent duplicate
+ // "locked-foreground" notifications when multipe wake locks are held.
+ if (aState.EqualsASCII("locked-foreground")) {
+ WAKE_LOCK_LOG("WinWakeLock: Blocking screen saver");
+ // We block the screen saver by periodically resetting the screen
+ // saver timeout.
+ StartTimer();
+ // Prevent the display turning off. On Win7 and later this also
+ // blocks the screen saver, but we need the timer started above
+ // to block on Win XP and Vista.
+ SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS);
+ } else {
+ WAKE_LOCK_LOG("WinWakeLock: Unblocking screen saver");
+ // Re-enable screen saver.
+ StopTimer();
+ // Unblock display turning off.
+ SetThreadExecutionState(ES_CONTINUOUS);
+ }
+ return NS_OK;
+ }
+
+ void StartTimer() {
+ ResetScreenSaverTimeout();
+ MOZ_ASSERT(!mTimer);
+ if (mTimer) {
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create screen saver timeout reset timer");
+ return;
+ }
+ // The minimum screensaver timeout that can be specified with Windows' UI
+ // is 60 seconds. We set a timer to re-jig the screen saver 10 seconds
+ // before we expect the timer to run out, but always at least in 1 second
+ // intervals. We reset the timer at a max of 50 seconds, so that if the
+ // user changes the timeout using the UI, we won't be caught out.
+ int32_t timeout = std::max(std::min(50, (int32_t)mScreenSaverTimeout - 10), 1);
+ uint32_t timeoutMs = (uint32_t)timeout * 1000;
+ WAKE_LOCK_LOG("WinWakeLock: Setting periodic timer for %d ms", timeoutMs);
+ rv = timer->InitWithCallback(this,
+ timeoutMs,
+ nsITimer::TYPE_REPEATING_SLACK);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to initialize screen saver timeout reset timer");
+ return;
+ }
+
+ mTimer = timer.forget();
+ }
+
+ void StopTimer() {
+ WAKE_LOCK_LOG("WinWakeLock: StopTimer()");
+ if (!mTimer) {
+ return;
+ }
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ // Resets the operating system's timeout for when to disable the screen.
+ // Called periodically to keep the screensaver off.
+ void ResetScreenSaverTimeout() {
+ if (SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &mScreenSaverTimeout, 0)) {
+ SystemParametersInfo(SPI_SETSCREENSAVETIMEOUT, mScreenSaverTimeout, NULL, 0);
+ }
+ WAKE_LOCK_LOG("WinWakeLock: ResetScreenSaverTimeout() mScreenSaverTimeout=%d", mScreenSaverTimeout);
+ }
+
+ UINT mScreenSaverTimeout = 60;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener, nsITimerCallback)
+StaticRefPtr<WinWakeLockListener> 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;
+ }
+}
+
+namespace mozilla {
+namespace widget {
+// Native event callback message.
+UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID");
+} }
+
+const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated";
+UINT sTaskbarButtonCreatedMsg;
+
+/* static */
+UINT nsAppShell::GetTaskbarButtonCreatedMessage() {
+ return sTaskbarButtonCreatedMsg;
+}
+
+namespace mozilla {
+namespace crashreporter {
+void LSPAnnotate();
+} // namespace crashreporter
+} // namespace mozilla
+
+using mozilla::crashreporter::LSPAnnotate;
+
+//-------------------------------------------------------------------------
+
+/*static*/ LRESULT CALLBACK
+nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg == sAppShellGeckoMsgId) {
+ nsAppShell *as = reinterpret_cast<nsAppShell *>(lParam);
+ as->NativeEventCallback();
+ NS_RELEASE(as);
+ return TRUE;
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+nsAppShell::~nsAppShell()
+{
+ 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);
+ }
+}
+
+nsresult
+nsAppShell::Init()
+{
+#ifdef MOZ_CRASHREPORTER
+ LSPAnnotate();
+#endif
+
+ mLastNativeEventScheduled = TimeStamp::NowLoRes();
+
+ mozilla::ipc::windows::InitUIThread();
+
+ sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
+ NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message");
+
+ WNDCLASSW wc;
+ HINSTANCE module = GetModuleHandle(nullptr);
+
+ const wchar_t *const kWindowClass = L"nsAppShell:EventWindowClass";
+ if (!GetClassInfoW(module, kWindowClass, &wc)) {
+ 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;
+ RegisterClassW(&wc);
+ }
+
+ mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow",
+ 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr);
+ NS_ENSURE_STATE(mEventWnd);
+
+ return nsBaseAppShell::Init();
+}
+
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ // Content processes initialize audio later through PContent using audio
+ // tray id information pulled from the browser process AudioSession. This
+ // way the two share a single volume control.
+ // Note StopAudioSession() is called from nsAppRunner.cpp after xpcom is torn
+ // down to insure the browser shuts down after child processes.
+ if (XRE_IsParentProcess()) {
+ 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.
+ AddScreenWakeLockListener();
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ RemoveScreenWakeLockListener();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ return nsBaseAppShell::Exit();
+}
+
+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(nullptr);
+ mNativeCallbackPending = false;
+ } else {
+ mNativeCallbackPending = true;
+ }
+}
+
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ // Post a message to the hidden message window
+ NS_ADDREF_THIS(); // will be released when the event is processed
+ {
+ 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;
+ bool uiMessage = false;
+
+ // 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");
+ uiMessage = gotMessage;
+ }
+
+ if (!gotMessage) {
+ gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
+ uiMessage =
+ (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) ||
+ (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) ||
+ (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST);
+ }
+
+ 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::HangMonitor::NotifyActivity(
+ uiMessage ? mozilla::HangMonitor::kUIActivity :
+ mozilla::HangMonitor::kActivityNoUIAVail);
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
+ IMEHandler::ProcessRawKeyMessage(msg)) {
+ continue; // the message is consumed.
+ }
+
+ // Store Printer Properties messages for reposting, because 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.
+ if (msg.message == MOZ_WM_PRINTER_PROPERTIES_COMPLETION ||
+ msg.message == MOZ_WM_PRINTER_PROPERTIES_FAILURE) {
+ mMsgsToRepost.push_back(msg);
+ continue;
+ }
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ } else if (mayWait) {
+ // Block and wait for any posted application message
+ mozilla::HangMonitor::Suspend();
+ {
+ GeckoProfilerSleepRAII profiler_sleep;
+ 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..199c300095
--- /dev/null
+++ b/widget/windows/nsAppShell.h
@@ -0,0 +1,59 @@
+/* -*- 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 : 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();
+ NS_IMETHOD Exit();
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool mayWait);
+ virtual ~nsAppShell();
+
+ static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM);
+
+protected:
+ HWND mEventWnd;
+ bool mNativeCallbackPending;
+
+ Mutex mLastNativeEventScheduledMutex;
+ TimeStamp mLastNativeEventScheduled;
+ std::vector<MSG> mMsgsToRepost;
+};
+
+#endif // nsAppShell_h__
diff --git a/widget/windows/nsBidiKeyboard.cpp b/widget/windows/nsBidiKeyboard.cpp
new file mode 100644
index 0000000000..1fac24d6c5
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.cpp
@@ -0,0 +1,187 @@
+/* -*- 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 "prmem.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*) PR_Malloc(keyboards * sizeof(HKL));
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Call again to fill the buffer
+ if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) {
+ PR_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;
+ }
+ }
+ PR_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();
+}
diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h
new file mode 100644
index 0000000000..b3b56f48f6
--- /dev/null
+++ b/widget/windows/nsBidiKeyboard.h
@@ -0,0 +1,37 @@
+/* -*- 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..1afee34961
--- /dev/null
+++ b/widget/windows/nsClipboard.cpp
@@ -0,0 +1,1036 @@
+/* -*- 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 <ole2.h>
+#include <shlobj.h>
+#include <intshcut.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDataObj.h"
+#include "nsIClipboardOwner.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIFormatConverter.h"
+#include "nsITransferable.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageClipboard.h"
+#include "nsIWidget.h"
+#include "nsIComponentManager.h"
+#include "nsWidgetsCID.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIOutputStream.h"
+#include "nsEscape.h"
+#include "nsIObserverService.h"
+
+using mozilla::LogLevel;
+
+PRLogModuleInfo* gWin32ClipboardLog = nullptr;
+
+// oddly, this isn't in the MSVC headers anywhere.
+UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format");
+UINT nsClipboard::CF_CUSTOMTYPES = ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
+
+
+//-------------------------------------------------------------------------
+//
+// nsClipboard constructor
+//
+//-------------------------------------------------------------------------
+nsClipboard::nsClipboard() : nsBaseClipboard()
+{
+ if (!gWin32ClipboardLog) {
+ gWin32ClipboardLog = PR_NewLogModule("nsClipboard");
+ }
+
+ mIgnoreEmptyNotification = false;
+ 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_SHUTDOWN_OBSERVER_ID, PR_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_TEXT;
+ else if (strcmp(aMimeStr, kUnicodeMime) == 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 = CF_HTML;
+ else if (strcmp(aMimeStr, kCustomTypesMime) == 0)
+ format = CF_CUSTOMTYPES;
+ else
+ format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
+
+ return format;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, IDataObject ** aDataObj, nsIURI * uri)
+{
+ if (nullptr == aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create our native DataObject that implements
+ // the OLE IDataObject interface
+ nsDataObj * dataObj = new nsDataObj(uri);
+
+ if (!dataObj)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ dataObj->AddRef();
+
+ // Now set it up with all the right data flavors & enums
+ nsresult res = SetupNativeDataObject(aTransferable, dataObj);
+ if (NS_OK == res) {
+ *aDataObj = dataObj;
+ } else {
+ delete dataObj;
+ }
+ return res;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SetupNativeDataObject(nsITransferable * aTransferable, IDataObject * aDataObj)
+{
+ if (nullptr == aTransferable || nullptr == aDataObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsDataObj * dObj = static_cast<nsDataObj *>(aDataObj);
+
+ // Now give the Transferable to the DataObject
+ // for getting the data out of it
+ dObj->SetTransferable(aTransferable);
+
+ // Get the transferable list of data flavors
+ nsCOMPtr<nsIArray> dfList;
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList));
+
+ // Walk through flavors that contain data and register them
+ // into the DataObj as supported flavors
+ uint32_t i;
+ uint32_t cnt;
+ dfList->GetLength(&cnt);
+ for (i=0;i<cnt;i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(dfList, i);
+ if ( currentFlavor ) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ // 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, 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, &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 ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
+ // if we find text/unicode, also advertise text/plain (which we will convert
+ // on our own in nsDataObj::GetText().
+ FORMATETC textFE;
+ SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kTextMime, &textFE);
+ }
+ else if ( strcmp(flavorStr, kHTMLMime) == 0 ) {
+ // 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, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kHTMLMime, &htmlFE);
+ }
+ else if ( strcmp(flavorStr, kURLMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 ||
+ strcmp(flavorStr, kJPGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 ||
+ strcmp(flavorStr, kNativeImageMime) == 0 ) {
+ // 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, &imageFE);
+ // Add DIBv3
+ SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr, &imageFE);
+ }
+ else if ( strcmp(flavorStr, kFilePromiseMime) == 0 ) {
+ // 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);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::SetNativeClipboardData ( int32_t aWhichClipboard )
+{
+ if ( aWhichClipboard != kGlobalClipboard )
+ return NS_ERROR_FAILURE;
+
+ mIgnoreEmptyNotification = true;
+
+ // make sure we have a good transferable
+ if (nullptr == mTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ IDataObject * dataObj;
+ if ( NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj, nullptr)) ) { // this add refs dataObj
+ ::OleSetClipboard(dataObj);
+ dataObj->Release();
+ } else {
+ // Clear the native clipboard
+ ::OleSetClipboard(nullptr);
+ }
+
+ mIgnoreEmptyNotification = false;
+
+ return NS_OK;
+}
+
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void ** aData, uint32_t * aLen)
+{
+ // 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 2 bytes (but don't report them!) and
+ // null them out to ensure that all of our strlen calls will succeed.
+ nsresult result = NS_ERROR_FAILURE;
+ if (aHGBL != nullptr) {
+ LPSTR lpStr = (LPSTR) GlobalLock(aHGBL);
+ DWORD allocSize = GlobalSize(aHGBL);
+ char* data = static_cast<char*>(malloc(allocSize + sizeof(char16_t)));
+ if ( data ) {
+ memcpy ( data, lpStr, allocSize );
+ data[allocSize] = data[allocSize + 1] = '\0'; // null terminate for safety
+
+ GlobalUnlock(aHGBL);
+ *aData = data;
+ *aLen = allocSize;
+
+ 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)
+{
+ HGLOBAL hglb;
+ nsresult result = NS_ERROR_FAILURE;
+
+ HWND nativeWin = nullptr;
+ if (::OpenClipboard(nativeWin)) {
+ hglb = ::GetClipboardData(aFormat);
+ result = GetGlobalData(hglb, aData, aLen);
+ ::CloseClipboard();
+ }
+ return result;
+}
+
+static void DisplayErrCode(HRESULT hres)
+{
+#if defined(DEBUG_rods) || defined(DEBUG_pinkerton)
+ if (hres == E_INVALIDARG) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
+ } else
+ if (hres == E_UNEXPECTED) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
+ } else
+ if (hres == E_OUTOFMEMORY) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
+ } else
+ if (hres == DV_E_LINDEX ) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
+ } else
+ if (hres == DV_E_FORMATETC) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
+ } else
+ if (hres == DV_E_TYMED) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
+ } else
+ if (hres == DV_E_DVASPECT) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
+ } else
+ if (hres == OLE_E_NOTRUNNING) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
+ } else
+ if (hres == STG_E_MEDIUMFULL) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
+ } else
+ if (hres == DV_E_CLIPFORMAT) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
+ } else
+ if (hres == S_OK) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
+ } else {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
+ ("****** DisplayErrCode 0x%X\n", hres));
+ }
+#endif
+}
+
+//-------------------------------------------------------------------------
+static HRESULT 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);
+ DisplayErrCode(hres);
+ if (S_OK == hres) {
+ hres = aDataObject->GetData(pFE, pSTM);
+ DisplayErrCode(hres);
+ }
+ 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)
+{
+ 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);
+
+ // 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;
+ unsigned char * clipboardData;
+ if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, (void **)&clipboardData, &allocLen)))
+ {
+ nsImageFromClipboard converter;
+ nsIInputStream * inputStream;
+ converter.GetEncodedImageStream(clipboardData, aMIMEImageFormat, &inputStream); // addrefs for us, don't release
+ if ( inputStream ) {
+ *aData = inputStream;
+ *aLen = sizeof(nsIInputStream*);
+ result = NS_OK;
+ }
+ }
+ } 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)));
+ if ( buffer ) {
+ ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
+ *aData = buffer;
+ *aLen = fileNameLen * sizeof(char16_t);
+ result = NS_OK;
+ }
+ else
+ result = NS_ERROR_OUT_OF_MEMORY;
+ }
+ GlobalUnlock (stm.hGlobal) ;
+
+ } break;
+
+ default: {
+ if ( fe.cfFormat == fileDescriptorFlavorA || fe.cfFormat == fileDescriptorFlavorW || 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 == CF_HTML ) {
+ // 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 == CF_CUSTOMTYPES) {
+ // 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_LOG(gWin32ClipboardLog, LogLevel::Info,
+ ("*********************** TYMED_GDI\n"));
+#endif
+ } break;
+
+ default:
+ break;
+ } //switch
+
+ ReleaseStgMedium(&stm);
+ }
+
+ return result;
+}
+
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
+ UINT anIndex,
+ nsIWidget * aWindow,
+ nsITransferable * aTransferable)
+{
+ // 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)
+ nsCOMPtr<nsIArray> flavorList;
+ res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) );
+ if ( NS_FAILED(res) )
+ return NS_ERROR_FAILURE;
+
+ // Walk through flavors and see which flavor is on the clipboard them on the native clipboard,
+ uint32_t i;
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ for (i=0;i<cnt;i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if ( currentFlavor ) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ UINT format = GetFormat(flavorStr);
+
+ // 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, &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 ( strcmp(flavorStr, kUnicodeMime) == 0 )
+ dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen );
+ else if ( strcmp(flavorStr, kURLMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kFileMime) == 0 ) {
+ // 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 ( strcmp(flavorStr, kNativeHTMLMime) == 0 ) {
+ 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 ( strcmp(flavorStr, kHTMLMime) == 0 ) {
+ 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 ( strcmp(flavorStr, kJPEGImageMime) == 0 ||
+ strcmp(flavorStr, kJPGImageMime) == 0 ||
+ strcmp(flavorStr, kPNGImageMime) == 0) {
+ nsIInputStream * imageStream = reinterpret_cast<nsIInputStream*>(data);
+ genericDataWrapper = do_QueryInterface(imageStream);
+ NS_IF_RELEASE(imageStream);
+ }
+ else {
+ // Treat custom types as a string of bytes.
+ if (strcmp(flavorStr, kCustomTypesMime) != 0) {
+ // 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 ( flavorStr, &data, &signedLen );
+ dataLen = signedLen;
+
+ if (strcmp(flavorStr, kRTFMime) == 0) {
+ // 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, genericDataWrapper, dataLen);
+ 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
+//
+// we are looking for text/unicode and we failed to find it on the clipboard first,
+// try again with text/plain. If that is present, convert it to unicode.
+//
+bool
+nsClipboard :: FindUnicodeFromPlainText ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen )
+{
+ // we are looking for text/unicode and we failed to find it on the clipboard first,
+ // try again with text/plain. If that is present, convert it to unicode.
+ nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime), 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 )
+{
+ 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 + NS_LITERAL_STRING("\n") + 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 )
+{
+ 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 + NS_LITERAL_STRING("\n") + 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 + NS_LITERAL_STRING("\n") + urlString);
+ *outDataLen = NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ }
+ }
+
+ return dataFound;
+} // FindURLFromNativeURL
+
+//
+// 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, NS_LITERAL_STRING(".url"), nsCaseInsensitiveStringComparator());
+} // IsInternetShortcut
+
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard )
+{
+ // make sure we have a good transferable
+ if ( !aTransferable || aWhichClipboard != kGlobalClipboard )
+ return NS_ERROR_FAILURE;
+
+ nsresult res;
+
+ // This makes sure we can use the OLE functionality for the clipboard
+ IDataObject * dataObj;
+ if (S_OK == ::OleGetClipboard(&dataObj)) {
+ // Use OLE IDataObject for clipboard operations
+ res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
+ dataObj->Release();
+ }
+ else {
+ // do it the old manual way
+ res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
+ }
+ return res;
+
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t 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.
+ if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
+ OleSetClipboard(nullptr);
+ }
+ return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList,
+ uint32_t aLength,
+ int32_t aWhichClipboard,
+ bool *_retval)
+{
+ *_retval = false;
+ if (aWhichClipboard != kGlobalClipboard || !aFlavorList)
+ return NS_OK;
+
+ for (uint32_t i = 0;i < aLength; ++i) {
+#ifdef DEBUG
+ if (strcmp(aFlavorList[i], kTextMime) == 0)
+ NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" );
+#endif
+
+ UINT format = GetFormat(aFlavorList[i]);
+ if (IsClipboardFormatAvailable(format)) {
+ *_retval = true;
+ break;
+ }
+ else {
+ // We haven't found the exact flavor the client asked for, but maybe we can
+ // still find it from something else that's on the clipboard...
+ if (strcmp(aFlavorList[i], kUnicodeMime) == 0) {
+ // 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.
+ if (IsClipboardFormatAvailable(GetFormat(kTextMime)))
+ *_retval = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h
new file mode 100644
index 0000000000..18308d6dbb
--- /dev/null
+++ b/widget/windows/nsClipboard.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 nsClipboard_h__
+#define nsClipboard_h__
+
+#include "nsBaseClipboard.h"
+#include "nsIObserver.h"
+#include "nsIURI.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
+
+ // nsIClipboard
+ NS_IMETHOD HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool *_retval) override;
+ NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override;
+
+ // Internal Native Routines
+ static nsresult CreateNativeDataObject(nsITransferable * aTransferable,
+ IDataObject ** aDataObj,
+ nsIURI * uri);
+ static nsresult SetupNativeDataObject(nsITransferable * aTransferable,
+ IDataObject * aDataObj);
+ 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 CF_HTML;
+ static UINT CF_CUSTOMTYPES;
+
+protected:
+ NS_IMETHOD SetNativeClipboardData ( int32_t aWhichClipboard ) override;
+ NS_IMETHOD GetNativeClipboardData ( nsITransferable * aTransferable, 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 ) ;
+
+ 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..777169f978
--- /dev/null
+++ b/widget/windows/nsColorPicker.cpp
@@ -0,0 +1,212 @@
+/* -*- 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/AutoRestore.h"
+#include "nsIWidget.h"
+#include "nsString.h"
+#include "WidgetUtils.h"
+
+using namespace mozilla::widget;
+
+namespace
+{
+// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
+// temporary child windows of mParentWidget created to address RTL issues
+// in picker dialogs. We are responsible for destroying these.
+class AutoDestroyTmpWindow
+{
+public:
+ explicit AutoDestroyTmpWindow(HWND aTmpWnd) :
+ mWnd(aTmpWnd) {
+ }
+
+ ~AutoDestroyTmpWindow() {
+ if (mWnd)
+ DestroyWindow(mWnd);
+ }
+
+ inline HWND get() const { return mWnd; }
+private:
+ HWND mWnd;
+};
+
+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,
+ nsIWidget* aParentWidget,
+ nsIColorPickerShownCallback* aCallback)
+ : mInitialColor(aInitialColor)
+ , mColor(aInitialColor)
+ , mParentWidget(aParentWidget)
+ , mCallback(aCallback)
+{
+}
+
+NS_IMETHODIMP
+AsyncColorChooser::Run()
+{
+ static COLORREF sCustomColors[16] = {0} ;
+
+ 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;
+
+ AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ CHOOSECOLOR options;
+ options.lStructSize = sizeof(options);
+ options.hwndOwner = adtw.get();
+ options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
+ options.rgbResult = mInitialColor;
+ options.lpCustColors = sCustomColors;
+ 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_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));
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIColorPicker
+
+nsColorPicker::nsColorPicker()
+{
+}
+
+nsColorPicker::~nsColorPicker()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* parent,
+ const nsAString& title,
+ const nsAString& aInitialColor)
+{
+ NS_PRECONDITION(parent,
+ "Null parent passed to colorpicker, no color picker for you!");
+ mParentWidget = WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
+ mInitialColor = ColorStringToRGB(aInitialColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback)
+{
+ NS_ENSURE_ARG(aCallback);
+ nsCOMPtr<nsIRunnable> event = new AsyncColorChooser(mInitialColor,
+ mParentWidget,
+ aCallback);
+ return NS_DispatchToMainThread(event);
+}
diff --git a/widget/windows/nsColorPicker.h b/widget/windows/nsColorPicker.h
new file mode 100644
index 0000000000..2227ba604a
--- /dev/null
+++ b/widget/windows/nsColorPicker.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 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,
+ 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;
+ COLORREF mColor;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+class nsColorPicker :
+ public nsIColorPicker
+{
+ virtual ~nsColorPicker();
+
+public:
+ nsColorPicker();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* parent, const nsAString& title,
+ const nsAString& aInitialColor);
+ NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback);
+
+private:
+ COLORREF mInitialColor;
+ nsCOMPtr<nsIWidget> mParentWidget;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp
new file mode 100644
index 0000000000..fc45968ae3
--- /dev/null
+++ b/widget/windows/nsDataObj.cpp
@@ -0,0 +1,2166 @@
+/* -*- 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 <ole2.h>
+#include <shlobj.h>
+
+#include "nsDataObj.h"
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsReadableUtils.h"
+#include "nsITransferable.h"
+#include "nsISupportsPrimitives.h"
+#include "IEnumFE.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsXPIDLString.h"
+#include "nsImageClipboard.h"
+#include "nsCRT.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "nsEscape.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Services.h"
+#include "nsIOutputStream.h"
+#include "nsXPCOMStrings.h"
+#include "nscore.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+
+#include "WinUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/WindowsVersion.h"
+#include <algorithm>
+
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener)
+
+//-----------------------------------------------------------------------------
+// CStream implementation
+nsDataObj::CStream::CStream() :
+ mChannelRead(false),
+ mStreamRead(0)
+{
+}
+
+//-----------------------------------------------------------------------------
+nsDataObj::CStream::~CStream()
+{
+}
+
+//-----------------------------------------------------------------------------
+// helper - initializes the stream
+nsresult nsDataObj::CStream::Init(nsIURI *pSourceURI,
+ uint32_t aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal)
+{
+ // 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_DATA_INHERITS,
+ aContentPolicyType,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_FROM_CACHE);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mChannel->AsyncOpen2(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,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset, // offset within the stream
+ uint32_t aCount) // bytes available on this call
+{
+ // Extend the write buffer for the incoming data.
+ uint8_t* buffer = mChannelData.AppendElements(aCount, fallible);
+ if (!buffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NS_ASSERTION((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.
+ nsresult rv;
+ uint32_t odaBytesReadTotal = 0;
+ do {
+ uint32_t bytesReadByCall = 0;
+ rv = aInputStream->Read((char*)(buffer + odaBytesReadTotal),
+ aCount, &bytesReadByCall);
+ odaBytesReadTotal += bytesReadByCall;
+ } while (aCount < odaBytesReadTotal && NS_SUCCEEDED(rv));
+ return rv;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ mChannelResult = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ 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.
+ while (!mChannelRead) {
+ // Pump messages
+ NS_ProcessNextEvent(nullptr, true);
+ }
+
+ if (!mChannelData.Length())
+ mChannelResult = NS_ERROR_FAILURE;
+
+ return mChannelResult;
+}
+
+//-----------------------------------------------------------------------------
+// IStream
+STDMETHODIMP nsDataObj::CStream::Clone(IStream** ppStream)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::Commit(DWORD dwFrags)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::CopyTo(IStream* pDestStream,
+ ULARGE_INTEGER nBytesToCopy,
+ ULARGE_INTEGER* nBytesRead,
+ ULARGE_INTEGER* nBytesWritten)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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::CStream::Revert(void)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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::CStream::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.LowPart = (DWORD)mChannelData.Length();
+ statstg->grfMode = STGM_READ;
+ statstg->grfLocksSupported = LOCK_ONLYONCE;
+ statstg->clsid = CLSID_NULL;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::UnlockRegion(ULARGE_INTEGER nStart,
+ ULARGE_INTEGER nBytes,
+ DWORD dwFlags)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------------------------------
+STDMETHODIMP nsDataObj::CStream::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(getter_AddRefs(requestingPrincipal));
+ MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal");
+ // default transferable content policy is nsIContentPolicy::TYPE_OTHER
+ uint32_t contentPolicyType = nsIContentPolicy::TYPE_OTHER;
+ mTransferable->GetContentPolicyType(&contentPolicyType);
+ rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal);
+ if (NS_FAILED(rv))
+ {
+ pStream->Release();
+ return E_FAIL;
+ }
+ *outStream = pStream;
+
+ return S_OK;
+}
+
+static GUID CLSID_nsDataObj =
+ { 0x1bba7640, 0xdf52, 0x11cf, { 0x82, 0x7b, 0, 0xa0, 0x24, 0x3a, 0xe5, 0x05 } };
+
+/*
+ * deliberately not using MAX_PATH. This is because on platforms < XP
+ * a file created with a long filename may be mishandled by the shell
+ * resulting in it not being able to be deleted or moved.
+ * See bug 250392 for more details.
+ */
+#define NS_MAX_FILEDESCRIPTOR 128 + 1
+
+/*
+ * Class nsDataObj
+ */
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+nsDataObj::nsDataObj(nsIURI * uri)
+ : m_cRef(0), mTransferable(nullptr),
+ mIsAsyncMode(FALSE), mIsInOperation(FALSE)
+{
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
+ NS_LITERAL_CSTRING("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_IAsyncOperation == riid) {
+ *ppv = static_cast<IAsyncOperation*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::AddRef()
+{
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this));
+ return m_cRef;
+}
+
+
+//-----------------------------------------------------
+STDMETHODIMP_(ULONG) nsDataObj::Release()
+{
+ --m_cRef;
+
+ NS_LOG_RELEASE(this, m_cRef, "nsDataObj");
+ 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. Delete file anyway and destroy this obj if there's a problem.
+ if (mCachedTempFile) {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ mTimer->InitWithFuncCallback(nsDataObj::RemoveTempFile, this,
+ 500, nsITimer::TYPE_ONE_SHOT);
+ return AddRef();
+ }
+ mCachedTempFile->Remove(false);
+ mCachedTempFile = nullptr;
+ }
+
+ 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;
+
+ 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;
+}
+
+// IAsyncOperation 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;
+}
+
+//-----------------------------------------------------
+// GetData and SetData helper functions
+//-----------------------------------------------------
+HRESULT nsDataObj::AddSetFormat(FORMATETC& aFE)
+{
+ return S_OK;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::AddGetFormat(FORMATETC& aFE)
+{
+ 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)
+{
+ ULONG result = E_FAIL;
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(), getter_AddRefs(genericDataWrapper), &len);
+ nsCOMPtr<imgIContainer> image ( do_QueryInterface(genericDataWrapper) );
+ if ( !image ) {
+ // Check if the image was put in an nsISupportsInterfacePointer wrapper.
+ // This might not be necessary any more, but could be useful for backwards
+ // compatibility.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if ( ptr ) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ image = do_QueryInterface(supports);
+ }
+ }
+
+ if ( image ) {
+ // use the |nsImageToClipboard| helper class to build up a bitmap. We now own
+ // the bits, and pass them back to the OS in |aSTG|.
+ nsImageToClipboard converter(image, aFormat.cfFormat == CF_DIBV5);
+ HANDLE bits = nullptr;
+ nsresult rv = converter.GetPicture ( &bits );
+ if ( NS_SUCCEEDED(rv) && bits ) {
+ aSTG.hGlobal = bits;
+ aSTG.tymed = TYMED_HGLOBAL;
+ result = S_OK;
+ }
+ } // if we have an image
+ else
+ NS_WARNING ( "Definitely not an image on clipboard" );
+ return result;
+}
+
+
+
+//
+// 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
+
+//
+// Given a unicode string, we ensure that it contains only characters which are valid within
+// the file system. Remove all forbidden characters from the name, and completely disallow
+// any title that starts with a forbidden name and extension (e.g. "nul" is invalid, but
+// "nul." and "nul.txt" are also invalid and will cause problems).
+//
+// It would seem that this is more functionality suited to being in nsIFile.
+//
+static void
+MangleTextToValidFilename(nsString & aText)
+{
+ static const char* forbiddenNames[] = {
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ "CON", "PRN", "AUX", "NUL", "CLOCK$"
+ };
+
+ aText.StripChars(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS);
+ aText.CompressWhitespace(true, true);
+ uint32_t nameLen;
+ for (size_t n = 0; n < ArrayLength(forbiddenNames); ++n) {
+ nameLen = (uint32_t) strlen(forbiddenNames[n]);
+ if (aText.EqualsIgnoreCase(forbiddenNames[n], nameLen)) {
+ // invalid name is either the entire string, or a prefix with a period
+ if (aText.Length() == nameLen || aText.CharAt(nameLen) == char16_t('.')) {
+ aText.Truncate();
+ break;
+ }
+ }
+ }
+}
+
+//
+// Given a unicode string, convert it down to a valid local charset filename
+// with the supplied extension. 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
+CreateFilenameFromTextA(nsString & aText, const char * aExtension,
+ char * aFilename, uint32_t aFilenameLen)
+{
+ // ensure that the supplied name doesn't have invalid characters. If
+ // a valid mangled filename couldn't be created then it will leave the
+ // text empty.
+ MangleTextToValidFilename(aText);
+ if (aText.IsEmpty())
+ return false;
+
+ // repeatably call WideCharToMultiByte as long as the title doesn't fit in the buffer
+ // available to us. Continually reduce the length of the source title until the MBCS
+ // version will fit in the buffer with room for the supplied extension. Doing it this
+ // way ensures that even in MBCS environments there will be a valid MBCS filename of
+ // the correct length.
+ int maxUsableFilenameLen = aFilenameLen - strlen(aExtension) - 1; // space for ext + null byte
+ int currLen, textLen = (int) std::min(aText.Length(), aFilenameLen);
+ char defaultChar = '_';
+ do {
+ currLen = WideCharToMultiByte(CP_ACP,
+ WC_COMPOSITECHECK|WC_DEFAULTCHAR,
+ aText.get(), textLen--, aFilename, maxUsableFilenameLen, &defaultChar, nullptr);
+ }
+ while (currLen == 0 && textLen > 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ if (currLen > 0 && textLen > 0) {
+ strcpy(&aFilename[currLen], aExtension);
+ return true;
+ }
+ else {
+ // empty names aren't permitted
+ return false;
+ }
+}
+
+static bool
+CreateFilenameFromTextW(nsString & aText, const wchar_t * aExtension,
+ wchar_t * aFilename, uint32_t aFilenameLen)
+{
+ // ensure that the supplied name doesn't have invalid characters. If
+ // a valid mangled filename couldn't be created then it will leave the
+ // text empty.
+ MangleTextToValidFilename(aText);
+ if (aText.IsEmpty())
+ return false;
+
+ const int extensionLen = wcslen(aExtension);
+ if (aText.Length() + extensionLen + 1 > aFilenameLen)
+ aText.Truncate(aFilenameLen - extensionLen - 1);
+ wcscpy(&aFilename[0], aText.get());
+ wcscpy(&aFilename[aText.Length()], aExtension);
+ return true;
+}
+
+#define PAGEINFO_PROPERTIES "chrome://navigator/locale/pageInfo.properties"
+
+static bool
+GetLocalizedString(const char16_t * aName, nsXPIDLString & aString)
+{
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ 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, getter_Copies(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 (!CreateFilenameFromTextA(title, ".URL",
+ fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) {
+ nsXPIDLString untitled;
+ if (!GetLocalizedString(u"noPageTitle", untitled) ||
+ !CreateFilenameFromTextA(untitled, ".URL",
+ fileGroupDescA->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) {
+ 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 (!CreateFilenameFromTextW(title, L".URL",
+ fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) {
+ nsXPIDLString untitled;
+ if (!GetLocalizedString(u"noPageTitle", untitled) ||
+ !CreateFilenameFromTextW(untitled, L".URL",
+ fileGroupDescW->fgd[0].cFileName, NS_MAX_FILEDESCRIPTOR)) {
+ 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;
+ }
+
+ const char *shortcutFormatStr;
+ int totalLen;
+ nsCString path;
+ if (!Preferences::GetBool(kShellIconPref, true) ||
+ !IsVistaOrLater()) {
+ 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;
+
+ mozilla::widget::FaviconHelper::ObtainCachedIconFile(aUri, aUriHash, mIOThread, true);
+
+ rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+ rv = icoFile->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, E_FAIL);
+
+ shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n"
+ "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n"
+ "IconIndex=0\r\n";
+ const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice
+ totalLen = formatLen + asciiUrl.Length() +
+ path.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
+ HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_SHARE, totalLen);
+ if ( !hGlobalMemory )
+ return E_OUTOFMEMORY;
+
+ char* contents = reinterpret_cast<char*>(::GlobalLock(hGlobalMemory));
+ if ( !contents ) {
+ ::GlobalFree( hGlobalMemory );
+ 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(), path.get());
+ }
+
+ ::GlobalUnlock(hGlobalMemory);
+ aSTG.hGlobal = hGlobalMemory;
+ 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
+ nsCOMPtr<nsIArray> flavorList;
+ mTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ NS_ENSURE_TRUE(flavorList, false);
+
+ // try to find requested flavour
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ for (uint32_t i = 0; i < cnt; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (currentFlavor) {
+ nsAutoCString flavorStr;
+ currentFlavor->GetData(flavorStr);
+ if (flavorStr.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;
+ uint32_t len;
+
+ // if someone asks for text/plain, look up text/unicode instead in the transferable.
+ const char* flavorStr;
+ const nsPromiseFlatCString& flat = PromiseFlatCString(aDataFlavor);
+ if (aDataFlavor.EqualsLiteral("text/plain"))
+ flavorStr = kUnicodeMime;
+ else
+ flavorStr = flat.get();
+
+ // NOTE: CreateDataFromPrimitive creates new memory, that needs to be deleted
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ mTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &len);
+ if ( !len )
+ return E_FAIL;
+ nsPrimitiveHelpers::CreateDataFromPrimitive ( flavorStr, 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::CF_HTML ) {
+ // 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::CF_CUSTOMTYPES ) {
+ // 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;
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper),
+ &len);
+ nsCOMPtr<nsIFile> file ( do_QueryInterface(genericDataWrapper) );
+
+ if (!file)
+ {
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if (ptr) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ file = do_QueryInterface(supports);
+ }
+ }
+
+ 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) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+
+ mTransferable->GetTransferData(kNativeImageMime, getter_AddRefs(genericDataWrapper), &len);
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper));
+
+ if (!image) {
+ // Check if the image was put in an nsISupportsInterfacePointer wrapper.
+ // This might not be necessary any more, but could be useful for backwards
+ // compatibility.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericDataWrapper));
+ if (ptr) {
+ nsCOMPtr<nsISupports> supports;
+ ptr->GetData(getter_AddRefs(supports));
+ image = do_QueryInterface(supports);
+ }
+ }
+
+ if (!image)
+ return E_FAIL;
+
+ // Use the clipboard helper class to build up a memory bitmap.
+ nsImageToClipboard converter(image);
+ HANDLE bits = nullptr;
+ rv = converter.GetPicture(&bits); // Clipboard routines return a global handle we own.
+
+ if (NS_FAILED(rv) || !bits)
+ return E_FAIL;
+
+ // We now own these bits!
+ uint32_t bitmapSize = GlobalSize(bits);
+ if (!bitmapSize) {
+ GlobalFree(bits);
+ 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) {
+ GlobalFree(bits);
+ 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)) {
+ GlobalFree(bits);
+ 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)) {
+ GlobalFree(bits);
+ return E_FAIL;
+ }
+
+ char * bm = (char *)GlobalLock(bits);
+
+ BITMAPFILEHEADER fileHdr;
+ BITMAPINFOHEADER *bmpHdr = (BITMAPINFOHEADER*)bm;
+
+ fileHdr.bfType = ((WORD) ('M' << 8) | 'B');
+ fileHdr.bfSize = GlobalSize (bits) + sizeof(fileHdr);
+ fileHdr.bfReserved1 = 0;
+ fileHdr.bfReserved2 = 0;
+ fileHdr.bfOffBits = (DWORD) (sizeof(fileHdr) + bmpHdr->biSize);
+
+ uint32_t writeCount = 0;
+ if (NS_FAILED(outStream->Write((const char *)&fileHdr, sizeof(fileHdr), &writeCount)) ||
+ NS_FAILED(outStream->Write((const char *)bm, bitmapSize, &writeCount)))
+ rv = NS_ERROR_FAILURE;
+
+ outStream->Close();
+
+ GlobalUnlock(bits);
+ GlobalFree(bits);
+
+ if (NS_FAILED(rv))
+ return E_FAIL;
+ }
+
+ // 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_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, 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;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::GetMetafilePict(FORMATETC&, STGMEDIUM&)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetBitmap(FORMATETC&, STGMEDIUM&)
+{
+ return E_NOTIMPL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetDib(FORMATETC&, STGMEDIUM&)
+{
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetText (FORMATETC& aFE, STGMEDIUM& aSTG)
+{
+ return E_FAIL;
+}
+
+//-----------------------------------------------------
+HRESULT nsDataObj::SetMetafilePict(FORMATETC&, STGMEDIUM&)
+{
+ return E_FAIL;
+}
+
+
+
+//-----------------------------------------------------
+//-----------------------------------------------------
+CLSID nsDataObj::GetClassID() const
+{
+ return CLSID_nsDataObj;
+}
+
+//-----------------------------------------------------
+// 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;
+
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) {
+ 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), &len)) ||
+ NS_SUCCEEDED(mTransferable->GetTransferData(kURLPrivateMime, getter_AddRefs(genericURL), &len)) ) {
+ 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;
+
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ if ( NS_SUCCEEDED(mTransferable->GetTransferData(kURLMime, getter_AddRefs(genericURL), &len)) ) {
+ 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, (len/2) - (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
+// http://msdn.microsoft.com/workshop/networking/clipboard/htmlclipboard.asp
+// 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);
+ const char* const numPlaceholder = "00000000";
+ const char* const startHTMLPrefix = "Version:0.9\r\nStartHTML:";
+ const char* const endHTMLPrefix = "\r\nEndHTML:";
+ const char* const startFragPrefix = "\r\nStartFragment:";
+ const char* const endFragPrefix = "\r\nEndFragment:";
+ const char* const startSourceURLPrefix = "\r\nSourceURL:";
+ const char* const endFragTrailer = "\r\n";
+
+ // Do we already have mSourceURL from a drag?
+ if (mSourceURL.IsEmpty()) {
+ nsAutoString url;
+ ExtractShortcutURL(url);
+
+ AppendUTF16toUTF8(url, mSourceURL);
+ }
+
+ const int32_t kSourceURLLength = mSourceURL.Length();
+ const int32_t kNumberLength = strlen(numPlaceholder);
+
+ const int32_t kTotalHeaderLen = strlen(startHTMLPrefix) +
+ strlen(endHTMLPrefix) +
+ strlen(startFragPrefix) +
+ strlen(endFragPrefix) +
+ strlen(endFragTrailer) +
+ (kSourceURLLength > 0 ? strlen(startSourceURLPrefix) : 0) +
+ kSourceURLLength +
+ (4 * kNumberLength);
+
+ NS_NAMED_LITERAL_CSTRING(htmlHeaderString, "<html><body>\r\n");
+
+ NS_NAMED_LITERAL_CSTRING(fragmentHeaderString, "<!--StartFragment-->");
+
+ nsDependentCString trailingString(
+ "<!--EndFragment-->\r\n"
+ "</body>\r\n"
+ "</html>");
+
+ // calculate the offsets
+ int32_t startHTMLOffset = kTotalHeaderLen;
+ int32_t startFragOffset = startHTMLOffset
+ + htmlHeaderString.Length()
+ + fragmentHeaderString.Length();
+
+ int32_t endFragOffset = startFragOffset
+ + inHTMLString.Length();
+
+ int32_t endHTMLOffset = endFragOffset
+ + trailingString.Length();
+
+ // now build the final version
+ nsCString clipboardString;
+ clipboardString.SetCapacity(endHTMLOffset);
+
+ clipboardString.Append(startHTMLPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", startHTMLOffset));
+
+ clipboardString.Append(endHTMLPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", endHTMLOffset));
+
+ clipboardString.Append(startFragPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", startFragOffset));
+
+ clipboardString.Append(endFragPrefix);
+ clipboardString.Append(nsPrintfCString("%08u", endFragOffset));
+
+ if (kSourceURLLength > 0) {
+ clipboardString.Append(startSourceURLPrefix);
+ clipboardString.Append(mSourceURL);
+ }
+
+ clipboardString.Append(endFragTrailer);
+
+ clipboardString.Append(htmlHeaderString);
+ clipboardString.Append(fragmentHeaderString);
+ clipboardString.Append(inHTMLString);
+ clipboardString.Append(trailingString);
+
+ *outPlatformHTML = ToNewCString(clipboardString);
+ 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;
+ uint32_t dataSize = 0;
+ mTransferable->GetTransferData(kFilePromiseURLMime, getter_AddRefs(urlPrimitive), &dataSize);
+ 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;
+ mTransferable->GetTransferData(kFilePromiseDestFilename, getter_AddRefs(fileNamePrimitive), &dataSize);
+ 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);
+ }
+ if (srcFileName.IsEmpty())
+ return E_FAIL;
+
+ // make the name safe for the filesystem
+ MangleTextToValidFilename(srcFileName);
+
+ 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_UTF16ToCString(wideFileName, NS_CSTRING_ENCODING_NATIVE_FILESYSTEM, nativeFileName);
+
+ strncpy(fileGroupDescA->fgd[0].cFileName, nativeFileName.get(), NS_MAX_FILEDESCRIPTOR - 1);
+ fileGroupDescA->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 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(), NS_MAX_FILEDESCRIPTOR - 1);
+ fileGroupDescW->fgd[0].cFileName[NS_MAX_FILEDESCRIPTOR - 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;
+}
+
+void nsDataObj::RemoveTempFile(nsITimer* aTimer, void* aClosure)
+{
+ nsDataObj *timedDataObj = static_cast<nsDataObj *>(aClosure);
+ if (timedDataObj->mCachedTempFile) {
+ timedDataObj->mCachedTempFile->Remove(false);
+ timedDataObj->mCachedTempFile = nullptr;
+ }
+ timedDataObj->Release();
+}
diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h
new file mode 100644
index 0000000000..2d7fb75ee6
--- /dev/null
+++ b/widget/windows/nsDataObj.h
@@ -0,0 +1,296 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIInputStream.h"
+#include "nsIStreamListener.h"
+#include "nsIChannel.h"
+#include "nsCOMArray.h"
+#include "nsITimer.h"
+
+class nsIThread;
+class nsIPrincipal;
+
+// The SDK shipping with VC11 has renamed IAsyncOperation to
+// IDataObjectAsyncCapability. We try to detect this, and rename this in our
+// code too to make sure that we pick the correct name when building.
+#ifdef __IDataObjectAsyncCapability_INTERFACE_DEFINED__
+#define IAsyncOperation IDataObjectAsyncCapability
+#define IID_IAsyncOperation IID_IDataObjectAsyncCapability
+#else
+// XXX for older version of PSDK where IAsyncOperation and related stuff is not available
+// but thisdefine should be removed when parocles config is updated
+#ifndef __IAsyncOperation_INTERFACE_DEFINED__
+// IAsyncOperation interface definition
+EXTERN_C const IID IID_IAsyncOperation;
+
+MIDL_INTERFACE("3D8B0590-F691-11d2-8EA9-006097DF5BD4")
+IAsyncOperation : public IUnknown
+{
+ virtual HRESULT STDMETHODCALLTYPE SetAsyncMode(BOOL fDoOpAsync) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetAsyncMode(BOOL *pfIsOpAsync) = 0;
+ virtual HRESULT STDMETHODCALLTYPE StartOperation(IBindCtx *pbcReserved) = 0;
+ virtual HRESULT STDMETHODCALLTYPE InOperation(BOOL *pfInAsyncOp) = 0;
+ virtual HRESULT STDMETHODCALLTYPE EndOperation(HRESULT hResult,
+ IBindCtx *pbcReserved,
+ DWORD dwEffects) = 0;
+};
+// this is not defined in the old headers for some reason
+#ifndef FD_PROGRESSUI
+ #define FD_PROGRESSUI 0x4000
+#endif
+
+#endif // __IAsyncOperation_INTERFACE_DEFINED__
+#endif // __IDataObjectAsyncCapability_INTERFACE_DEFINED__
+
+/*
+ * CFSTR_SHELLURL is deprecated and doesn't have a Unicode version.
+ * Therefore we are using CFSTR_INETURL instead of CFSTR_SHELLURL.
+ * See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_basics/shell_basics_programming/transferring/clipboard.asp
+ */
+#ifndef CFSTR_INETURLA
+#define CFSTR_INETURLA L"UniformResourceLocator"
+#endif
+#ifndef CFSTR_INETURLW
+#define CFSTR_INETURLW L"UniformResourceLocatorW"
+#endif
+
+// For support of MinGW w32api v2.4.
+// When the next version of w32api is released with shlobj.h rev 1.35
+// http://sources.redhat.com/cgi-bin/cvsweb.cgi/src/winsup/w32api/include/shlobj.h?cvsroot=src
+// then that can be made the base required version and this code should be removed.
+#ifndef CFSTR_FILEDESCRIPTORA
+# define CFSTR_FILEDESCRIPTORA L"FileGroupDescriptor"
+#endif
+#ifndef CFSTR_FILEDESCRIPTORW
+# define CFSTR_FILEDESCRIPTORW L"FileGroupDescriptorW"
+#endif
+
+class CEnumFormatEtc;
+class nsITransferable;
+
+/*
+ * 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 IAsyncOperation
+{
+
+protected:
+ nsCOMPtr<nsIThread> mIOThread;
+
+ public: // construction, destruction
+ nsDataObj(nsIURI *uri = nullptr);
+ virtual ~nsDataObj();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef ();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release ();
+
+ // support for clipboard
+ virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE);
+ void SetTransferable(nsITransferable * aTransferable);
+
+ // Return the registered OLE class ID of this object's CfDataObj.
+ CLSID GetClassID() const;
+
+ 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);
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData (LPFORMATETC pFE);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // 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);
+
+ // Turn off advising of a previous call to DAdvise which set pConn.
+ STDMETHODIMP DUnadvise (DWORD pConn);
+
+ // 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);
+
+ // IAsyncOperation methods
+ STDMETHOD(EndOperation)(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects);
+ STDMETHOD(GetAsyncMode)(BOOL *pfIsOpAsync);
+ STDMETHOD(InOperation)(BOOL *pfInAsyncOp);
+ STDMETHOD(SetAsyncMode)(BOOL fDoOpAsync);
+ STDMETHOD(StartOperation)(IBindCtx *pbcReserved);
+
+ public: // other methods
+
+ // Gets the filename from the kFilePromiseURLMime flavour
+ HRESULT GetDownloadDetails(nsIURI **aSourceURI,
+ nsAString &aFilename);
+
+ protected:
+ // help determine the kind of drag
+ bool IsFlavourPresent(const char *inFlavour);
+
+ virtual HRESULT AddSetFormat(FORMATETC& FE);
+ virtual HRESULT AddGetFormat(FORMATETC& FE);
+
+ virtual HRESULT GetFile ( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT GetText ( const nsACString& aDF, FORMATETC& aFE, STGMEDIUM & aSTG );
+ virtual HRESULT GetDib ( const nsACString& inFlavor, FORMATETC &, STGMEDIUM & aSTG );
+ virtual HRESULT GetMetafilePict(FORMATETC& FE, STGMEDIUM& STM);
+
+ virtual HRESULT DropImage( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT DropFile( FORMATETC& aFE, STGMEDIUM& aSTG );
+ virtual HRESULT DropTempFile( FORMATETC& aFE, STGMEDIUM& aSTG );
+
+ virtual HRESULT GetUniformResourceLocator ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ;
+ virtual HRESULT ExtractUniformResourceLocatorA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT ExtractUniformResourceLocatorW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptor ( FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode ) ;
+ virtual HRESULT GetFileContents ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetPreferredDropEffect ( FORMATETC& aFE, STGMEDIUM& aSTG );
+
+ virtual HRESULT SetBitmap(FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetDib (FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetText (FORMATETC& FE, STGMEDIUM& STM);
+ virtual HRESULT SetMetafilePict(FORMATETC& FE, STGMEDIUM& STM);
+
+ // Provide the structures needed for an internet shortcut by the shell
+ virtual HRESULT GetFileDescriptorInternetShortcutA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptorInternetShortcutW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileContentsInternetShortcut ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+
+ // IStream implementation
+ virtual HRESULT GetFileDescriptor_IStreamA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual HRESULT GetFileDescriptor_IStreamW ( FORMATETC& aFE, STGMEDIUM& aSTG ) ;
+ virtual 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;
+
+ BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const;
+
+ ULONG m_cRef; // the reference count
+
+ nsTArray<nsCString> mDataFlavors;
+
+ nsITransferable * mTransferable; // nsDataObj owns and ref counts nsITransferable,
+ // the nsITransferable does know anything about the nsDataObj
+
+ CEnumFormatEtc * m_enumFE; // Ownership Rules:
+ // nsDataObj owns and ref counts CEnumFormatEtc,
+
+ nsCOMPtr<nsIFile> mCachedTempFile;
+
+ BOOL mIsAsyncMode;
+ BOOL mIsInOperation;
+ ///////////////////////////////////////////////////////////////////////////////
+ // CStream class implementation
+ // this class is used in Drag and drop with download sample
+ // called from IDataObject::GetData
+ class CStream : public IStream, public nsIStreamListener
+ {
+ nsCOMPtr<nsIChannel> mChannel;
+ FallibleTArray<uint8_t> mChannelData;
+ bool mChannelRead;
+ nsresult mChannelResult;
+ uint32_t mStreamRead;
+
+ protected:
+ virtual ~CStream();
+ nsresult WaitForCompletion();
+
+ public:
+ CStream();
+ nsresult Init(nsIURI *pSourceURI,
+ uint32_t aContentPolicyType,
+ nsIPrincipal* aRequestingPrincipal);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ // IUnknown
+ STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult);
+
+ // IStream
+ STDMETHOD(Clone)(IStream** ppStream);
+ STDMETHOD(Commit)(DWORD dwFrags);
+ STDMETHOD(CopyTo)(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy, ULARGE_INTEGER* nBytesRead, ULARGE_INTEGER* nBytesWritten);
+ STDMETHOD(LockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags);
+ STDMETHOD(Read)(void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead);
+ STDMETHOD(Revert)(void);
+ STDMETHOD(Seek)(LARGE_INTEGER nMove, DWORD dwOrigin, ULARGE_INTEGER* nNewPos);
+ STDMETHOD(SetSize)(ULARGE_INTEGER nNewSize);
+ STDMETHOD(Stat)(STATSTG* statstg, DWORD dwFlags);
+ STDMETHOD(UnlockRegion)(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags);
+ STDMETHOD(Write)(const void* pvBuffer, ULONG nBytesToRead, ULONG* nBytesRead);
+ };
+
+ HRESULT CreateStream(IStream **outStream);
+
+ 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);
+ static void RemoveTempFile(nsITimer* aTimer, void* aClosure);
+};
+
+
+#endif // _NSDATAOBJ_H_
diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp
new file mode 100644
index 0000000000..7399272e78
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.cpp
@@ -0,0 +1,405 @@
+/* -*- 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()
+ : m_cRef(0)
+{
+}
+
+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_IAsyncOperation == riid) {
+ *ppv = static_cast<IAsyncOperation*>(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..06bd36a7d0
--- /dev/null
+++ b/widget/windows/nsDataObjCollection.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 _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();
+ ~nsDataObjCollection();
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP_(ULONG) AddRef ();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release ();
+
+ public: // DataGet and DataSet helper methods
+ virtual HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+ virtual 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);
+
+ // from nsPIDataObjCollection
+ void AddDataObject(IDataObject * aDataObj);
+ int32_t GetNumDataObjects() { return mDataObjects.Length(); }
+ nsDataObj* GetDataObjectAt(uint32_t aItem)
+ { return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>()); }
+
+ // Return the registered OLE class ID of this object's CfDataObj.
+ CLSID GetClassID() const;
+
+ 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);
+
+ // Similar to GetData except that the caller allocates the structure
+ // referenced by pSTM.
+ STDMETHODIMP GetDataHere (LPFORMATETC pFE, LPSTGMEDIUM pSTM);
+
+ // Returns S_TRUE if this object supports the format specified by pSTM,
+ // S_FALSE otherwise.
+ STDMETHODIMP QueryGetData (LPFORMATETC pFE);
+
+ // 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);
+
+ protected:
+ ULONG m_cRef; // the reference count
+
+ nsTArray<RefPtr<nsDataObj> > mDataObjects;
+};
+
+#endif //
diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp
new file mode 100644
index 0000000000..b7750be58f
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -0,0 +1,669 @@
+/* -*- 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/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetWindows.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+
+#include "prmem.h"
+
+#include <winspool.h>
+
+#include "nsIWidget.h"
+
+#include "nsTArray.h"
+#include "nsIPrintSettingsWin.h"
+
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsStringEnumerator.h"
+
+#include "gfxWindowsSurface.h"
+
+#include "nsIFileStreams.h"
+#include "nsIWindowWatcher.h"
+#include "nsIDOMWindow.h"
+#include "mozilla/Services.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/gfx/Logging.h"
+
+#include "mozilla/Logging.h"
+static mozilla::LazyLogModule kWidgetPrintingLogMod("printing-widget");
+#define PR_PL(_p1) MOZ_LOG(kWidgetPrintingLogMod, mozilla::LogLevel::Debug, _p1)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static const wchar_t kDriverName[] = L"WINSPOOL";
+
+//----------------------------------------------------------------------------------
+// The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecWin
+// The PrinterEnumerator creates the printer info
+// but the nsDeviceContextSpecWin cleans it up
+// If it gets created (via the Page Setup Dialog) but the user never prints anything
+// then it will never be delete, so this class takes care of that.
+class GlobalPrinters {
+public:
+ static GlobalPrinters* GetInstance() { return &mGlobalPrinters; }
+ ~GlobalPrinters() { FreeGlobalPrinters(); }
+
+ void FreeGlobalPrinters();
+
+ bool PrintersAreAllocated() { return mPrinters != nullptr; }
+ LPWSTR GetItemFromList(int32_t aInx) { return mPrinters?mPrinters->ElementAt(aInx):nullptr; }
+ nsresult EnumeratePrinterList();
+ void GetDefaultPrinterName(nsString& aDefaultPrinterName);
+ uint32_t GetNumPrinters() { return mPrinters?mPrinters->Length():0; }
+
+protected:
+ GlobalPrinters() {}
+ nsresult EnumerateNativePrinters();
+ void ReallocatePrinters();
+
+ static GlobalPrinters mGlobalPrinters;
+ static nsTArray<LPWSTR>* mPrinters;
+};
+//---------------
+// static members
+GlobalPrinters GlobalPrinters::mGlobalPrinters;
+nsTArray<LPWSTR>* GlobalPrinters::mPrinters = nullptr;
+
+struct AutoFreeGlobalPrinters
+{
+ ~AutoFreeGlobalPrinters() {
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+ }
+};
+
+//----------------------------------------------------------------------------------
+nsDeviceContextSpecWin::nsDeviceContextSpecWin()
+{
+ mDriverName = nullptr;
+ mDeviceName = nullptr;
+ mDevMode = nullptr;
+
+}
+
+
+//----------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec)
+
+nsDeviceContextSpecWin::~nsDeviceContextSpecWin()
+{
+ SetDeviceName(nullptr);
+ SetDriverName(nullptr);
+ SetDevMode(nullptr);
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(mPrintSettings));
+ if (psWin) {
+ psWin->SetDeviceName(nullptr);
+ psWin->SetDriverName(nullptr);
+ psWin->SetDevMode(nullptr);
+ }
+
+ // Free them, we won't need them for a while
+ GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+}
+
+
+//------------------------------------------------------------------
+// helper
+static char16_t * GetDefaultPrinterNameFromGlobalPrinters()
+{
+ nsAutoString printerName;
+ GlobalPrinters::GetInstance()->GetDefaultPrinterName(printerName);
+ return ToNewUnicode(printerName);
+}
+
+//----------------------------------------------------------------------------------
+NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPrintSettings,
+ bool aIsPrintPreview)
+{
+ mPrintSettings = aPrintSettings;
+
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ if (aPrintSettings) {
+ // If we're in the child and printing via the parent or we're printing to
+ // PDF we only need information from the print settings.
+ mPrintSettings->GetOutputFormat(&mOutputFormat);
+ if ((XRE_IsContentProcess() &&
+ Preferences::GetBool("print.print_via_parent")) ||
+ mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ if (psWin) {
+ char16_t* deviceName;
+ char16_t* driverName;
+ psWin->GetDeviceName(&deviceName); // creates new memory (makes a copy)
+ psWin->GetDriverName(&driverName); // creates new memory (makes a copy)
+
+ LPDEVMODEW devMode;
+ psWin->GetDevMode(&devMode); // creates new memory (makes a copy)
+
+ if (deviceName && driverName && 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);
+
+ // clean up
+ free(deviceName);
+ free(driverName);
+
+ return NS_OK;
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - deviceName/driverName/devMode was NULL!\n"));
+ if (deviceName) free(deviceName);
+ if (driverName) free(driverName);
+ if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode);
+ }
+ }
+ } else {
+ PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n"));
+ }
+
+ // Get the Printer Name to be used and output format.
+ char16_t * printerName = nullptr;
+ if (mPrintSettings) {
+ mPrintSettings->GetPrinterName(&printerName);
+ }
+
+ // If there is no name then use the default printer
+ if (!printerName || (printerName && !*printerName)) {
+ printerName = GetDefaultPrinterNameFromGlobalPrinters();
+ }
+
+ NS_ASSERTION(printerName, "We have to have a printer name");
+ if (!printerName || !*printerName) return rv;
+
+ return GetDataFromPrinter(printerName, mPrintSettings);
+}
+
+//----------------------------------------------------------
+// Helper Function - Free and reallocate the string
+static void CleanAndCopyString(wchar_t*& aStr, const wchar_t* aNewStr)
+{
+ if (aStr != nullptr) {
+ if (aNewStr != nullptr && wcslen(aStr) > wcslen(aNewStr)) { // reuse it if we can
+ wcscpy(aStr, aNewStr);
+ return;
+ } else {
+ PR_Free(aStr);
+ aStr = nullptr;
+ }
+ }
+
+ if (nullptr != aNewStr) {
+ aStr = (wchar_t *)PR_Malloc(sizeof(wchar_t)*(wcslen(aNewStr) + 1));
+ wcscpy(aStr, aNewStr);
+ }
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget()
+{
+ NS_ASSERTION(mDevMode, "DevMode can't be NULL here");
+
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ nsXPIDLString filename;
+ mPrintSettings->GetToFileName(getter_Copies(filename));
+
+ 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;
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ nsresult rv = file->InitWithPath(filename);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFileOutputStream> stream = do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ rv = stream->Init(file, -1, -1, 0);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return PrintTargetPDF::CreateOrNull(stream, IntSize::Truncate(width, height));
+ }
+
+ if (mDevMode) {
+ NS_WARNING_ASSERTION(mDriverName, "No driver!");
+ HDC dc = ::CreateDCW(mDriverName, mDeviceName, 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;
+}
+
+float
+nsDeviceContextSpecWin::GetDPI()
+{
+ // To match the previous printing code we need to return 72 when printing to
+ // PDF and 144 when printing to a Windows surface.
+ return mOutputFormat == nsIPrintSettings::kOutputFormatPDF ? 72.0f : 144.0f;
+}
+
+float
+nsDeviceContextSpecWin::GetPrintingScale()
+{
+ MOZ_ASSERT(mPrintSettings);
+
+ // To match the previous printing code there is no scaling for PDF.
+ if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return 1.0f;
+ }
+
+ // The print settings will have the resolution stored from the real device.
+ int32_t resolution;
+ mPrintSettings->GetResolution(&resolution);
+ return float(resolution) / GetDPI();
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDeviceName(char16ptr_t aDeviceName)
+{
+ CleanAndCopyString(mDeviceName, aDeviceName);
+}
+
+//----------------------------------------------------------------------------------
+void nsDeviceContextSpecWin::SetDriverName(char16ptr_t aDriverName)
+{
+ CleanAndCopyString(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(char16ptr_t aName, nsIPrintSettings* aPS)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!GlobalPrinters::GetInstance()->PrintersAreAllocated()) {
+ rv = GlobalPrinters::GetInstance()->EnumeratePrinterList();
+ if (NS_FAILED(rv)) {
+ PR_PL(("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't enumerate printers!\n"));
+ DISPLAY_LAST_ERROR
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsHPRINTER hPrinter = nullptr;
+ wchar_t *name = (wchar_t*)aName; // 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() = %08x\n",
+ aName ? NS_ConvertUTF16toUTF8(aName).get() : "", GetLastError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ 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, aName, 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: %d/0x%x\n", ret, ret));
+ DISPLAY_LAST_ERROR
+ return NS_ERROR_FAILURE;
+ }
+
+ SetDevMode(pDevMode); // cache the pointer and takes responsibility for the memory
+
+ SetDeviceName(aName);
+
+ SetDriverName(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 Enumerator
+//***********************************************************
+nsPrinterEnumeratorWin::nsPrinterEnumeratorWin()
+{
+}
+
+nsPrinterEnumeratorWin::~nsPrinterEnumeratorWin()
+{
+ // Do not free printers here
+ // GlobalPrinters::GetInstance()->FreeGlobalPrinters();
+}
+
+NS_IMPL_ISUPPORTS(nsPrinterEnumeratorWin, nsIPrinterEnumerator)
+
+//----------------------------------------------------------------------------------
+// Return the Default Printer name
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::GetDefaultPrinterName(char16_t * *aDefaultPrinterName)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPrinterName);
+
+ *aDefaultPrinterName = GetDefaultPrinterNameFromGlobalPrinters(); // helper
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::InitPrintSettingsFromPrinter(const char16_t *aPrinterName, nsIPrintSettings *aPrintSettings)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterName);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ if (!*aPrinterName) {
+ return NS_OK;
+ }
+
+ // When printing to PDF on Windows there is no associated printer driver.
+ int16_t outputFormat;
+ aPrintSettings->GetOutputFormat(&outputFormat);
+ if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
+ return NS_OK;
+ }
+
+ RefPtr<nsDeviceContextSpecWin> devSpecWin = new nsDeviceContextSpecWin();
+ if (!devSpecWin) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (NS_FAILED(GlobalPrinters::GetInstance()->EnumeratePrinterList())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AutoFreeGlobalPrinters autoFreeGlobalPrinters;
+
+ // 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.
+ char16ptr_t printerName = aPrinterName;
+ 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;
+}
+
+
+//----------------------------------------------------------------------------------
+// Enumerate all the Printers from the global array and pass their
+// names back (usually to script)
+NS_IMETHODIMP
+nsPrinterEnumeratorWin::GetPrinterNameList(nsIStringEnumerator **aPrinterNameList)
+{
+ NS_ENSURE_ARG_POINTER(aPrinterNameList);
+ *aPrinterNameList = nullptr;
+
+ nsresult rv = GlobalPrinters::GetInstance()->EnumeratePrinterList();
+ if (NS_FAILED(rv)) {
+ PR_PL(("***** nsDeviceContextSpecWin::GetPrinterNameList - Couldn't enumerate printers!\n"));
+ return rv;
+ }
+
+ uint32_t numPrinters = GlobalPrinters::GetInstance()->GetNumPrinters();
+ nsTArray<nsString> *printers = new nsTArray<nsString>(numPrinters);
+ if (!printers)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsString* names = printers->AppendElements(numPrinters);
+ for (uint32_t printerInx = 0; printerInx < numPrinters; ++printerInx) {
+ LPWSTR name = GlobalPrinters::GetInstance()->GetItemFromList(printerInx);
+ names[printerInx].Assign(name);
+ }
+
+ return NS_NewAdoptingStringEnumerator(aPrinterNameList, printers);
+}
+
+//----------------------------------------------------------------------------------
+//-- Global Printers
+//----------------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------------
+// THe array hold the name and port for each printer
+void
+GlobalPrinters::ReallocatePrinters()
+{
+ if (PrintersAreAllocated()) {
+ FreeGlobalPrinters();
+ }
+ mPrinters = new nsTArray<LPWSTR>();
+ NS_ASSERTION(mPrinters, "Printers Array is NULL!");
+}
+
+//----------------------------------------------------------------------------------
+void
+GlobalPrinters::FreeGlobalPrinters()
+{
+ if (mPrinters != nullptr) {
+ for (uint32_t i=0;i<mPrinters->Length();i++) {
+ free(mPrinters->ElementAt(i));
+ }
+ delete mPrinters;
+ mPrinters = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------------------
+nsresult
+GlobalPrinters::EnumerateNativePrinters()
+{
+ nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE;
+ PR_PL(("-----------------------\n"));
+ PR_PL(("EnumerateNativePrinters\n"));
+
+ WCHAR szDefaultPrinterName[1024];
+ DWORD status = GetProfileStringW(L"devices", 0, L",",
+ szDefaultPrinterName,
+ ArrayLength(szDefaultPrinterName));
+ if (status > 0) {
+ DWORD count = 0;
+ LPWSTR sPtr = szDefaultPrinterName;
+ LPWSTR ePtr = szDefaultPrinterName + status;
+ LPWSTR prvPtr = sPtr;
+ while (sPtr < ePtr) {
+ if (*sPtr == 0) {
+ LPWSTR name = wcsdup(prvPtr);
+ mPrinters->AppendElement(name);
+ PR_PL(("Printer Name: %s\n", prvPtr));
+ prvPtr = sPtr+1;
+ count++;
+ }
+ sPtr++;
+ }
+ rv = NS_OK;
+ }
+ PR_PL(("-----------------------\n"));
+ return rv;
+}
+
+//------------------------------------------------------------------
+// Uses the GetProfileString to get the default printer from the registry
+void
+GlobalPrinters::GetDefaultPrinterName(nsString& aDefaultPrinterName)
+{
+ aDefaultPrinterName.Truncate();
+ WCHAR szDefaultPrinterName[1024];
+ DWORD status = GetProfileStringW(L"windows", L"device", 0,
+ szDefaultPrinterName,
+ ArrayLength(szDefaultPrinterName));
+ if (status > 0) {
+ WCHAR comma = ',';
+ LPWSTR sPtr = szDefaultPrinterName;
+ while (*sPtr != comma && *sPtr != 0)
+ sPtr++;
+ if (*sPtr == comma) {
+ *sPtr = 0;
+ }
+ aDefaultPrinterName = szDefaultPrinterName;
+ } else {
+ aDefaultPrinterName = EmptyString();
+ }
+
+ PR_PL(("DEFAULT PRINTER [%s]\n", aDefaultPrinterName.get()));
+}
+
+//----------------------------------------------------------------------------------
+// This goes and gets the list of available printers and puts
+// the default printer at the beginning of the list
+nsresult
+GlobalPrinters::EnumeratePrinterList()
+{
+ // reallocate and get a new list each time it is asked for
+ // this deletes the list and re-allocates them
+ ReallocatePrinters();
+
+ // any of these could only fail with an OUT_MEMORY_ERROR
+ // PRINTER_ENUM_LOCAL should get the network printers on Win95
+ nsresult rv = EnumerateNativePrinters();
+ if (NS_FAILED(rv)) return rv;
+
+ // get the name of the default printer
+ nsAutoString defPrinterName;
+ GetDefaultPrinterName(defPrinterName);
+
+ // put the default printer at the beginning of list
+ if (!defPrinterName.IsEmpty()) {
+ for (uint32_t i=0;i<mPrinters->Length();i++) {
+ LPWSTR name = mPrinters->ElementAt(i);
+ if (defPrinterName.Equals(name)) {
+ if (i > 0) {
+ LPWSTR ptr = mPrinters->ElementAt(0);
+ mPrinters->ElementAt(0) = name;
+ mPrinters->ElementAt(i) = ptr;
+ }
+ break;
+ }
+ }
+ }
+
+ // make sure we at least tried to get the printers
+ if (!PrintersAreAllocated()) {
+ PR_PL(("***** nsDeviceContextSpecWin::EnumeratePrinterList - Printers aren`t allocated\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h
new file mode 100644
index 0000000000..e3706a291f
--- /dev/null
+++ b/widget/windows/nsDeviceContextSpecWin.h
@@ -0,0 +1,84 @@
+/* -*- 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 "nsIPrinterEnumerator.h"
+#include "nsIPrintSettings.h"
+#include "nsISupportsPrimitives.h"
+#include <windows.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+
+class nsIWidget;
+
+class nsDeviceContextSpecWin : public nsIDeviceContextSpec
+{
+public:
+ nsDeviceContextSpecWin();
+
+ NS_DECL_ISUPPORTS
+
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) override { return NS_OK; }
+ NS_IMETHOD EndDocument() override { return NS_OK; }
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+
+ float GetDPI() final;
+
+ float GetPrintingScale() final;
+
+ void GetDriverName(wchar_t *&aDriverName) const { aDriverName = mDriverName; }
+ void GetDeviceName(wchar_t *&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(char16ptr_t aName, nsIPrintSettings* aPS = nullptr);
+
+protected:
+
+ void SetDeviceName(char16ptr_t aDeviceName);
+ void SetDriverName(char16ptr_t aDriverName);
+ void SetDevMode(LPDEVMODEW aDevMode);
+
+ virtual ~nsDeviceContextSpecWin();
+
+ wchar_t* mDriverName;
+ wchar_t* mDeviceName;
+ LPDEVMODEW mDevMode;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative;
+};
+
+
+//-------------------------------------------------------------------------
+// Printer Enumerator
+//-------------------------------------------------------------------------
+class nsPrinterEnumeratorWin final : public nsIPrinterEnumerator
+{
+ ~nsPrinterEnumeratorWin();
+
+public:
+ nsPrinterEnumeratorWin();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTERENUMERATOR
+};
+
+#endif
diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp
new file mode 100644
index 0000000000..0040f550f9
--- /dev/null
+++ b/widget/windows/nsDragService.cpp
@@ -0,0 +1,641 @@
+/* -*- 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 "nsIDocument.h"
+#include "nsDataObjCollection.h"
+
+#include "nsArrayUtils.h"
+#include "nsString.h"
+#include "nsEscape.h"
+#include "nsIScreenManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURL.h"
+#include "nsCWebBrowserPersist.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsUnicharUtils.h"
+#include "gfxContext.h"
+#include "nsRect.h"
+#include "nsMathUtils.h"
+#include "WinUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+//-------------------------------------------------------------------------
+//
+// DragService constructor
+//
+//-------------------------------------------------------------------------
+nsDragService::nsDragService()
+ : mDataObject(nullptr), mSentLocalDropEvent(false)
+{
+}
+
+//-------------------------------------------------------------------------
+//
+// DragService destructor
+//
+//-------------------------------------------------------------------------
+nsDragService::~nsDragService()
+{
+ NS_IF_RELEASE(mDataObject);
+}
+
+bool
+nsDragService::CreateDragImage(nsIDOMNode *aDOMNode,
+ nsIScriptableRegion *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;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ 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,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType)
+{
+ // Try and get source URI of the items that are being dragged
+ nsIURI *uri = nullptr;
+
+ nsCOMPtr<nsIDocument> doc(do_QueryInterface(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) {
+ // set the requestingPrincipal on the transferable
+ nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode);
+ trans->SetRequestingPrincipal(node->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ 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) {
+ // set the requestingPrincipal on the transferable
+ nsCOMPtr<nsINode> node = do_QueryInterface(mSourceNode);
+ trans->SetRequestingPrincipal(node->NodePrincipal());
+ trans->SetContentPolicyType(mContentPolicyType);
+ 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 bool
+LayoutDevicePointToCSSPoint(const LayoutDevicePoint& aDevPos,
+ CSSPoint& aCSSPos)
+{
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenMgr) {
+ return false;
+ }
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForRect(NSToIntRound(aDevPos.x), NSToIntRound(aDevPos.y),
+ 1, 1, getter_AddRefs(screen));
+ if (!screen) {
+ return false;
+ }
+
+ int32_t w,h; // unused
+ LayoutDeviceIntPoint screenOriginDev;
+ screen->GetRect(&screenOriginDev.x, &screenOriginDev.y, &w, &h);
+
+ double scale;
+ screen->GetDefaultCSSScaleFactor(&scale);
+ LayoutDeviceToCSSScale devToCSSScale =
+ CSSToLayoutDeviceScale(scale).Inverse();
+
+ // Desktop pixels and CSS pixels share the same screen origin.
+ CSSIntPoint screenOriginCSS;
+ screen->GetRectDisplayPix(&screenOriginCSS.x, &screenOriginCSS.y, &w, &h);
+
+ aCSSPos = (aDevPos - screenOriginDev) * devToCSSScale + screenOriginCSS;
+ return true;
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+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<IAsyncOperation> pAsyncOp;
+ // Offer to do an async drag
+ if (SUCCEEDED(aDataObj->QueryInterface(IID_IAsyncOperation,
+ getter_AddRefs(pAsyncOp)))) {
+ pAsyncOp->SetAsyncMode(VARIANT_TRUE);
+ } else {
+ NS_NOTREACHED("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 nsIDOMDataTransfer 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();
+ CSSPoint cssPos;
+ if (!LayoutDevicePointToCSSPoint(LayoutDevicePoint(GET_X_LPARAM(pos),
+ GET_Y_LPARAM(pos)),
+ cssPos)) {
+ // fallback to the simple scaling
+ POINT pt = { GET_X_LPARAM(pos), GET_Y_LPARAM(pos) };
+ HMONITOR monitor = ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
+ double dpiScale = widget::WinUtils::LogToPhysFactor(monitor);
+ cssPos.x = GET_X_LPARAM(pos) / dpiScale;
+ cssPos.y = GET_Y_LPARAM(pos) / dpiScale;
+ }
+ // We have to abuse SetDragEndPoint to pass CSS pixels because
+ // Event::GetScreenCoords will not convert pixels for dragend events
+ // until bug 1224754 is fixed.
+ SetDragEndPoint(LayoutDeviceIntPoint(NSToIntRound(cssPos.x),
+ NSToIntRound(cssPos.y)));
+ EndDragSession(true);
+
+ 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 (dataObjCol) {
+ *aNumItems = dataObjCol->GetNumDataObjects();
+ }
+ else {
+ // 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 = 0;
+ }
+ }
+ else {
+ // 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 (mDataObject->QueryGetData(&fe2) == S_OK) {
+ STGMEDIUM stm;
+ if (mDataObject->GetData(&fe2, &stm) == S_OK) {
+ HDROP hdrop = (HDROP)GlobalLock(stm.hGlobal);
+ *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;
+ }
+ else
+ *aNumItems = 1;
+ }
+ else
+ *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;
+ SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ if (mDataObject->QueryGetData(&fe2) == S_OK)
+ 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);
+
+ 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)
+ return NS_ERROR_FAILURE;
+
+#ifdef DEBUG
+ if (strcmp(aDataFlavor, kTextMime) == 0)
+ NS_WARNING("DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD");
+#endif
+
+ *_retval = false;
+
+ 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!
+ }
+ }
+ } // if special collection object
+ else {
+ // 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!
+ else {
+ // We haven't found the exact flavor the client asked for, but
+ // maybe we can still find it from something else that's on the
+ // clipboard
+ if (strcmp(aDataFlavor, kUnicodeMime) == 0) {
+ // 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.
+ format = nsClipboard::GetFormat(kTextMime);
+ SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
+ if (mDataObject->QueryGetData(&fe) == S_OK)
+ *_retval = true; // found it!
+ }
+ else 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!
+ }
+ } // else try again
+ }
+
+ 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)
+{
+ // 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);
+ NS_IF_RELEASE(mDataObject);
+
+ return NS_OK;
+}
diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h
new file mode 100644
index 0000000000..7a2d297094
--- /dev/null
+++ b/widget/windows/nsDragService.h
@@ -0,0 +1,61 @@
+/* -*- 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
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t anItem);
+ NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
+ NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
+ NS_IMETHOD EndDragSession(bool aDoneDrag);
+
+ // native impl.
+ NS_IMETHOD SetIDataObject(IDataObject * aDataObj);
+ NS_IMETHOD StartInvokingDragSession(IDataObject * aDataObj,
+ uint32_t aActionType);
+
+ // A drop occurred within the application vs. outside of it.
+ void SetDroppedLocal();
+
+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(nsIDOMNode *aDOMNode,
+ nsIScriptableRegion *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..53857cf5e4
--- /dev/null
+++ b/widget/windows/nsFilePicker.cpp
@@ -0,0 +1,1383 @@
+/* -*- 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 <shlobj.h>
+#include <shlwapi.h>
+#include <cderr.h>
+
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsWindow.h"
+#include "nsILoadContext.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsIStringBundle.h"
+#include "nsEnumeratorUtils.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsToolkit.h"
+#include "WinUtils.h"
+#include "nsPIDOMWindow.h"
+#include "GeckoProfiler.h"
+
+using mozilla::IsVistaOrLater;
+using mozilla::IsWin8OrLater;
+using mozilla::MakeUnique;
+using mozilla::mscom::EnsureMTA;
+using mozilla::UniquePtr;
+using namespace mozilla::widget;
+
+char16_t *nsFilePicker::mLastUsedUnicodeDirectory;
+char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 };
+
+static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty";
+static const DWORD kDialogTimerID = 9999;
+static const unsigned long kDialogTimerTimeout = 300;
+
+#define MAX_EXTENSION_LENGTH 10
+#define FILE_BUFFER_SIZE 4096
+
+typedef DWORD FILEOPENDIALOGOPTIONS;
+
+///////////////////////////////////////////////////////////////////////////////
+// Helper classes
+
+// Manages matching SuppressBlurEvents calls on the parent widget.
+class AutoSuppressEvents
+{
+public:
+ explicit AutoSuppressEvents(nsIWidget* aWidget) :
+ mWindow(static_cast<nsWindow *>(aWidget)) {
+ SuppressWidgetEvents(true);
+ }
+
+ ~AutoSuppressEvents() {
+ SuppressWidgetEvents(false);
+ }
+private:
+ void SuppressWidgetEvents(bool aFlag) {
+ if (mWindow) {
+ mWindow->SuppressBlurEvents(aFlag);
+ }
+ }
+ RefPtr<nsWindow> mWindow;
+};
+
+// Manages the current working path.
+class AutoRestoreWorkingPath
+{
+public:
+ AutoRestoreWorkingPath() {
+ DWORD bufferLength = GetCurrentDirectoryW(0, nullptr);
+ mWorkingPath = MakeUnique<wchar_t[]>(bufferLength);
+ if (GetCurrentDirectoryW(bufferLength, mWorkingPath.get()) == 0) {
+ mWorkingPath = nullptr;
+ }
+ }
+
+ ~AutoRestoreWorkingPath() {
+ if (HasWorkingPath()) {
+ ::SetCurrentDirectoryW(mWorkingPath.get());
+ }
+ }
+
+ inline bool HasWorkingPath() const {
+ return mWorkingPath != nullptr;
+ }
+private:
+ UniquePtr<wchar_t[]> mWorkingPath;
+};
+
+// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
+// temporary child windows of mParentWidget created to address RTL issues
+// in picker dialogs. We are responsible for destroying these.
+class AutoDestroyTmpWindow
+{
+public:
+ explicit AutoDestroyTmpWindow(HWND aTmpWnd) :
+ mWnd(aTmpWnd) {
+ }
+
+ ~AutoDestroyTmpWindow() {
+ if (mWnd)
+ DestroyWindow(mWnd);
+ }
+
+ inline HWND get() const { return mWnd; }
+private:
+ HWND mWnd;
+};
+
+// Manages matching PickerOpen/PickerClosed calls on the parent widget.
+class AutoWidgetPickerState
+{
+public:
+ explicit AutoWidgetPickerState(nsIWidget* aWidget) :
+ mWindow(static_cast<nsWindow *>(aWidget)) {
+ PickerState(true);
+ }
+
+ ~AutoWidgetPickerState() {
+ PickerState(false);
+ }
+private:
+ void PickerState(bool aFlag) {
+ if (mWindow) {
+ if (aFlag)
+ mWindow->PickerOpen();
+ else
+ mWindow->PickerClosed();
+ }
+ }
+ RefPtr<nsWindow> mWindow;
+};
+
+// Manages a simple callback timer
+class AutoTimerCallbackCancel
+{
+public:
+ AutoTimerCallbackCancel(nsFilePicker* aTarget,
+ nsTimerCallbackFunc aCallbackFunc) {
+ Init(aTarget, aCallbackFunc);
+ }
+
+ ~AutoTimerCallbackCancel() {
+ if (mPickerCallbackTimer) {
+ mPickerCallbackTimer->Cancel();
+ }
+ }
+
+private:
+ void Init(nsFilePicker* aTarget,
+ nsTimerCallbackFunc aCallbackFunc) {
+ mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mPickerCallbackTimer) {
+ NS_WARNING("do_CreateInstance for timer failed??");
+ return;
+ }
+ mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc,
+ aTarget,
+ kDialogTimerTimeout,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ nsCOMPtr<nsITimer> mPickerCallbackTimer;
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker
+
+nsFilePicker::nsFilePicker() :
+ mSelectedType(1)
+ , mDlgWnd(nullptr)
+ , mFDECookie(0)
+{
+ CoInitialize(nullptr);
+}
+
+nsFilePicker::~nsFilePicker()
+{
+ if (mLastUsedUnicodeDirectory) {
+ free(mLastUsedUnicodeDirectory);
+ mLastUsedUnicodeDirectory = nullptr;
+ }
+ CoUninitialize();
+}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy *aParent, const nsAString& aTitle, int16_t aMode)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent);
+ nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
+ mLoadContext = do_QueryInterface(docShell);
+
+ return nsBaseFilePicker::Init(aParent, aTitle, aMode);
+}
+
+STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult)
+{
+ *ppvResult = nullptr;
+ if (IID_IUnknown == refiid ||
+ refiid == IID_IFileDialogEvents) {
+ *ppvResult = this;
+ }
+
+ if (nullptr != *ppvResult) {
+ ((LPUNKNOWN)*ppvResult)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+/*
+ * XP picker callbacks
+ */
+
+// Show - Display the file dialog
+int CALLBACK
+BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if (uMsg == BFFM_INITIALIZED)
+ {
+ char16_t * filePath = (char16_t *) lpData;
+ if (filePath)
+ ::SendMessageW(hwnd, BFFM_SETSELECTIONW,
+ TRUE /* true because lpData is a path string */,
+ lpData);
+ }
+ return 0;
+}
+
+static void
+EnsureWindowVisible(HWND hwnd)
+{
+ // Obtain the monitor which has the largest area of intersection
+ // with the window, or nullptr if there is no intersection.
+ HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ // The window is not visible, we should reposition it to the same place as its parent
+ HWND parentHwnd = GetParent(hwnd);
+ RECT parentRect;
+ GetWindowRect(parentHwnd, &parentRect);
+ SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0,
+ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ }
+}
+
+// Callback hook which will ensure that the window is visible. Currently
+// only in use on os <= XP.
+UINT_PTR CALLBACK
+nsFilePicker::FilePickerHook(HWND hwnd,
+ UINT msg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ switch(msg) {
+ case WM_NOTIFY:
+ {
+ LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
+ if (!lpofn || !lpofn->lpOFN) {
+ return 0;
+ }
+
+ if (CDN_INITDONE == lpofn->hdr.code) {
+ // The Window will be automatically moved to the last position after
+ // CDN_INITDONE. We post a message to ensure the window will be visible
+ // so it will be done after the automatic last position window move.
+ PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0);
+ }
+ }
+ break;
+ case MOZ_WM_ENSUREVISIBLE:
+ EnsureWindowVisible(GetParent(hwnd));
+ break;
+ case WM_INITDIALOG:
+ {
+ OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
+ SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
+ nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData);
+ if (picker) {
+ picker->SetDialogHandle(hwnd);
+ SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ // Check to see if our parent has been torn down, if so, we close too.
+ if (wParam == kDialogTimerID) {
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
+ if (picker && picker->ClosePickerIfNeeded(true)) {
+ KillTimer(hwnd, kDialogTimerID);
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+
+// Callback hook which will dynamically allocate a buffer large enough
+// for the file picker dialog. Currently only in use on os <= XP.
+UINT_PTR CALLBACK
+nsFilePicker::MultiFilePickerHook(HWND hwnd,
+ UINT msg,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ // Finds the child drop down of a File Picker dialog and sets the
+ // maximum amount of text it can hold when typed in manually.
+ // A wParam of 0 mean 0x7FFFFFFE characters.
+ HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr,
+ L"ComboBoxEx32", nullptr );
+ if(comboBox)
+ SendMessage(comboBox, CB_LIMITTEXT, 0, 0);
+ // Store our nsFilePicker ptr for future use
+ OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam);
+ SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData);
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(pofn->lCustData);
+ if (picker) {
+ picker->SetDialogHandle(hwnd);
+ SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr);
+ }
+ }
+ break;
+ case WM_NOTIFY:
+ {
+ LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
+ if (!lpofn || !lpofn->lpOFN) {
+ return 0;
+ }
+ // CDN_SELCHANGE is sent when the selection in the list box of the file
+ // selection dialog changes
+ if (lpofn->hdr.code == CDN_SELCHANGE) {
+ HWND parentHWND = GetParent(hwnd);
+
+ // Get the required size for the selected files buffer
+ UINT newBufLength = 0;
+ int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND,
+ nullptr, 0);
+ if(requiredBufLength >= 0)
+ newBufLength += requiredBufLength;
+ else
+ newBufLength += MAX_PATH;
+
+ // If the user selects multiple files, the buffer contains the
+ // current directory followed by the file names of the selected
+ // files. So make room for the directory path. If the user
+ // selects a single file, it is no harm to add extra space.
+ requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND,
+ nullptr, 0);
+ if(requiredBufLength >= 0)
+ newBufLength += requiredBufLength;
+ else
+ newBufLength += MAX_PATH;
+
+ // Check if lpstrFile and nMaxFile are large enough
+ if (newBufLength > lpofn->lpOFN->nMaxFile) {
+ if (lpofn->lpOFN->lpstrFile)
+ delete[] lpofn->lpOFN->lpstrFile;
+
+ // We allocate FILE_BUFFER_SIZE more bytes than is needed so that
+ // if the user selects a file and holds down shift and down to
+ // select additional items, we will not continuously reallocate
+ newBufLength += FILE_BUFFER_SIZE;
+
+ wchar_t* filesBuffer = new wchar_t[newBufLength];
+ ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t));
+
+ lpofn->lpOFN->lpstrFile = filesBuffer;
+ lpofn->lpOFN->nMaxFile = newBufLength;
+ }
+ }
+ }
+ break;
+ case WM_TIMER:
+ {
+ // Check to see if our parent has been torn down, if so, we close too.
+ if (wParam == kDialogTimerID) {
+ nsFilePicker* picker =
+ reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp));
+ if (picker && picker->ClosePickerIfNeeded(true)) {
+ KillTimer(hwnd, kDialogTimerID);
+ }
+ }
+ }
+ break;
+ }
+
+ return FilePickerHook(hwnd, msg, wParam, lParam);
+}
+
+/*
+ * Vista+ callbacks
+ */
+
+HRESULT
+nsFilePicker::OnFileOk(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnFolderChanging(IFileDialog *pfd,
+ IShellItem *psiFolder)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnFolderChange(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnSelectionChange(IFileDialog *pfd)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnShareViolation(IFileDialog *pfd,
+ IShellItem *psi,
+ FDE_SHAREVIOLATION_RESPONSE *pResponse)
+{
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnTypeChange(IFileDialog *pfd)
+{
+ // Failures here result in errors due to security concerns.
+ RefPtr<IOleWindow> win;
+ pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win));
+ if (!win) {
+ NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog.");
+ return S_OK;
+ }
+ HWND hwnd = nullptr;
+ win->GetWindow(&hwnd);
+ if (!hwnd) {
+ NS_ERROR("Could not retrieve the HWND for IFileDialog.");
+ return S_OK;
+ }
+
+ SetDialogHandle(hwnd);
+ return S_OK;
+}
+
+HRESULT
+nsFilePicker::OnOverwrite(IFileDialog *pfd,
+ IShellItem *psi,
+ FDE_OVERWRITE_RESPONSE *pResponse)
+{
+ return S_OK;
+}
+
+/*
+ * Close on parent close logic
+ */
+
+bool
+nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog)
+{
+ if (!mParentWidget || !mDlgWnd)
+ return false;
+
+ nsWindow *win = static_cast<nsWindow *>(mParentWidget.get());
+ // Note, the xp callbacks hand us an inner window, so we have to step up
+ // one to get the actual dialog.
+ HWND dlgWnd;
+ if (aIsXPDialog)
+ dlgWnd = GetParent(mDlgWnd);
+ else
+ dlgWnd = mDlgWnd;
+ if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) {
+ wchar_t className[64];
+ // Make sure we have the right window
+ if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) &&
+ !wcscmp(className, L"#32770") &&
+ DestroyWindow(dlgWnd)) {
+ mDlgWnd = nullptr;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx)
+{
+ nsFilePicker* picker = (nsFilePicker*)aCtx;
+ if (picker->ClosePickerIfNeeded(false)) {
+ aTimer->Cancel();
+ }
+}
+
+void
+nsFilePicker::SetDialogHandle(HWND aWnd)
+{
+ if (!aWnd || mDlgWnd)
+ return;
+ mDlgWnd = aWnd;
+}
+
+/*
+ * Folder picker invocation
+ */
+
+// Open the older XP style folder picker dialog. We end up in this call
+// on XP systems or when platform is built without the longhorn SDK.
+bool
+nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir)
+{
+ bool result = false;
+
+ auto dirBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE);
+ wcsncpy(dirBuffer.get(), aInitialDir.get(), FILE_BUFFER_SIZE);
+ dirBuffer[FILE_BUFFER_SIZE-1] = '\0';
+
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ BROWSEINFOW browserInfo = {0};
+ browserInfo.pidlRoot = nullptr;
+ browserInfo.pszDisplayName = dirBuffer.get();
+ browserInfo.lpszTitle = mTitle.get();
+ browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
+ browserInfo.hwndOwner = adtw.get();
+ browserInfo.iImage = 0;
+ browserInfo.lParam = reinterpret_cast<LPARAM>(this);
+
+ if (!aInitialDir.IsEmpty()) {
+ // the dialog is modal so that |initialDir.get()| will be valid in
+ // BrowserCallbackProc. Thus, we don't need to clone it.
+ browserInfo.lParam = (LPARAM) aInitialDir.get();
+ browserInfo.lpfn = &BrowseCallbackProc;
+ } else {
+ browserInfo.lParam = 0;
+ browserInfo.lpfn = nullptr;
+ }
+
+ LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo);
+ if (list) {
+ result = ::SHGetPathFromIDListW(list, dirBuffer.get());
+ if (result)
+ mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer.get()));
+ // free PIDL
+ CoTaskMemFree(list);
+ }
+
+ return result;
+}
+
+/*
+ * Show a folder picker post Windows XP
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @param aWasInitError Out parameter will hold true if there was an error
+ * before the folder picker is shown.
+ * @return true if a file was selected successfully.
+*/
+bool
+nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError)
+{
+ if (!IsWin8OrLater()) {
+ // Some Windows 7 users are experiencing a race condition when some dlls
+ // that are loaded by the file picker cause a crash while attempting to shut
+ // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold
+ // an additional reference to the MTA that should prevent this race, since
+ // the MTA will remain alive until shutdown.
+ EnsureMTA ensureMTA;
+ }
+
+ RefPtr<IFileOpenDialog> dialog;
+ if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC,
+ IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ aWasInitError = false;
+
+ // hook up event callbacks
+ dialog->Advise(this, &mFDECookie);
+
+ // options
+ FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS;
+ dialog->SetOptions(fos);
+
+ // initial strings
+ dialog->SetTitle(mTitle.get());
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ dialog->SetOkButtonLabel(mOkButtonLabel.get());
+ }
+
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(
+ WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ dialog->SetFolder(folder);
+ }
+ }
+
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ // display
+ RefPtr<IShellItem> item;
+ if (FAILED(dialog->Show(adtw.get())) ||
+ FAILED(dialog->GetResult(getter_AddRefs(item))) ||
+ !item) {
+ dialog->Unadvise(mFDECookie);
+ return false;
+ }
+ dialog->Unadvise(mFDECookie);
+
+ // results
+
+ // If the user chose a Win7 Library, resolve to the library's
+ // default save folder.
+ RefPtr<IShellItem> folderPath;
+ RefPtr<IShellLibrary> shellLib;
+ CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC,
+ 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
+ return WinUtils::GetShellItemPath(item, mUnicodeFile);
+}
+
+/*
+ * File open and save picker invocation
+ */
+
+/* static */ bool
+nsFilePicker::GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType)
+{
+ MOZ_SEH_TRY {
+ if (aType == PICKER_TYPE_OPEN)
+ return ::GetOpenFileNameW(ofn);
+ else if (aType == PICKER_TYPE_SAVE)
+ return ::GetSaveFileNameW(ofn);
+ } MOZ_SEH_EXCEPT(true) {
+ NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!");
+ }
+ return false;
+}
+
+bool
+nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType)
+{
+ if (!ofn)
+ return false;
+ AutoWidgetPickerState awps(mParentWidget);
+ return GetFileNameWrapper(ofn, aType);
+}
+
+bool
+nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir)
+{
+ OPENFILENAMEW ofn = {0};
+ ofn.lStructSize = sizeof(ofn);
+ nsString filterBuffer = mFilterList;
+
+ auto fileBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE);
+ wcsncpy(fileBuffer.get(), mDefaultFilePath.get(), FILE_BUFFER_SIZE);
+ fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated
+
+ if (!aInitialDir.IsEmpty()) {
+ ofn.lpstrInitialDir = aInitialDir.get();
+ }
+
+ AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+
+ ofn.lpstrTitle = (LPCWSTR)mTitle.get();
+ ofn.lpstrFilter = (LPCWSTR)filterBuffer.get();
+ ofn.nFilterIndex = mSelectedType;
+ ofn.lpstrFile = fileBuffer.get();
+ ofn.nMaxFile = FILE_BUFFER_SIZE;
+ ofn.hwndOwner = adtw.get();
+ ofn.lCustData = reinterpret_cast<LPARAM>(this);
+ ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT |
+ OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING |
+ OFN_EXPLORER;
+
+ // Windows Vista and up won't allow you to use the new looking dialogs with
+ // a hook procedure. The hook procedure fixes a problem on XP dialogs for
+ // file picker visibility. Vista and up automatically ensures the file
+ // picker is always visible.
+ if (!IsVistaOrLater()) {
+ ofn.lpfnHook = FilePickerHook;
+ ofn.Flags |= OFN_ENABLEHOOK;
+ }
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ ofn.Flags |= OFN_DONTADDTORECENT;
+ }
+
+ NS_NAMED_LITERAL_STRING(htmExt, "html");
+
+ if (!mDefaultExtension.IsEmpty()) {
+ ofn.lpstrDefExt = mDefaultExtension.get();
+ } else if (IsDefaultPathHtml()) {
+ // Get file extension from suggested filename to detect if we are
+ // saving an html file.
+ // This is supposed to append ".htm" if user doesn't supply an
+ // extension but the behavior is sort of weird:
+ // - Often appends ".html" even if you have an extension
+ // - It obeys your extension if you put quotes around name
+ ofn.lpstrDefExt = htmExt.get();
+ }
+
+ // When possible, instead of using OFN_NOCHANGEDIR to ensure the current
+ // working directory will not change from this call, we will retrieve the
+ // current working directory before the call and restore it after the
+ // call. This flag causes problems on Windows XP for paths that are
+ // selected like C:test.txt where the user is currently at C:\somepath
+ // In which case expected result should be C:\somepath\test.txt
+ AutoRestoreWorkingPath restoreWorkingPath;
+ // If we can't get the current working directory, the best case is to
+ // use the OFN_NOCHANGEDIR flag
+ if (!restoreWorkingPath.HasWorkingPath()) {
+ ofn.Flags |= OFN_NOCHANGEDIR;
+ }
+
+ bool result = false;
+
+ switch(mMode) {
+ case modeOpen:
+ // FILE MUST EXIST!
+ ofn.Flags |= OFN_FILEMUSTEXIST;
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ break;
+
+ case modeOpenMultiple:
+ ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
+
+ // The hook set here ensures that the buffer returned will always be
+ // large enough to hold all selected files. The hook may modify the
+ // value of ofn.lpstrFile and deallocate the old buffer that it pointed
+ // to (fileBuffer). The hook assumes that the passed in value is heap
+ // allocated and that the returned value should be freed by the caller.
+ // If the hook changes the buffer, it will deallocate the old buffer.
+ // This fix would be nice to have in Vista and up, but it would force
+ // the file picker to use the old style dialogs because hooks are not
+ // allowed in the new file picker UI. We need to eventually move to
+ // the new Common File Dialogs for Vista and up.
+ if (!IsVistaOrLater()) {
+ ofn.lpfnHook = MultiFilePickerHook;
+ fileBuffer.release();
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ fileBuffer.reset(ofn.lpstrFile);
+ } else {
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN);
+ }
+ break;
+
+ case modeSave:
+ {
+ ofn.Flags |= OFN_NOREADONLYRETURN;
+
+ // Don't follow shortcuts when saving a shortcut, this can be used
+ // to trick users (bug 271732)
+ if (IsDefaultPathLink())
+ ofn.Flags |= OFN_NODEREFERENCELINKS;
+
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
+ if (!result) {
+ // Error, find out what kind.
+ if (GetLastError() == ERROR_INVALID_PARAMETER ||
+ CommDlgExtendedError() == FNERR_INVALIDFILENAME) {
+ // Probably the default file name is too long or contains illegal
+ // characters. Try again, without a starting file name.
+ ofn.lpstrFile[0] = L'\0';
+ result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE);
+ }
+ }
+ }
+ break;
+
+ default:
+ NS_NOTREACHED("unsupported file picker mode");
+ return false;
+ }
+
+ if (!result)
+ return false;
+
+ // Remember what filter type the user selected
+ mSelectedType = (int16_t)ofn.nFilterIndex;
+
+ // Single file selection, we're done
+ if (mMode != modeOpenMultiple) {
+ GetQualifiedPath(fileBuffer.get(), mUnicodeFile);
+ return true;
+ }
+
+ // Set user-selected location of file or directory. From msdn's "Open and
+ // Save As Dialog Boxes" section:
+ // If you specify OFN_EXPLORER, the directory and file name strings are '\0'
+ // separated, with an extra '\0' character after the last file name. This
+ // format enables the Explorer-style dialog boxes to return long file names
+ // that include spaces.
+ wchar_t *current = fileBuffer.get();
+
+ nsAutoString dirName(current);
+ // Sometimes dirName contains a trailing slash and sometimes it doesn't:
+ if (current[dirName.Length() - 1] != '\\')
+ dirName.Append((char16_t)'\\');
+
+ while (current && *current && *(current + wcslen(current) + 1)) {
+ current = current + wcslen(current) + 1;
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(file, false);
+
+ // Only prepend the directory if the path specified is a relative path
+ nsAutoString path;
+ if (PathIsRelativeW(current)) {
+ path = dirName + nsDependentString(current);
+ } else {
+ path = current;
+ }
+
+ nsAutoString canonicalizedPath;
+ GetQualifiedPath(path.get(), canonicalizedPath);
+ if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
+ !mFiles.AppendObject(file))
+ return false;
+ }
+
+ // Handle the case where the user selected just one file. From msdn: If you
+ // specify OFN_ALLOWMULTISELECT and the user selects only one file the
+ // lpstrFile string does not have a separator between the path and file name.
+ if (current && *current && (current == fileBuffer.get())) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ NS_ENSURE_TRUE(file, false);
+
+ nsAutoString canonicalizedPath;
+ GetQualifiedPath(current, canonicalizedPath);
+ if (NS_FAILED(file->InitWithPath(canonicalizedPath)) ||
+ !mFiles.AppendObject(file))
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Show a file picker post Windows XP
+ *
+ * @param aInitialDir The initial directory, the last used directory will be
+ * used if left blank.
+ * @param aWasInitError Out parameter will hold true if there was an error
+ * before the file picker is shown.
+ * @return true if a file was selected successfully.
+*/
+bool
+nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+
+ if (!IsWin8OrLater()) {
+ // Some Windows 7 users are experiencing a race condition when some dlls
+ // that are loaded by the file picker cause a crash while attempting to shut
+ // down the COM multithreaded apartment. By instantiating EnsureMTA, we hold
+ // an additional reference to the MTA that should prevent this race, since
+ // the MTA will remain alive until shutdown.
+ EnsureMTA ensureMTA;
+ }
+
+ RefPtr<IFileDialog> dialog;
+ if (mMode != modeSave) {
+ if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC,
+ IID_IFileOpenDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ } else {
+ if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC,
+ IID_IFileSaveDialog,
+ getter_AddRefs(dialog)))) {
+ aWasInitError = true;
+ return false;
+ }
+ }
+ aWasInitError = false;
+
+ // hook up event callbacks
+ dialog->Advise(this, &mFDECookie);
+
+ // options
+
+ FILEOPENDIALOGOPTIONS fos = 0;
+ fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT |
+ FOS_FORCEFILESYSTEM;
+
+ // Handle add to recent docs settings
+ if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
+ fos |= FOS_DONTADDTORECENT;
+ }
+
+ // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this
+ // just in case.
+ AutoRestoreWorkingPath arw;
+
+ // 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;
+ }
+
+ dialog->SetOptions(fos);
+
+ // initial strings
+
+ // title
+ dialog->SetTitle(mTitle.get());
+
+ // default filename
+ if (!mDefaultFilename.IsEmpty()) {
+ dialog->SetFileName(mDefaultFilename.get());
+ }
+
+ NS_NAMED_LITERAL_STRING(htmExt, "html");
+
+ // default extension to append to new files
+ if (!mDefaultExtension.IsEmpty()) {
+ dialog->SetDefaultExtension(mDefaultExtension.get());
+ } else if (IsDefaultPathHtml()) {
+ dialog->SetDefaultExtension(htmExt.get());
+ }
+
+ // initial location
+ if (!aInitialDir.IsEmpty()) {
+ RefPtr<IShellItem> folder;
+ if (SUCCEEDED(
+ WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr,
+ IID_IShellItem,
+ getter_AddRefs(folder)))) {
+ dialog->SetFolder(folder);
+ }
+ }
+
+ // filter types and the default index
+ if (!mComFilterList.IsEmpty()) {
+ dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get());
+ dialog->SetFileTypeIndex(mSelectedType);
+ }
+
+ // display
+
+ {
+ AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ?
+ mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr));
+ AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc);
+ AutoWidgetPickerState awps(mParentWidget);
+
+ if (FAILED(dialog->Show(adtw.get()))) {
+ dialog->Unadvise(mFDECookie);
+ return false;
+ }
+ dialog->Unadvise(mFDECookie);
+ }
+
+ // results
+
+ // Remember what filter type the user selected
+ UINT filterIdxResult;
+ if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) {
+ mSelectedType = (int16_t)filterIdxResult;
+ }
+
+ // single selection
+ if (mMode != modeOpenMultiple) {
+ RefPtr<IShellItem> item;
+ if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item)
+ return false;
+ return WinUtils::GetShellItemPath(item, mUnicodeFile);
+ }
+
+ // multiple selection
+ RefPtr<IFileOpenDialog> openDlg;
+ dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg));
+ if (!openDlg) {
+ // should not happen
+ return false;
+ }
+
+ RefPtr<IShellItemArray> items;
+ if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) {
+ return false;
+ }
+
+ DWORD count = 0;
+ items->GetCount(&count);
+ for (unsigned int idx = 0; idx < count; idx++) {
+ RefPtr<IShellItem> item;
+ nsAutoString str;
+ if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) {
+ if (!WinUtils::GetShellItemPath(item, str))
+ continue;
+ nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
+ if (file && NS_SUCCEEDED(file->InitWithPath(str)))
+ mFiles.AppendObject(file);
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIFilePicker impl.
+
+NS_IMETHODIMP
+nsFilePicker::ShowW(int16_t *aReturnVal)
+{
+ NS_ENSURE_ARG_POINTER(aReturnVal);
+
+ *aReturnVal = returnCancel;
+
+ AutoSuppressEvents supress(mParentWidget);
+
+ 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 = mLastUsedUnicodeDirectory;
+ }
+
+ // Clear previous file selections
+ mUnicodeFile.Truncate();
+ mFiles.Clear();
+
+ // On Win10, the picker doesn't support per-monitor DPI, so we open it
+ // with our context set temporarily to system-dpi-aware
+ WinUtils::AutoSystemDpiAware dpiAwareness;
+
+ // Launch the XP file/folder picker on XP and as a fallback on Vista+.
+ // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111)
+ // ClassFactory cannot supply requested class" when the checkbox for
+ // Disable Visual Themes is on in the compatability tab within the shortcut
+ // properties.
+ bool result = false, wasInitError = true;
+ if (mMode == modeGetFolder) {
+ if (IsVistaOrLater())
+ result = ShowFolderPicker(initialDir, wasInitError);
+ if (!result && wasInitError)
+ result = ShowXPFolderPicker(initialDir);
+ } else {
+ if (IsVistaOrLater())
+ result = ShowFilePicker(initialDir, wasInitError);
+ if (!result && wasInitError)
+ result = ShowXPFilePicker(initialDir);
+ }
+
+ // exit, and return returnCancel in aReturnVal
+ if (!result)
+ return NS_OK;
+
+ RememberLastUsedDirectory();
+
+ int16_t retValue = returnOK;
+ if (mMode == modeSave) {
+ // Windows does not return resultReplace, we must check if file
+ // already exists.
+ nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
+ bool flag = false;
+ if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) &&
+ NS_SUCCEEDED(file->Exists(&flag)) && flag) {
+ retValue = returnReplace;
+ }
+ }
+
+ *aReturnVal = retValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Show(int16_t *aReturnVal)
+{
+ return ShowW(aReturnVal);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ if (mUnicodeFile.IsEmpty())
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
+
+ NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+ file->InitWithPath(mUnicodeFile);
+
+ NS_ADDREF(*aFile = file);
+
+ 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);
+}
+
+// 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("\\");
+ 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(".");
+ 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(FILE_ILLEGAL_CHARACTERS, '-');
+ mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-');
+
+ 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);
+}
+
+void
+nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath)
+{
+ // Prefer a qualified path over a non qualified path.
+ // Things like c:file.txt would be accepted in Win XP but would later
+ // fail to open from the download manager.
+ wchar_t qualifiedFileBuffer[MAX_PATH];
+ if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) {
+ aOutPath.Assign(qualifiedFileBuffer);
+ } else {
+ aOutPath.Assign(aInPath);
+ }
+}
+
+void
+nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ mFilterList.Append(aTitle);
+ mFilterList.Append(char16_t('\0'));
+
+ if (aFilter.EqualsLiteral("..apps"))
+ mFilterList.AppendLiteral("*.exe;*.com");
+ else
+ {
+ nsAutoString filter(aFilter);
+ filter.StripWhitespace();
+ if (filter.EqualsLiteral("*"))
+ filter.AppendLiteral(".*");
+ mFilterList.Append(filter);
+ }
+
+ mFilterList.Append(char16_t('\0'));
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ if (IsVistaOrLater()) {
+ mComFilterList.Append(aTitle, aFilter);
+ } else {
+ AppendXPFilter(aTitle, aFilter);
+ }
+ return NS_OK;
+}
+
+void
+nsFilePicker::RememberLastUsedDirectory()
+{
+ nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) {
+ NS_WARNING("RememberLastUsedDirectory failed to init file path.");
+ return;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ nsAutoString newDir;
+ if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
+ !(mDisplayDirectory = do_QueryInterface(dir)) ||
+ NS_FAILED(mDisplayDirectory->GetPath(newDir)) ||
+ newDir.IsEmpty()) {
+ NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
+ return;
+ }
+
+ if (mLastUsedUnicodeDirectory) {
+ free(mLastUsedUnicodeDirectory);
+ mLastUsedUnicodeDirectory = nullptr;
+ }
+ mLastUsedUnicodeDirectory = 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);
+ if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) ||
+ StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
+ StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
+ return true;
+ return false;
+}
+
+bool
+nsFilePicker::IsDefaultPathHtml()
+{
+ int32_t extIndex = mDefaultFilePath.RFind(".");
+ 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;
+}
+
+void
+nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle, const nsAString& aFilter)
+{
+ COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement();
+ if (!pSpecForward) {
+ NS_WARNING("mSpecList realloc failed.");
+ return;
+ }
+ memset(pSpecForward, 0, sizeof(*pSpecForward));
+ nsString* pStr = mStrings.AppendElement(aTitle);
+ if (!pStr) {
+ NS_WARNING("mStrings.AppendElement failed.");
+ return;
+ }
+ pSpecForward->pszName = pStr->get();
+ pStr = mStrings.AppendElement(aFilter);
+ if (!pStr) {
+ NS_WARNING("mStrings.AppendElement failed.");
+ return;
+ }
+ if (aFilter.EqualsLiteral("..apps"))
+ pStr->AssignLiteral("*.exe;*.com");
+ else {
+ pStr->StripWhitespace();
+ if (pStr->EqualsLiteral("*"))
+ pStr->AppendLiteral(".*");
+ }
+ pSpecForward->pszSpec = pStr->get();
+}
diff --git a/widget/windows/nsFilePicker.h b/widget/windows/nsFilePicker.h
new file mode 100644
index 0000000000..90d8c15bc1
--- /dev/null
+++ b/widget/windows/nsFilePicker.h
@@ -0,0 +1,164 @@
+/* -*- 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>
+
+// For Vista IFileDialog interfaces which aren't exposed
+// unless _WIN32_WINNT >= _WIN32_WINNT_LONGHORN.
+#if _WIN32_WINNT < _WIN32_WINNT_LONGHORN
+#define _WIN32_WINNT_bak _WIN32_WINNT
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_LONGHORN
+#define _WIN32_IE_bak _WIN32_IE
+#undef _WIN32_IE
+#define _WIN32_IE _WIN32_IE_IE70
+#endif
+
+#include "nsIFile.h"
+#include "nsITimer.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;
+
+class nsBaseWinFilePicker :
+ public nsBaseFilePicker
+{
+public:
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString);
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString);
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension);
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension);
+
+protected:
+ nsString mDefaultFilePath;
+ nsString mDefaultFilename;
+ nsString mDefaultExtension;
+};
+
+/**
+ * Native Windows FileSelector wrapper
+ */
+
+class nsFilePicker :
+ public IFileDialogEvents,
+ public nsBaseWinFilePicker
+{
+ virtual ~nsFilePicker();
+public:
+ nsFilePicker();
+
+ NS_IMETHOD Init(mozIDOMWindowProxy *aParent, const nsAString& aTitle, int16_t aMode);
+
+ NS_DECL_ISUPPORTS
+
+ // IUnknown's QueryInterface
+ STDMETHODIMP QueryInterface(REFIID refiid, void** ppvResult);
+
+ // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker)
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex);
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex);
+ NS_IMETHOD GetFile(nsIFile * *aFile);
+ NS_IMETHOD GetFileURL(nsIURI * *aFileURL);
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles);
+ NS_IMETHOD Show(int16_t *aReturnVal);
+ NS_IMETHOD ShowW(int16_t *aReturnVal);
+ NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter);
+
+ // IFileDialogEvents
+ HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *pfd, IShellItem *psiFolder);
+ HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *pfd, IShellItem *psi, FDE_SHAREVIOLATION_RESPONSE *pResponse);
+ HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd);
+ HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *pfd, IShellItem *psi, FDE_OVERWRITE_RESPONSE *pResponse);
+
+protected:
+ enum PickerType {
+ PICKER_TYPE_OPEN,
+ PICKER_TYPE_SAVE,
+ };
+
+ /* method from nsBaseFilePicker */
+ virtual void InitNative(nsIWidget *aParent,
+ const nsAString& aTitle);
+ static void GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath);
+ void GetFilterListArray(nsString& aFilterList);
+ static bool GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType);
+ bool FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType);
+ bool ShowXPFolderPicker(const nsString& aInitialDir);
+ bool ShowXPFilePicker(const nsString& aInitialDir);
+ bool ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError);
+ bool ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError);
+ void AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter);
+ void RememberLastUsedDirectory();
+ bool IsPrivacyModeEnabled();
+ bool IsDefaultPathLink();
+ bool IsDefaultPathHtml();
+ void SetDialogHandle(HWND aWnd);
+ bool ClosePickerIfNeeded(bool aIsXPDialog);
+ static void PickerCallbackTimerFunc(nsITimer *aTimer, void *aPicker);
+ static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+ static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsString mTitle;
+ nsCString mFile;
+ nsString mFilterList;
+ int16_t mSelectedType;
+ nsCOMArray<nsIFile> mFiles;
+ static char mLastUsedDirectory[];
+ nsString mUnicodeFile;
+ static char16_t *mLastUsedUnicodeDirectory;
+ HWND mDlgWnd;
+
+ class ComDlgFilterSpec
+ {
+ public:
+ ComDlgFilterSpec() {}
+ ~ComDlgFilterSpec() {}
+
+ const uint32_t Length() {
+ return mSpecList.Length();
+ }
+
+ const bool IsEmpty() {
+ return (mSpecList.Length() == 0);
+ }
+
+ const COMDLG_FILTERSPEC* get() {
+ return mSpecList.Elements();
+ }
+
+ void Append(const nsAString& aTitle, const nsAString& aFilter);
+ private:
+ AutoTArray<COMDLG_FILTERSPEC, 1> mSpecList;
+ AutoTArray<nsString, 2> mStrings;
+ };
+
+ ComDlgFilterSpec mComFilterList;
+ DWORD mFDECookie;
+};
+
+#if defined(_WIN32_WINNT_bak)
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_bak
+#undef _WIN32_IE
+#define _WIN32_IE _WIN32_IE_bak
+#endif
+
+#endif // nsFilePicker_h__
diff --git a/widget/windows/nsIdleServiceWin.cpp b/widget/windows/nsIdleServiceWin.cpp
new file mode 100644
index 0000000000..4bafe7253c
--- /dev/null
+++ b/widget/windows/nsIdleServiceWin.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIdleServiceWin.h"
+#include <windows.h>
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceWin, nsIdleService)
+
+bool
+nsIdleServiceWin::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;
+}
+
+bool
+nsIdleServiceWin::UsePollMode()
+{
+ return true;
+}
diff --git a/widget/windows/nsIdleServiceWin.h b/widget/windows/nsIdleServiceWin.h
new file mode 100644
index 0000000000..c3fb1243fd
--- /dev/null
+++ b/widget/windows/nsIdleServiceWin.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsIdleServiceWin_h__
+#define nsIdleServiceWin_h__
+
+#include "nsIdleService.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 nsIdleServiceWin : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceWin> GetInstance()
+ {
+ RefPtr<nsIdleServiceWin> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceWin>();
+ if (!idleService) {
+ idleService = new nsIdleServiceWin();
+ }
+
+ return idleService.forget();
+ }
+
+protected:
+ nsIdleServiceWin() { }
+ virtual ~nsIdleServiceWin() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceWin_h__
diff --git a/widget/windows/nsImageClipboard.cpp b/widget/windows/nsImageClipboard.cpp
new file mode 100644
index 0000000000..fab62eab58
--- /dev/null
+++ b/widget/windows/nsImageClipboard.cpp
@@ -0,0 +1,497 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsImageClipboard.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsITransferable.h"
+#include "nsGfxCIID.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "imgIEncoder.h"
+#include "nsLiteralString.h"
+#include "nsComponentManagerUtils.h"
+
+#define BFH_LENGTH 14
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+/* Things To Do 11/8/00
+
+Check image metrics, can we support them? Do we need to?
+Any other render format? HTML?
+
+*/
+
+
+//
+// nsImageToClipboard ctor
+//
+// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard
+//
+nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5)
+ : mImage(aInImage)
+ , mWantDIBV5(aWantDIBV5)
+{
+ // nothing to do here
+}
+
+
+//
+// nsImageToClipboard dtor
+//
+// Clean up after ourselves. We know that we have created the bitmap
+// successfully if we still have a pointer to the header.
+//
+nsImageToClipboard::~nsImageToClipboard()
+{
+}
+
+
+//
+// GetPicture
+//
+// Call to get the actual bits that go on the clipboard. If an error
+// ocurred during conversion, |outBits| will be null.
+//
+// NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
+//
+nsresult
+nsImageToClipboard :: GetPicture ( HANDLE* outBits )
+{
+ NS_ASSERTION ( outBits, "Bad parameter" );
+
+ return CreateFromImage ( mImage, outBits );
+
+} // GetPicture
+
+
+//
+// CalcSize
+//
+// Computes # of bytes needed by a bitmap with the specified attributes.
+//
+int32_t
+nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes )
+{
+ int32_t HeaderMem = sizeof(BITMAPINFOHEADER);
+
+ // add size of pallette to header size
+ if (aBitsPerPixel < 16)
+ HeaderMem += aColors * sizeof(RGBQUAD);
+
+ if (aHeight < 0)
+ aHeight = -aHeight;
+
+ return (HeaderMem + (aHeight * aSpanBytes));
+}
+
+
+//
+// CalcSpanLength
+//
+// Computes the span bytes for determining the overall size of the image
+//
+int32_t
+nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount)
+{
+ int32_t spanBytes = (aWidth * aBitCount) >> 5;
+
+ if ((aWidth * aBitCount) & 0x1F)
+ spanBytes++;
+ spanBytes <<= 2;
+
+ return spanBytes;
+}
+
+
+//
+// CreateFromImage
+//
+// Do the work to setup the bitmap header and copy the bits out of the
+// image.
+//
+nsresult
+nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
+{
+ nsresult rv;
+ *outBitmap = nullptr;
+
+ RefPtr<SourceSurface> surface =
+ inImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface;
+ if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = surface->GetDataSurface();
+ } else {
+ // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8
+ // directly below once bug 995807 is fixed.
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(surface,
+ SurfaceFormat::B8G8R8A8);
+ }
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t format;
+ nsAutoString options;
+ if (mWantDIBV5) {
+ options.AppendLiteral("version=5;bpp=");
+ } else {
+ options.AppendLiteral("version=3;bpp=");
+ }
+ switch (dataSurface->GetFormat()) {
+ case SurfaceFormat::B8G8R8A8:
+ format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ options.AppendInt(32);
+ break;
+#if 0
+ // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed.
+ case SurfaceFormat::B8G8R8X8:
+ format = imgIEncoder::INPUT_FORMAT_RGB;
+ options.AppendInt(24);
+ break;
+#endif
+ default:
+ NS_NOTREACHED("Unexpected surface format");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
+
+ rv = encoder->InitFromData(map.mData, 0,
+ dataSurface->GetSize().width,
+ dataSurface->GetSize().height,
+ map.mStride,
+ format, options);
+ dataSurface->Unmap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t size;
+ encoder->GetImageBufferUsed(&size);
+ NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE);
+ HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT,
+ size - BFH_LENGTH);
+ if (!glob)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char *dst = (char*) ::GlobalLock(glob);
+ char *src;
+ rv = encoder->GetImageBuffer(&src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
+ ::GlobalUnlock(glob);
+
+ *outBitmap = (HANDLE)glob;
+ return NS_OK;
+}
+
+nsImageFromClipboard :: nsImageFromClipboard ()
+{
+ // nothing to do here
+}
+
+nsImageFromClipboard :: ~nsImageFromClipboard ( )
+{
+}
+
+//
+// GetEncodedImageStream
+//
+// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream
+//
+nsresult
+nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream )
+{
+ NS_ENSURE_ARG_POINTER (aInputStream);
+ NS_ENSURE_ARG_POINTER (aMIMEFormat);
+ nsresult rv;
+ *aInputStream = nullptr;
+
+ // pull the size information out of the BITMAPINFO header and
+ // initialize the image
+ BITMAPINFO* header = (BITMAPINFO *) aClipboardData;
+ int32_t width = header->bmiHeader.biWidth;
+ int32_t height = header->bmiHeader.biHeight;
+ // neg. heights mean the Y axis is inverted and we don't handle that case
+ NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
+
+ unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
+
+ if (rgbData) {
+ BYTE * pGlobal = (BYTE *) aClipboardData;
+ // Convert the clipboard image into RGB packed pixel data
+ rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData);
+ // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
+
+ // Map image/jpg to image/jpeg (which is how the encoder is registered).
+ if (strcmp(aMIMEFormat, kJPGImageMime) == 0)
+ encoderCID.AppendLiteral("image/jpeg");
+ else
+ encoderCID.Append(aMIMEFormat);
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv);
+ if (NS_SUCCEEDED(rv)){
+ rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */,
+ imgIEncoder::INPUT_FORMAT_RGB, EmptyString());
+ if (NS_SUCCEEDED(rv)) {
+ encoder.forget(aInputStream);
+ }
+ }
+ }
+ delete [] rgbData;
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+} // GetImage
+
+//
+// InvertRows
+//
+// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
+//
+void
+nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow)
+{
+ if (!aNumBytesPerRow)
+ return;
+
+ uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
+ unsigned char * row = new unsigned char[aNumBytesPerRow];
+
+ uint32_t currentRow = 0;
+ uint32_t lastRow = (numRows - 1) * aNumBytesPerRow;
+ while (currentRow < lastRow)
+ {
+ // store the current row into a temporary buffer
+ memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow);
+ lastRow -= aNumBytesPerRow;
+ currentRow += aNumBytesPerRow;
+ }
+
+ delete[] row;
+}
+
+//
+// ConvertColorBitMap
+//
+// Takes the clipboard bitmap and converts it into a RGB packed pixel values.
+//
+nsresult
+nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer)
+{
+ uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount;
+ uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
+ uint32_t bytesPerPixel = bitCount / 8;
+
+ if (bitCount <= 4)
+ bytesPerPixel = 1;
+
+ // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
+ // correspond to biWidth.
+ uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
+ if (rowSize % 4)
+ rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
+
+ // if our buffer includes a color map, skip over it
+ if (bitCount <= 8)
+ {
+ int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
+ aInputBuffer += bytesToSkip;
+ }
+
+ bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
+
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ // color table consists of 3 DWORDS containing the color masks...
+ colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0])));
+ colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1])));
+ colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2])));
+ CalcBitShift(&colorMasks);
+ aInputBuffer += 3 * sizeof(DWORD);
+ }
+ else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
+ {
+ // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
+ imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
+ }
+
+ // The windows clipboard image format inverts the rows
+ InvertRows(aInputBuffer, imageSize, rowSize);
+
+ if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t index = 0;
+ uint32_t writeIndex = 0;
+
+ unsigned char redValue, greenValue, blueValue;
+ uint8_t colorTableEntry = 0;
+ int8_t bit; // used for grayscale bitmaps where each bit is a pixel
+ uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
+ uint32_t pos = 0;
+
+ while (index < imageSize)
+ {
+ switch (bitCount)
+ {
+ case 1:
+ for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
+ {
+ colorTableEntry = (aInputBuffer[index] >> bit) & 1;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ break;
+ case 4:
+ {
+ // each aInputBuffer[index] entry contains data for two pixels.
+ // read the first pixel
+ colorTableEntry = aInputBuffer[index] >> 4;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+
+ if (numPixelsLeftInRow) // now read the second pixel
+ {
+ colorTableEntry = aInputBuffer[index] & 0xF;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ }
+ break;
+ case 8:
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;
+ numPixelsLeftInRow--;
+ pos += 1;
+ break;
+ case 16:
+ {
+ uint16_t num = 0;
+ num = (uint8_t) aInputBuffer[index+1];
+ num <<= 8;
+ num |= (uint8_t) aInputBuffer[index];
+
+ redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
+ greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
+ blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
+
+ // now we have the right RGB values...
+ aOutBuffer[writeIndex++] = redValue;
+ aOutBuffer[writeIndex++] = greenValue;
+ aOutBuffer[writeIndex++] = blueValue;
+ numPixelsLeftInRow--;
+ pos += 2;
+ }
+ break;
+ case 32:
+ case 24:
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t val = *((uint32_t*) (aInputBuffer + index) );
+ aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
+ numPixelsLeftInRow--;
+ pos += 4; // we read in 4 bytes of data in order to process this pixel
+ }
+ else
+ {
+ aOutBuffer[writeIndex++] = aInputBuffer[index+2];
+ aOutBuffer[writeIndex++] = aInputBuffer[index+1];
+ aOutBuffer[writeIndex++] = aInputBuffer[index];
+ numPixelsLeftInRow--;
+ pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
+ }
+ break;
+ default:
+ // This is probably the wrong place to check this...
+ return NS_ERROR_FAILURE;
+ }
+
+ index += bytesPerPixel; // increment our loop counter
+
+ if (!numPixelsLeftInRow)
+ {
+ if (rowSize != pos)
+ {
+ // advance index to skip over remaining padding bytes
+ index += (rowSize - pos);
+ }
+ numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
+ pos = 0;
+ }
+
+ } // while we still have bytes to process
+ }
+
+ return NS_OK;
+}
+
+void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength)
+{
+ // find the rightmost 1
+ uint8_t pos;
+ bool started = false;
+ aBegin = aLength = 0;
+ for (pos = 0; pos <= 31; pos++)
+ {
+ if (!started && (aMask & (1 << pos)))
+ {
+ aBegin = pos;
+ started = true;
+ }
+ else if (started && !(aMask & (1 << pos)))
+ {
+ aLength = pos - aBegin;
+ break;
+ }
+ }
+}
+
+void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask)
+{
+ uint8_t begin, length;
+ // red
+ CalcBitmask(aColorMask->red, begin, length);
+ aColorMask->redRightShift = begin;
+ aColorMask->redLeftShift = 8 - length;
+ // green
+ CalcBitmask(aColorMask->green, begin, length);
+ aColorMask->greenRightShift = begin;
+ aColorMask->greenLeftShift = 8 - length;
+ // blue
+ CalcBitmask(aColorMask->blue, begin, length);
+ aColorMask->blueRightShift = begin;
+ aColorMask->blueLeftShift = 8 - length;
+}
diff --git a/widget/windows/nsImageClipboard.h b/widget/windows/nsImageClipboard.h
new file mode 100644
index 0000000000..25b33cc56c
--- /dev/null
+++ b/widget/windows/nsImageClipboard.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 nsImageClipboard_h
+#define nsImageClipboard_h
+
+/* Things To Do 11/8/00
+
+Check image metrics, can we support them? Do we need to?
+Any other render format? HTML?
+
+*/
+
+#include "nsError.h"
+#include <windows.h>
+
+#include "nsCOMPtr.h"
+#include "imgIContainer.h"
+#include "nsIInputStream.h"
+
+
+//
+// nsImageToClipboard
+//
+// A utility class that takes an imgIContainer and does all the bitmap magic
+// to allow us to put it on the clipboard
+//
+class nsImageToClipboard
+{
+public:
+ nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5 = true);
+ ~nsImageToClipboard();
+
+ // Call to get the actual bits that go on the clipboard. If |nullptr|, the
+ // setup operations have failed.
+ //
+ // NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
+ nsresult GetPicture ( HANDLE* outBits ) ;
+
+private:
+
+ // Computes # of bytes needed by a bitmap with the specified attributes.
+ int32_t CalcSize(int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes);
+ int32_t CalcSpanLength(uint32_t aWidth, uint32_t aBitCount);
+
+ // Do the work
+ nsresult CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap );
+
+ nsCOMPtr<imgIContainer> mImage; // the image we're working with
+ bool mWantDIBV5;
+
+}; // class nsImageToClipboard
+
+
+struct bitFields {
+ uint32_t red;
+ uint32_t green;
+ uint32_t blue;
+ uint8_t redLeftShift;
+ uint8_t redRightShift;
+ uint8_t greenLeftShift;
+ uint8_t greenRightShift;
+ uint8_t blueLeftShift;
+ uint8_t blueRightShift;
+};
+
+//
+// nsImageFromClipboard
+//
+// A utility class that takes a DIB from the win32 clipboard and does
+// all the bitmap magic to convert it to a PNG or a JPEG in the form of a nsIInputStream
+//
+class nsImageFromClipboard
+{
+public:
+ nsImageFromClipboard () ;
+ ~nsImageFromClipboard ( ) ;
+
+ // Retrieve the newly created image
+ nsresult GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** outImage);
+
+private:
+
+ void InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow);
+ nsresult ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer);
+ void CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength);
+ void CalcBitShift(bitFields * aColorMask);
+
+}; // nsImageFromClipboard
+
+#endif
diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..7c427ac9fe
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.cpp
@@ -0,0 +1,701 @@
+/* -*- 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 <windows.h>
+#include <shellapi.h>
+#include "nsStyleConsts.h"
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+#include "gfxFont.h"
+#include "WinUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsVersion.h"
+#include "gfxFontConstants.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+//static
+LookAndFeel::OperatingSystemVersion
+nsLookAndFeel::GetOperatingSystemVersion()
+{
+ static OperatingSystemVersion version = eOperatingSystemVersion_Unknown;
+
+ if (version != eOperatingSystemVersion_Unknown) {
+ return version;
+ }
+
+ if (IsWin10OrLater()) {
+ version = eOperatingSystemVersion_Windows10;
+ } else if (IsWin8OrLater()) {
+ version = eOperatingSystemVersion_Windows8;
+ } else if (IsWin7OrLater()) {
+ version = eOperatingSystemVersion_Windows7;
+ } else if (IsVistaOrLater()) {
+ version = eOperatingSystemVersion_WindowsVista;
+ } else {
+ version = eOperatingSystemVersion_WindowsXP;
+ }
+
+ return version;
+}
+
+static nsresult GetColorFromTheme(nsUXThemeClass cls,
+ int32_t aPart,
+ int32_t aState,
+ int32_t aPropId,
+ nscolor &aColor)
+{
+ COLORREF color;
+ HRESULT hr = GetThemeColor(nsUXThemeData::GetTheme(cls), aPart, aState, aPropId, &color);
+ if (hr == S_OK)
+ {
+ aColor = COLOREF_2_NSRGB(color);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+static int32_t GetSystemParam(long flag, int32_t def)
+{
+ DWORD value;
+ return ::SystemParametersInfo(flag, 0, &value, 0) ? value : def;
+}
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+ , mUseAccessibilityTheme(0)
+{
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE,
+ WinUtils::IsTouchDeviceSupportPresent());
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ nsresult res = NS_OK;
+
+ int idx;
+ switch (aID) {
+ case eColorID_WindowBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_WindowForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID_WidgetBackground:
+ idx = COLOR_BTNFACE;
+ break;
+ case eColorID_WidgetForeground:
+ idx = COLOR_BTNTEXT;
+ break;
+ case eColorID_WidgetSelectBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID_WidgetSelectForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_Widget3DHighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case eColorID_Widget3DShadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case eColorID_TextBackground:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_TextForeground:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ return NS_OK;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ return NS_OK;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ return NS_OK;
+
+ // New CSS 2 Color definitions
+ case eColorID_activeborder:
+ idx = COLOR_ACTIVEBORDER;
+ break;
+ case eColorID_activecaption:
+ idx = COLOR_ACTIVECAPTION;
+ break;
+ case eColorID_appworkspace:
+ idx = COLOR_APPWORKSPACE;
+ break;
+ case eColorID_background:
+ idx = COLOR_BACKGROUND;
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ idx = COLOR_BTNFACE;
+ break;
+ case eColorID_buttonhighlight:
+ idx = COLOR_BTNHIGHLIGHT;
+ break;
+ case eColorID_buttonshadow:
+ idx = COLOR_BTNSHADOW;
+ break;
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ idx = COLOR_BTNTEXT;
+ break;
+ case eColorID_captiontext:
+ idx = COLOR_CAPTIONTEXT;
+ break;
+ case eColorID_graytext:
+ idx = COLOR_GRAYTEXT;
+ break;
+ case eColorID_highlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_menuhover:
+ idx = COLOR_HIGHLIGHT;
+ break;
+ case eColorID__moz_menubarhovertext:
+ if (!IsVistaOrLater() || !IsAppThemed())
+ {
+ idx = nsUXThemeData::sFlatMenus ?
+ COLOR_HIGHLIGHTTEXT :
+ COLOR_MENUTEXT;
+ break;
+ }
+ // Fall through
+ case eColorID__moz_menuhovertext:
+ if (IsVistaOrLater() && IsAppThemed())
+ {
+ res = ::GetColorFromTheme(eUXMenu,
+ MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ // fall through to highlight case
+ }
+ case eColorID_highlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID_inactiveborder:
+ idx = COLOR_INACTIVEBORDER;
+ break;
+ case eColorID_inactivecaption:
+ idx = COLOR_INACTIVECAPTION;
+ break;
+ case eColorID_inactivecaptiontext:
+ idx = COLOR_INACTIVECAPTIONTEXT;
+ break;
+ case eColorID_infobackground:
+ idx = COLOR_INFOBK;
+ break;
+ case eColorID_infotext:
+ idx = COLOR_INFOTEXT;
+ break;
+ case eColorID_menu:
+ idx = COLOR_MENU;
+ break;
+ case eColorID_menutext:
+ case eColorID__moz_menubartext:
+ idx = COLOR_MENUTEXT;
+ break;
+ case eColorID_scrollbar:
+ idx = COLOR_SCROLLBAR;
+ break;
+ case eColorID_threeddarkshadow:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case eColorID_threedface:
+ idx = COLOR_3DFACE;
+ break;
+ case eColorID_threedhighlight:
+ idx = COLOR_3DHIGHLIGHT;
+ break;
+ case eColorID_threedlightshadow:
+ idx = COLOR_3DLIGHT;
+ break;
+ case eColorID_threedshadow:
+ idx = COLOR_3DSHADOW;
+ break;
+ case eColorID_window:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID_windowframe:
+ idx = COLOR_WINDOWFRAME;
+ break;
+ case eColorID_windowtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_oddtreerow:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ idx = COLOR_WINDOW;
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dialog:
+ case eColorID__moz_cellhighlight:
+ idx = COLOR_3DFACE;
+ break;
+ case eColorID__moz_win_mediatext:
+ if (IsVistaOrLater() && IsAppThemed()) {
+ res = ::GetColorFromTheme(eUXMediaToolbar,
+ TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_win_communicationstext:
+ if (IsVistaOrLater() && IsAppThemed())
+ {
+ res = ::GetColorFromTheme(eUXCommunicationsToolbar,
+ TP_BUTTON, TS_NORMAL, TMT_TEXTCOLOR, aColor);
+ if (NS_SUCCEEDED(res))
+ return res;
+ }
+ // if we've gotten here just return -moz-dialogtext instead
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ idx = COLOR_WINDOWTEXT;
+ break;
+ case eColorID__moz_dragtargetzone:
+ idx = COLOR_HIGHLIGHTTEXT;
+ break;
+ case eColorID__moz_buttondefault:
+ idx = COLOR_3DDKSHADOW;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ idx = COLOR_HOTLIGHT;
+ break;
+ default:
+ idx = COLOR_WINDOW;
+ break;
+ }
+
+ DWORD color = ::GetSysColor(idx);
+ aColor = COLOREF_2_NSRGB(color);
+
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = (int32_t)::GetCaretBlinkTime();
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ // This will default to the Windows' default
+ // (400ms) on error.
+ aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400);
+ break;
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case eIntID_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 eIntID_DragThresholdY:
+ aResult = ::GetSystemMetrics(SM_CYDRAG) - 1;
+ break;
+ case eIntID_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.
+ if (XRE_IsContentProcess()) {
+ // If we're running in the content process, then the parent should
+ // have sent us the accessibility state when nsLookAndFeel
+ // initialized, and stashed it in the mUseAccessibilityTheme cache.
+ aResult = mUseAccessibilityTheme;
+ } else {
+ // Otherwise, we can ask the OS to see if we're using High Contrast
+ // mode.
+ aResult = nsUXThemeData::IsHighContrastOn();
+ }
+ break;
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 0;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_WindowsClassic:
+ aResult = !IsAppThemed();
+ break;
+ case eIntID_TouchEnabled:
+ aResult = WinUtils::IsTouchDeviceSupportPresent();
+ break;
+ case eIntID_WindowsDefaultTheme:
+ aResult = nsUXThemeData::IsDefaultWindowTheme();
+ break;
+ case eIntID_WindowsThemeIdentifier:
+ aResult = nsUXThemeData::GetNativeThemeId();
+ break;
+
+ case eIntID_OperatingSystemVersionIdentifier:
+ {
+ aResult = GetOperatingSystemVersion();
+ break;
+ }
+
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_DWMCompositor:
+ aResult = nsUXThemeData::CheckForCompositor();
+ break;
+ case eIntID_WindowsGlass:
+ // Aero Glass is only available prior to Windows 8 when DWM is used.
+ aResult = (nsUXThemeData::CheckForCompositor() && !IsWin8OrLater());
+ break;
+ case eIntID_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;
+ // fall through for the right-to-left handling.
+ 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 eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DASHED;
+ break;
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = true;
+ break;
+ case eIntID_UseOverlayScrollbars:
+ aResult = false;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ aResult = 0;
+ break;
+ case eIntID_ScrollbarDisplayOnMouseMove:
+ aResult = 1;
+ break;
+ case eIntID_ScrollbarFadeBeginDelay:
+ aResult = 2500;
+ break;
+ case eIntID_ScrollbarFadeDuration:
+ aResult = 350;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+static bool
+GetSysFontInfo(HDC aHDC, LookAndFeel::FontID anID,
+ nsString &aFontName,
+ gfxFontStyle &aFontStyle)
+{
+ LOGFONTW* ptrLogFont = nullptr;
+ LOGFONTW logFont;
+ NONCLIENTMETRICSW ncm;
+ char16_t name[LF_FACESIZE];
+ bool useShellDlg = false;
+
+ // Depending on which stock font we want, there are a couple of
+ // places we might have to look it up.
+ switch (anID) {
+ case LookAndFeel::eFont_Icon:
+ if (!::SystemParametersInfoW(SPI_GETICONTITLELOGFONT,
+ sizeof(logFont), (PVOID)&logFont, 0))
+ return false;
+
+ ptrLogFont = &logFont;
+ break;
+
+ default:
+ ncm.cbSize = sizeof(NONCLIENTMETRICSW);
+ if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
+ sizeof(ncm), (PVOID)&ncm, 0))
+ return false;
+
+ switch (anID) {
+ case LookAndFeel::eFont_Menu:
+ case LookAndFeel::eFont_PullDownMenu:
+ ptrLogFont = &ncm.lfMenuFont;
+ break;
+ case LookAndFeel::eFont_Caption:
+ ptrLogFont = &ncm.lfCaptionFont;
+ break;
+ case LookAndFeel::eFont_SmallCaption:
+ ptrLogFont = &ncm.lfSmCaptionFont;
+ break;
+ case LookAndFeel::eFont_StatusBar:
+ case LookAndFeel::eFont_Tooltips:
+ ptrLogFont = &ncm.lfStatusFont;
+ break;
+ case LookAndFeel::eFont_Widget:
+ case LookAndFeel::eFont_Dialog:
+ case LookAndFeel::eFont_Button:
+ case LookAndFeel::eFont_Field:
+ case LookAndFeel::eFont_List:
+ // 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.
+ useShellDlg = true;
+ // Fall through so that we can get size from lfMessageFont;
+ // but later we'll use the (virtual) "MS Shell Dlg 2" font name
+ // instead of the LOGFONT's.
+ default:
+ ptrLogFont = &ncm.lfMessageFont;
+ break;
+ }
+ break;
+ }
+
+ // Get scaling factor from physical to logical pixels
+ double pixelScale = 1.0 / WinUtils::SystemScaleFactor();
+
+ // 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 = -ptrLogFont->lfHeight;
+ if (pixelHeight < 0) {
+ HFONT hFont = ::CreateFontIndirectW(ptrLogFont);
+ if (!hFont)
+ return false;
+ HGDIOBJ hObject = ::SelectObject(aHDC, hFont);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(aHDC, &tm);
+ ::SelectObject(aHDC, hObject);
+ ::DeleteObject(hFont);
+ 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;
+
+ aFontStyle.size = pixelHeight;
+
+ // FIXME: What about oblique?
+ aFontStyle.style =
+ (ptrLogFont->lfItalic) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL;
+
+ // FIXME: Other weights?
+ aFontStyle.weight =
+ (ptrLogFont->lfWeight == FW_BOLD ?
+ NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL);
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+
+ aFontStyle.systemFont = true;
+
+ if (useShellDlg) {
+ aFontName = NS_LITERAL_STRING("MS Shell Dlg 2");
+ } else {
+ memcpy(name, ptrLogFont->lfFaceName, LF_FACESIZE*sizeof(char16_t));
+ aFontName = name;
+ }
+
+ return true;
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID anID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ HDC tdc = GetDC(nullptr);
+ bool status = GetSysFontInfo(tdc, anID, aFontName, aFontStyle);
+ ReleaseDC(nullptr, tdc);
+ // now convert the logical font size from GetSysFontInfo into device pixels for layout
+ aFontStyle.size *= aDevPixPerCSSPixel;
+ return status;
+}
+
+/* virtual */
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf
+ return UNICODE_BLACK_CIRCLE_CHAR;
+}
+
+nsTArray<LookAndFeelInt>
+nsLookAndFeel::GetIntCacheImpl()
+{
+ nsTArray<LookAndFeelInt> lookAndFeelIntCache =
+ nsXPLookAndFeel::GetIntCacheImpl();
+
+ LookAndFeelInt useAccessibilityTheme;
+ useAccessibilityTheme.id = eIntID_UseAccessibilityTheme;
+ useAccessibilityTheme.value = GetInt(eIntID_UseAccessibilityTheme);
+ lookAndFeelIntCache.AppendElement(useAccessibilityTheme);
+
+ return lookAndFeelIntCache;
+}
+
+void
+nsLookAndFeel::SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache)
+{
+ for (auto entry : aLookAndFeelIntCache) {
+ if (entry.id == eIntID_UseAccessibilityTheme) {
+ mUseAccessibilityTheme = entry.value;
+ break;
+ }
+ }
+}
+
diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h
new file mode 100644
index 0000000000..bc2d158b6f
--- /dev/null
+++ b/widget/windows/nsLookAndFeel.h
@@ -0,0 +1,65 @@
+/* -*- 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 "nsXPLookAndFeel.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
+typedef enum _AR_STATE
+{
+ AR_ENABLED = 0x0,
+ AR_DISABLED = 0x1,
+ AR_SUPPRESSED = 0x2,
+ AR_REMOTESESSION = 0x4,
+ AR_MULTIMON = 0x8,
+ AR_NOSENSOR = 0x10,
+ AR_NOT_SUPPORTED = 0x20,
+ AR_DOCKED = 0x40,
+ AR_LAPTOP = 0x80
+} AR_STATE, *PAR_STATE;
+#endif
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+ static OperatingSystemVersion GetOperatingSystemVersion();
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl();
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+
+private:
+ int32_t mUseAccessibilityTheme;
+};
+
+#endif
diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp
new file mode 100644
index 0000000000..8579070767
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.cpp
@@ -0,0 +1,109 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsToolkit.h"
+#include "nsWidgetsCID.h"
+#include "nsIDragService.h"
+
+/*
+ * class nsNativeDragSource
+ */
+nsNativeDragSource::nsNativeDragSource(nsIDOMDataTransfer* aDataTransfer) :
+ m_cRef(0),
+ m_hCursor(nullptr),
+ mUserCancelled(false)
+{
+ mDataTransfer = do_QueryInterface(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)
+{
+ static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+ 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..19d1275993
--- /dev/null
+++ b/widget/windows/nsNativeDragSource.h
@@ -0,0 +1,66 @@
+/* -*- 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 "nsIDOMDataTransfer.h"
+#include "nsCOMPtr.h"
+#include <ole2.h>
+#include <oleidl.h>
+#include "mozilla/Attributes.h"
+
+//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);
+ nsNativeDragSource(nsIDOMDataTransfer* 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
+ nsCOMPtr<nsIDOMDataTransfer> 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..1686642a30
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.cpp
@@ -0,0 +1,486 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsIDOMNode.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;
+
+/* Define Interface IDs */
+static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID);
+
+// This is cached for Leave notification
+static POINTL gDragLastPoint;
+
+/*
+ * 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)
+{
+ static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+ mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ /*
+ * Create/Get the DragService that we have implemented
+ */
+ CallGetService(kCDragServiceCID, &mDragService);
+}
+
+nsNativeDragTarget::~nsNativeDragTarget()
+{
+ NS_RELEASE(mDragService);
+
+ 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)
+{
+ nsEventStatus status;
+ 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.inputSource = static_cast<nsBaseDragService*>(mDragService)->GetInputSource();
+
+ mWidget->DispatchEvent(&event, status);
+}
+
+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);
+ 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()) {
+ 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);
+ 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;
+ }
+
+ // 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()) {
+ POINT pt = { ptl.x, ptl.y };
+ GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
+ }
+
+ mDragService->FireDragEventAtSource(eDrag);
+ // 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<nsIDOMNode> 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).
+ mDragService->EndDragSession(false);
+ }
+ }
+
+ // 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) {
+ mDragService->EndDragSession(false);
+ }
+ 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);
+ 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));
+ serv->EndDragSession(true);
+
+ // 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..fbc28fc929
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.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 _nsNativeDragTarget_h_
+#define _nsNativeDragTarget_h_
+
+#include "nsCOMPtr.h"
+#include "nsIDragSession.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:
+ 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.
+ 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.
+ 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.
+ STDMETHODIMP Drop(LPDATAOBJECT pSource, DWORD grfKeyState,
+ POINTL point, DWORD* pEffect);
+ /**
+ * Cancel the current drag session, if any.
+ */
+ void DragCancel();
+
+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;
+ nsIDragService * mDragService;
+ // Drag target helper
+ IDropTargetHelper * GetDropTargetHelper();
+
+
+private:
+ // Drag target helper
+ IDropTargetHelper * mDropTargetHelper;
+};
+
+#endif // _nsNativeDragTarget_h_
+
+
diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp
new file mode 100644
index 0000000000..4ff6b0af96
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -0,0 +1,4168 @@
+/* -*- 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 "mozilla/EventStates.h"
+#include "mozilla/Logging.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsDeviceContext.h"
+#include "nsRenderingContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsTransform2D.h"
+#include "nsThemeConstants.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsLookAndFeel.h"
+#include "nsMenuFrame.h"
+#include "nsGkAtoms.h"
+#include <malloc.h>
+#include "nsWindow.h"
+#include "nsIComboboxControlFrame.h"
+#include "prinrval.h"
+#include "WinUtils.h"
+
+#include "gfxPlatform.h"
+#include "gfxContext.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "gfxWindowsNativeDrawing.h"
+
+#include "nsUXThemeData.h"
+#include "nsUXThemeConstants.h"
+#include <algorithm>
+
+using mozilla::IsVistaOrLater;
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
+
+nsNativeThemeWin::nsNativeThemeWin() :
+ mProgressDeterminateTimeStamp(TimeStamp::Now()),
+ mProgressIndeterminateTimeStamp(TimeStamp::Now())
+{
+ // If there is a relevant change in forms.css for windows platform,
+ // static widget style variables (e.g. sButtonBorderSize) should be
+ // reinitialized here.
+}
+
+nsNativeThemeWin::~nsNativeThemeWin()
+{
+ nsUXThemeData::Invalidate();
+}
+
+static int32_t
+GetTopLevelWindowActiveState(nsIFrame *aFrame)
+{
+ // Used by window frame and button box rendering. We can end up in here in
+ // the content process when rendering one of these moz styles freely in a
+ // page. Bail in this case, there is no applicable window focus state.
+ if (!XRE_IsParentProcess()) {
+ return mozilla::widget::themeconst::FS_INACTIVE;
+ }
+ // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
+ // until it finds a real window.
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ nsWindowBase * window = static_cast<nsWindowBase*>(widget);
+ if (!window)
+ return mozilla::widget::themeconst::FS_INACTIVE;
+ if (widget && !window->IsTopLevelWidget() &&
+ !(window = window->GetParentWindowBase(false)))
+ return mozilla::widget::themeconst::FS_INACTIVE;
+
+ if (window->GetWindowHandle() == ::GetActiveWindow())
+ return mozilla::widget::themeconst::FS_ACTIVE;
+ return mozilla::widget::themeconst::FS_INACTIVE;
+}
+
+static int32_t
+GetWindowFrameButtonState(nsIFrame* aFrame, EventStates eventState)
+{
+ if (GetTopLevelWindowActiveState(aFrame) ==
+ mozilla::widget::themeconst::FS_INACTIVE) {
+ if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ return mozilla::widget::themeconst::BS_HOT;
+ return mozilla::widget::themeconst::BS_INACTIVE;
+ }
+
+ if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
+ return mozilla::widget::themeconst::BS_PUSHED;
+ return mozilla::widget::themeconst::BS_HOT;
+ }
+ return mozilla::widget::themeconst::BS_NORMAL;
+}
+
+static int32_t
+GetClassicWindowFrameButtonState(EventStates eventState)
+{
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE) &&
+ eventState.HasState(NS_EVENT_STATE_HOVER))
+ return DFCS_BUTTONPUSH|DFCS_PUSHED;
+ return DFCS_BUTTONPUSH;
+}
+
+static bool
+IsTopLevelMenu(nsIFrame *aFrame)
+{
+ bool isTopLevel(false);
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+ return isTopLevel;
+}
+
+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
+ double scaleFactor = nsIWidget::DefaultScaleOverride();
+ if (scaleFactor <= 0.0) {
+ scaleFactor = WinUtils::LogToPhysFactor(hdc);
+ }
+ 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;
+}
+
+/* DrawThemeBGRTLAware - render a theme part based on rtl state.
+ * Some widgets are not direction-neutral and need to be drawn reversed for
+ * RTL. Windows provides a way to do this with SetLayout, but this reverses
+ * the entire drawing area of a given device context, which means that its
+ * use will also affect the positioning of the widget. There are two ways
+ * to work around this:
+ *
+ * Option 1: Alter the position of the rect that we send so that we cancel
+ * out the positioning effects of SetLayout
+ * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto
+ * that, and then transfer the results back to our DC
+ *
+ * This function tries to implement option 1, under the assumption that the
+ * correct way to reverse the effects of SetLayout is to translate the rect
+ * such that the offset from the DC bitmap's left edge to the old rect's
+ * left edge is equal to the offset from the DC bitmap's right edge to the
+ * new rect's right edge. In other words,
+ * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right)
+ */
+static HRESULT
+DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart, int aState,
+ const RECT *aWidgetRect, const RECT *aClipRect,
+ bool aIsRtl)
+{
+ NS_ASSERTION(aTheme, "Bad theme handle.");
+ NS_ASSERTION(aHdc, "Bad hdc.");
+ NS_ASSERTION(aWidgetRect, "Bad rect.");
+ NS_ASSERTION(aClipRect, "Bad clip rect.");
+
+ if (!aIsRtl) {
+ return DrawThemeBackground(aTheme, aHdc, aPart, aState,
+ aWidgetRect, aClipRect);
+ }
+
+ HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP);
+ BITMAP bitmap;
+ POINT vpOrg;
+
+ if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) &&
+ GetViewportOrgEx(aHdc, &vpOrg)) {
+ RECT newWRect(*aWidgetRect);
+ newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2*vpOrg.x);
+ newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2*vpOrg.x);
+
+ RECT newCRect;
+ RECT *newCRectPtr = nullptr;
+
+ if (aClipRect) {
+ newCRect.top = aClipRect->top;
+ newCRect.bottom = aClipRect->bottom;
+ newCRect.left = bitmap.bmWidth - (aClipRect->right + 2*vpOrg.x);
+ newCRect.right = bitmap.bmWidth - (aClipRect->left + 2*vpOrg.x);
+ newCRectPtr = &newCRect;
+ }
+
+ SetLayout(aHdc, LAYOUT_RTL);
+ HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect,
+ newCRectPtr);
+ SetLayout(aHdc, 0);
+ if (SUCCEEDED(hr)) {
+ return hr;
+ }
+ }
+ return DrawThemeBackground(aTheme, aHdc, aPart, aState,
+ aWidgetRect, aClipRect);
+}
+
+/*
+ * Caption button padding data - 'hot' button padding.
+ * These areas are considered hot, in that they activate
+ * a button when hovered or clicked. The button graphic
+ * is drawn inside the padding border. Unrecognized themes
+ * are treated as their recognized counterparts for now.
+ * left top right bottom
+ * classic min 1 2 0 1
+ * classic max 0 2 1 1
+ * classic close 1 2 2 1
+ *
+ * aero basic min 1 2 0 2
+ * aero basic max 0 2 1 2
+ * aero basic close 1 2 1 2
+ *
+ * xp theme min 0 2 0 2
+ * xp theme max 0 2 1 2
+ * xp theme close 1 2 2 2
+ *
+ * 'cold' button padding - generic button padding, should
+ * be handled in css.
+ * left top right bottom
+ * classic min 0 0 0 0
+ * classic max 0 0 0 0
+ * classic close 0 0 0 0
+ *
+ * aero basic min 0 0 1 0
+ * aero basic max 1 0 0 0
+ * aero basic close 0 0 0 0
+ *
+ * xp theme min 0 0 1 0
+ * xp theme max 1 0 0 0
+ * xp theme close 0 0 0 0
+ */
+
+enum CaptionDesktopTheme {
+ CAPTION_CLASSIC = 0,
+ CAPTION_BASIC,
+ CAPTION_XPTHEME,
+};
+
+enum CaptionButton {
+ CAPTIONBUTTON_MINIMIZE = 0,
+ CAPTIONBUTTON_RESTORE,
+ CAPTIONBUTTON_CLOSE,
+};
+
+struct CaptionButtonPadding {
+ RECT hotPadding[3];
+};
+
+// RECT: left, top, right, bottom
+static CaptionButtonPadding buttonData[3] = {
+ {
+ { { 1, 2, 0, 1 }, { 0, 2, 1, 1 }, { 1, 2, 2, 1 } }
+ },
+ {
+ { { 1, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } }
+ },
+ {
+ { { 0, 2, 0, 2 }, { 0, 2, 1, 2 }, { 1, 2, 2, 2 } }
+ }
+};
+
+// Adds "hot" caption button padding to minimum widget size.
+static void
+AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) {
+ if (!aSize)
+ return;
+ RECT offset;
+ if (!IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else if (!IsVistaOrLater())
+ offset = buttonData[CAPTION_XPTHEME].hotPadding[button];
+ else
+ offset = buttonData[CAPTION_BASIC].hotPadding[button];
+ aSize->width += offset.left + offset.right;
+ aSize->height += offset.top + offset.bottom;
+}
+
+// If we've added padding to the minimum widget size, offset
+// the area we draw into to compensate.
+static void
+OffsetBackgroundRect(RECT& rect, CaptionButton button) {
+ RECT offset;
+ if (!IsAppThemed())
+ offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
+ else if (!IsVistaOrLater())
+ offset = buttonData[CAPTION_XPTHEME].hotPadding[button];
+ else
+ offset = buttonData[CAPTION_BASIC].hotPadding[button];
+ rect.left += offset.left;
+ rect.top += offset.top;
+ rect.right -= offset.right;
+ rect.bottom -= offset.bottom;
+}
+
+/*
+ * 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 kProgressHorizontalVistaOverlaySize = 120;
+// The width of the overlay used for the horizontal indeterminate progress bars on XP.
+static const int32_t kProgressHorizontalXPOverlaySize = 55;
+// 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)
+{
+ if (aIsVertical) {
+ if (IsVistaOrLater()) {
+ return PP_MOVEOVERLAYVERT;
+ }
+ return PP_CHUNKVERT;
+ } else {
+ if (IsVistaOrLater()) {
+ return PP_MOVEOVERLAY;
+ }
+ return PP_CHUNK;
+ }
+}
+
+/*
+ * 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 (IsVistaOrLater()) {
+ if (aIsVertical) {
+ return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
+ : kProgressVerticalOverlaySize;
+ }
+ return kProgressHorizontalVistaOverlaySize;
+ }
+ return kProgressHorizontalXPOverlaySize;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * DrawChunkProgressMeter - renders an xp style chunked progress meter. Called
+ * by DrawProgressMeter.
+ *
+ * @param aTheme progress theme handle
+ * @param aHdc hdc returned by gfxWindowsNativeDrawing
+ * @param aPart the PP_X progress part
+ * @param aState the theme state
+ * @param aFrame the elements frame
+ * @param aWidgetRect bounding rect for the widget
+ * @param aClipRect dirty rect that needs drawing.
+ * @param aAppUnits app units per device pixel
+ * @param aIsIndeterm is an indeterminate progress?
+ * @param aIsVertical render a vertical progress?
+ * @param aIsRtl direction is rtl
+ */
+static void
+DrawChunkProgressMeter(HTHEME aTheme, HDC aHdc, int aPart,
+ int aState, nsIFrame* aFrame, RECT* aWidgetRect,
+ RECT* aClipRect, gfxFloat aAppUnits, bool aIsIndeterm,
+ bool aIsVertical, bool aIsRtl)
+{
+ NS_ASSERTION(aTheme, "Bad theme.");
+ NS_ASSERTION(aHdc, "Bad hdc.");
+ NS_ASSERTION(aWidgetRect, "Bad rect.");
+ NS_ASSERTION(aClipRect, "Bad clip rect.");
+ NS_ASSERTION(aFrame, "Bad frame.");
+
+ // For horizontal meters, the theme lib paints the right graphic but doesn't
+ // paint the chunks, so we do that manually. For vertical meters, the theme
+ // library draws everything correctly.
+ if (aIsVertical) {
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect);
+ return;
+ }
+
+ // query for the proper chunk metrics
+ int chunkSize, spaceSize;
+ if (FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState,
+ TMT_PROGRESSCHUNKSIZE, &chunkSize)) ||
+ FAILED(GetThemeMetric(aTheme, aHdc, aPart, aState,
+ TMT_PROGRESSSPACESIZE, &spaceSize))) {
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect, aClipRect);
+ return;
+ }
+
+ // render chunks
+ if (!aIsRtl || aIsIndeterm) {
+ for (int chunk = aWidgetRect->left; chunk <= aWidgetRect->right;
+ chunk += (chunkSize+spaceSize)) {
+ if (!aIsIndeterm && ((chunk + chunkSize) > aWidgetRect->right)) {
+ // aWidgetRect->right represents the end of the meter. Partial blocks
+ // don't get rendered with one exception, so exit here if we don't have
+ // a full chunk to draw.
+ // The above is true *except* when the meter is at 100% fill, in which
+ // case Windows renders any remaining partial block. Query the parent
+ // frame to find out if we're at 100%.
+ if (!IsProgressMeterFilled(aFrame)) {
+ break;
+ }
+ }
+ RECT bounds =
+ { chunk, aWidgetRect->top, chunk + chunkSize, aWidgetRect->bottom };
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect);
+ }
+ } else {
+ // rtl needs to grow in the opposite direction to look right.
+ for (int chunk = aWidgetRect->right; chunk >= aWidgetRect->left;
+ chunk -= (chunkSize+spaceSize)) {
+ if ((chunk - chunkSize) < aWidgetRect->left) {
+ if (!IsProgressMeterFilled(aFrame)) {
+ break;
+ }
+ }
+ RECT bounds =
+ { chunk - chunkSize, aWidgetRect->top, chunk, aWidgetRect->bottom };
+ DrawThemeBackground(aTheme, aHdc, aPart, aState, &bounds, aClipRect);
+ }
+ }
+}
+
+/*
+ * 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 aWidgetType 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, int aWidgetType,
+ HANDLE aTheme, HDC aHdc,
+ int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect,
+ gfxFloat aAppUnits)
+{
+ 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;
+ if (!IsVistaOrLater()) {
+ // Adjust clipping out by one pixel. XP progress meters are inset,
+ // Vista+ are not.
+ InflateRect(&adjWidgetRect, 1, 1);
+ InflateRect(&adjClipRect, 1, 1);
+ }
+
+ 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;
+ }
+
+ EventStates eventStates = GetContentState(parentFrame, aWidgetType);
+ bool vertical = IsVerticalProgress(parentFrame) ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
+ bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates);
+ bool animate = indeterminate;
+
+ if (IsVistaOrLater()) {
+ // 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;
+ }
+ } else if (!indeterminate) {
+ // XP progress meters are 'chunk' style.
+ DrawChunkProgressMeter(aTheme, aHdc, aPart, aState, aFrame,
+ &adjWidgetRect, &adjClipRect, aAppUnits,
+ indeterminate, vertical, IsFrameRTL(aFrame));
+ }
+
+ if (animate) {
+ // Indeterminate rendering
+ int32_t overlayPart = GetProgressOverlayStyle(vertical);
+ RECT overlayRect =
+ CalculateProgressOverlayRect(aFrame, &adjWidgetRect, vertical,
+ indeterminate, false);
+ if (IsVistaOrLater()) {
+ DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
+ &adjClipRect);
+ } else {
+ DrawChunkProgressMeter(aTheme, aHdc, overlayPart, aState, aFrame,
+ &overlayRect, &adjClipRect, aAppUnits,
+ indeterminate, vertical, IsFrameRTL(aFrame));
+ }
+
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
+ NS_WARNING("unable to animate progress widget!");
+ }
+ }
+}
+
+HANDLE
+nsNativeThemeWin::GetTheme(uint8_t aWidgetType)
+{
+ if (!IsVistaOrLater()) {
+ // On XP or earlier, render dropdowns as textfields;
+ // doing it the right way works fine with the MS themes,
+ // but breaks on a lot of custom themes (presumably because MS
+ // apps do the textfield border business as well).
+ if (aWidgetType == NS_THEME_MENULIST)
+ aWidgetType = NS_THEME_TEXTFIELD;
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_GROUPBOX:
+ return nsUXThemeData::GetTheme(eUXButton);
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_FOCUS_OUTLINE:
+ return nsUXThemeData::GetTheme(eUXEdit);
+ case NS_THEME_TOOLTIP:
+ // XP/2K3 should force a classic treatment of tooltips
+ return !IsVistaOrLater() ?
+ nullptr : nsUXThemeData::GetTheme(eUXTooltip);
+ case NS_THEME_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXRebar);
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXMediaRebar);
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXCommunicationsRebar);
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ return nsUXThemeData::GetTheme(eUXBrowserTabBarRebar);
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_SEPARATOR:
+ return nsUXThemeData::GetTheme(eUXToolbar);
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ return nsUXThemeData::GetTheme(eUXProgress);
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ return nsUXThemeData::GetTheme(eUXTab);
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ return nsUXThemeData::GetTheme(eUXScrollbar);
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ return nsUXThemeData::GetTheme(eUXTrackbar);
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ return nsUXThemeData::GetTheme(eUXSpin);
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ return nsUXThemeData::GetTheme(eUXStatus);
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ return nsUXThemeData::GetTheme(eUXCombobox);
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ return nsUXThemeData::GetTheme(eUXHeader);
+ case NS_THEME_LISTBOX:
+ case NS_THEME_LISTITEM:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_TREEITEM:
+ return nsUXThemeData::GetTheme(eUXListview);
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUIMAGE:
+ case NS_THEME_MENUITEMTEXT:
+ return nsUXThemeData::GetTheme(eUXMenu);
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ return nsUXThemeData::GetTheme(eUXWindowFrame);
+ }
+ return nullptr;
+}
+
+int32_t
+nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType,
+ bool wantFocused)
+{
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ return TS_ACTIVE;
+ if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ return TS_HOVER;
+ if (wantFocused && eventState.HasState(NS_EVENT_STATE_FOCUS))
+ return TS_FOCUSED;
+
+ return TS_NORMAL;
+}
+
+bool
+nsNativeThemeWin::IsMenuActive(nsIFrame *aFrame, uint8_t aWidgetType)
+{
+ 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, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState)
+{
+ if (!IsVistaOrLater()) {
+ // See GetTheme
+ if (aWidgetType == NS_THEME_MENULIST)
+ aWidgetType = NS_THEME_TEXTFIELD;
+ }
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ } else if (IsOpenButton(aFrame) ||
+ IsCheckedButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aWidgetType, 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 NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO;
+
+ enum InputState {
+ UNCHECKED = 0, CHECKED, INDETERMINATE
+ };
+ InputState inputState = UNCHECKED;
+ bool isXULCheckboxRadio = false;
+
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ } else {
+ if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
+ inputState = CHECKED;
+ } if (isCheckbox && GetIndeterminate(aFrame)) {
+ inputState = INDETERMINATE;
+ }
+
+ EventStates eventState =
+ GetContentState(isXULCheckboxRadio ? aFrame->GetParent() : aFrame,
+ aWidgetType);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ } else {
+ aState = StandardGetState(aFrame, aWidgetType, false);
+ }
+ }
+
+ // 4 unchecked states, 4 checked states, 4 indeterminate states.
+ aState += inputState * 4;
+ return NS_OK;
+ }
+ case NS_THEME_GROUPBOX: {
+ aPart = BP_GROUPBOX;
+ aState = TS_NORMAL;
+ // Since we don't support groupbox disabled and GBS_DISABLED looks the
+ // same as GBS_NORMAL don't bother supporting GBS_DISABLED.
+ return NS_OK;
+ }
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsVistaOrLater()) {
+ /* 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 (IsDisabled(aFrame, eventState)) {
+ aState = TFS_EDITBORDER_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ /* no special read-only state */
+ aState = TFS_EDITBORDER_NORMAL;
+ } else {
+ nsIContent* content = aFrame->GetContent();
+
+ /* XUL textboxes don't get focused themselves, because they have child
+ * html:input.. but we can check the XUL focused attributes on them
+ */
+ if (content && content->IsXULElement() && IsFocused(aFrame))
+ aState = TFS_EDITBORDER_FOCUSED;
+ else if (eventState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS))
+ aState = TFS_EDITBORDER_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TFS_EDITBORDER_HOVER;
+ else
+ aState = TFS_EDITBORDER_NORMAL;
+ }
+ } else {
+ aPart = TFP_TEXTFIELD;
+
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else if (IsReadOnly(aFrame))
+ aState = TFS_READONLY;
+ else
+ aState = StandardGetState(aFrame, aWidgetType, true);
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_FOCUS_OUTLINE: {
+ if (IsVistaOrLater()) {
+ // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT
+ aPart = TFP_TEXTFIELD; //TFP_EDITBORDER_NOSCROLL;
+ aState = TS_FOCUSED; //TFS_EDITBORDER_FOCUSED;
+ } else {
+ aPart = TFP_TEXTFIELD;
+ aState = TS_FOCUSED;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_TOOLTIP: {
+ aPart = TTP_STANDARD;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL: {
+ // Note IsVerticalProgress only tests for orient css attrribute,
+ // NS_THEME_PROGRESSBAR_VERTICAL is dedicated to -moz-appearance:
+ // progressbar-vertical.
+ bool vertical = IsVerticalProgress(aFrame) ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL;
+ aPart = vertical ? PP_BARVERT : PP_BAR;
+ aState = PBBS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL: {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
+ IsVerticalProgress(parentFrame)) {
+ aPart = IsVistaOrLater() ?
+ PP_FILLVERT : PP_CHUNKVERT;
+ } else {
+ aPart = IsVistaOrLater() ?
+ PP_FILL : PP_CHUNK;
+ }
+
+ aState = PBBVS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TOOLBARBUTTON: {
+ aPart = BP_BUTTON;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+ if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ return NS_OK;
+ }
+
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_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 NS_THEME_SEPARATOR: {
+ aPart = TP_SEPARATOR;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT: {
+ aPart = SP_BUTTON;
+ aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP)*4;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState += TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState += TS_DISABLED;
+ else {
+ nsIFrame *parent = aFrame->GetParent();
+ EventStates parentState =
+ GetContentState(parent, parent->StyleDisplay()->mAppearance);
+ if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState += TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState += TS_HOVER;
+ else if (IsVistaOrLater() &&
+ parentState.HasState(NS_EVENT_STATE_HOVER))
+ aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) + SP_BUTTON_IMPLICIT_HOVER_BASE;
+ else
+ aState += TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL: {
+ aPart = (aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL) ?
+ SP_TRACKSTARTHOR : SP_TRACKSTARTVERT;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL: {
+ aPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ?
+ SP_THUMBHOR : SP_THUMBVERT;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else {
+ if (eventState.HasState(NS_EVENT_STATE_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 (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_RANGE:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL: {
+ if (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE &&
+ IsRangeHorizontal(aFrame))) {
+ aPart = TKP_TRACK;
+ aState = TRS_NORMAL;
+ } else {
+ aPart = TKP_TRACKVERT;
+ aState = TRVS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL: {
+ if (aWidgetType == NS_THEME_RANGE_THUMB) {
+ if (IsRangeHorizontal(aFrame)) {
+ aPart = TKP_THUMBBOTTOM;
+ } else {
+ aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
+ }
+ } else {
+ aPart = (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL) ?
+ TKP_THUMB : TKP_THUMBVERT;
+ }
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState)) {
+ aState = TKP_DISABLED;
+ }
+ else {
+ if (eventState.HasState(NS_EVENT_STATE_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 (eventState.HasState(NS_EVENT_STATE_FOCUS))
+ aState = TKP_FOCUSED;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ aPart = (aWidgetType == NS_THEME_SPINNER_UPBUTTON) ?
+ SPNP_UP : SPNP_DOWN;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (!aFrame)
+ aState = TS_NORMAL;
+ else if (IsDisabled(aFrame, eventState))
+ aState = TS_DISABLED;
+ else
+ aState = StandardGetState(aFrame, aWidgetType, false);
+ return NS_OK;
+ }
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL: {
+ aState = 0;
+ if (IsVistaOrLater()) {
+ // On vista, they have a part
+ aPart = RP_BACKGROUND;
+ } else {
+ // Otherwise, they don't. (But I bet
+ // RP_BACKGROUND would work here, too);
+ aPart = 0;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_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 NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER: {
+ aPart = (aWidgetType - NS_THEME_STATUSBARPANEL) + 1;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_LISTBOX: {
+ aPart = TREEVIEW_BODY;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TABPANELS: {
+ aPart = TABP_PANELS;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TABPANEL: {
+ aPart = TABP_PANEL;
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_TAB: {
+ aPart = TABP_TAB;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ if (IsDisabled(aFrame, eventState)) {
+ 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, aWidgetType, true);
+
+ return NS_OK;
+ }
+ case NS_THEME_TREEHEADERSORTARROW: {
+ // XXX Probably will never work due to a bug in the Luna theme.
+ aPart = 4;
+ aState = 1;
+ return NS_OK;
+ }
+ case NS_THEME_TREEHEADERCELL: {
+ aPart = 1;
+ if (!aFrame) {
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+
+ aState = StandardGetState(aFrame, aWidgetType, true);
+
+ return NS_OK;
+ }
+ case NS_THEME_MENULIST: {
+ nsIContent* content = aFrame->GetContent();
+ bool isHTML = content && content->IsHTMLElement();
+ bool useDropBorder = isHTML || IsMenuListEditable(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ /* 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 (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ } else if (IsReadOnly(aFrame)) {
+ aState = TS_NORMAL;
+ } else if (IsOpenButton(aFrame)) {
+ aState = TS_ACTIVE;
+ } else {
+ if (useDropBorder && (eventState.HasState(NS_EVENT_STATE_FOCUS) || IsFocused(aFrame)))
+ aState = TS_ACTIVE;
+ else if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState = TS_ACTIVE;
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER))
+ aState = TS_HOVER;
+ else
+ aState = TS_NORMAL;
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_MENULIST_BUTTON: {
+ bool isHTML = IsHTMLContent(aFrame);
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame;
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the parent.
+ if (isHTML || isMenulist)
+ aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ aPart = IsVistaOrLater() ?
+ CBP_DROPMARKER_VISTA : CBP_DROPMARKER;
+
+ // For HTML controls with author styling, we should fall
+ // back to the old dropmarker style to avoid clashes with
+ // author-specified backgrounds and borders (bug #441034)
+ if (isHTML && IsWidgetStyled(aFrame->PresContext(), aFrame, NS_THEME_MENULIST))
+ aPart = CBP_DROPMARKER;
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState = TS_DISABLED;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame);
+ isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
+ }
+ else
+ isOpen = IsOpenButton(aFrame);
+
+ if (IsVistaOrLater()) {
+ if (isHTML || IsMenuListEditable(aFrame)) {
+ if (isOpen) {
+ /* Hover is propagated, but we need to know whether we're
+ * hovering just the combobox frame, not the dropdown frame.
+ * But, we can't get that information, since hover is on the
+ * content node, and they share the same content node. So,
+ * instead, we cheat -- if the dropdown is open, we always
+ * show the hover state. This looks fine in practice.
+ */
+ aState = TS_HOVER;
+ return NS_OK;
+ }
+ } else {
+ /* On Vista, the dropdown indicator on a menulist button in
+ * chrome is not given a hover effect. When the frame isn't
+ * isn't HTML content, we cheat and force the dropdown state
+ * to be normal. (Bug 430434)
+ */
+ aState = TS_NORMAL;
+ return NS_OK;
+ }
+ }
+
+ aState = TS_NORMAL;
+
+ // Dropdown button active state doesn't need :hover.
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) {
+ if (isOpen && (isHTML || isMenulist)) {
+ // XXX Button should look active until the mouse is released, but
+ // without making it look active when the popup is clicked.
+ return NS_OK;
+ }
+ aState = TS_ACTIVE;
+ }
+ else if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
+ // No hover effect for XUL menulists and autocomplete dropdown buttons
+ // while the dropdown menu is open.
+ if (isOpen) {
+ // XXX HTML select dropdown buttons should have the hover effect when
+ // hovering the combobox frame, but not the popup frame.
+ return NS_OK;
+ }
+ aState = TS_HOVER;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_MENUPOPUP: {
+ aPart = MENU_POPUPBACKGROUND;
+ aState = MB_ACTIVE;
+ return NS_OK;
+ }
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ bool isHover = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ isTopLevel = IsTopLevelMenu(aFrame);
+
+ if (menuFrame)
+ isOpen = menuFrame->IsOpen();
+
+ isHover = IsMenuActive(aFrame, aWidgetType);
+
+ if (isTopLevel) {
+ aPart = MENU_BARITEM;
+
+ if (isOpen)
+ aState = MBI_PUSHED;
+ else if (isHover)
+ aState = MBI_HOT;
+ else
+ aState = MBI_NORMAL;
+
+ // the disabled states are offset by 3
+ if (IsDisabled(aFrame, eventState))
+ aState += 3;
+ } else {
+ aPart = MENU_POPUPITEM;
+
+ if (isHover)
+ aState = MPI_HOT;
+ else
+ aState = MPI_NORMAL;
+
+ // the disabled states are offset by 2
+ if (IsDisabled(aFrame, eventState))
+ aState += 2;
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUSEPARATOR:
+ aPart = MENU_POPUPSEPARATOR;
+ aState = 0;
+ return NS_OK;
+ case NS_THEME_MENUARROW:
+ {
+ aPart = MENU_POPUPSUBMENU;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL;
+ return NS_OK;
+ }
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ aPart = MENU_POPUPCHECK;
+ aState = MC_CHECKMARKNORMAL;
+
+ // Radio states are offset by 2
+ if (aWidgetType == NS_THEME_MENURADIO)
+ aState += 2;
+
+ // the disabled states are offset by 1
+ if (IsDisabled(aFrame, eventState))
+ aState += 1;
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_MENUIMAGE:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+
+ case NS_THEME_WINDOW_TITLEBAR:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aPart = mozilla::widget::themeconst::WP_MINBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ aPart = mozilla::widget::themeconst::WP_MAXBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON;
+ aState = GetWindowFrameButtonState(aFrame, GetContentState(aFrame, aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ aPart = -1;
+ aState = 0;
+ return NS_OK;
+ }
+
+ aPart = 0;
+ aState = 0;
+ return NS_ERROR_FAILURE;
+}
+
+static bool
+AssumeThemePartAndStateAreTransparent(int32_t aPart, int32_t aState)
+{
+ if (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(nsIFrame* aFrame)
+{
+ if (WinUtils::IsPerMonitorDPIAware()) {
+ nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
+ if (rootWidget) {
+ double systemScale = WinUtils::SystemScaleFactor();
+ return rootWidget->GetDefaultScale().scale / systemScale;
+ }
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme)
+ return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect, aDirtyRect);
+
+ // ^^ without the right sdk, assume xp theming and fall through.
+ if (nsUXThemeData::CheckForCompositor()) {
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ // Nothing to draw, these areas are glass. Minimum dimensions
+ // should be set, so xul content should be layed out correctly.
+ return NS_OK;
+ break;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ // Not conventional bitmaps, can't be retrieved. If we fall
+ // through here and call the theme library we'll get aero
+ // basic bitmaps.
+ return NS_OK;
+ break;
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ // Nothing to draw, this is the glass background.
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ // We handle these through nsIWidget::UpdateThemeGeometries
+ return NS_OK;
+ break;
+ }
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (AssumeThemePartAndStateAreTransparent(part, state)) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxContext> ctx = aContext->ThebesContext();
+ gfxContextMatrixAutoSaveRestore save(ctx);
+
+ double themeScale = GetThemeDpiScaleFactor(aFrame);
+ if (themeScale != 1.0) {
+ ctx->SetMatrix(ctx->CurrentMatrix().Scale(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.ScaleInverse(p2a * themeScale);
+ dr.ScaleInverse(p2a * themeScale);
+
+ gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
+
+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 (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
+ // Clip out the left and right corners of the frame, all we want in
+ // is the middle section.
+ widgetRect.left -= GetSystemMetrics(SM_CXFRAME);
+ widgetRect.right += GetSystemMetrics(SM_CXFRAME);
+ } else if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
+ // The origin of the window is off screen when maximized and windows
+ // doesn't compensate for this in rendering the background. Push the
+ // top of the bitmap down by SM_CYFRAME so we get the full graphic.
+ widgetRect.top += GetSystemMetrics(SM_CYFRAME);
+ } else if (aWidgetType == NS_THEME_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;
+ }
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
+ }
+
+ // 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 (aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCALE_VERTICAL) {
+ 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 (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE && 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 (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO)
+ {
+ bool isChecked = false;
+ isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked);
+
+ if (isChecked)
+ {
+ int bgState = MCB_NORMAL;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // the disabled states are offset by 1
+ if (IsDisabled(aFrame, eventState))
+ bgState += 1;
+
+ SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc));
+
+ RECT checkBGRect = widgetRect;
+ if (IsFrameRTL(aFrame)) {
+ checkBGRect.left = checkBGRect.right-checkboxBGSize.cx;
+ } else {
+ checkBGRect.right = checkBGRect.left+checkboxBGSize.cx;
+ }
+
+ // Center the checkbox background vertically in the menuitem
+ checkBGRect.top += (checkBGRect.bottom - checkBGRect.top)/2 - checkboxBGSize.cy/2;
+ checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState, &checkBGRect, &clipRect);
+
+ MARGINS checkMargins = GetCheckboxMargins(theme, hdc);
+ RECT checkRect = checkBGRect;
+ checkRect.left += checkMargins.cxLeftWidth;
+ checkRect.right -= checkMargins.cxRightWidth;
+ checkRect.top += checkMargins.cyTopHeight;
+ checkRect.bottom -= checkMargins.cyBottomHeight;
+ DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect, &clipRect);
+ }
+ }
+ else if (aWidgetType == NS_THEME_MENUPOPUP)
+ {
+ DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0, &widgetRect, &clipRect);
+ SIZE borderSize;
+ GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE, &borderSize);
+
+ RECT bgRect = widgetRect;
+ bgRect.top += borderSize.cy;
+ bgRect.bottom -= borderSize.cy;
+ bgRect.left += borderSize.cx;
+ bgRect.right -= borderSize.cx;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0, &bgRect, &clipRect);
+
+ SIZE gutterSize(GetGutterSize(theme, hdc));
+
+ RECT gutterRect;
+ gutterRect.top = bgRect.top;
+ gutterRect.bottom = bgRect.bottom;
+ if (IsFrameRTL(aFrame)) {
+ gutterRect.right = bgRect.right;
+ gutterRect.left = gutterRect.right-gutterSize.cx;
+ } else {
+ gutterRect.left = bgRect.left;
+ gutterRect.right = gutterRect.left+gutterSize.cx;
+ }
+
+ DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0,
+ &gutterRect, &clipRect, IsFrameRTL(aFrame));
+ }
+ else if (aWidgetType == NS_THEME_MENUSEPARATOR)
+ {
+ SIZE gutterSize(GetGutterSize(theme,hdc));
+
+ RECT sepRect = widgetRect;
+ if (IsFrameRTL(aFrame))
+ sepRect.right -= gutterSize.cx;
+ else
+ sepRect.left += gutterSize.cx;
+
+ DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0, &sepRect, &clipRect);
+ }
+ else if (aWidgetType == NS_THEME_MENUARROW)
+ {
+ // We're dpi aware and as such on systems that have dpi > 96 set, the
+ // theme library expects us to do proper positioning and scaling of glyphs.
+ // For NS_THEME_MENUARROW, layout may hand us a widget rect larger than the
+ // glyph rect we request in GetMinimumWidgetSize. To prevent distortion we
+ // have to position and scale what we draw.
+
+ SIZE glyphSize;
+ GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize);
+
+ int32_t widgetHeight = widgetRect.bottom - widgetRect.top;
+
+ RECT renderRect = widgetRect;
+
+ // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In
+ // Firefox some menu items provide the full height of the item to us, in
+ // others our widget rect is the exact dims of our arrow glyph. Adjust the
+ // vertical position by the added space, if any exists.
+ renderRect.top += ((widgetHeight - glyphSize.cy) / 2);
+ renderRect.bottom = renderRect.top + glyphSize.cy;
+ // I'm using the width of the arrow glyph for the arrow-side padding.
+ // AFAICT there doesn't appear to be a theme constant we can query
+ // for this value. Generally this looks correct, and has the added
+ // benefit of being a dpi adjusted value.
+ if (!IsFrameRTL(aFrame)) {
+ renderRect.right = widgetRect.right - glyphSize.cx;
+ renderRect.left = renderRect.right - glyphSize.cx;
+ } else {
+ renderRect.left = glyphSize.cx;
+ renderRect.right = renderRect.left + glyphSize.cx;
+ }
+ DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect,
+ IsFrameRTL(aFrame));
+ }
+ // The following widgets need to be RTL-aware
+ else if (aWidgetType == NS_THEME_RESIZER ||
+ aWidgetType == NS_THEME_MENULIST_BUTTON)
+ {
+ DrawThemeBGRTLAware(theme, hdc, part, state,
+ &widgetRect, &clipRect, IsFrameRTL(aFrame));
+ }
+ else if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
+ 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 (aWidgetType == NS_THEME_PROGRESSBAR ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL) {
+ // 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 (aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL) {
+ DrawThemedProgressMeter(aFrame, aWidgetType, theme, hdc, part, state,
+ &widgetRect, &clipRect, p2a);
+ }
+ else if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ // Inflate 'widgetRect' with the focus outline size.
+ nsIntMargin border;
+ if (NS_SUCCEEDED(GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
+ aFrame, aWidgetType, &border))) {
+ widgetRect.left -= border.left;
+ widgetRect.right += border.right;
+ widgetRect.top -= border.top;
+ widgetRect.bottom += border.bottom;
+ }
+
+ DTBGOPTS opts = {
+ sizeof(DTBGOPTS),
+ DTBG_OMITCONTENT | DTBG_CLIPRECT,
+ clipRect
+ };
+ DrawThemeBackgroundEx(theme, hdc, part, state, &widgetRect, &opts);
+ }
+ // 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 and scale elements
+ // XXX it'd be nice to draw these outside of the frame
+ if (aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCALE_VERTICAL) {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ if (contentState.HasState(NS_EVENT_STATE_FOCUS)) {
+ 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 (aWidgetType == NS_THEME_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 NS_THEME_TOOLBOX.
+ theme = GetTheme(NS_THEME_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);
+ }
+ else if (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL)
+ {
+ // Draw the decorative gripper for the scrollbar thumb button, if it fits
+
+ SIZE gripSize;
+ MARGINS thumbMgns;
+ int gripPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) ?
+ SP_GRIPPERHOR : SP_GRIPPERVERT;
+
+ if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE, &gripSize) == S_OK &&
+ GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr, &thumbMgns) == S_OK &&
+ gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <= widgetRect.right - widgetRect.left &&
+ gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <= widgetRect.bottom - widgetRect.top)
+ {
+ DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect);
+ }
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ if (nativeDrawing.ShouldRenderAgain())
+ goto RENDER_AGAIN;
+
+ nativeDrawing.PaintToContext();
+
+ return NS_OK;
+}
+
+static void
+ScaleForFrameDPI(nsIntMargin* 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);
+ }
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ HANDLE theme = GetTheme(aWidgetType);
+ nsresult rv = NS_OK;
+ if (!theme) {
+ rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ aResult->top = aResult->bottom = aResult->left = aResult->right = 0;
+
+ if (!WidgetIsContainer(aWidgetType) ||
+ aWidgetType == NS_THEME_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
+ aWidgetType == NS_THEME_STATUSBAR ||
+ aWidgetType == NS_THEME_RESIZER || aWidgetType == NS_THEME_TABPANEL ||
+ aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL ||
+ aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ||
+ aWidgetType == NS_THEME_MENUITEM || aWidgetType == NS_THEME_CHECKMENUITEM ||
+ aWidgetType == NS_THEME_RADIOMENUITEM || aWidgetType == NS_THEME_MENUPOPUP ||
+ aWidgetType == NS_THEME_MENUIMAGE || aWidgetType == NS_THEME_MENUITEMTEXT ||
+ aWidgetType == NS_THEME_SEPARATOR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
+ aWidgetType == NS_THEME_WIN_GLASS || aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS)
+ return NS_OK; // Don't worry about it.
+
+ int32_t part, state;
+ rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aWidgetType == NS_THEME_TOOLBAR) {
+ // make space for the separator line above all toolbars but the first
+ if (state == 0)
+ aResult->top = TB_SEPARATOR_HEIGHT;
+ return NS_OK;
+ }
+
+ // 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(theme, nullptr, part, state, &outerRect, &contentRect);
+
+ if (FAILED(res))
+ return NS_ERROR_FAILURE;
+
+ // Now compute the delta in each direction and place it in our
+ // nsIntMargin struct.
+ aResult->top = contentRect.top - outerRect.top;
+ aResult->bottom = outerRect.bottom - contentRect.bottom;
+ aResult->left = contentRect.left - outerRect.left;
+ aResult->right = outerRect.right - contentRect.right;
+
+ // Remove the edges for tabs that are before or after the selected tab,
+ if (aWidgetType == NS_THEME_TAB) {
+ if (IsLeftToSelectedTab(aFrame))
+ // Remove the right edge, since we won't be drawing it.
+ aResult->right = 0;
+ else if (IsRightToSelectedTab(aFrame))
+ // Remove the left edge, since we won't be drawing it.
+ aResult->left = 0;
+ }
+
+ if (aFrame && (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE)) {
+ 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.
+ aResult->top++;
+ aResult->left++;
+ aResult->bottom++;
+ aResult->right++;
+ }
+ }
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+}
+
+bool
+nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ }
+
+ bool ok = true;
+
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
+ aResult->SizeTo(0, 0, 0, 0);
+
+ // aero glass doesn't display custom buttons
+ if (nsUXThemeData::CheckForCompositor())
+ return true;
+
+ // button padding for standard windows
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX) {
+ aResult->top = GetSystemMetrics(SM_CXFRAME);
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ // Content padding
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
+ aResult->SizeTo(0, 0, 0, 0);
+ // XXX Maximized windows have an offscreen offset equal to
+ // the border padding. This should be addressed in nsWindow,
+ // but currently can't be, see UpdateNonClientMargins.
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED)
+ aResult->top = GetSystemMetrics(SM_CXFRAME)
+ + GetSystemMetrics(SM_CXPADDEDBORDER);
+ return ok;
+ }
+
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme) {
+ ok = ClassicGetWidgetPadding(aContext, aFrame, aWidgetType, aResult);
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ if (aWidgetType == NS_THEME_MENUPOPUP)
+ {
+ SIZE popupSize;
+ GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr, TS_TRUE, &popupSize);
+ aResult->top = aResult->bottom = popupSize.cy;
+ aResult->left = aResult->right = popupSize.cx;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ }
+
+ if (IsVistaOrLater()) {
+ if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_MENULIST)
+ {
+ /* If we have author-specified padding for these elements, don't do the fixups below */
+ if (aFrame->PresContext()->HasAuthorSpecifiedRules(aFrame, NS_AUTHOR_SPECIFIED_PADDING))
+ return false;
+ }
+
+ /* 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 (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
+ aResult->top = aResult->bottom = 2;
+ aResult->left = aResult->right = 2;
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+ } else if (IsHTMLContent(aFrame) && aWidgetType == NS_THEME_MENULIST) {
+ /* 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 (aWidgetType)
+ {
+ case NS_THEME_MENUIMAGE:
+ right = 8;
+ left = 3;
+ break;
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ right = 8;
+ left = 0;
+ break;
+ case NS_THEME_MENUITEMTEXT:
+ // There seem to be exactly 4 pixels from the edge
+ // of the gutter to the text: 2px margin (CSS) + 2px padding (here)
+ {
+ SIZE size(GetGutterSize(theme, nullptr));
+ left = size.cx + 2;
+ }
+ break;
+ case NS_THEME_MENUSEPARATOR:
+ {
+ SIZE size(GetGutterSize(theme, nullptr));
+ left = size.cx + 5;
+ top = 10;
+ bottom = 7;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (IsFrameRTL(aFrame))
+ {
+ aResult->right = left;
+ aResult->left = right;
+ }
+ else
+ {
+ aResult->right = right;
+ aResult->left = left;
+ }
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return ok;
+}
+
+bool
+nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsRect* 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.
+ */
+#if 0
+ if (IsVistaOrLater()) {
+ /* 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 (aWidgetType == NS_THEME_MENULIST_BUTTON &&
+ IsHTMLContent(aFrame) &&
+ !IsWidgetStyled(aFrame->GetParent()->PresContext(),
+ aFrame->GetParent(),
+ NS_THEME_MENULIST))
+ {
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ /* Note: no overflow on the left */
+ nsMargin m(p2a, p2a, p2a, 0);
+ aOverflowRect->Inflate (m);
+ return true;
+ }
+ }
+#endif
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ nsIntMargin border;
+ nsresult rv = GetWidgetBorder(aContext, aFrame, aWidgetType, &border);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(border.top, p2a),
+ NSIntPixelsToAppUnits(border.right, p2a),
+ NSIntPixelsToAppUnits(border.bottom, p2a),
+ NSIntPixelsToAppUnits(border.left, p2a));
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult, bool* aIsOverridable)
+{
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+ nsresult rv = NS_OK;
+
+ HANDLE theme = GetTheme(aWidgetType);
+ if (!theme) {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ switch (aWidgetType) {
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_WIN_MEDIA_TOOLBOX:
+ case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
+ case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ return NS_OK; // Don't worry about it.
+ }
+
+ if (aWidgetType == NS_THEME_MENUITEM && IsTopLevelMenu(aFrame))
+ return NS_OK; // Don't worry about it for top level menus
+
+ // 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 (aWidgetType) {
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_MENULIST_BUTTON: {
+ rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult, aIsOverridable);
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ if(!IsTopLevelMenu(aFrame))
+ {
+ SIZE gutterSize(GetGutterSize(theme, nullptr));
+ aResult->width = gutterSize.cx;
+ aResult->height = gutterSize.cy;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+
+ case NS_THEME_MENUIMAGE:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ {
+ SIZE boxSize(GetGutterSize(theme, nullptr));
+ aResult->width = boxSize.cx+2;
+ aResult->height = boxSize.cy;
+ *aIsOverridable = false;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+
+ case NS_THEME_MENUITEMTEXT:
+ return NS_OK;
+
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ // 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 NS_THEME_RESIZER:
+ *aIsOverridable = false;
+ break;
+
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ {
+ *aIsOverridable = false;
+ // on Vista, GetThemePartAndState returns odd values for
+ // scale thumbs, so use a hardcoded size instead.
+ if (IsVistaOrLater()) {
+ if (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE_THUMB && IsRangeHorizontal(aFrame))) {
+ aResult->width = 12;
+ aResult->height = 20;
+ }
+ else {
+ aResult->width = 20;
+ aResult->height = 12;
+ }
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR:
+ {
+ if (nsLookAndFeel::GetInt(
+ nsLookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
+ ::GetSystemMetrics(SM_CYVSCROLL));
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+ }
+ break;
+ }
+
+ case NS_THEME_SEPARATOR:
+ // that's 2px left margin, 2px right margin and 2px separator
+ // (the margin is drawn as part of the separator, though)
+ aResult->width = 6;
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+
+ case NS_THEME_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;
+
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ // The only way to get accurate titlebar button info is to query a
+ // window w/buttons when it's visible. nsWindow takes care of this and
+ // stores that info in nsUXThemeData.
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_RESTORE].cy;
+ // For XP, subtract 4 from system metrics dimensions.
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_MINIMIZE].cy;
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_CLOSE].cy;
+ if (!IsVistaOrLater()) {
+ aResult->width -= 4;
+ aResult->height -= 4;
+ }
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ aResult->height = GetSystemMetrics(SM_CYCAPTION);
+ aResult->height += GetSystemMetrics(SM_CYFRAME);
+ aResult->height += GetSystemMetrics(SM_CXPADDEDBORDER);
+ // On Win8.1, we don't want this scaling, because Windows doesn't scale
+ // the non-client area of the window, and we can end up with ugly overlap
+ // of the window frame controls into the tab bar or content area. But on
+ // Win10, we render the window controls ourselves, and the result looks
+ // better if we do apply this scaling (particularly with themes such as
+ // DevEdition; see bug 1267636).
+ if (IsWin10OrLater()) {
+ ScaleForFrameDPI(aResult, aFrame);
+ }
+ *aIsOverridable = false;
+ return rv;
+
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ if (nsUXThemeData::CheckForCompositor()) {
+ aResult->width = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx;
+ aResult->height = nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy
+ - GetSystemMetrics(SM_CYFRAME)
+ - GetSystemMetrics(SM_CXPADDEDBORDER);
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
+ aResult->width += 1;
+ aResult->height -= 2;
+ }
+ *aIsOverridable = false;
+ return rv;
+ }
+ break;
+
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ *aIsOverridable = false;
+ return rv;
+ }
+
+ int32_t part, state;
+ rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
+ if (NS_FAILED(rv))
+ return rv;
+
+ HDC hdc = ::GetDC(NULL);
+ if (!hdc)
+ return NS_ERROR_FAILURE;
+
+ SIZE sz;
+ GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz);
+ aResult->width = sz.cx;
+ aResult->height = sz.cy;
+
+ switch(aWidgetType) {
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aResult->width++;
+ aResult->height = aResult->height / 2 + 1;
+ break;
+
+ case NS_THEME_MENUSEPARATOR:
+ {
+ SIZE gutterSize(GetGutterSize(theme, hdc));
+ aResult->width += gutterSize.cx;
+ break;
+ }
+
+ case NS_THEME_MENUARROW:
+ {
+ // Use the width of the arrow glyph as padding. See the drawing
+ // code for details.
+ aResult->width *= 2;
+ break;
+ }
+ }
+
+ ::ReleaseDC(nullptr, hdc);
+
+ ScaleForFrameDPI(aResult, aFrame);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ if (aWidgetType == NS_THEME_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
+ aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
+ aWidgetType == NS_THEME_TOOLBAR ||
+ aWidgetType == NS_THEME_STATUSBAR || aWidgetType == NS_THEME_STATUSBARPANEL ||
+ aWidgetType == NS_THEME_RESIZERPANEL ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
+ aWidgetType == NS_THEME_PROGRESSBAR ||
+ aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
+ aWidgetType == NS_THEME_TOOLTIP ||
+ aWidgetType == NS_THEME_TABPANELS ||
+ aWidgetType == NS_THEME_TABPANEL ||
+ aWidgetType == NS_THEME_SEPARATOR ||
+ aWidgetType == NS_THEME_WIN_GLASS ||
+ aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) {
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
+ aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_LEFT ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT ||
+ aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // On Vista, the scrollbar buttons need to change state when the track has/doesn't have hover
+ if (!IsVistaOrLater() &&
+ (aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ||
+ aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL)) {
+ *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 (IsVistaOrLater() &&
+ (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_MENULIST_BUTTON) &&
+ 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();
+ return NS_OK;
+}
+
+bool
+nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // XXXdwh We can go even further and call the API to ask if support exists for
+ // specific widgets.
+
+ if (aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
+ return false;
+
+ if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
+ return true;
+ }
+
+ HANDLE theme = nullptr;
+ if (aWidgetType == NS_THEME_CHECKBOX_CONTAINER)
+ theme = GetTheme(NS_THEME_CHECKBOX);
+ else if (aWidgetType == NS_THEME_RADIO_CONTAINER)
+ theme = GetTheme(NS_THEME_RADIO);
+ else
+ theme = GetTheme(aWidgetType);
+
+ if (theme && aWidgetType == NS_THEME_RESIZER)
+ return true;
+
+ if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aWidgetType)))
+ // turn off theming for some HTML widgets styled by the page
+ return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType));
+
+ return false;
+}
+
+bool
+nsNativeThemeWin::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // XXXdwh At some point flesh all of this out.
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_CHECKBOX)
+ return false;
+ return true;
+}
+
+bool
+nsNativeThemeWin::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ return false;
+}
+
+bool
+nsNativeThemeWin::ThemeNeedsComboboxDropmarker()
+{
+ return true;
+}
+
+bool
+nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType
+nsNativeThemeWin::ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ return eThemeGeometryTypeWindowButtons;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+bool
+nsNativeThemeWin::ShouldHideScrollbars()
+{
+ return WinUtils::ShouldHideScrollbars();
+}
+
+nsITheme::Transparency
+nsNativeThemeWin::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_STATUSBAR:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them. This better be
+ // true across all Windows themes! If it's not true, we should
+ // paint an opaque background for them to make it true!
+ return eOpaque;
+ case NS_THEME_WIN_GLASS:
+ case NS_THEME_WIN_BORDERLESS_GLASS:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_RANGE:
+ return eTransparent;
+ }
+
+ HANDLE theme = GetTheme(aWidgetType);
+ // For the classic theme we don't really have a way of knowing
+ if (!theme) {
+ // menu backgrounds and tooltips which can't be themed are opaque
+ if (aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_TOOLTIP) {
+ return eOpaque;
+ }
+ return eUnknownTransparency;
+ }
+
+ int32_t part, state;
+ nsresult rv = GetThemePartAndState(aFrame, aWidgetType, 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,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_RESIZER:
+ {
+ // The classic native resizer has an opaque grey background which doesn't
+ // match the usually white background of the scrollable container, so
+ // only support the native resizer if not in a scrollframe.
+ nsIFrame* parentFrame = aFrame->GetParent();
+ return (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame);
+ }
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ // Classic non-flat menus are handled almost entirely through CSS.
+ if (!nsUXThemeData::sFlatMenus)
+ return false;
+ case NS_THEME_BUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_MENULIST:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MENUITEMTEXT:
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
+ return true;
+ }
+ return false;
+}
+
+nsresult
+nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_BUTTON:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2;
+ break;
+ case NS_THEME_STATUSBAR:
+ (*aResult).bottom = (*aResult).left = (*aResult).right = 0;
+ (*aResult).top = 2;
+ break;
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_TAB:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_FOCUS_OUTLINE:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 2;
+ break;
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL: {
+ (*aResult).top = 1;
+ (*aResult).left = 1;
+ (*aResult).bottom = 1;
+ (*aResult).right = aFrame->GetNextSibling() ? 3 : 1;
+ break;
+ }
+ case NS_THEME_TOOLTIP:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ break;
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ break;
+ case NS_THEME_MENUBAR:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 0;
+ break;
+ case NS_THEME_MENUPOPUP:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 3;
+ break;
+ default:
+ (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 0;
+ break;
+ }
+ return NS_OK;
+}
+
+bool
+nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ int32_t part, state;
+ bool focused;
+
+ if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused)))
+ return false;
+
+ if (part == 1) { // top-level menu
+ if (nsUXThemeData::sFlatMenus || !(state & DFCS_PUSHED)) {
+ (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right = 2;
+ }
+ else {
+ // make top-level menus look sunken when pushed in the Classic look
+ (*aResult).top = (*aResult).left = 3;
+ (*aResult).bottom = (*aResult).right = 1;
+ }
+ }
+ else {
+ (*aResult).top = 0;
+ (*aResult).bottom = (*aResult).left = (*aResult).right = 2;
+ }
+ return true;
+ }
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+nsresult
+nsNativeThemeWin::ClassicGetMinimumWidgetSize(nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ (*aResult).width = (*aResult).height = 0;
+ *aIsOverridable = true;
+ switch (aWidgetType) {
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ (*aResult).width = (*aResult).height = 13;
+ break;
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK);
+ (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK);
+ break;
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = 8; // No good metrics available for this
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL);
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ // XXX HACK We should be able to have a minimum height for the scrollbar
+ // track. However, this causes problems when uncollapsing a scrollbar
+ // inside a tree. See bug 201379 for details.
+
+ // (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB) << 1;
+ break;
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ {
+ aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
+ ::GetSystemMetrics(SM_CYVSCROLL));
+ break;
+ }
+ case NS_THEME_RANGE_THUMB: {
+ if (IsRangeHorizontal(aFrame)) {
+ (*aResult).width = 12;
+ (*aResult).height = 20;
+ } else {
+ (*aResult).width = 20;
+ (*aResult).height = 12;
+ }
+ *aIsOverridable = false;
+ break;
+ }
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ (*aResult).width = 12;
+ (*aResult).height = 20;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ (*aResult).width = 20;
+ (*aResult).height = 12;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_MENULIST_BUTTON:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ break;
+ case NS_THEME_MENULIST:
+ case NS_THEME_BUTTON:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ // no minimum widget size
+ break;
+ case NS_THEME_RESIZER: {
+ NONCLIENTMETRICS nc;
+ nc.cbSize = sizeof(nc);
+ if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), &nc, 0))
+ (*aResult).width = (*aResult).height = abs(nc.lfStatusFont.lfHeight) + 4;
+ else
+ (*aResult).width = (*aResult).height = 15;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
+ (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB);
+ // Without theming, divide the thumb size by two in order to look more
+ // native
+ if (!GetTheme(aWidgetType))
+ (*aResult).height >>= 1;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB);
+ (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
+ // Without theming, divide the thumb size by two in order to look more
+ // native
+ if (!GetTheme(aWidgetType))
+ (*aResult).width >>= 1;
+ *aIsOverridable = false;
+ break;
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1;
+ break;
+ }
+ case NS_THEME_MENUSEPARATOR:
+ {
+ aResult->width = 0;
+ aResult->height = 10;
+ break;
+ }
+
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ case NS_THEME_WINDOW_TITLEBAR:
+ aResult->height = GetSystemMetrics(SM_CYCAPTION);
+ aResult->height += GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aResult->width = GetSystemMetrics(SM_CXFRAME);
+ aResult->height = 0;
+ break;
+
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aResult->height = GetSystemMetrics(SM_CYFRAME);
+ aResult->width = 0;
+ break;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ aResult->width = GetSystemMetrics(SM_CXSIZE);
+ aResult->height = GetSystemMetrics(SM_CYSIZE);
+ // XXX I have no idea why these caption metrics are always off,
+ // but they are.
+ aResult->width -= 2;
+ aResult->height -= 4;
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
+ }
+ break;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+
+nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState, bool& aFocused)
+{
+ aFocused = false;
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON: {
+ EventStates contentState;
+
+ aPart = DFC_BUTTON;
+ aState = DFCS_BUTTONPUSH;
+ aFocused = false;
+
+ contentState = GetContentState(aFrame, aWidgetType);
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else if (IsOpenButton(aFrame))
+ aState |= DFCS_PUSHED;
+ else if (IsCheckedButton(aFrame))
+ aState |= DFCS_CHECKED;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
+ aState |= DFCS_PUSHED;
+ const nsStyleUserInterface *uiData = aFrame->StyleUserInterface();
+ // The down state is flat if the button is focusable
+ if (uiData->mUserFocus == StyleUserFocus::Normal) {
+ if (!aFrame->GetContent()->IsHTMLElement())
+ aState |= DFCS_FLAT;
+
+ aFocused = true;
+ }
+ }
+ if (contentState.HasState(NS_EVENT_STATE_FOCUS) ||
+ (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
+ aFocused = true;
+ }
+
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ EventStates contentState;
+ aFocused = false;
+
+ aPart = DFC_BUTTON;
+ aState = 0;
+ nsIContent* content = aFrame->GetContent();
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ bool isChecked = GetCheckedOrSelected(aFrame, !isCheckbox);
+ bool isIndeterminate = isCheckbox && GetIndeterminate(aFrame);
+
+ if (isCheckbox) {
+ // indeterminate state takes precedence over checkedness.
+ if (isIndeterminate) {
+ aState = DFCS_BUTTON3STATE | DFCS_CHECKED;
+ } else {
+ aState = DFCS_BUTTONCHECK;
+ }
+ } else {
+ aState = DFCS_BUTTONRADIO;
+ }
+ if (isChecked) {
+ aState |= DFCS_CHECKED;
+ }
+
+ contentState = GetContentState(aFrame, aWidgetType);
+ if (!content->IsXULElement() &&
+ contentState.HasState(NS_EVENT_STATE_FOCUS)) {
+ aFocused = true;
+ }
+
+ if (IsDisabled(aFrame, contentState)) {
+ aState |= DFCS_INACTIVE;
+ } else if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+ NS_EVENT_STATE_HOVER)) {
+ aState |= DFCS_PUSHED;
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM: {
+ bool isTopLevel = false;
+ bool isOpen = false;
+ nsMenuFrame *menuFrame = do_QueryFrame(aFrame);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // We indicate top-level-ness using aPart. 0 is a normal menu item,
+ // 1 is a top-level menu item. The state of the item is composed of
+ // DFCS_* flags only.
+ aPart = 0;
+ aState = 0;
+
+ if (menuFrame) {
+ // If this is a real menu item, we should check if it is part of the
+ // main menu bar or not, and if it is a container, as these affect
+ // rendering.
+ isTopLevel = menuFrame->IsOnMenuBar();
+ isOpen = menuFrame->IsOpen();
+ }
+
+ if (IsDisabled(aFrame, eventState))
+ aState |= DFCS_INACTIVE;
+
+ if (isTopLevel) {
+ aPart = 1;
+ if (isOpen)
+ aState |= DFCS_PUSHED;
+ }
+
+ if (IsMenuActive(aFrame, aWidgetType))
+ aState |= DFCS_HOT;
+
+ return NS_OK;
+ }
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW: {
+ aState = 0;
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsDisabled(aFrame, eventState))
+ aState |= DFCS_INACTIVE;
+ if (IsMenuActive(aFrame, aWidgetType))
+ aState |= DFCS_HOT;
+
+ if (aWidgetType == NS_THEME_MENUCHECKBOX || aWidgetType == NS_THEME_MENURADIO) {
+ if (IsCheckedButton(aFrame))
+ aState |= DFCS_CHECKED;
+ } else if (IsFrameRTL(aFrame)) {
+ aState |= DFCS_RTL;
+ }
+ return NS_OK;
+ }
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_FOCUS_OUTLINE:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_GROUPBOX:
+ // these don't use DrawFrameControl
+ return NS_OK;
+ case NS_THEME_MENULIST_BUTTON: {
+
+ aPart = DFC_SCROLL;
+ aState = DFCS_SCROLLCOMBOBOX;
+
+ nsIFrame* parentFrame = aFrame->GetParent();
+ bool isHTML = IsHTMLContent(aFrame);
+ bool isMenulist = !isHTML && parentFrame->GetType() == nsGkAtoms::menuFrame;
+ bool isOpen = false;
+
+ // HTML select and XUL menulist dropdown buttons get state from the parent.
+ if (isHTML || isMenulist)
+ aFrame = parentFrame;
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ if (IsDisabled(aFrame, eventState)) {
+ aState |= DFCS_INACTIVE;
+ return NS_OK;
+ }
+
+ if (isHTML) {
+ nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame);
+ isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
+ }
+ else
+ isOpen = IsOpenButton(aFrame);
+
+ // XXX Button should look active until the mouse is released, but
+ // without making it look active when the popup is clicked.
+ if (isOpen && (isHTML || isMenulist))
+ return NS_OK;
+
+ // Dropdown button active state doesn't need :hover.
+ if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED | DFCS_FLAT;
+
+ return NS_OK;
+ }
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT: {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ aPart = DFC_SCROLL;
+ switch (aWidgetType) {
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ aState = DFCS_SCROLLUP;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ aState = DFCS_SCROLLDOWN;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ aState = DFCS_SCROLLLEFT;
+ break;
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ aState = DFCS_SCROLLRIGHT;
+ break;
+ }
+
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED | DFCS_FLAT;
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ EventStates contentState = GetContentState(aFrame, aWidgetType);
+
+ aPart = DFC_SCROLL;
+ switch (aWidgetType) {
+ case NS_THEME_SPINNER_UPBUTTON:
+ aState = DFCS_SCROLLUP;
+ break;
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ aState = DFCS_SCROLLDOWN;
+ break;
+ }
+
+ if (IsDisabled(aFrame, contentState))
+ aState |= DFCS_INACTIVE;
+ else {
+ if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
+ aState |= DFCS_PUSHED;
+ }
+
+ return NS_OK;
+ }
+ case NS_THEME_RESIZER:
+ aPart = DFC_SCROLL;
+ aState = (IsFrameRTL(aFrame)) ?
+ DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP;
+ return NS_OK;
+ case NS_THEME_MENUSEPARATOR:
+ aPart = 0;
+ aState = 0;
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR:
+ aPart = mozilla::widget::themeconst::WP_CAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
+ aState = GetTopLevelWindowActiveState(aFrame);
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONCLOSE |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMIN |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONMAX |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ aPart = DFC_CAPTION;
+ aState = DFCS_CAPTIONRESTORE |
+ GetClassicWindowFrameButtonState(GetContentState(aFrame,
+ aWidgetType));
+ return NS_OK;
+ }
+ 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);
+}
+
+static void DrawMenuImage(HDC hdc, const RECT& rc, int32_t aComponent, uint32_t aColor)
+{
+ // This procedure creates a memory bitmap to contain the check mark, draws
+ // it into the bitmap (it is a mask image), then composes it onto the menu
+ // item in appropriate colors.
+ HDC hMemoryDC = ::CreateCompatibleDC(hdc);
+ if (hMemoryDC) {
+ // XXXjgr We should ideally be caching these, but we wont be notified when
+ // they change currently, so we can't do so easily. Same for the bitmap.
+ int checkW = ::GetSystemMetrics(SM_CXMENUCHECK);
+ int checkH = ::GetSystemMetrics(SM_CYMENUCHECK);
+
+ HBITMAP hMonoBitmap = ::CreateBitmap(checkW, checkH, 1, 1, nullptr);
+ if (hMonoBitmap) {
+
+ HBITMAP hPrevBitmap = (HBITMAP) ::SelectObject(hMemoryDC, hMonoBitmap);
+ if (hPrevBitmap) {
+
+ // XXXjgr This will go pear-shaped if the image is bigger than the
+ // provided rect. What should we do?
+ RECT imgRect = { 0, 0, checkW, checkH };
+ POINT imgPos = {
+ rc.left + (rc.right - rc.left - checkW) / 2,
+ rc.top + (rc.bottom - rc.top - checkH) / 2
+ };
+
+ // XXXzeniko Windows renders these 1px lower than you'd expect
+ if (aComponent == DFCS_MENUCHECK || aComponent == DFCS_MENUBULLET)
+ imgPos.y++;
+
+ ::DrawFrameControl(hMemoryDC, &imgRect, DFC_MENU, aComponent);
+ COLORREF oldTextCol = ::SetTextColor(hdc, 0x00000000);
+ COLORREF oldBackCol = ::SetBkColor(hdc, 0x00FFFFFF);
+ ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCAND);
+ ::SetTextColor(hdc, ::GetSysColor(aColor));
+ ::SetBkColor(hdc, 0x00000000);
+ ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0, SRCPAINT);
+ ::SetTextColor(hdc, oldTextCol);
+ ::SetBkColor(hdc, oldBackCol);
+ ::SelectObject(hMemoryDC, hPrevBitmap);
+ }
+ ::DeleteObject(hMonoBitmap);
+ }
+ ::DeleteDC(hMemoryDC);
+ }
+}
+
+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(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ int32_t part, state;
+ bool focused;
+ nsresult rv;
+ rv = ClassicGetThemePartAndState(aFrame, aWidgetType, 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.ScaleInverse(p2a);
+ dr.ScaleInverse(p2a);
+
+ RefPtr<gfxContext> ctx = aContext->ThebesContext();
+
+ gfxWindowsNativeDrawing nativeDrawing(ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
+
+RENDER_AGAIN:
+
+ HDC hdc = nativeDrawing.BeginNativeDrawing();
+ if (!hdc)
+ return NS_ERROR_FAILURE;
+
+ nativeDrawing.TransformToNativeRect(tr, widgetRect);
+
+ rv = NS_OK;
+ switch (aWidgetType) {
+ // Draw button
+ case NS_THEME_BUTTON: {
+ if (focused) {
+ // draw dark button focus border first
+ HBRUSH brush;
+ brush = ::GetSysColorBrush(COLOR_3DDKSHADOW);
+ if (brush)
+ ::FrameRect(hdc, &widgetRect, brush);
+ InflateRect(&widgetRect, -1, -1);
+ }
+ // fall-through...
+ }
+ // Draw controls supported by DrawFrameControl
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_RESIZER: {
+ int32_t oldTA;
+ // setup DC to make DrawFrameControl draw correctly
+ 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 NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD: {
+ // Draw inset edge
+ ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ // Fill in background
+ if (IsDisabled(aFrame, eventState) ||
+ (aFrame->GetContent()->IsXULElement() &&
+ IsReadOnly(aFrame)))
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
+ else
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_WINDOW+1));
+
+ break;
+ }
+ case NS_THEME_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 ToolTip background
+ case NS_THEME_TOOLTIP:
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME));
+ InflateRect(&widgetRect, -1, -1);
+ ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK));
+
+ break;
+ case NS_THEME_GROUPBOX:
+ ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST);
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
+ break;
+ // Draw 3D face background controls
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ // Draw 3D border
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+ InflateRect(&widgetRect, -1, -1);
+ // fall through
+ case NS_THEME_TABPANEL:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_RESIZERPANEL: {
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_BTNFACE+1));
+
+ break;
+ }
+ // Draw 3D inset statusbar panel
+ case NS_THEME_STATUSBARPANEL: {
+ if (aFrame->GetNextSibling())
+ widgetRect.right -= 2; // space between sibling status panels
+
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
+
+ break;
+ }
+ // Draw scrollbar thumb
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
+
+ break;
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
+ if (IsDisabled(aFrame, eventState)) {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
+ (HBRUSH) COLOR_3DHILIGHT);
+ }
+
+ break;
+ }
+ // Draw scrollbar track background
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL: {
+
+ // Windows fills in the scrollbar track differently
+ // depending on whether these are equal
+ DWORD color3D, colorScrollbar, colorWindow;
+
+ color3D = ::GetSysColor(COLOR_3DFACE);
+ colorWindow = ::GetSysColor(COLOR_WINDOW);
+ colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR);
+
+ if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar))
+ // Use solid brush
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_SCROLLBAR+1));
+ else
+ {
+ DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE,
+ (HBRUSH) COLOR_SCROLLBAR+1);
+ }
+ // XXX should invert the part of the track being clicked here
+ // but the track is never :active
+
+ break;
+ }
+ // Draw scale track background
+ case NS_THEME_RANGE:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALE_HORIZONTAL: {
+ 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 (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
+ (aWidgetType == NS_THEME_RANGE && 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 NS_THEME_PROGRESSCHUNK_VERTICAL:
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1));
+ break;
+
+ case NS_THEME_PROGRESSCHUNK: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aWidgetType);
+
+ bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates);
+ bool vertical = IsVerticalProgress(stateFrame) ||
+ aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
+
+ 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 NS_THEME_TAB: {
+ DrawTab(hdc, widgetRect,
+ IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
+ IsSelectedTab(aFrame),
+ !IsRightToSelectedTab(aFrame),
+ !IsLeftToSelectedTab(aFrame));
+
+ break;
+ }
+ case NS_THEME_TABPANELS:
+ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_SOFT | BF_MIDDLE |
+ BF_LEFT | BF_RIGHT | BF_BOTTOM);
+
+ break;
+ case NS_THEME_MENUBAR:
+ break;
+ case NS_THEME_MENUPOPUP:
+ NS_ASSERTION(nsUXThemeData::sFlatMenus, "Classic menus are styled entirely through CSS");
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENU+1));
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW));
+ break;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ // part == 0 for normal items
+ // part == 1 for top-level menu items
+ if (nsUXThemeData::sFlatMenus) {
+ // Not disabled and hot/pushed.
+ if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_MENUHILIGHT+1));
+ ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_HIGHLIGHT));
+ }
+ } else {
+ if (part == 1) {
+ if ((state & DFCS_INACTIVE) == 0) {
+ if ((state & DFCS_PUSHED) != 0) {
+ ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT);
+ } else if ((state & DFCS_HOT) != 0) {
+ ::DrawEdge(hdc, &widgetRect, BDR_RAISEDINNER, BF_RECT);
+ }
+ }
+ } else {
+ if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
+ ::FillRect(hdc, &widgetRect, (HBRUSH) (COLOR_HIGHLIGHT+1));
+ }
+ }
+ }
+ break;
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ if (!(state & DFCS_CHECKED))
+ break; // nothin' to do
+ case NS_THEME_MENUARROW: {
+ uint32_t color = COLOR_MENUTEXT;
+ if ((state & DFCS_INACTIVE))
+ color = COLOR_GRAYTEXT;
+ else if ((state & DFCS_HOT))
+ color = COLOR_HIGHLIGHTTEXT;
+
+ if (aWidgetType == NS_THEME_MENUCHECKBOX)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color);
+ else if (aWidgetType == NS_THEME_MENURADIO)
+ DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color);
+ else if (aWidgetType == NS_THEME_MENUARROW)
+ DrawMenuImage(hdc, widgetRect,
+ (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW,
+ color);
+ break;
+ }
+ case NS_THEME_MENUSEPARATOR: {
+ // separators are offset by a bit (see menu.css)
+ widgetRect.left++;
+ widgetRect.right--;
+
+ // This magic number is brought to you by the value in menu.css
+ widgetRect.top += 4;
+ // Our rectangles are 1 pixel high (see border size in menu.css)
+ widgetRect.bottom = widgetRect.top+1;
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW+1));
+ widgetRect.top++;
+ widgetRect.bottom++;
+ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT+1));
+ break;
+ }
+
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
+ {
+ RECT rect = widgetRect;
+ int32_t offset = GetSystemMetrics(SM_CXFRAME);
+
+ // first fill the area to the color of the window background
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE+1));
+
+ // inset the caption area so it doesn't overflow.
+ rect.top += offset;
+ // if enabled, draw a gradient titlebar background, otherwise
+ // fill with a solid color.
+ BOOL bFlag = TRUE;
+ SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0);
+ if (!bFlag) {
+ if (state == mozilla::widget::themeconst::FS_ACTIVE)
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION+1));
+ else
+ FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION+1));
+ } else {
+ DWORD startColor, endColor;
+ if (state == mozilla::widget::themeconst::FS_ACTIVE) {
+ startColor = GetSysColor(COLOR_ACTIVECAPTION);
+ endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION);
+ } else {
+ startColor = GetSysColor(COLOR_INACTIVECAPTION);
+ endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION);
+ }
+
+ TRIVERTEX vertex[2];
+ vertex[0].x = rect.left;
+ vertex[0].y = rect.top;
+ vertex[0].Red = GetRValue(startColor) << 8;
+ vertex[0].Green = GetGValue(startColor) << 8;
+ vertex[0].Blue = GetBValue(startColor) << 8;
+ vertex[0].Alpha = 0;
+
+ vertex[1].x = rect.right;
+ vertex[1].y = rect.bottom;
+ vertex[1].Red = GetRValue(endColor) << 8;
+ vertex[1].Green = GetGValue(endColor) << 8;
+ vertex[1].Blue = GetBValue(endColor) << 8;
+ vertex[1].Alpha = 0;
+
+ GRADIENT_RECT gRect;
+ gRect.UpperLeft = 0;
+ gRect.LowerRight = 1;
+ // available on win2k & up
+ GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H);
+ }
+
+ if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
+ // frame things up with a top raised border.
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP);
+ }
+ break;
+ }
+
+ case NS_THEME_WINDOW_FRAME_LEFT:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT);
+ break;
+
+ case NS_THEME_WINDOW_FRAME_RIGHT:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT);
+ break;
+
+ case NS_THEME_WINDOW_FRAME_BOTTOM:
+ DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM);
+ break;
+
+ case NS_THEME_WINDOW_BUTTON_CLOSE:
+ case NS_THEME_WINDOW_BUTTON_MINIMIZE:
+ case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
+ case NS_THEME_WINDOW_BUTTON_RESTORE:
+ {
+ if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
+ aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
+ }
+ else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
+ OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
+ }
+ int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+ DrawFrameControl(hdc, &widgetRect, part, state);
+ SetTextAlign(hdc, oldTA);
+ 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(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_FOCUS_OUTLINE:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+ // need to check these others
+ case NS_THEME_RANGE:
+ case NS_THEME_RANGE_THUMB:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_VERTICAL:
+ case NS_THEME_SCROLLBAR_HORIZONTAL:
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_RESIZER:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_TAB:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_MENUBAR:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ break;
+
+ // the dropdown button /almost/ renders correctly with scaling,
+ // except that the graphic in the dropdown button (the downward arrow)
+ // doesn't get scaled up.
+ case NS_THEME_MENULIST_BUTTON:
+ // these are definitely no; they're all graphics that don't get scaled up
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_RADIOMENUITEM:
+ case NS_THEME_MENUCHECKBOX:
+ case NS_THEME_MENURADIO:
+ case NS_THEME_MENUARROW:
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+ }
+
+ return
+ gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+ gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+ gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+}
+
+///////////////////////////////////////////
+// Creation Routine
+///////////////////////////////////////////
+
+// from nsWindow.cpp
+extern bool gDisableNativeTheme;
+
+nsresult NS_NewNativeTheme(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ if (gDisableNativeTheme)
+ return NS_ERROR_NO_INTERFACE;
+
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsNativeThemeWin* theme = new nsNativeThemeWin();
+ if (!theme)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return theme->QueryInterface(aIID, aResult);
+}
diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h
new file mode 100644
index 0000000000..f206494447
--- /dev/null
+++ b/widget/windows/nsNativeThemeWin.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 nsNativeThemeWin_h
+#define nsNativeThemeWin_h
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsNativeTheme.h"
+#include "gfxTypes.h"
+#include <windows.h>
+#include "mozilla/TimeStamp.h"
+#include "nsSize.h"
+
+class nsNativeThemeWin : private nsNativeTheme,
+ public nsITheme {
+ virtual ~nsNativeThemeWin();
+
+public:
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ bool ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ bool WidgetIsContainer(uint8_t aWidgetType) override;
+
+ bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+
+ bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override;
+
+ enum {
+ eThemeGeometryTypeWindowButtons = eThemeGeometryTypeUnknown + 1
+ };
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+
+ virtual bool ShouldHideScrollbars() override;
+
+ nsNativeThemeWin();
+
+protected:
+ HANDLE GetTheme(uint8_t aWidgetType);
+ nsresult GetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState);
+ nsresult ClassicGetThemePartAndState(nsIFrame* aFrame, uint8_t aWidgetType,
+ int32_t& aPart, int32_t& aState, bool& aFocused);
+ nsresult ClassicDrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aClipRect);
+ nsresult ClassicGetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult);
+ bool ClassicGetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult);
+ nsresult ClassicGetMinimumWidgetSize(nsIFrame* aFrame, uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable);
+ bool ClassicThemeSupportsWidget(nsIFrame* aFrame, uint8_t aWidgetType);
+ void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back,
+ HBRUSH defaultBack);
+ uint32_t GetWidgetNativeDrawingFlags(uint8_t aWidgetType);
+ int32_t StandardGetState(nsIFrame* aFrame, uint8_t aWidgetType, bool wantFocused);
+ bool IsMenuActive(nsIFrame* aFrame, uint8_t aWidgetType);
+ RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect,
+ bool aIsVertical, bool aIsIndeterminate,
+ bool aIsClassic);
+ void DrawThemedProgressMeter(nsIFrame* aFrame, int aWidgetType,
+ HANDLE aTheme, HDC aHdc,
+ int aPart, int aState,
+ RECT* aWidgetRect, RECT* aClipRect,
+ gfxFloat aAppUnits);
+
+private:
+ TimeStamp mProgressDeterminateTimeStamp;
+ TimeStamp mProgressIndeterminateTimeStamp;
+};
+
+#endif
diff --git a/widget/windows/nsPrintOptionsWin.cpp b/widget/windows/nsPrintOptionsWin.cpp
new file mode 100644
index 0000000000..97b15fa3fb
--- /dev/null
+++ b/widget/windows/nsPrintOptionsWin.cpp
@@ -0,0 +1,162 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsPrintOptionsWin.h"
+#include "nsPrintSettingsWin.h"
+#include "nsPrintDialogUtil.h"
+
+#include "nsGfxCIID.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsWindowsHelpers.h"
+#include "ipc/IPCMessageUtils.h"
+
+const char kPrinterEnumeratorContractID[] = "@mozilla.org/gfx/printerenumerator;1";
+
+using namespace mozilla::embedding;
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsWin.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsWin::nsPrintOptionsWin()
+{
+
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintOptionsImpl.h
+ * @update 6/21/00 dwc
+ */
+nsPrintOptionsWin::~nsPrintOptionsWin()
+{
+}
+
+NS_IMETHODIMP
+nsPrintOptionsWin::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Windows wants this information for its print dialogs
+ if (aWBP) {
+ aWBP->GetIsFramesetDocument(&data->isFramesetDocument());
+ aWBP->GetIsFramesetFrameSelected(&data->isFramesetFrameSelected());
+ aWBP->GetIsIFrameSelected(&data->isIFrameSelected());
+ aWBP->GetIsRangeSelection(&data->isRangeSelection());
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings);
+ if (!psWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char16_t* deviceName;
+ char16_t* driverName;
+
+ psWin->GetDeviceName(&deviceName);
+ psWin->GetDriverName(&driverName);
+
+ data->deviceName().Assign(deviceName);
+ data->driverName().Assign(driverName);
+
+ free(deviceName);
+ free(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()) {
+ psWin->GetPrintableWidthInInches(&data->printableWidthInInches());
+ psWin->GetPrintableHeightInInches(&data->printableHeightInInches());
+
+ // 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::MAX_MESSAGE_SIZE) {
+ 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().SwapElements(arrayBuf);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsWin::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsresult rv = nsPrintOptions::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().get());
+ psWin->SetDriverName(data.driverName().get());
+
+ psWin->SetPrintableWidthInInches(data.printableWidthInInches());
+ psWin->SetPrintableHeightInInches(data.printableHeightInInches());
+
+ 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 nsPrintOptionsWin::_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/nsPrintOptionsWin.h b/widget/windows/nsPrintOptionsWin.h
new file mode 100644
index 0000000000..7a8a89fc92
--- /dev/null
+++ b/widget/windows/nsPrintOptionsWin.h
@@ -0,0 +1,36 @@
+/* -*- 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 nsPrintOptionsWin_h__
+#define nsPrintOptionsWin_h__
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsPrintOptionsImpl.h"
+
+class nsIPrintSettings;
+class nsIWebBrowserPrint;
+
+//*****************************************************************************
+//*** nsPrintOptions
+//*****************************************************************************
+class nsPrintOptionsWin : public nsPrintOptions
+{
+public:
+ nsPrintOptionsWin();
+ virtual ~nsPrintOptionsWin();
+
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+};
+
+
+
+#endif /* nsPrintOptions_h__ */
diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp
new file mode 100644
index 0000000000..80e73e530b
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.cpp
@@ -0,0 +1,528 @@
+/* -*- 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"
+
+// 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) :
+ mDeviceName(nullptr),
+ mDriverName(nullptr),
+ mDevMode(nullptr)
+{
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ * See documentation in nsPrintSettingsWin.h
+ * @update
+ */
+nsPrintSettingsWin::~nsPrintSettingsWin()
+{
+ if (mDeviceName) free(mDeviceName);
+ if (mDriverName) free(mDriverName);
+ if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const char16_t * aDeviceName)
+{
+ if (mDeviceName) {
+ free(mDeviceName);
+ }
+ mDeviceName = aDeviceName?wcsdup(char16ptr_t(aDeviceName)):nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(char16_t **aDeviceName)
+{
+ NS_ENSURE_ARG_POINTER(aDeviceName);
+ *aDeviceName = mDeviceName?reinterpret_cast<char16_t*>(wcsdup(mDeviceName)):nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const char16_t * aDriverName)
+{
+ if (mDriverName) {
+ free(mDriverName);
+ }
+ mDriverName = aDriverName?wcsdup(char16ptr_t(aDriverName)):nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(char16_t **aDriverName)
+{
+ NS_ENSURE_ARG_POINTER(aDriverName);
+ *aDriverName = mDriverName?reinterpret_cast<char16_t*>(wcsdup(mDriverName)):nullptr;
+ 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;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetPrintableWidthInInches(double* aPrintableWidthInInches)
+{
+ MOZ_ASSERT(aPrintableWidthInInches);
+ *aPrintableWidthInInches = mPrintableWidthInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::SetPrintableWidthInInches(double aPrintableWidthInInches)
+{
+ mPrintableWidthInInches = aPrintableWidthInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetPrintableHeightInInches(double* aPrintableHeightInInches)
+{
+ MOZ_ASSERT(aPrintableHeightInInches);
+ *aPrintableHeightInInches = mPrintableHeightInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::SetPrintableHeightInInches(double aPrintableHeightInInches)
+{
+ mPrintableHeightInInches = aPrintableHeightInInches;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsWin::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ // If printable page size not set, fall back to nsPrintSettings.
+ if (mPrintableWidthInInches == 0l || mPrintableHeightInInches == 0l) {
+ return nsPrintSettings::GetEffectivePageSize(aWidth, aHeight);
+ }
+
+ if (mOrientation == kPortraitOrientation) {
+ *aWidth = NS_INCHES_TO_TWIPS(mPrintableWidthInInches);
+ *aHeight = NS_INCHES_TO_TWIPS(mPrintableHeightInInches);
+ } else {
+ *aHeight = NS_INCHES_TO_TWIPS(mPrintableWidthInInches);
+ *aWidth = NS_INCHES_TO_TWIPS(mPrintableHeightInInches);
+ }
+ return NS_OK;
+}
+
+void
+nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode)
+{
+ MOZ_ASSERT(aHdc);
+ MOZ_ASSERT(aDevMode);
+
+ mIsInitedFromPrinter = true;
+ if (aDevMode->dmFields & DM_ORIENTATION) {
+ mOrientation = int32_t(aDevMode->dmOrientation == DMORIENT_PORTRAIT
+ ? kPortraitOrientation : kLandscapeOrientation);
+ }
+
+ if (aDevMode->dmFields & DM_COPIES) {
+ mNumCopies = aDevMode->dmCopies;
+ }
+
+ // 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) {
+ mPaperData = aDevMode->dmPaperSize;
+ // If not a paper size we know about, the unit will be the last one saved.
+ if (mPaperData > 0 &&
+ mPaperData < int32_t(mozilla::ArrayLength(kPaperSizeUnits))) {
+ mPaperSizeUnit = kPaperSizeUnits[mPaperData];
+ }
+ } else {
+ mPaperData = -1;
+ }
+
+ // The length and width in DEVMODE are always in tenths of a millimeter.
+ double sizeUnitToTenthsOfAmm =
+ 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L);
+ if (aDevMode->dmFields & DM_PAPERLENGTH) {
+ mPaperHeight = aDevMode->dmPaperLength / sizeUnitToTenthsOfAmm;
+ } else {
+ mPaperHeight = -1l;
+ }
+
+ if (aDevMode->dmFields & DM_PAPERWIDTH) {
+ mPaperWidth = aDevMode->dmPaperWidth / sizeUnitToTenthsOfAmm;
+ } else {
+ mPaperWidth = -1l;
+ }
+
+ // On Windows we currently create a surface using the printable area of the
+ // page and don't set the unwriteable [sic] margins. Using the unwriteable
+ // margins doesn't appear to work on Windows, but I am not sure if this is a
+ // bug elsewhere in our code or a Windows quirk.
+ // Note: we only scale the printing using the LOGPIXELSY, so we use that
+ // when calculating the surface width as well as the height.
+ int32_t printableWidthInDots = GetDeviceCaps(aHdc, HORZRES);
+ int32_t printableHeightInDots = GetDeviceCaps(aHdc, VERTRES);
+ int32_t heightDPI = GetDeviceCaps(aHdc, LOGPIXELSY);
+
+ // Keep these values in portrait format, so we can reflect our own changes
+ // to mOrientation.
+ if (mOrientation == kPortraitOrientation) {
+ mPrintableWidthInInches = double(printableWidthInDots) / heightDPI;
+ mPrintableHeightInInches = double(printableHeightInDots) / heightDPI;
+ } else {
+ mPrintableHeightInInches = double(printableWidthInDots) / heightDPI;
+ mPrintableWidthInInches = double(printableHeightInDots) / heightDPI;
+ }
+
+ // Using Y to match existing code for print scaling calculations.
+ mResolution = heightDPI;
+}
+
+void
+nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode)
+{
+ MOZ_ASSERT(aDevMode);
+
+ if (mPaperData >= 0) {
+ aDevMode->dmPaperSize = mPaperData;
+ aDevMode->dmFields |= DM_PAPERSIZE;
+ } else {
+ aDevMode->dmPaperSize = 0;
+ aDevMode->dmFields &= ~DM_PAPERSIZE;
+ }
+
+ // The length and width in DEVMODE are always in tenths of a millimeter.
+ double sizeUnitToTenthsOfAmm =
+ 10L * (mPaperSizeUnit == kPaperSizeInches ? MM_PER_INCH_FLOAT : 1L);
+
+ // Note: small page sizes can be required here for sticker, label and slide
+ // printers etc. see bug 1271900.
+ if (mPaperHeight > 0) {
+ aDevMode->dmPaperLength = mPaperHeight * sizeUnitToTenthsOfAmm;
+ aDevMode->dmFields |= DM_PAPERLENGTH;
+ } else {
+ aDevMode->dmPaperLength = 0;
+ aDevMode->dmFields &= ~DM_PAPERLENGTH;
+ }
+
+ if (mPaperWidth > 0) {
+ aDevMode->dmPaperWidth = mPaperWidth * sizeUnitToTenthsOfAmm;
+ aDevMode->dmFields |= DM_PAPERWIDTH;
+ } else {
+ aDevMode->dmPaperWidth = 0;
+ aDevMode->dmFields &= ~DM_PAPERWIDTH;
+ }
+
+ // Setup Orientation
+ aDevMode->dmOrientation = mOrientation == kPortraitOrientation
+ ? DMORIENT_PORTRAIT : DMORIENT_LANDSCAPE;
+ aDevMode->dmFields |= DM_ORIENTATION;
+
+ // Setup Number of Copies
+ aDevMode->dmCopies = mNumCopies;
+ aDevMode->dmFields |= DM_COPIES;
+}
+
+//-------------------------------------------
+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;
+
+ if (mDeviceName) {
+ free(mDeviceName);
+ }
+
+ if (mDriverName) {
+ free(mDriverName);
+ }
+
+ // Use free because we used the native malloc to create the memory
+ if (mDevMode) {
+ ::HeapFree(::GetProcessHeap(), 0, mDevMode);
+ }
+
+ mDeviceName = rhs.mDeviceName?wcsdup(rhs.mDeviceName):nullptr;
+ mDriverName = rhs.mDriverName?wcsdup(rhs.mDriverName):nullptr;
+
+ 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;
+}
+
+//----------------------------------------------------------------------
+// Testing of assign and clone
+// This define turns on the testing module below
+// so at start up it writes and reads the prefs.
+#ifdef DEBUG_rodsX
+#include "nsIPrintOptions.h"
+#include "nsIServiceManager.h"
+class Tester {
+public:
+ Tester();
+};
+Tester::Tester()
+{
+ nsCOMPtr<nsIPrintSettings> ps;
+ nsresult rv;
+ nsCOMPtr<nsIPrintOptions> printService = do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = printService->CreatePrintSettings(getter_AddRefs(ps));
+ }
+
+ if (ps) {
+ ps->SetPrintOptions(nsIPrintSettings::kPrintOddPages, true);
+ ps->SetPrintOptions(nsIPrintSettings::kPrintEvenPages, false);
+ ps->SetMarginTop(1.0);
+ ps->SetMarginLeft(1.0);
+ ps->SetMarginBottom(1.0);
+ ps->SetMarginRight(1.0);
+ ps->SetScaling(0.5);
+ ps->SetPrintBGColors(true);
+ ps->SetPrintBGImages(true);
+ ps->SetPrintRange(15);
+ ps->SetHeaderStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetHeaderStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetHeaderStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetFooterStrLeft(NS_ConvertUTF8toUTF16("Left").get());
+ ps->SetFooterStrCenter(NS_ConvertUTF8toUTF16("Center").get());
+ ps->SetFooterStrRight(NS_ConvertUTF8toUTF16("Right").get());
+ ps->SetPaperName(NS_ConvertUTF8toUTF16("Paper Name").get());
+ ps->SetPaperData(1);
+ ps->SetPaperWidth(100.0);
+ ps->SetPaperHeight(50.0);
+ ps->SetPaperSizeUnit(nsIPrintSettings::kPaperSizeMillimeters);
+ ps->SetPrintReversed(true);
+ ps->SetPrintInColor(true);
+ ps->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ ps->SetPrintCommand(NS_ConvertUTF8toUTF16("Command").get());
+ ps->SetNumCopies(2);
+ ps->SetPrinterName(NS_ConvertUTF8toUTF16("Printer Name").get());
+ ps->SetPrintToFile(true);
+ ps->SetToFileName(NS_ConvertUTF8toUTF16("File Name").get());
+ ps->SetPrintPageDelay(1000);
+
+ nsCOMPtr<nsIPrintSettings> ps2;
+ if (NS_SUCCEEDED(rv)) {
+ rv = printService->CreatePrintSettings(getter_AddRefs(ps2));
+ }
+
+ ps2->Assign(ps);
+
+ nsCOMPtr<nsIPrintSettings> psClone;
+ ps2->Clone(getter_AddRefs(psClone));
+
+ }
+
+}
+Tester gTester;
+#endif
diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h
new file mode 100644
index 0000000000..d4a31b41e2
--- /dev/null
+++ b/widget/windows/nsPrintSettingsWin.h
@@ -0,0 +1,59 @@
+/* -*- 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);
+
+ /**
+ * Makes a new copy
+ */
+ virtual nsresult _Clone(nsIPrintSettings **_retval);
+
+ /**
+ * Assigns values
+ */
+ virtual nsresult _Assign(nsIPrintSettings* aPS);
+
+ /**
+ * Assignment
+ */
+ nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs);
+
+ NS_IMETHOD GetEffectivePageSize(double *aWidth, double *aHeight) override;
+
+protected:
+ void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW *& aOutDevMode);
+
+ wchar_t* mDeviceName;
+ wchar_t* mDriverName;
+ LPDEVMODEW mDevMode;
+ double mPrintableWidthInInches = 0l;
+ double mPrintableHeightInInches = 0l;
+};
+
+
+
+#endif /* nsPrintSettingsWin_h__ */
diff --git a/widget/windows/nsScreenManagerWin.cpp b/widget/windows/nsScreenManagerWin.cpp
new file mode 100644
index 0000000000..5440be7dd7
--- /dev/null
+++ b/widget/windows/nsScreenManagerWin.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/. */
+
+#include "nsScreenManagerWin.h"
+#include "mozilla/gfx/2D.h"
+#include "nsScreenWin.h"
+#include "gfxWindowsPlatform.h"
+#include "nsIWidget.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+BOOL CALLBACK CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioCount);
+
+nsScreenManagerWin::nsScreenManagerWin()
+ : mNumberOfScreens(0)
+{
+ // nothing to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenManagerWin::~nsScreenManagerWin()
+{
+}
+
+
+// addref, release, QI
+NS_IMPL_ISUPPORTS(nsScreenManagerWin, nsIScreenManager)
+
+
+//
+// CreateNewScreenObject
+//
+// Utility routine. Creates a new screen object from the given device handle
+//
+// NOTE: For this "single-monitor" impl, we just always return the cached primary
+// screen. This should change when a multi-monitor impl is done.
+//
+nsIScreen*
+nsScreenManagerWin::CreateNewScreenObject(HMONITOR inScreen)
+{
+ nsIScreen* retScreen = nullptr;
+
+ // look through our screen list, hoping to find it. If it's not there,
+ // add it and return the new one.
+ for (unsigned i = 0; i < mScreenList.Length(); ++i) {
+ ScreenListItem& curr = mScreenList[i];
+ if (inScreen == curr.mMon) {
+ NS_IF_ADDREF(retScreen = curr.mScreen.get());
+ return retScreen;
+ }
+ } // for each screen.
+
+ retScreen = new nsScreenWin(inScreen);
+ mScreenList.AppendElement(ScreenListItem(inScreen, retScreen));
+
+ NS_IF_ADDREF(retScreen);
+ return retScreen;
+}
+
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForId(uint32_t aId, nsIScreen **outScreen)
+{
+ *outScreen = nullptr;
+
+ for (unsigned i = 0; i < mScreenList.Length(); ++i) {
+ ScreenListItem& curr = mScreenList[i];
+ uint32_t id;
+ nsresult rv = curr.mScreen->GetId(&id);
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ NS_IF_ADDREF(*outScreen = curr.mScreen.get());
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//
+// ScreenForRect
+//
+// Returns the screen that contains the rectangle. If the rect overlaps
+// multiple screens, it picks the screen with the greatest area of intersection.
+//
+// The coordinates are in pixels (not twips) and in logical screen coordinates.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForRect(int32_t inLeft, int32_t inTop,
+ int32_t inWidth, int32_t inHeight,
+ nsIScreen **outScreen)
+{
+ if (!(inWidth || inHeight)) {
+ NS_WARNING("trying to find screen for sizeless window, using primary monitor");
+ *outScreen = CreateNewScreenObject(nullptr); // addrefs
+ return NS_OK;
+ }
+
+ gfx::Rect logicalBounds(inLeft, inTop, inWidth, inHeight);
+ HMONITOR genScreen = widget::WinUtils::MonitorFromRect(logicalBounds);
+
+ *outScreen = CreateNewScreenObject(genScreen); // addrefs
+
+ return NS_OK;
+
+} // ScreenForRect
+
+
+//
+// GetPrimaryScreen
+//
+// The screen with the menubar/taskbar. This shouldn't be needed very
+// often.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::GetPrimaryScreen(nsIScreen** aPrimaryScreen)
+{
+ *aPrimaryScreen = CreateNewScreenObject(nullptr); // addrefs
+ return NS_OK;
+
+} // GetPrimaryScreen
+
+
+//
+// CountMonitors
+//
+// Will be called once for every monitor in the system. Just
+// increments the parameter, which holds a ptr to a PRUin32 holding the
+// count up to this point.
+//
+BOOL CALLBACK
+CountMonitors(HMONITOR, HDC, LPRECT, LPARAM ioParam)
+{
+ uint32_t* countPtr = reinterpret_cast<uint32_t*>(ioParam);
+ ++(*countPtr);
+
+ return TRUE; // continue the enumeration
+
+} // CountMonitors
+
+
+//
+// GetNumberOfScreens
+//
+// Returns how many physical screens are available.
+//
+NS_IMETHODIMP
+nsScreenManagerWin::GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ if (mNumberOfScreens)
+ *aNumberOfScreens = mNumberOfScreens;
+ else {
+ uint32_t count = 0;
+ BOOL result = ::EnumDisplayMonitors(nullptr, nullptr, (MONITORENUMPROC)CountMonitors, (LPARAM)&count);
+ if (!result)
+ return NS_ERROR_FAILURE;
+ *aNumberOfScreens = mNumberOfScreens = count;
+ }
+
+ return NS_OK;
+
+} // GetNumberOfScreens
+
+NS_IMETHODIMP
+nsScreenManagerWin::GetSystemDefaultScale(float *aDefaultScale)
+{
+ HMONITOR primary = widget::WinUtils::GetPrimaryMonitor();
+ *aDefaultScale = float(widget::WinUtils::LogToPhysFactor(primary));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerWin::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
+{
+ HMONITOR mon = MonitorFromWindow((HWND) aWidget, MONITOR_DEFAULTTOPRIMARY);
+ *outScreen = CreateNewScreenObject(mon);
+ return NS_OK;
+}
diff --git a/widget/windows/nsScreenManagerWin.h b/widget/windows/nsScreenManagerWin.h
new file mode 100644
index 0000000000..c4757418a3
--- /dev/null
+++ b/widget/windows/nsScreenManagerWin.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 nsScreenManagerWin_h___
+#define nsScreenManagerWin_h___
+
+#include "nsIScreenManager.h"
+
+#include <windows.h>
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+class nsIScreen;
+
+//------------------------------------------------------------------------
+
+class ScreenListItem
+{
+public:
+ ScreenListItem ( HMONITOR inMon, nsIScreen* inScreen )
+ : mMon(inMon), mScreen(inScreen) { } ;
+
+ HMONITOR mMon;
+ nsCOMPtr<nsIScreen> mScreen;
+};
+
+class nsScreenManagerWin final : public nsIScreenManager
+{
+public:
+ nsScreenManagerWin ( );
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+private:
+ ~nsScreenManagerWin();
+
+ nsIScreen* CreateNewScreenObject ( HMONITOR inScreen ) ;
+
+ uint32_t mNumberOfScreens;
+
+ // cache the screens to avoid the memory allocations
+ AutoTArray<ScreenListItem, 8> mScreenList;
+
+};
+
+#endif // nsScreenManagerWin_h___
diff --git a/widget/windows/nsScreenWin.cpp b/widget/windows/nsScreenWin.cpp
new file mode 100644
index 0000000000..beacbf05fd
--- /dev/null
+++ b/widget/windows/nsScreenWin.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 "nsScreenWin.h"
+#include "nsCoord.h"
+#include "nsIWidget.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+
+static uint32_t sScreenId;
+
+nsScreenWin::nsScreenWin(HMONITOR inScreen)
+ : mScreen(inScreen)
+ , mId(++sScreenId)
+{
+#ifdef DEBUG
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+ NS_ASSERTION(::GetDeviceCaps(hDCScreen, TECHNOLOGY) == DT_RASDISPLAY, "Not a display screen");
+ ::ReleaseDC(nullptr,hDCScreen);
+#endif
+
+ // nothing else to do. I guess we could cache a bunch of information
+ // here, but we want to ask the device at runtime in case anything
+ // has changed.
+}
+
+
+nsScreenWin::~nsScreenWin()
+{
+ // nothing to see here.
+}
+
+
+NS_IMETHODIMP
+nsScreenWin::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenWin::GetRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ BOOL success = FALSE;
+ if (mScreen) {
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ success = ::GetMonitorInfoW(mScreen, &info);
+ if (success) {
+ *outLeft = info.rcMonitor.left;
+ *outTop = info.rcMonitor.top;
+ *outWidth = info.rcMonitor.right - info.rcMonitor.left;
+ *outHeight = info.rcMonitor.bottom - info.rcMonitor.top;
+ }
+ }
+ if (!success) {
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+
+ *outTop = *outLeft = 0;
+ *outWidth = ::GetDeviceCaps(hDCScreen, HORZRES);
+ *outHeight = ::GetDeviceCaps(hDCScreen, VERTRES);
+
+ ::ReleaseDC(nullptr, hDCScreen);
+ }
+ return NS_OK;
+
+} // GetRect
+
+
+NS_IMETHODIMP
+nsScreenWin::GetAvailRect(int32_t *outLeft, int32_t *outTop, int32_t *outWidth, int32_t *outHeight)
+{
+ BOOL success = FALSE;
+
+ if (mScreen) {
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ success = ::GetMonitorInfoW(mScreen, &info);
+ if (success) {
+ *outLeft = info.rcWork.left;
+ *outTop = info.rcWork.top;
+ *outWidth = info.rcWork.right - info.rcWork.left;
+ *outHeight = info.rcWork.bottom - info.rcWork.top;
+ }
+ }
+ if (!success) {
+ RECT workArea;
+ ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
+ *outLeft = workArea.left;
+ *outTop = workArea.top;
+ *outWidth = workArea.right - workArea.left;
+ *outHeight = workArea.bottom - workArea.top;
+ }
+
+ return NS_OK;
+
+} // GetAvailRect
+
+NS_IMETHODIMP
+nsScreenWin::GetRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ // on per-monitor-dpi config, display pixels are device pixels
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+ }
+ int32_t left, top, width, height;
+ nsresult rv = GetRect(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen);
+ *outLeft = NSToIntRound(left * scaleFactor);
+ *outTop = NSToIntRound(top * scaleFactor);
+ *outWidth = NSToIntRound(width * scaleFactor);
+ *outHeight = NSToIntRound(height * scaleFactor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenWin::GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ // on per-monitor-dpi config, display pixels are device pixels
+ return GetAvailRect(outLeft, outTop, outWidth, outHeight);
+ }
+ int32_t left, top, width, height;
+ nsresult rv = GetAvailRect(&left, &top, &width, &height);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ double scaleFactor = 1.0 / widget::WinUtils::LogToPhysFactor(mScreen);
+ *outLeft = NSToIntRound(left * scaleFactor);
+ *outTop = NSToIntRound(top * scaleFactor);
+ *outWidth = NSToIntRound(width * scaleFactor);
+ *outHeight = NSToIntRound(height * scaleFactor);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsScreenWin :: GetPixelDepth(int32_t *aPixelDepth)
+{
+ //XXX not sure how to get this info for multiple monitors, this might be ok...
+ HDC hDCScreen = ::GetDC(nullptr);
+ NS_ASSERTION(hDCScreen,"GetDC Failure");
+
+ int32_t depth = ::GetDeviceCaps(hDCScreen, BITSPIXEL);
+ if (depth == 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.)
+ depth = 24;
+ }
+ *aPixelDepth = depth;
+
+ ::ReleaseDC(nullptr, hDCScreen);
+ return NS_OK;
+
+} // GetPixelDepth
+
+
+NS_IMETHODIMP
+nsScreenWin::GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth(aColorDepth);
+
+} // GetColorDepth
+
+
+NS_IMETHODIMP
+nsScreenWin::GetContentsScaleFactor(double *aContentsScaleFactor)
+{
+ if (widget::WinUtils::IsPerMonitorDPIAware()) {
+ *aContentsScaleFactor = 1.0;
+ } else {
+ *aContentsScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenWin::GetDefaultCSSScaleFactor(double* aScaleFactor)
+{
+ double scale = nsIWidget::DefaultScaleOverride();
+ if (scale > 0.0) {
+ *aScaleFactor = scale;
+ } else {
+ *aScaleFactor = widget::WinUtils::LogToPhysFactor(mScreen);
+ }
+ return NS_OK;
+}
diff --git a/widget/windows/nsScreenWin.h b/widget/windows/nsScreenWin.h
new file mode 100644
index 0000000000..0560c55342
--- /dev/null
+++ b/widget/windows/nsScreenWin.h
@@ -0,0 +1,45 @@
+/* -*- 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 nsScreenWin_h___
+#define nsScreenWin_h___
+
+#include <windows.h>
+#include "nsBaseScreen.h"
+
+//------------------------------------------------------------------------
+
+class nsScreenWin final : public nsBaseScreen
+{
+public:
+ nsScreenWin ( HMONITOR inScreen );
+ ~nsScreenWin();
+
+ NS_IMETHOD GetId(uint32_t* aId);
+
+ // These methods return the size in device (physical) pixels
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+
+ // And these methods get the screen size in 'desktop pixels' (AKA 'logical pixels')
+ // that are dependent on the logical DPI setting in windows
+ NS_IMETHOD GetRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight);
+
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override;
+
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor) override;
+
+private:
+ HMONITOR mScreen;
+ uint32_t mId;
+};
+
+#endif // nsScreenWin_h___
diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp
new file mode 100644
index 0000000000..a7e3f8e7ca
--- /dev/null
+++ b/widget/windows/nsSound.cpp
@@ -0,0 +1,305 @@
+/* -*- 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 "plstr.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 "nsSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "prmem.h"
+
+#include "nsNativeCharsetUtils.h"
+#include "nsThreadUtils.h"
+
+using mozilla::LogLevel;
+
+PRLogModuleInfo* gWin32SoundLog = nullptr;
+
+class nsSoundPlayer: public mozilla::Runnable {
+public:
+ nsSoundPlayer(nsSound *aSound, const wchar_t* aSoundName) :
+ mSoundName(aSoundName), mSound(aSound)
+ {
+ Init();
+ }
+
+ nsSoundPlayer(nsSound *aSound, const nsAString& aSoundName) :
+ mSoundName(aSoundName), mSound(aSound)
+ {
+ Init();
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+protected:
+ nsString mSoundName;
+ nsSound *mSound; // Strong, but this will be released from SoundReleaser.
+ nsCOMPtr<nsIThread> mThread;
+
+ void Init()
+ {
+ NS_GetCurrentThread(getter_AddRefs(mThread));
+ NS_ASSERTION(mThread, "failed to get current thread");
+ NS_IF_ADDREF(mSound);
+ }
+
+ class SoundReleaser: public mozilla::Runnable {
+ public:
+ SoundReleaser(nsSound* aSound) :
+ mSound(aSound)
+ {
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+ protected:
+ nsSound *mSound;
+ };
+};
+
+NS_IMETHODIMP
+nsSoundPlayer::Run()
+{
+ PR_SetCurrentThreadName("Play Sound");
+
+ NS_PRECONDITION(!mSoundName.IsEmpty(), "Sound name should not be empty");
+ ::PlaySoundW(mSoundName.get(), nullptr,
+ SND_NODEFAULT | SND_ALIAS | SND_ASYNC);
+ nsCOMPtr<nsIRunnable> releaser = new SoundReleaser(mSound);
+ // Don't release nsSound from here, because here is not an owning thread of
+ // the nsSound. nsSound must be released in its owning thread.
+ mThread->Dispatch(releaser, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSoundPlayer::SoundReleaser::Run()
+{
+ mSound->ShutdownOldPlayerThread();
+ NS_IF_RELEASE(mSound);
+ return NS_OK;
+}
+
+
+#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)
+
+
+nsSound::nsSound()
+{
+ if (!gWin32SoundLog) {
+ gWin32SoundLog = PR_NewLogModule("nsSound");
+ }
+
+ mLastSound = nullptr;
+}
+
+nsSound::~nsSound()
+{
+ NS_ASSERTION(!mPlayerThread, "player thread is not null but should be");
+ PurgeLastSound();
+}
+
+void nsSound::ShutdownOldPlayerThread()
+{
+ nsCOMPtr<nsIThread> playerThread(mPlayerThread.forget());
+ if (playerThread)
+ playerThread->Shutdown();
+}
+
+void nsSound::PurgeLastSound()
+{
+ if (mLastSound) {
+ // Halt any currently playing sound.
+ ::PlaySound(nullptr, nullptr, SND_PURGE);
+
+ // Now delete the buffer.
+ free(mLastSound);
+ mLastSound = nullptr;
+ }
+}
+
+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)
+{
+ // 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;
+ }
+
+ ShutdownOldPlayerThread();
+ PurgeLastSound();
+
+ if (data && dataLen > 0) {
+ DWORD flags = SND_MEMORY | SND_NODEFAULT;
+ // We try to make a copy so we can play it async.
+ mLastSound = (uint8_t *) malloc(dataLen);
+ if (mLastSound) {
+ memcpy(mLastSound, data, dataLen);
+ data = mLastSound;
+ flags |= SND_ASYNC;
+ }
+ ::PlaySoundW(reinterpret_cast<LPCWSTR>(data), 0, flags);
+ }
+
+ 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_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsSound::Init()
+{
+ // 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.
+ ::PlaySound(nullptr, nullptr, SND_PURGE);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ ShutdownOldPlayerThread();
+ PurgeLastSound();
+
+ if (!NS_IsMozAliasSound(aSoundAlias)) {
+ if (aSoundAlias.IsEmpty())
+ return NS_OK;
+ nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, aSoundAlias);
+ NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+
+ uint32_t eventId;
+ if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
+ eventId = EVENT_NEW_MAIL_RECEIVED;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
+ eventId = EVENT_CONFIRM_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
+ eventId = EVENT_ALERT_DIALOG_OPEN;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
+ eventId = EVENT_MENU_EXECUTE;
+ else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
+ eventId = EVENT_MENU_POPUP;
+ else
+ return NS_OK;
+
+ return PlayEventSound(eventId);
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId)
+{
+ ShutdownOldPlayerThread();
+ 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");
+
+ nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, sound);
+ NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h
new file mode 100644
index 0000000000..fc3ea30ca6
--- /dev/null
+++ b/widget/windows/nsSound.h
@@ -0,0 +1,37 @@
+/* -*- 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 "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+
+class nsIThread;
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+
+{
+public:
+ nsSound();
+ void ShutdownOldPlayerThread();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+private:
+ virtual ~nsSound();
+ void PurgeLastSound();
+
+private:
+ uint8_t* mLastSound;
+ nsCOMPtr<nsIThread> mPlayerThread;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp
new file mode 100644
index 0000000000..6c3cc0508e
--- /dev/null
+++ b/widget/windows/nsToolkit.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 "nsToolkit.h"
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "nsWidgetsCID.h"
+#include "prmon.h"
+#include "prtime.h"
+#include "nsIServiceManager.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();
+ nsUXThemeData::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..14bb22c189
--- /dev/null
+++ b/widget/windows/nsToolkit.h
@@ -0,0 +1,49 @@
+/* -*- 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 "nsITimer.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..731dcedf25
--- /dev/null
+++ b/widget/windows/nsUXThemeConstants.h
@@ -0,0 +1,251 @@
+/* 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
+
+// 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
+
+// Vista only; implict 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 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
+};
+
+enum FRAMESTATES {
+ FS_ACTIVE = 1,
+ FS_INACTIVE = 2
+};
+
+enum {
+ BS_NORMAL = 1,
+ BS_HOT = 2,
+ BS_PUSHED = 3,
+ BS_DISABLED = 4,
+ BS_INACTIVE = 5 /* undocumented, inactive caption button */
+};
+
+}}} // mozilla::widget::themeconst
+
+#endif
diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp
new file mode 100644
index 0000000000..bcbd32484c
--- /dev/null
+++ b/widget/windows/nsUXThemeData.cpp
@@ -0,0 +1,405 @@
+/* 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"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const wchar_t
+nsUXThemeData::kThemeLibraryName[] = L"uxtheme.dll";
+
+HANDLE
+nsUXThemeData::sThemes[eUXNumClasses];
+
+HMODULE
+nsUXThemeData::sThemeDLL = nullptr;
+
+bool
+nsUXThemeData::sFlatMenus = false;
+
+bool nsUXThemeData::sTitlebarInfoPopulatedAero = false;
+bool nsUXThemeData::sTitlebarInfoPopulatedThemed = false;
+const int NUM_COMMAND_BUTTONS = 4;
+SIZE nsUXThemeData::sCommandButtons[NUM_COMMAND_BUTTONS];
+
+void
+nsUXThemeData::Teardown() {
+ Invalidate();
+ if(sThemeDLL)
+ FreeLibrary(sThemeDLL);
+}
+
+void
+nsUXThemeData::Initialize()
+{
+ ::ZeroMemory(sThemes, sizeof(sThemes));
+ NS_ASSERTION(!sThemeDLL, "nsUXThemeData being initialized twice!");
+
+ CheckForCompositor(true);
+ Invalidate();
+}
+
+void
+nsUXThemeData::Invalidate() {
+ for(int i = 0; i < eUXNumClasses; i++) {
+ if(sThemes[i]) {
+ CloseThemeData(sThemes[i]);
+ sThemes[i] = nullptr;
+ }
+ }
+ BOOL useFlat = FALSE;
+ sFlatMenus = ::SystemParametersInfo(SPI_GETFLATMENU, 0, &useFlat, 0) ?
+ useFlat : false;
+}
+
+HANDLE
+nsUXThemeData::GetTheme(nsUXThemeClass cls) {
+ NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!");
+ if(!sThemes[cls])
+ {
+ sThemes[cls] = OpenThemeData(nullptr, GetClassName(cls));
+ }
+ return sThemes[cls];
+}
+
+HMODULE
+nsUXThemeData::GetThemeDLL() {
+ if (!sThemeDLL)
+ sThemeDLL = ::LoadLibraryW(kThemeLibraryName);
+ return sThemeDLL;
+}
+
+const wchar_t *nsUXThemeData::GetClassName(nsUXThemeClass cls) {
+ switch(cls) {
+ case eUXButton:
+ return L"Button";
+ case eUXEdit:
+ return L"Edit";
+ case eUXTooltip:
+ return L"Tooltip";
+ case eUXRebar:
+ return L"Rebar";
+ case eUXMediaRebar:
+ return L"Media::Rebar";
+ case eUXCommunicationsRebar:
+ return L"Communications::Rebar";
+ case eUXBrowserTabBarRebar:
+ return L"BrowserTabBar::Rebar";
+ case eUXToolbar:
+ return L"Toolbar";
+ case eUXMediaToolbar:
+ return L"Media::Toolbar";
+ case eUXCommunicationsToolbar:
+ return L"Communications::Toolbar";
+ case eUXProgress:
+ return L"Progress";
+ case eUXTab:
+ return L"Tab";
+ case eUXScrollbar:
+ return L"Scrollbar";
+ case eUXTrackbar:
+ return L"Trackbar";
+ case eUXSpin:
+ return L"Spin";
+ case eUXStatus:
+ return L"Status";
+ case eUXCombobox:
+ return L"Combobox";
+ case eUXHeader:
+ return L"Header";
+ case eUXListview:
+ return L"Listview";
+ case eUXMenu:
+ return L"Menu";
+ case eUXWindowFrame:
+ return L"Window";
+ default:
+ NS_NOTREACHED("unknown uxtheme class");
+ return L"";
+ }
+}
+
+// static
+void
+nsUXThemeData::InitTitlebarInfo()
+{
+ // Pre-populate with generic metrics. These likley will not match
+ // the current theme, but they insure the buttons at least show up.
+ sCommandButtons[0].cx = GetSystemMetrics(SM_CXSIZE);
+ sCommandButtons[0].cy = GetSystemMetrics(SM_CYSIZE);
+ sCommandButtons[1].cx = sCommandButtons[2].cx = sCommandButtons[0].cx;
+ sCommandButtons[1].cy = sCommandButtons[2].cy = sCommandButtons[0].cy;
+ sCommandButtons[3].cx = sCommandButtons[0].cx * 3;
+ sCommandButtons[3].cy = sCommandButtons[0].cy;
+
+ // Use system metrics for pre-vista, otherwise trigger a
+ // refresh on the next layout.
+ sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed =
+ !IsVistaOrLater();
+}
+
+// static
+void
+nsUXThemeData::UpdateTitlebarInfo(HWND aWnd)
+{
+ if (!aWnd)
+ return;
+
+ if (!sTitlebarInfoPopulatedAero && nsUXThemeData::CheckForCompositor()) {
+ RECT captionButtons;
+ if (SUCCEEDED(WinUtils::dwmGetWindowAttributePtr(aWnd,
+ DWMWA_CAPTION_BUTTON_BOUNDS,
+ &captionButtons,
+ sizeof(captionButtons)))) {
+ sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cx = captionButtons.right - captionButtons.left - 3;
+ sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy = (captionButtons.bottom - captionButtons.top) - 1;
+ sTitlebarInfoPopulatedAero = true;
+ }
+ }
+
+ // NB: sTitlebarInfoPopulatedThemed is always true pre-vista.
+ if (sTitlebarInfoPopulatedThemed || IsWin8OrLater())
+ return;
+
+ // Query a temporary, visible window with command buttons to get
+ // the right metrics.
+ WNDCLASSW wc;
+ wc.style = 0;
+ wc.lpfnWndProc = ::DefWindowProcW;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = nsToolkit::mDllInstance;
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = kClassNameTemp;
+ ::RegisterClassW(&wc);
+
+ // Create a transparent descendant of the window passed in. This
+ // keeps the window from showing up on the desktop or the taskbar.
+ // Note the parent (browser) window is usually still hidden, we
+ // don't want to display it, so we can't query it directly.
+ HWND hWnd = CreateWindowExW(WS_EX_LAYERED,
+ kClassNameTemp, L"",
+ WS_OVERLAPPEDWINDOW,
+ 0, 0, 0, 0, aWnd, nullptr,
+ nsToolkit::mDllInstance, nullptr);
+ NS_ASSERTION(hWnd, "UpdateTitlebarInfo window creation failed.");
+
+ int showType = SW_SHOWNA;
+ // We try to avoid activating this window, but on Aero basic (aero without
+ // compositor) and aero lite (special theme for win server 2012/2013) we may
+ // get the wrong information if the window isn't activated, so we have to:
+ if (sThemeId == LookAndFeel::eWindowsTheme_AeroLite ||
+ (sThemeId == LookAndFeel::eWindowsTheme_Aero && !nsUXThemeData::CheckForCompositor())) {
+ showType = SW_SHOW;
+ }
+ ShowWindow(hWnd, showType);
+ TITLEBARINFOEX info = {0};
+ info.cbSize = sizeof(TITLEBARINFOEX);
+ SendMessage(hWnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&info);
+ DestroyWindow(hWnd);
+
+ // Only set if we have valid data for all three buttons we use.
+ if ((info.rgrect[2].right - info.rgrect[2].left) == 0 ||
+ (info.rgrect[3].right - info.rgrect[3].left) == 0 ||
+ (info.rgrect[5].right - info.rgrect[5].left) == 0) {
+ NS_WARNING("WM_GETTITLEBARINFOEX query failed to find usable metrics.");
+ return;
+ }
+ // minimize
+ sCommandButtons[0].cx = info.rgrect[2].right - info.rgrect[2].left;
+ sCommandButtons[0].cy = info.rgrect[2].bottom - info.rgrect[2].top;
+ // maximize/restore
+ sCommandButtons[1].cx = info.rgrect[3].right - info.rgrect[3].left;
+ sCommandButtons[1].cy = info.rgrect[3].bottom - info.rgrect[3].top;
+ // close
+ sCommandButtons[2].cx = info.rgrect[5].right - info.rgrect[5].left;
+ sCommandButtons[2].cy = info.rgrect[5].bottom - info.rgrect[5].top;
+
+#ifdef DEBUG
+ // Verify that all values for the command buttons are positive values
+ // otherwise we have cached bad values for the caption buttons
+ for (int i = 0; i < NUM_COMMAND_BUTTONS; i++) {
+ MOZ_ASSERT(sCommandButtons[i].cx > 0);
+ MOZ_ASSERT(sCommandButtons[i].cy > 0);
+ }
+#endif
+
+ sTitlebarInfoPopulatedThemed = true;
+}
+
+// visual style (aero glass, aero basic)
+// theme (aero, luna, zune)
+// theme color (silver, olive, blue)
+// system colors
+
+struct THEMELIST {
+ LPCWSTR name;
+ int type;
+};
+
+const THEMELIST knownThemes[] = {
+ { L"aero.msstyles", WINTHEME_AERO },
+ { L"aerolite.msstyles", WINTHEME_AERO_LITE },
+ { L"luna.msstyles", WINTHEME_LUNA },
+ { L"zune.msstyles", WINTHEME_ZUNE },
+ { L"royale.msstyles", WINTHEME_ROYALE }
+};
+
+const THEMELIST knownColors[] = {
+ { L"normalcolor", WINTHEMECOLOR_NORMAL },
+ { L"homestead", WINTHEMECOLOR_HOMESTEAD },
+ { L"metallic", WINTHEMECOLOR_METALLIC }
+};
+
+LookAndFeel::WindowsTheme
+nsUXThemeData::sThemeId = LookAndFeel::eWindowsTheme_Generic;
+
+bool
+nsUXThemeData::sIsDefaultWindowsTheme = false;
+bool
+nsUXThemeData::sIsHighContrastOn = false;
+
+// static
+LookAndFeel::WindowsTheme
+nsUXThemeData::GetNativeThemeId()
+{
+ return sThemeId;
+}
+
+// static
+bool nsUXThemeData::IsDefaultWindowTheme()
+{
+ return sIsDefaultWindowsTheme;
+}
+
+bool nsUXThemeData::IsHighContrastOn()
+{
+ return sIsHighContrastOn;
+}
+
+// static
+bool nsUXThemeData::CheckForCompositor(bool aUpdateCache)
+{
+ static BOOL sCachedValue = FALSE;
+ if (aUpdateCache && WinUtils::dwmIsCompositionEnabledPtr) {
+ WinUtils::dwmIsCompositionEnabledPtr(&sCachedValue);
+ }
+ return sCachedValue;
+}
+
+// static
+void
+nsUXThemeData::UpdateNativeThemeInfo()
+{
+ // Trigger a refresh of themed button metrics if needed
+ sTitlebarInfoPopulatedThemed = !IsVistaOrLater();
+
+ sIsDefaultWindowsTheme = false;
+ sThemeId = LookAndFeel::eWindowsTheme_Generic;
+
+ HIGHCONTRAST highContrastInfo;
+ highContrastInfo.cbSize = sizeof(HIGHCONTRAST);
+ if (SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrastInfo, 0)) {
+ sIsHighContrastOn = ((highContrastInfo.dwFlags & HCF_HIGHCONTRASTON) != 0);
+ } else {
+ sIsHighContrastOn = false;
+ }
+
+ if (!IsAppThemed()) {
+ sThemeId = LookAndFeel::eWindowsTheme_Classic;
+ return;
+ }
+
+ WCHAR themeFileName[MAX_PATH + 1];
+ WCHAR themeColor[MAX_PATH + 1];
+ if (FAILED(GetCurrentThemeName(themeFileName,
+ MAX_PATH,
+ themeColor,
+ MAX_PATH,
+ nullptr, 0))) {
+ sThemeId = LookAndFeel::eWindowsTheme_Classic;
+ return;
+ }
+
+ LPCWSTR themeName = wcsrchr(themeFileName, L'\\');
+ themeName = themeName ? themeName + 1 : themeFileName;
+
+ WindowsTheme theme = WINTHEME_UNRECOGNIZED;
+ for (size_t i = 0; i < ArrayLength(knownThemes); ++i) {
+ if (!lstrcmpiW(themeName, knownThemes[i].name)) {
+ theme = (WindowsTheme)knownThemes[i].type;
+ break;
+ }
+ }
+
+ if (theme == WINTHEME_UNRECOGNIZED)
+ return;
+
+ // We're using the default theme if we're using any of Aero, Aero Lite, or
+ // luna. However, on Win8, GetCurrentThemeName (see above) returns
+ // AeroLite.msstyles for the 4 builtin highcontrast themes as well. Those
+ // themes "don't count" as default themes, so we specifically check for high
+ // contrast mode in that situation.
+ if (!(IsWin8OrLater() && sIsHighContrastOn) &&
+ (theme == WINTHEME_AERO || theme == WINTHEME_AERO_LITE || theme == WINTHEME_LUNA)) {
+ sIsDefaultWindowsTheme = true;
+ }
+
+ if (theme != WINTHEME_LUNA) {
+ switch(theme) {
+ case WINTHEME_AERO:
+ sThemeId = LookAndFeel::eWindowsTheme_Aero;
+ return;
+ case WINTHEME_AERO_LITE:
+ sThemeId = LookAndFeel::eWindowsTheme_AeroLite;
+ return;
+ case WINTHEME_ZUNE:
+ sThemeId = LookAndFeel::eWindowsTheme_Zune;
+ return;
+ case WINTHEME_ROYALE:
+ sThemeId = LookAndFeel::eWindowsTheme_Royale;
+ return;
+ default:
+ NS_WARNING("unhandled theme type.");
+ return;
+ }
+ }
+
+ // calculate the luna color scheme
+ WindowsThemeColor color = WINTHEMECOLOR_UNRECOGNIZED;
+ for (size_t i = 0; i < ArrayLength(knownColors); ++i) {
+ if (!lstrcmpiW(themeColor, knownColors[i].name)) {
+ color = (WindowsThemeColor)knownColors[i].type;
+ break;
+ }
+ }
+
+ switch(color) {
+ case WINTHEMECOLOR_NORMAL:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaBlue;
+ return;
+ case WINTHEMECOLOR_HOMESTEAD:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaOlive;
+ return;
+ case WINTHEMECOLOR_METALLIC:
+ sThemeId = LookAndFeel::eWindowsTheme_LunaSilver;
+ return;
+ default:
+ NS_WARNING("unhandled theme color.");
+ return;
+ }
+}
diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h
new file mode 100644
index 0000000000..2688ec6592
--- /dev/null
+++ b/widget/windows/nsUXThemeData.h
@@ -0,0 +1,120 @@
+/* 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 "WinUtils.h"
+
+#include <dwmapi.h>
+
+#include "nsWindowDefs.h"
+
+// These window messages are not defined in dwmapi.h
+#ifndef WM_DWMCOMPOSITIONCHANGED
+#define WM_DWMCOMPOSITIONCHANGED 0x031E
+#endif
+
+// Windows 7 additions
+#ifndef WM_DWMSENDICONICTHUMBNAIL
+#define WM_DWMSENDICONICTHUMBNAIL 0x0323
+#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326
+#endif
+
+#define DWMWA_FORCE_ICONIC_REPRESENTATION 7
+#define DWMWA_HAS_ICONIC_BITMAP 10
+
+enum nsUXThemeClass {
+ eUXButton = 0,
+ eUXEdit,
+ eUXTooltip,
+ eUXRebar,
+ eUXMediaRebar,
+ eUXCommunicationsRebar,
+ eUXBrowserTabBarRebar,
+ eUXToolbar,
+ eUXMediaToolbar,
+ eUXCommunicationsToolbar,
+ eUXProgress,
+ eUXTab,
+ eUXScrollbar,
+ eUXTrackbar,
+ eUXSpin,
+ eUXStatus,
+ eUXCombobox,
+ eUXHeader,
+ eUXListview,
+ eUXMenu,
+ eUXWindowFrame,
+ eUXNumClasses
+};
+
+// Native windows style constants
+enum WindowsTheme {
+ WINTHEME_UNRECOGNIZED = 0,
+ WINTHEME_CLASSIC = 1, // no theme
+ WINTHEME_AERO = 2,
+ WINTHEME_LUNA = 3,
+ WINTHEME_ROYALE = 4,
+ WINTHEME_ZUNE = 5,
+ WINTHEME_AERO_LITE = 6
+};
+enum WindowsThemeColor {
+ WINTHEMECOLOR_UNRECOGNIZED = 0,
+ WINTHEMECOLOR_NORMAL = 1,
+ WINTHEMECOLOR_HOMESTEAD = 2,
+ WINTHEMECOLOR_METALLIC = 3
+};
+
+#define CMDBUTTONIDX_MINIMIZE 0
+#define CMDBUTTONIDX_RESTORE 1
+#define CMDBUTTONIDX_CLOSE 2
+#define CMDBUTTONIDX_BUTTONBOX 3
+
+class nsUXThemeData {
+ static HMODULE sThemeDLL;
+ static HANDLE sThemes[eUXNumClasses];
+
+ static const wchar_t *GetClassName(nsUXThemeClass);
+
+public:
+ static const wchar_t kThemeLibraryName[];
+ static bool sFlatMenus;
+ static bool sTitlebarInfoPopulatedAero;
+ static bool sTitlebarInfoPopulatedThemed;
+ static SIZE sCommandButtons[4];
+ static mozilla::LookAndFeel::WindowsTheme sThemeId;
+ static bool sIsDefaultWindowsTheme;
+ static bool sIsHighContrastOn;
+
+ static void Initialize();
+ static void Teardown();
+ static void Invalidate();
+ static HANDLE GetTheme(nsUXThemeClass cls);
+ static HMODULE GetThemeDLL();
+
+ // nsWindow calls this to update desktop settings info
+ static void InitTitlebarInfo();
+ static void UpdateTitlebarInfo(HWND aWnd);
+
+ static void UpdateNativeThemeInfo();
+ static mozilla::LookAndFeel::WindowsTheme GetNativeThemeId();
+ static bool IsDefaultWindowTheme();
+ static bool IsHighContrastOn();
+
+ // This method returns the cached compositor state. Most
+ // callers should call without the argument. The cache
+ // should be modified only when the application receives
+ // WM_DWMCOMPOSITIONCHANGED. This rule prevents inconsistent
+ // results for two or more calls which check the state during
+ // composition transition.
+ static bool CheckForCompositor(bool aUpdateCache = false);
+};
+#endif // __UXThemeData_h__
diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..7e6ffd638a
--- /dev/null
+++ b/widget/windows/nsWidgetFactory.cpp
@@ -0,0 +1,262 @@
+/* -*- 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 "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsdefs.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIdleServiceWin.h"
+#include "nsLookAndFeel.h"
+#include "nsScreenManagerWin.h"
+#include "nsSound.h"
+#include "WinMouseScrollHandler.h"
+#include "KeyboardLayout.h"
+#include "GfxInfo.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"
+#include "nsNativeThemeWin.h"
+#include "nsWindow.h"
+// Content processes
+#include "nsFilePickerProxy.h"
+
+// Drag & Drop, Clipboard
+#include "nsClipboardHelper.h"
+#include "nsClipboard.h"
+#include "nsBidiKeyboard.h"
+#include "nsDragService.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+
+#include "WinTaskbar.h"
+#include "JumpListBuilder.h"
+#include "JumpListItem.h"
+
+#include "WindowsUIUtils.h"
+
+#ifdef NS_PRINTING
+#include "nsDeviceContextSpecWin.h"
+#include "nsPrintOptionsWin.h"
+#include "nsPrintSession.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static nsresult
+WindowConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIWidget> widget = new nsWindow;
+ return widget->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+ChildWindowConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIWidget> widget = new ChildWindow;
+ return widget->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+FilePickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIFilePicker> picker = new nsFilePicker;
+ return picker->QueryInterface(aIID, aResult);
+}
+
+static nsresult
+ColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID,
+ void **aResult)
+{
+ *aResult = nullptr;
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsCOMPtr<nsIColorPicker> picker = new nsColorPicker;
+ return picker->QueryInterface(aIID, aResult);
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerWin)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceWin, nsIdleServiceWin::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(WinTaskbar)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListBuilder)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListItem)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListSeparator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListLink)
+NS_GENERIC_FACTORY_CONSTRUCTOR(JumpListShortcut)
+NS_GENERIC_FACTORY_CONSTRUCTOR(WindowsUIUtils)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(TaskbarPreviewCallback)
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsWin, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrinterEnumeratorWin)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecWin)
+#endif
+
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_TASKBAR_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTBUILDER_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTITEM_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSEPARATOR_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTLINK_CID);
+NS_DEFINE_NAMED_CID(NS_WIN_JUMPLISTSHORTCUT_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOWS_UIUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_TASKBARPREVIEWCALLBACK_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTER_ENUMERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+#endif
+
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, WindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, ChildWindowConstructor },
+ { &kNS_FILEPICKER_CID, false, nullptr, FilePickerConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_COLORPICKER_CID, false, nullptr, ColorPickerConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor, Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerWinConstructor,
+ Module::MAIN_PROCESS_ONLY },
+ { &kNS_GFXINFO_CID, false, nullptr, GfxInfoConstructor },
+ { &kNS_THEMERENDERER_CID, false, nullptr, NS_NewNativeTheme },
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceWinConstructor },
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_WIN_TASKBAR_CID, false, nullptr, WinTaskbarConstructor },
+ { &kNS_WIN_JUMPLISTBUILDER_CID, false, nullptr, JumpListBuilderConstructor },
+ { &kNS_WIN_JUMPLISTITEM_CID, false, nullptr, JumpListItemConstructor },
+ { &kNS_WIN_JUMPLISTSEPARATOR_CID, false, nullptr, JumpListSeparatorConstructor },
+ { &kNS_WIN_JUMPLISTLINK_CID, false, nullptr, JumpListLinkConstructor },
+ { &kNS_WIN_JUMPLISTSHORTCUT_CID, false, nullptr, JumpListShortcutConstructor },
+ { &kNS_WINDOWS_UIUTILS_CID, false, nullptr, WindowsUIUtilsConstructor },
+ { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor, Module::MAIN_PROCESS_ONLY },
+ { &kNS_TASKBARPREVIEWCALLBACK_CID, false, nullptr, TaskbarPreviewCallbackConstructor },
+#ifdef NS_PRINTING
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsWinConstructor },
+ { &kNS_PRINTER_ENUMERATOR_CID, false, nullptr, nsPrinterEnumeratorWinConstructor },
+ { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecWinConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/win;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/win;1", &kNS_CHILD_CID },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/appshell/win;1", &kNS_APPSHELL_CID, Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/windows-taskbar;1", &kNS_WIN_TASKBAR_CID },
+ { "@mozilla.org/windows-jumplistbuilder;1", &kNS_WIN_JUMPLISTBUILDER_CID },
+ { "@mozilla.org/windows-jumplistitem;1", &kNS_WIN_JUMPLISTITEM_CID },
+ { "@mozilla.org/windows-jumplistseparator;1", &kNS_WIN_JUMPLISTSEPARATOR_CID },
+ { "@mozilla.org/windows-jumplistlink;1", &kNS_WIN_JUMPLISTLINK_CID },
+ { "@mozilla.org/windows-jumplistshortcut;1", &kNS_WIN_JUMPLISTSHORTCUT_CID },
+ { "@mozilla.org/windows-ui-utils;1", &kNS_WINDOWS_UIUTILS_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID, Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/taskbar-preview-callback;1", &kNS_TASKBARPREVIEWCALLBACK_CID },
+#ifdef NS_PRINTING
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { "@mozilla.org/gfx/printerenumerator;1", &kNS_PRINTER_ENUMERATOR_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+#endif
+ { nullptr }
+};
+
+static void
+nsWidgetWindowsModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ KeyboardLayout::Shutdown();
+ MouseScrollHandler::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetWindowsModuleDtor,
+ Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetModule) = &kWidgetModule;
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp
new file mode 100644
index 0000000000..faec5ec220
--- /dev/null
+++ b/widget/windows/nsWinGesture.cpp
@@ -0,0 +1,590 @@
+/* -*- 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 "nsIDOMSimpleGestureEvent.h"
+#include "nsIDOMWheelEvent.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TouchEvents.h"
+
+#include <cmath>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll";
+HMODULE nsWinGesture::sLibraryHandle = nullptr;
+nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr;
+nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr;
+nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr;
+nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr;
+nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr;
+nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr;
+nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr;
+nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr;
+
+nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr;
+nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr;
+nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr;
+nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr;
+
+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()
+{
+ if (getGestureInfo) {
+ return true;
+ } else if (sLibraryHandle) {
+ return false;
+ }
+
+ sLibraryHandle = ::LoadLibraryW(kGestureLibraryName);
+ HMODULE hTheme = nsUXThemeData::GetThemeDLL();
+
+ // gesture interfaces
+ if (sLibraryHandle) {
+ getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo");
+ closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle");
+ getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs");
+ setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig");
+ getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig");
+ registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow");
+ unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow");
+ getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo");
+ closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle");
+ }
+
+ if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs ||
+ !setGestureConfig || !getGestureConfig) {
+ getGestureInfo = nullptr;
+ closeGestureInfoHandle = nullptr;
+ getGestureExtraArgs = nullptr;
+ setGestureConfig = nullptr;
+ getGestureConfig = nullptr;
+ return false;
+ }
+
+ if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) {
+ registerTouchWindow = nullptr;
+ unregisterTouchWindow = nullptr;
+ getTouchInputInfo = nullptr;
+ closeTouchInputHandle = nullptr;
+ }
+
+ // panning feedback interfaces
+ if (hTheme) {
+ beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback");
+ endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback");
+ updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback");
+ }
+
+ if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) {
+ beginPanningFeedback = nullptr;
+ endPanningFeedback = nullptr;
+ updatePanningFeedback = nullptr;
+ }
+
+ // 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)
+{
+ if (!getGestureInfo)
+ return false;
+
+ 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, GCOUNT, (PGESTURECONFIG)&config);
+}
+
+/* Helpers */
+
+bool nsWinGesture::IsAvailable()
+{
+ return getGestureInfo != nullptr;
+}
+
+bool nsWinGesture::RegisterTouchWindow(HWND hWnd)
+{
+ if (!registerTouchWindow)
+ return false;
+
+ return registerTouchWindow(hWnd, TWF_WANTPALM);
+}
+
+bool nsWinGesture::UnregisterTouchWindow(HWND hWnd)
+{
+ if (!unregisterTouchWindow)
+ return false;
+
+ return unregisterTouchWindow(hWnd);
+}
+
+bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs)
+{
+ if (!getTouchInputInfo)
+ return false;
+
+ return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT));
+}
+
+bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput)
+{
+ if (!closeTouchInputHandle)
+ return false;
+
+ return closeTouchInputHandle(hTouchInput);
+}
+
+bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo)
+{
+ if (!getGestureInfo || !hGestureInfo || !pGestureInfo)
+ return false;
+
+ ZeroMemory(pGestureInfo, sizeof(GESTUREINFO));
+ pGestureInfo->cbSize = sizeof(GESTUREINFO);
+
+ return getGestureInfo(hGestureInfo, pGestureInfo);
+}
+
+bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo)
+{
+ if (!getGestureInfo || !hGestureInfo)
+ return false;
+
+ return closeGestureInfoHandle(hGestureInfo);
+}
+
+bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs)
+{
+ if (!getGestureInfo || !hGestureInfo || !pExtraArgs)
+ return false;
+
+ return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs);
+}
+
+bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig)
+{
+ if (!getGestureInfo || !pGestureConfig)
+ return false;
+
+ return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG));
+}
+
+bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig)
+{
+ if (!getGestureInfo || !pGestureConfig)
+ return false;
+
+ return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG));
+}
+
+bool nsWinGesture::BeginPanningFeedback(HWND hWnd)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return beginPanningFeedback(hWnd);
+}
+
+bool nsWinGesture::EndPanningFeedback(HWND hWnd)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return endPanningFeedback(hWnd, TRUE);
+}
+
+bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia)
+{
+ if (!beginPanningFeedback)
+ return false;
+
+ return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia);
+}
+
+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 = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ else if (evt.mDelta < 0)
+ evt.mDirection = nsIDOMSimpleGestureEvent::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);
+ 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 = nsIDOMWheelEvent::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..24c1f6b2d8
--- /dev/null
+++ b/widget/windows/nsWinGesture.h
@@ -0,0 +1,288 @@
+/* -*- 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"
+
+// Desktop builds target apis for 502. Win8 Metro builds target 602.
+#if WINVER < 0x0602
+
+DECLARE_HANDLE(HGESTUREINFO);
+
+/*
+ * Gesture flags - GESTUREINFO.dwFlags
+ */
+#define GF_BEGIN 0x00000001
+#define GF_INERTIA 0x00000002
+#define GF_END 0x00000004
+
+/*
+ * Gesture configuration structure
+ * - Used in SetGestureConfig and GetGestureConfig
+ * - Note that any setting not included in either GESTURECONFIG.dwWant or
+ * GESTURECONFIG.dwBlock will use the parent window's preferences or
+ * system defaults.
+ */
+typedef struct tagGESTURECONFIG {
+ DWORD dwID; // gesture ID
+ DWORD dwWant; // settings related to gesture ID that are to be turned on
+ DWORD dwBlock; // settings related to gesture ID that are to be turned off
+} GESTURECONFIG, *PGESTURECONFIG;
+
+/*
+ * Gesture information structure
+ * - Pass the HGESTUREINFO received in the WM_GESTURE message lParam into the
+ * GetGestureInfo function to retrieve this information.
+ * - If cbExtraArgs is non-zero, pass the HGESTUREINFO received in the WM_GESTURE
+ * message lParam into the GetGestureExtraArgs function to retrieve extended
+ * argument information.
+ */
+typedef struct tagGESTUREINFO {
+ UINT cbSize; // size, in bytes, of this structure (including variable length Args field)
+ DWORD dwFlags; // see GF_* flags
+ DWORD dwID; // gesture ID, see GID_* defines
+ HWND hwndTarget; // handle to window targeted by this gesture
+ POINTS ptsLocation; // current location of this gesture
+ DWORD dwInstanceID; // internally used
+ DWORD dwSequenceID; // internally used
+ ULONGLONG ullArguments; // arguments for gestures whose arguments fit in 8 BYTES
+ UINT cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture
+} GESTUREINFO, *PGESTUREINFO;
+typedef GESTUREINFO const * PCGESTUREINFO;
+
+/*
+ * Gesture notification structure
+ * - The WM_GESTURENOTIFY message lParam contains a pointer to this structure.
+ * - The WM_GESTURENOTIFY message notifies a window that gesture recognition is
+ * in progress and a gesture will be generated if one is recognized under the
+ * current gesture settings.
+ */
+typedef struct tagGESTURENOTIFYSTRUCT {
+ UINT cbSize; // size, in bytes, of this structure
+ DWORD dwFlags; // unused
+ HWND hwndTarget; // handle to window targeted by the gesture
+ POINTS ptsLocation; // starting location
+ DWORD dwInstanceID; // internally used
+} GESTURENOTIFYSTRUCT, *PGESTURENOTIFYSTRUCT;
+
+
+/*
+ * Gesture argument helpers
+ * - Angle should be a double in the range of -2pi to +2pi
+ * - Argument should be an unsigned 16-bit value
+ */
+#define GID_ROTATE_ANGLE_TO_ARGUMENT(_arg_) ((USHORT)((((_arg_) + 2.0 * 3.14159265) / (4.0 * 3.14159265)) * 65535.0))
+#define GID_ROTATE_ANGLE_FROM_ARGUMENT(_arg_) ((((double)(_arg_) / 65535.0) * 4.0 * 3.14159265) - 2.0 * 3.14159265)
+
+/*
+ * Gesture configuration flags
+ */
+#define GC_ALLGESTURES 0x00000001
+
+#define GC_ZOOM 0x00000001
+
+#define GC_PAN 0x00000001
+#define GC_PAN_WITH_SINGLE_FINGER_VERTICALLY 0x00000002
+#define GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY 0x00000004
+#define GC_PAN_WITH_GUTTER 0x00000008
+#define GC_PAN_WITH_INERTIA 0x00000010
+
+#define GC_ROTATE 0x00000001
+
+#define GC_TWOFINGERTAP 0x00000001
+
+#define GC_PRESSANDTAP 0x00000001
+
+/*
+ * Gesture IDs
+ */
+#define GID_BEGIN 1
+#define GID_END 2
+#define GID_ZOOM 3
+#define GID_PAN 4
+#define GID_ROTATE 5
+#define GID_TWOFINGERTAP 6
+#define GID_PRESSANDTAP 7
+
+// Maximum number of gestures that can be included
+// in a single call to SetGestureConfig / GetGestureConfig
+#define GESTURECONFIGMAXCOUNT 256
+
+// If specified, GetGestureConfig returns consolidated configuration
+// for the specified window and it's parent window chain
+#define GCF_INCLUDE_ANCESTORS 0x00000001
+
+// Window events we need to respond to or receive
+#define WM_GESTURE 0x0119
+#define WM_GESTURENOTIFY 0x011A
+
+typedef struct _TOUCHINPUT {
+ LONG x;
+ LONG y;
+ HANDLE hSource;
+ DWORD dwID;
+ DWORD dwFlags;
+ DWORD dwMask;
+ DWORD dwTime;
+ ULONG_PTR dwExtraInfo;
+ DWORD cxContact;
+ DWORD cyContact;
+} TOUCHINPUT, *PTOUCHINPUT;
+
+typedef HANDLE HTOUCHINPUT;
+
+#define WM_TOUCH 0x0240
+
+#define TOUCHEVENTF_MOVE 0x0001
+#define TOUCHEVENTF_DOWN 0x0002
+#define TOUCHEVENTF_UP 0x0004
+#define TOUCHEVENTF_INRANGE 0x0008
+#define TOUCHEVENTF_PRIMARY 0x0010
+#define TOUCHEVENTF_NOCOALESCE 0x0020
+#define TOUCHEVENTF_PEN 0x0040
+#define TOUCHEVENTF_PALM 0x0080
+
+#define TOUCHINPUTMASKF_TIMEFROMSYSTEM 0x0001
+#define TOUCHINPUTMASKF_EXTRAINFO 0x0002
+#define TOUCHINPUTMASKF_CONTACTAREA 0x0004
+
+#define TOUCH_COORD_TO_PIXEL(C) (C/100)
+
+#define TWF_FINETOUCH 0x0001
+#define TWF_WANTPALM 0x0002
+
+#endif // WINVER < 0x0602
+
+// 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();
+ bool RegisterTouchWindow(HWND hWnd);
+ bool UnregisterTouchWindow(HWND hWnd);
+ bool GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs);
+ bool CloseTouchInputHandle(HTOUCHINPUT hTouchInput);
+ bool IsAvailable();
+
+ // 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);
+
+public:
+ // Helpers
+ bool GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo);
+ bool CloseGestureInfoHandle(HGESTUREINFO hGestureInfo);
+ bool GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs);
+ bool SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig);
+ bool GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig);
+ bool BeginPanningFeedback(HWND hWnd);
+ bool EndPanningFeedback(HWND hWnd);
+ bool UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia);
+
+protected:
+
+private:
+ // Function prototypes
+ typedef BOOL (WINAPI * GetGestureInfoPtr)(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo);
+ typedef BOOL (WINAPI * CloseGestureInfoHandlePtr)(HGESTUREINFO hGestureInfo);
+ typedef BOOL (WINAPI * GetGestureExtraArgsPtr)(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs);
+ typedef BOOL (WINAPI * SetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, UINT cIDs, PGESTURECONFIG pGestureConfig, UINT cbSize);
+ typedef BOOL (WINAPI * GetGestureConfigPtr)(HWND hwnd, DWORD dwReserved, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig, UINT cbSize);
+ typedef BOOL (WINAPI * BeginPanningFeedbackPtr)(HWND hWnd);
+ typedef BOOL (WINAPI * EndPanningFeedbackPtr)(HWND hWnd, BOOL fAnimateBack);
+ typedef BOOL (WINAPI * UpdatePanningFeedbackPtr)(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia);
+ typedef BOOL (WINAPI * RegisterTouchWindowPtr)(HWND hWnd, ULONG flags);
+ typedef BOOL (WINAPI * UnregisterTouchWindowPtr)(HWND hWnd);
+ typedef BOOL (WINAPI * GetTouchInputInfoPtr)(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs, int32_t cbSize);
+ typedef BOOL (WINAPI * CloseTouchInputHandlePtr)(HTOUCHINPUT hTouchInput);
+
+ // Static function pointers
+ static GetGestureInfoPtr getGestureInfo;
+ static CloseGestureInfoHandlePtr closeGestureInfoHandle;
+ static GetGestureExtraArgsPtr getGestureExtraArgs;
+ static SetGestureConfigPtr setGestureConfig;
+ static GetGestureConfigPtr getGestureConfig;
+ static BeginPanningFeedbackPtr beginPanningFeedback;
+ static EndPanningFeedbackPtr endPanningFeedback;
+ static UpdatePanningFeedbackPtr updatePanningFeedback;
+ static RegisterTouchWindowPtr registerTouchWindow;
+ static UnregisterTouchWindowPtr unregisterTouchWindow;
+ static GetTouchInputInfoPtr getTouchInputInfo;
+ static CloseTouchInputHandlePtr closeTouchInputHandle;
+
+ // Delay load info
+ bool InitLibrary();
+
+ static HMODULE sLibraryHandle;
+ static const wchar_t kGestureLibraryName[];
+
+ // 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..2172f2aa0b
--- /dev/null
+++ b/widget/windows/nsWindow.cpp
@@ -0,0 +1,8139 @@
+/* -*- 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.
+ *
+ * Related source:
+ *
+ * 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 "gfxPrefs.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include <algorithm>
+#include <limits>
+
+#include "nsWindow.h"
+
+#include <shellapi.h>
+#include <windows.h>
+#include <wtsapi32.h>
+#include <process.h>
+#include <commctrl.h>
+#include <unknwn.h>
+#include <psapi.h>
+
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "prenv.h"
+
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsIAppShell.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIKeyEventInPluginCallback.h"
+#include "nsITheme.h"
+#include "nsIObserverService.h"
+#include "nsIScreenManager.h"
+#include "imgIContainer.h"
+#include "nsIFile.h"
+#include "nsIRollupListener.h"
+#include "nsIServiceManager.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 "nsXPIDLString.h"
+#include "nsWidgetsCID.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "mozilla/Services.h"
+#include "nsNativeThemeWin.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+#include "nsIWindowMediator.h"
+#include "nsIServiceManager.h"
+#include "nsWindowGfx.h"
+#include "gfxWindowsPlatform.h"
+#include "Layers.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "nsISound.h"
+#include "SystemTimeConverter.h"
+#include "WinTaskbar.h"
+#include "WidgetUtils.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/gfx/2D.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIAppStartup.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/widget/WinNativeEventData.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsThemeConstants.h"
+#include "nsBidiKeyboard.h"
+#include "nsThemeConstants.h"
+#include "gfxConfig.h"
+#include "InProcessWinCompositorWidget.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/Platform.h"
+#if !defined(WINABLEAPI)
+#include <winable.h>
+#endif // !defined(WINABLEAPI)
+#endif // defined(ACCESSIBILITY)
+
+#include "nsIWinTaskbar.h"
+#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+#include "nsIWindowsUIUtils.h"
+
+#include "nsWindowDefs.h"
+
+#include "nsCrashOnException.h"
+#include "nsIXULRuntime.h"
+
+#include "nsIContent.h"
+
+#include "mozilla/HangMonitor.h"
+#include "WinIMEHandler.h"
+
+#include "npapi.h"
+
+#include <d3d11.h>
+
+#include "InkCollector.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
+
+#if !defined(WM_DPICHANGED)
+#define WM_DPICHANGED 0x02E0
+#endif
+
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/ScrollInputMethods.h"
+#include "ClientLayerManager.h"
+#include "InputData.h"
+
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+
+bool nsWindow::sDropShadowEnabled = true;
+uint32_t nsWindow::sInstanceCount = 0;
+bool nsWindow::sSwitchKeyboardLayout = false;
+BOOL nsWindow::sIsOleInitialized = FALSE;
+HCURSOR nsWindow::sHCursor = nullptr;
+imgIContainer* nsWindow::sCursorImgContainer = nullptr;
+nsWindow* nsWindow::sCurrentWindow = nullptr;
+bool nsWindow::sJustGotDeactivate = false;
+bool nsWindow::sJustGotActivate = false;
+bool nsWindow::sIsInMouseCapture = false;
+
+// imported in nsWidgetFactory.cpp
+TriStateBool nsWindow::sCanQuit = TRI_UNKNOWN;
+
+// Hook Data Memebers 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;
+
+// Mouse Clicks - static variable definitions for figuring
+// out 1 - 3 Clicks.
+POINT nsWindow::sLastMousePoint = {0};
+POINT nsWindow::sLastMouseMovePoint = {0};
+LONG nsWindow::sLastMouseDownTime = 0L;
+LONG nsWindow::sLastClickCount = 0L;
+BYTE nsWindow::sLastMouseButton = 0;
+
+// Trim heap on minimize. (initialized, but still true.)
+int nsWindow::sTrimOnMinimize = 2;
+
+TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN;
+
+WPARAM nsWindow::sMouseExitwParam = 0;
+LPARAM nsWindow::sMouseExitlParamScreen = 0;
+
+static SystemTimeConverter<DWORD>&
+TimeConverter() {
+ static SystemTimeConverter<DWORD> timeConverterSingleton;
+ return timeConverterSingleton;
+}
+
+namespace mozilla {
+
+class CurrentWindowsTimeGetter {
+public:
+ 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;
+
+// Global used in Show window enumerations.
+static bool gWindowsVisible = false;
+
+// True if we have sent a notification that we are suspending/sleeping.
+static bool gIsSleepMode = false;
+
+static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
+
+// General purpose user32.dll hook object
+static WindowsDllInterceptor sUser32Intercept;
+
+// 2 pixel offset for eTransparencyBorderlessGlass which equals the size of
+// the default window border Windows paints. Glass will be extended inward
+// this distance to remove the border.
+static const int32_t kGlassMarginAdjustment = 2;
+
+// 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;
+
+// Cached pointer events enabler value, True if pointer events are enabled.
+static bool gIsPointerEventsEnabled = 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) && defined(_M_IX86)
+
+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 (!IsWin8OrLater()) {
+ return;
+ }
+
+ 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);
+
+ // On touchscreen devices, tiptsf.dll will have been loaded when STA COM was
+ // first initialized.
+ if (!IsWin10OrLater() && GetModuleHandle(L"tiptsf.dll") &&
+ !sProcessCaretEventsStub) {
+ sTipTsfInterceptor.Init("tiptsf.dll");
+ DebugOnly<bool> ok = sTipTsfInterceptor.AddHook("ProcessCaretEvents",
+ reinterpret_cast<intptr_t>(&ProcessCaretEventsHook),
+ (void**) &sProcessCaretEventsStub);
+ MOZ_ASSERT(ok);
+ }
+
+ if (!sSendMessageTimeoutWStub) {
+ sUser32Intercept.Init("user32.dll");
+ DebugOnly<bool> hooked = sUser32Intercept.AddHook("SendMessageTimeoutW",
+ reinterpret_cast<intptr_t>(&SendMessageTimeoutWHook),
+ (void**) &sSendMessageTimeoutWStub);
+ 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 void CALLBACK ProcessCaretEventsHook(HWINEVENTHOOK aWinEventHook,
+ DWORD aEvent, HWND aHwnd,
+ LONG aObjectId, LONG aChildId,
+ DWORD aGeneratingTid,
+ DWORD aEventTime)
+ {
+ A11yInstantiationBlocker block;
+ sProcessCaretEventsStub(aWinEventHook, aEvent, aHwnd, aObjectId, aChildId,
+ aGeneratingTid, aEventTime);
+ }
+
+ 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
+ // thread.
+ if (!aMsgResult || aMsgCode != WM_GETOBJECT || aLParam != OBJID_CLIENT ||
+ !WinUtils::GetNSWindowPtr(aHwnd) ||
+ ::GetWindowThreadProcessId(aHwnd, nullptr) != ::GetCurrentThreadId() ||
+ !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 sTipTsfInterceptor;
+ static WINEVENTPROC sProcessCaretEventsStub;
+ static decltype(&SendMessageTimeoutW) sSendMessageTimeoutWStub;
+ static StaticAutoPtr<TIPMessageHandler> sInstance;
+
+ HHOOK mHook;
+ UINT mMessages[7];
+ uint32_t mA11yBlockCount;
+};
+
+WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor;
+WINEVENTPROC TIPMessageHandler::sProcessCaretEventsStub;
+decltype(&SendMessageTimeoutW) TIPMessageHandler::sSendMessageTimeoutWStub;
+StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
+
+} // namespace mozilla
+
+#endif // defined(ACCESSIBILITY) && defined(_M_IX86)
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: nsIWidget impl.
+ **
+ ** nsIWidget interface implementation, broken down into
+ ** sections.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow construction and destruction
+ *
+ **************************************************************/
+
+nsWindow::nsWindow()
+ : nsWindowBase()
+ , mResizeState(NOT_RESIZING)
+{
+ mIconSmall = nullptr;
+ mIconBig = nullptr;
+ mWnd = nullptr;
+ mTransitionWnd = nullptr;
+ mPaintDC = nullptr;
+ mPrevWndProc = nullptr;
+ mNativeDragTarget = nullptr;
+ mInDtor = false;
+ mIsVisible = false;
+ mIsTopWidgetWindow = false;
+ mUnicodeWidget = true;
+ mDisplayPanFeedback = false;
+ mTouchWindow = false;
+ mFutureMarginsToUse = false;
+ mCustomNonClient = false;
+ mHideChrome = false;
+ mFullscreenMode = false;
+ mMousePresent = false;
+ mDestroyCalled = false;
+ mHasTaskbarIconBeenCreated = false;
+ mMouseTransparent = false;
+ mPickerDisplayCount = 0;
+ mWindowType = eWindowType_child;
+ mBorderStyle = eBorderStyle_default;
+ mOldSizeMode = nsSizeMode_Normal;
+ mLastSizeMode = nsSizeMode_Normal;
+ mLastSize.width = 0;
+ mLastSize.height = 0;
+ mOldStyle = 0;
+ mOldExStyle = 0;
+ mPainting = 0;
+ mLastKeyboardLayout = 0;
+ mBlurSuppressLevel = 0;
+ mLastPaintEndTime = TimeStamp::Now();
+ mCachedHitTestPoint.x = 0;
+ mCachedHitTestPoint.y = 0;
+ mCachedHitTestTime = TimeStamp::Now();
+ mCachedHitTestResult = 0;
+#ifdef MOZ_XUL
+ mTransparencyMode = eTransparencyOpaque;
+ memset(&mGlassMargins, 0, sizeof mGlassMargins);
+#endif
+ DWORD background = ::GetSysColor(COLOR_BTNFACE);
+ mBrush = ::CreateSolidBrush(NSRGB_2_COLOREF(background));
+ mSendingSetText = false;
+ mDefaultScale = -1.0; // not yet set, will be calculated on first use
+
+ mTaskbarPreview = nullptr;
+
+ // Global initialization
+ if (!sInstanceCount) {
+ // Global app registration id for Win7 and up. See
+ // WinTaskbar.cpp for details.
+ mozilla::widget::WinTaskbar::RegisterAppUserModelID();
+ KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
+#if defined(ACCESSIBILITY) && defined(_M_IX86)
+ mozilla::TIPMessageHandler::Initialize();
+#endif // defined(ACCESSIBILITY) && defined(_M_IX86)
+ IMEHandler::Initialize();
+ if (SUCCEEDED(::OleInitialize(nullptr))) {
+ sIsOleInitialized = TRUE;
+ }
+ NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
+ MouseScrollHandler::Initialize();
+ // Init titlebar button info for custom frames.
+ nsUXThemeData::InitTitlebarInfo();
+ // Init theme data
+ nsUXThemeData::UpdateNativeThemeInfo();
+ RedirectedKeyDownMessageManager::Forget();
+ InkCollector::sInkCollector = new InkCollector();
+
+ Preferences::AddBoolVarCache(&gIsPointerEventsEnabled,
+ "dom.w3c_pointer_events.enabled",
+ gIsPointerEventsEnabled);
+ } // !sInstanceCount
+
+ mIdleService = nullptr;
+
+ mSizeConstraintsScale = GetDefaultScale().scale;
+
+ 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) {
+ InkCollector::sInkCollector->Shutdown();
+ InkCollector::sInkCollector = nullptr;
+ IMEHandler::Terminate();
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sIsOleInitialized) {
+ ::OleFlushClipboard();
+ ::OleUninitialize();
+ sIsOleInitialized = FALSE;
+ }
+ }
+
+ NS_IF_RELEASE(mNativeDragTarget);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+/**************************************************************
+ *
+ * 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;
+}
+
+static bool
+ShouldCacheTitleBarInfo(nsWindowType aWindowType, nsBorderStyle aBorderStyle)
+{
+ return (aWindowType == eWindowType_toplevel) &&
+ (aBorderStyle == eBorderStyle_default ||
+ aBorderStyle == eBorderStyle_all) &&
+ (!nsUXThemeData::sTitlebarInfoPopulatedThemed ||
+ !nsUXThemeData::sTitlebarInfoPopulatedAero);
+}
+
+// Create the proper widget
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ nsWidgetInitData defaultInitData;
+ if (!aInitData)
+ aInitData = &defaultInitData;
+
+ mUnicodeWidget = aInitData->mUnicode;
+
+ nsIWidget *baseParent = aInitData->mWindowType == eWindowType_dialog ||
+ aInitData->mWindowType == eWindowType_toplevel ||
+ aInitData->mWindowType == eWindowType_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;
+
+ DWORD style = WindowStyle();
+ DWORD extendedStyle = WindowExStyle();
+
+ if (mWindowType == eWindowType_popup) {
+ if (!aParent) {
+ parent = nullptr;
+ }
+
+ if (IsVistaOrLater() && !IsWin8OrLater() &&
+ HasBogusPopupsDropShadowOnMultiMonitor()) {
+ extendedStyle |= WS_EX_COMPOSITED;
+ }
+
+ if (aInitData->mMouseTransparent) {
+ // This flag makes the window transparent to mouse events
+ mMouseTransparent = true;
+ extendedStyle |= WS_EX_TRANSPARENT;
+ }
+ } else if (mWindowType == eWindowType_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->clipChildren) {
+ style |= WS_CLIPCHILDREN;
+ } else {
+ style &= ~WS_CLIPCHILDREN;
+ }
+ if (aInitData->clipSiblings) {
+ style |= WS_CLIPSIBLINGS;
+ }
+ }
+
+ const wchar_t* className;
+ if (aInitData->mDropShadow) {
+ className = GetWindowPopupClass();
+ } else {
+ className = GetWindowClass();
+ }
+ // Plugins are created in the disabled state so that they can't
+ // steal focus away from our main window. This is especially
+ // important if the plugin has loaded in a background tab.
+ if (aInitData->mWindowType == eWindowType_plugin ||
+ aInitData->mWindowType == eWindowType_plugin_ipc_chrome ||
+ aInitData->mWindowType == eWindowType_plugin_ipc_content) {
+ style |= WS_DISABLED;
+ }
+ 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 (mIsRTL && WinUtils::dwmSetWindowAttributePtr) {
+ DWORD dwAttribute = TRUE;
+ WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute, sizeof dwAttribute);
+ }
+
+ if (!IsPlugin() &&
+ mWindowType != eWindowType_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;
+ if (mUnicodeWidget)
+ oldWndProc = (WNDPROC)::SetWindowLongPtrW(scrollableWnd, GWLP_WNDPROC,
+ (LONG_PTR)nsWindow::WindowProc);
+ else
+ oldWndProc = (WNDPROC)::SetWindowLongPtrA(scrollableWnd, GWLP_WNDPROC,
+ (LONG_PTR)nsWindow::WindowProc);
+ ::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
+ }
+
+ SubclassWindow(TRUE);
+
+ // 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);
+
+ // If the internal variable set by the config.trim_on_minimize pref has not
+ // been initialized, and if this is the hidden window (conveniently created
+ // before any visible windows, and after the profile has been initialized),
+ // do some initialization work.
+ if (sTrimOnMinimize == 2 && mWindowType == eWindowType_invisible) {
+ // Our internal trim prevention logic is effective on 2K/XP at maintaining
+ // the working set when windows are minimized, but on Vista and up it has
+ // little to no effect. Since this feature has been the source of numerous
+ // bugs over the years, disable it (sTrimOnMinimize=1) on Vista and up.
+ sTrimOnMinimize =
+ Preferences::GetBool("config.trim_on_minimize",
+ IsVistaOrLater() ? 1 : 0);
+ sSwitchKeyboardLayout =
+ Preferences::GetBool("intl.keyboard.per_window_layout", false);
+ }
+
+ // Query for command button metric data for rendering the titlebar. We
+ // only do this once on the first window that has an actual titlebar
+ if (ShouldCacheTitleBarInfo(mWindowType, mBorderStyle)) {
+ nsUXThemeData::UpdateTitlebarInfo(mWnd);
+ }
+
+ return NS_OK;
+}
+
+// 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);
+
+ /**
+ * 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();
+
+ /* We should clear our cached resources now and not wait for the GC to
+ * delete the nsWindow. */
+ ClearCachedResources();
+
+ // 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.
+ *
+ **************************************************************/
+
+const wchar_t*
+nsWindow::RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle, LPWSTR aIconID) const
+{
+ 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 = mBrush;
+ 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);
+
+// Return the proper window class for everything except popups.
+const wchar_t*
+nsWindow::GetWindowClass() const
+{
+ switch (mWindowType) {
+ case eWindowType_invisible:
+ return RegisterWindowClass(kClassNameHidden, 0, gStockApplicationIcon);
+ case eWindowType_dialog:
+ return RegisterWindowClass(kClassNameDialog, 0, 0);
+ default:
+ return RegisterWindowClass(GetMainWindowClass(), 0, gStockApplicationIcon);
+ }
+}
+
+// Return the proper popup window class
+const wchar_t*
+nsWindow::GetWindowPopupClass() const
+{
+ return RegisterWindowClass(kClassNameDropShadow,
+ CS_XP_DROPSHADOW, gStockApplicationIcon);
+}
+
+/**************************************************************
+ *
+ * SECTION: Window styles utilities
+ *
+ * Return the proper windows styles and extended styles.
+ *
+ **************************************************************/
+
+// Return nsWindow styles
+DWORD nsWindow::WindowStyle()
+{
+ DWORD style;
+
+ switch (mWindowType) {
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ case eWindowType_child:
+ style = WS_OVERLAPPED;
+ break;
+
+ case eWindowType_dialog:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
+ DS_MODALFRAME | WS_CLIPCHILDREN;
+ if (mBorderStyle != eBorderStyle_default)
+ style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
+ break;
+
+ case eWindowType_popup:
+ style = WS_POPUP;
+ if (!HasGlass()) {
+ style |= WS_OVERLAPPED;
+ }
+ break;
+
+ default:
+ NS_ERROR("unknown border style");
+ // fall through
+
+ case eWindowType_toplevel:
+ case eWindowType_invisible:
+ style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
+ WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
+ break;
+ }
+
+ if (mBorderStyle != eBorderStyle_default && mBorderStyle != eBorderStyle_all) {
+ if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_border))
+ style &= ~WS_BORDER;
+
+ if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_title)) {
+ style &= ~WS_DLGFRAME;
+ style |= WS_POPUP;
+ style &= ~WS_CHILD;
+ }
+
+ if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_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 == eBorderStyle_none ||
+ !(mBorderStyle & (eBorderStyle_menu | eBorderStyle_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 == eBorderStyle_none || !(mBorderStyle & eBorderStyle_resizeh))
+ style &= ~WS_THICKFRAME;
+
+ if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_minimize))
+ style &= ~WS_MINIMIZEBOX;
+
+ if (mBorderStyle == eBorderStyle_none || !(mBorderStyle & eBorderStyle_maximize))
+ style &= ~WS_MAXIMIZEBOX;
+
+ if (IsPopupWithTitleBar()) {
+ style |= WS_CAPTION;
+ if (mBorderStyle & eBorderStyle_close) {
+ style |= WS_SYSMENU;
+ }
+ }
+ }
+
+ VERIFY_WINDOW_STYLE(style);
+ return style;
+}
+
+// Return nsWindow extended styles
+DWORD nsWindow::WindowExStyle()
+{
+ switch (mWindowType)
+ {
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ case eWindowType_child:
+ return 0;
+
+ case eWindowType_dialog:
+ return WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME;
+
+ case eWindowType_popup:
+ {
+ DWORD extendedStyle = WS_EX_TOOLWINDOW;
+ if (mPopupLevel == ePopupLevelTop)
+ extendedStyle |= WS_EX_TOPMOST;
+ return extendedStyle;
+ }
+ default:
+ NS_ERROR("unknown border style");
+ // fall through
+
+ case eWindowType_toplevel:
+ case eWindowType_invisible:
+ return WS_EX_WINDOWEDGE;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Window subclassing utilities
+ *
+ * Set or clear window subclasses on native windows. Used in
+ * Create and Destroy.
+ *
+ **************************************************************/
+
+// Subclass (or remove the subclass from) this component's nsWindow
+void nsWindow::SubclassWindow(BOOL bState)
+{
+ if (bState) {
+ if (!mWnd || !IsWindow(mWnd)) {
+ NS_ERROR("Invalid window handle");
+ }
+
+ if (mUnicodeWidget) {
+ mPrevWndProc =
+ reinterpret_cast<WNDPROC>(
+ SetWindowLongPtrW(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
+ } else {
+ mPrevWndProc =
+ reinterpret_cast<WNDPROC>(
+ SetWindowLongPtrA(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
+ }
+ NS_ASSERTION(mPrevWndProc, "Null standard window procedure");
+ // connect the this pointer to the nsWindow handle
+ WinUtils::SetNSWindowBasePtr(mWnd, this);
+ } else {
+ if (IsWindow(mWnd)) {
+ if (mUnicodeWidget) {
+ SetWindowLongPtrW(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(mPrevWndProc));
+ } else {
+ SetWindowLongPtrA(mWnd,
+ GWLP_WNDPROC,
+ reinterpret_cast<LONG_PTR>(mPrevWndProc));
+ }
+ }
+ WinUtils::SetNSWindowBasePtr(mWnd, nullptr);
+ mPrevWndProc = nullptr;
+ }
+}
+
+/**************************************************************
+ *
+ * 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
+NS_IMETHODIMP 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 NS_OK;
+ }
+ if (mWnd) {
+ // If we have no parent, SetParent should return the desktop.
+ VERIFY(::SetParent(mWnd, nullptr));
+ }
+ return NS_OK;
+}
+
+void
+nsWindow::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_PRECONDITION(aNewParent, "");
+
+ mParent = aNewParent;
+ if (mWindowType == eWindowType_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);
+ }
+}
+
+nsIWidget* nsWindow::GetParent(void)
+{
+ return GetParentWindow(false);
+}
+
+static int32_t RoundDown(double aDouble)
+{
+ return aDouble > 0 ? static_cast<int32_t>(floor(aDouble)) :
+ static_cast<int32_t>(ceil(aDouble));
+}
+
+float nsWindow::GetDPI()
+{
+ HDC dc = ::GetDC(mWnd);
+ if (!dc)
+ return 96.0f;
+
+ double heightInches = ::GetDeviceCaps(dc, VERTSIZE)/MM_PER_INCH_FLOAT;
+ int heightPx = ::GetDeviceCaps(dc, VERTRES);
+ ::ReleaseDC(mWnd, dc);
+ if (heightInches < 0.25) {
+ // Something's broken
+ return 96.0f;
+ }
+ return float(heightPx/heightInches);
+}
+
+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));
+}
+
+nsWindowBase*
+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 static_cast<nsWindowBase*>(widget);
+}
+
+BOOL CALLBACK
+nsWindow::EnumAllChildWindProc(HWND aWnd, LPARAM aParam)
+{
+ nsWindow *wnd = WinUtils::GetNSWindowPtr(aWnd);
+ if (wnd) {
+ ((nsWindow::WindowEnumCallback*)aParam)(wnd);
+ }
+ return TRUE;
+}
+
+BOOL CALLBACK
+nsWindow::EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam)
+{
+ nsWindow *wnd = WinUtils::GetNSWindowPtr(aWnd);
+ if (wnd) {
+ ((nsWindow::WindowEnumCallback*)aParam)(wnd);
+ }
+ EnumChildWindows(aWnd, EnumAllChildWindProc, aParam);
+ return TRUE;
+}
+
+void
+nsWindow::EnumAllWindows(WindowEnumCallback aCallback)
+{
+ EnumThreadWindows(GetCurrentThreadId(),
+ EnumAllThreadWindowProc,
+ (LPARAM)aCallback);
+}
+
+static already_AddRefed<SourceSurface>
+CreateSourceSurfaceForGfxSurface(gfxASurface* aSurface)
+{
+ MOZ_ASSERT(aSurface);
+ return Factory::CreateSourceSurfaceForCairoSurface(
+ aSurface->CairoSurface(), aSurface->GetSize(),
+ aSurface->GetSurfaceFormat());
+}
+
+nsWindow::ScrollSnapshot*
+nsWindow::EnsureSnapshotSurface(ScrollSnapshot& aSnapshotData,
+ const mozilla::gfx::IntSize& aSize)
+{
+ // If the surface doesn't exist or is the wrong size then create new one.
+ if (!aSnapshotData.surface || aSnapshotData.surface->GetSize() != aSize) {
+ aSnapshotData.surface = new gfxWindowsSurface(aSize, kScrollCaptureFormat);
+ aSnapshotData.surfaceHasSnapshot = false;
+ }
+
+ return &aSnapshotData;
+}
+
+already_AddRefed<SourceSurface>
+nsWindow::CreateScrollSnapshot()
+{
+ RECT clip = { 0 };
+ int rgnType = ::GetWindowRgnBox(mWnd, &clip);
+ if (rgnType == RGN_ERROR) {
+ // We failed to get the clip assume that we need a full fallback.
+ clip.left = 0;
+ clip.top = 0;
+ clip.right = mBounds.width;
+ clip.bottom = mBounds.height;
+ return GetFallbackScrollSnapshot(clip);
+ }
+
+ // Check that the window is in a position to snapshot. We don't check for
+ // clipped width as that doesn't currently matter for APZ scrolling.
+ if (clip.top || clip.bottom != mBounds.height) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+
+ HDC windowDC = ::GetDC(mWnd);
+ if (!windowDC) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+ auto releaseDC = MakeScopeExit([&] {
+ ::ReleaseDC(mWnd, windowDC);
+ });
+
+ gfx::IntSize snapshotSize(mBounds.width, mBounds.height);
+ ScrollSnapshot* snapshot;
+ if (clip.left || clip.right != mBounds.width) {
+ // Can't do a full snapshot, so use the partial snapshot.
+ snapshot = EnsureSnapshotSurface(mPartialSnapshot, snapshotSize);
+ } else {
+ snapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize);
+ }
+
+ // Note that we know that the clip is full height.
+ if (!::BitBlt(snapshot->surface->GetDC(), clip.left, 0, clip.right - clip.left,
+ clip.bottom, windowDC, clip.left, 0, SRCCOPY)) {
+ return GetFallbackScrollSnapshot(clip);
+ }
+ ::GdiFlush();
+ snapshot->surface->Flush();
+ snapshot->surfaceHasSnapshot = true;
+ snapshot->clip = clip;
+ mCurrentSnapshot = snapshot;
+
+ return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface);
+}
+
+already_AddRefed<SourceSurface>
+nsWindow::GetFallbackScrollSnapshot(const RECT& aRequiredClip)
+{
+ gfx::IntSize snapshotSize(mBounds.width, mBounds.height);
+
+ // If the current snapshot is the correct size and covers the required clip,
+ // just keep that by returning null.
+ // Note: we know the clip is always full height.
+ if (mCurrentSnapshot &&
+ mCurrentSnapshot->surface->GetSize() == snapshotSize &&
+ mCurrentSnapshot->clip.left <= aRequiredClip.left &&
+ mCurrentSnapshot->clip.right >= aRequiredClip.right) {
+ return nullptr;
+ }
+
+ // Otherwise we'll use the full snapshot, making sure it is big enough first.
+ mCurrentSnapshot = EnsureSnapshotSurface(mFullSnapshot, snapshotSize);
+
+ // If there is no snapshot, create a default.
+ if (!mCurrentSnapshot->surfaceHasSnapshot) {
+ gfx::SurfaceFormat format = mCurrentSnapshot->surface->GetSurfaceFormat();
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(
+ mCurrentSnapshot->surface->CairoSurface(),
+ mCurrentSnapshot->surface->GetSize(), &format);
+
+ DefaultFillScrollCapture(dt);
+ }
+
+ return CreateSourceSurfaceForGfxSurface(mCurrentSnapshot->surface);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Show
+ *
+ * Hide or show this component.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::Show(bool bState)
+{
+ if (mWindowType == eWindowType_popup) {
+ // See bug 603793. When we try to draw D3D9/10 windows with a drop shadow
+ // without the DWM on a secondary monitor, windows fails to composite
+ // our windows correctly. We therefor switch off the drop shadow for
+ // pop-up windows when the DWM is disabled and two monitors are
+ // connected.
+ if (HasBogusPopupsDropShadowOnMultiMonitor() &&
+ WinUtils::GetMonitorCount() > 1 &&
+ !nsUXThemeData::CheckForCompositor())
+ {
+ if (sDropShadowEnabled) {
+ ::SetClassLongA(mWnd, GCL_STYLE, 0);
+ sDropShadowEnabled = false;
+ }
+ } else {
+ if (!sDropShadowEnabled) {
+ ::SetClassLongA(mWnd, GCL_STYLE, CS_DROPSHADOW);
+ sDropShadowEnabled = true;
+ }
+ }
+
+ // 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 (!mIsVisible && wasVisible) {
+ ClearCachedResources();
+ }
+
+ if (mWnd) {
+ if (bState) {
+ if (!wasVisible && mWindowType == eWindowType_toplevel) {
+ // speed up the initial paint after show for
+ // top level windows:
+ syncInvalidate = true;
+ switch (mSizeMode) {
+ 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()) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ GetAttention(2);
+ }
+ break;
+ }
+ } else {
+ DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
+ if (wasVisible)
+ flags |= SWP_NOZORDER;
+
+ if (mWindowType == eWindowType_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);
+ ::SetWindowPos(mWnd, owner ? 0 : HWND_TOPMOST, 0, 0, 0, 0, flags);
+ } else {
+ if (mWindowType == eWindowType_dialog && !CanTakeFocus())
+ flags |= SWP_NOACTIVATE;
+
+ ::SetWindowPos(mWnd, HWND_TOP, 0, 0, 0, 0, flags);
+ }
+ }
+
+ if (!wasVisible && (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)) {
+ // when a toplevel window or dialog is shown, initialize the UI state
+ ::SendMessageW(mWnd, WM_CHANGEUISTATE, MAKEWPARAM(UIS_INITIALIZE, UISF_HIDEFOCUS | UISF_HIDEACCEL), 0);
+ }
+ } else {
+ // Clear contents to avoid ghosting of old content if we display
+ // this window again.
+ if (wasVisible && mTransparencyMode == eTransparencyTransparent) {
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->ClearTransparentWindow();
+ }
+ }
+ if (mWindowType != eWindowType_dialog) {
+ ::ShowWindow(mWnd, SW_HIDE);
+ } else {
+ ::SetWindowPos(mWnd, 0, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE |
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+ }
+
+#ifdef MOZ_XUL
+ if (!wasVisible && bState) {
+ Invalidate();
+ if (syncInvalidate && !mInDtor && !mOnDestroyCalled) {
+ ::UpdateWindow(mWnd);
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::IsVisible
+ *
+ * Returns the visibility state.
+ *
+ **************************************************************/
+
+// Return true if the whether the component is visible, false otherwise
+bool nsWindow::IsVisible() const
+{
+ return mIsVisible;
+}
+
+/**************************************************************
+ *
+ * SECTION: Window clipping utilities
+ *
+ * Used in Size and Move operations for setting the proper
+ * window clipping regions for window transparency.
+ *
+ **************************************************************/
+
+// XP and Vista visual styles sometimes require window clipping regions to be applied for proper
+// transparency. These routines are called on size and move operations.
+void nsWindow::ClearThemeRegion()
+{
+ if (IsVistaOrLater() && !HasGlass() &&
+ (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() &&
+ (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) {
+ SetWindowRgn(mWnd, nullptr, false);
+ }
+}
+
+void nsWindow::SetThemeRegion()
+{
+ // Popup types that have a visual styles region applied (bug 376408). This can be expanded
+ // for other window types as needed. The regions are applied generically to the base window
+ // so default constants are used for part and state. At some point we might need part and
+ // state values from nsNativeThemeWin's GetThemePartAndState, but currently windows that
+ // change shape based on state haven't come up.
+ if (IsVistaOrLater() && !HasGlass() &&
+ (mWindowType == eWindowType_popup && !IsPopupWithTitleBar() &&
+ (mPopupType == ePopupTypeTooltip || mPopupType == ePopupTypePanel))) {
+ HRGN hRgn = nullptr;
+ RECT rect = {0,0,mBounds.width,mBounds.height};
+
+ HDC dc = ::GetDC(mWnd);
+ GetThemeBackgroundRegion(nsUXThemeData::GetTheme(eUXTooltip), dc, TTP_STANDARD, TS_NORMAL, &rect, &hRgn);
+ if (hRgn) {
+ if (!SetWindowRgn(mWnd, hRgn, false)) // do not delete or alter hRgn if accepted.
+ DeleteObject(hRgn);
+ }
+ ::ReleaseDC(mWnd, dc);
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: Touch and APZ-related functions
+ *
+ **************************************************************/
+
+void nsWindow::RegisterTouchWindow() {
+ mTouchWindow = true;
+ mGesture.RegisterTouchWindow(mWnd);
+ ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
+}
+
+BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
+ nsWindow* win = WinUtils::GetNSWindowPtr(aWnd);
+ if (win)
+ win->mGesture.RegisterTouchWindow(aWnd);
+ return TRUE;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Move, nsIWidget::Resize,
+ * nsIWidget::Size, nsIWidget::BeginResizeDrag
+ *
+ * Repositioning and sizing a window.
+ *
+ **************************************************************/
+
+void
+nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+ SizeConstraints c = aConstraints;
+ if (mWindowType != eWindowType_popup) {
+ 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);
+ }
+ ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayerManager) {
+ int32_t maxSize = clientLayerManager->GetMaxTextureSize();
+ // 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, maxSize);
+ c.mMaxSize.height = std::min(c.mMaxSize.height, maxSize);
+ }
+
+ 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
+NS_IMETHODIMP nsWindow::Move(double aX, double aY)
+{
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_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 != eWindowType_popup && (mBounds.x == x) && (mBounds.y == y))
+ {
+ // Nothing to do, since it is already positioned correctly.
+ return NS_OK;
+ }
+
+ mBounds.x = x;
+ mBounds.y = 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
+ ClearThemeRegion();
+
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE;
+ // Workaround SetWindowPos bug with D3D9. If our window has a clip
+ // region, some drivers or OSes may incorrectly copy into the clipped-out
+ // area.
+ if (IsPlugin() &&
+ (!mLayerManager || mLayerManager->GetBackendType() == LayersBackend::LAYERS_D3D9) &&
+ mClipRects &&
+ (mClipRectCount != 1 || !mClipRects[0].IsEqualInterior(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height)))) {
+ flags |= SWP_NOCOPYBITS;
+ }
+ double oldScale = mDefaultScale;
+ mResizeState = IN_SIZEMOVE;
+ VERIFY(::SetWindowPos(mWnd, nullptr, x, y, 0, 0, flags));
+ mResizeState = NOT_RESIZING;
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+
+ SetThemeRegion();
+ }
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+// Resize this component
+NS_IMETHODIMP 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");
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.width == width && mBounds.height == height) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return NS_OK;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.width = width;
+ mBounds.height = height;
+
+ if (mWnd) {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE;
+
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+ double oldScale = mDefaultScale;
+ VERIFY(::SetWindowPos(mWnd, nullptr, 0, 0,
+ width, GetHeight(height), flags));
+ if (WinUtils::LogToPhysFactor(mWnd) != oldScale) {
+ ChangedDPI();
+ }
+ SetThemeRegion();
+ }
+
+ if (aRepaint)
+ Invalidate();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+// Resize this component
+NS_IMETHODIMP 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");
+
+ ConstrainSize(&width, &height);
+
+ // Avoid unnecessary resizing calls
+ if (mBounds.x == x && mBounds.y == y &&
+ mBounds.width == width && mBounds.height == height) {
+ if (aRepaint) {
+ Invalidate();
+ }
+ return NS_OK;
+ }
+
+ // Set cached value for lightweight and printing
+ mBounds.x = x;
+ mBounds.y = y;
+ mBounds.width = width;
+ mBounds.height = height;
+
+ if (mWnd) {
+ UINT flags = SWP_NOZORDER | SWP_NOACTIVATE;
+ if (!aRepaint) {
+ flags |= SWP_NOREDRAW;
+ }
+
+ ClearThemeRegion();
+ double oldScale = mDefaultScale;
+ VERIFY(::SetWindowPos(mWnd, nullptr, x, y,
+ width, GetHeight(height), flags));
+ 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);
+ // Every transition window is only used once.
+ mTransitionWnd = nullptr;
+ }
+ SetThemeRegion();
+ }
+
+ if (aRepaint)
+ Invalidate();
+
+ NotifyRollupGeometryChange();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical)
+{
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (aEvent->mClass != eMouseEventClass) {
+ // you can only begin a resize drag with a mouse event
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aEvent->AsMouseEvent()->button != WidgetMouseEvent::eLeftButton) {
+ // you can only begin a resize drag with the left mouse button
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // work out what sizemode we're talking about
+ WPARAM syscommand;
+ if (aVertical < 0) {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_TOPLEFT;
+ } else if (aHorizontal == 0) {
+ syscommand = SC_SIZE | WMSZ_TOP;
+ } else {
+ syscommand = SC_SIZE | WMSZ_TOPRIGHT;
+ }
+ } else if (aVertical == 0) {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_LEFT;
+ } else if (aHorizontal == 0) {
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ syscommand = SC_SIZE | WMSZ_RIGHT;
+ }
+ } else {
+ if (aHorizontal < 0) {
+ syscommand = SC_SIZE | WMSZ_BOTTOMLEFT;
+ } else if (aHorizontal == 0) {
+ syscommand = SC_SIZE | WMSZ_BOTTOM;
+ } else {
+ syscommand = SC_SIZE | WMSZ_BOTTOMRIGHT;
+ }
+ }
+
+ // resizing doesn't work if the mouse is already captured
+ CaptureMouse(false);
+
+ // find the top-level window
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd, true);
+
+ // tell Windows to start the resize
+ ::PostMessage(toplevelWnd, WM_SYSCOMMAND, syscommand,
+ POINTTOPOINTS(aEvent->mRefPoint));
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * 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)
+{
+ // Let's not try and do anything if we're already in that state.
+ // (This is needed to prevent problems when calling window.minimize(), which
+ // calls us directly, and then the OS triggers another call to us.)
+ if (aMode == mSizeMode)
+ return;
+
+ // save the requested state
+ mLastSizeMode = mSizeMode;
+ nsBaseWidget::SetSizeMode(aMode);
+ if (mIsVisible) {
+ int mode;
+
+ switch (aMode) {
+ case nsSizeMode_Fullscreen :
+ mode = SW_SHOW;
+ break;
+
+ case nsSizeMode_Maximized :
+ mode = SW_MAXIMIZE;
+ break;
+
+ case nsSizeMode_Minimized :
+ // Using SW_SHOWMINIMIZED prevents the working set from being trimmed but
+ // keeps the window active in the tray. So after the window is minimized,
+ // windows will fire WM_WINDOWPOSCHANGED (OnWindowPosChanged) at which point
+ // we will do some additional processing to get the active window set right.
+ // If sTrimOnMinimize is set, we let windows handle minimization normally
+ // using SW_MINIMIZE.
+ mode = sTrimOnMinimize ? SW_MINIMIZE : SW_SHOWMINIMIZED;
+ break;
+
+ default :
+ mode = SW_RESTORE;
+ }
+
+ // 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(mWnd) == SW_SHOWNORMAL && mode == SW_RESTORE)) {
+ ::ShowWindow(mWnd, mode);
+ }
+ // we activate here to ensure that the right child window is focused
+ if (mode == SW_MAXIMIZE || mode == SW_SHOW)
+ DispatchFocusToTopLevelWindow(true);
+ }
+}
+
+// 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(bool aAllowSlop, int32_t *aX, int32_t *aY)
+{
+ 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(*aX, *aY, logWidth, logHeight,
+ getter_AddRefs(screen));
+ if (mSizeMode != 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 (aAllowSlop) {
+ if (*aX < screenRect.left - logWidth + kWindowPositionSlop)
+ *aX = screenRect.left - logWidth + kWindowPositionSlop;
+ else if (*aX >= screenRect.right - kWindowPositionSlop)
+ *aX = screenRect.right - kWindowPositionSlop;
+
+ if (*aY < screenRect.top - logHeight + kWindowPositionSlop)
+ *aY = screenRect.top - logHeight + kWindowPositionSlop;
+ else if (*aY >= screenRect.bottom - kWindowPositionSlop)
+ *aY = screenRect.bottom - kWindowPositionSlop;
+
+ } else {
+
+ if (*aX < screenRect.left)
+ *aX = screenRect.left;
+ else if (*aX >= screenRect.right - logWidth)
+ *aX = screenRect.right - logWidth;
+
+ if (*aY < screenRect.top)
+ *aY = screenRect.top;
+ else if (*aY >= screenRect.bottom - logHeight)
+ *aY = screenRect.bottom - logHeight;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Enable, nsIWidget::IsEnabled
+ *
+ * Enabling and disabling the widget.
+ *
+ **************************************************************/
+
+// Enable/disable this component
+NS_IMETHODIMP nsWindow::Enable(bool bState)
+{
+ if (mWnd) {
+ ::EnableWindow(mWnd, bState);
+ }
+ return NS_OK;
+}
+
+// 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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::SetFocus(bool aRaise)
+{
+ if (mWnd) {
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [ top] raise=%d\n", aRaise));
+ } else {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** SetFocus: [child] raise=%d\n", aRaise));
+ }
+#endif
+ // Uniconify, if necessary
+ HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd);
+ if (aRaise && ::IsIconic(toplevelWnd)) {
+ ::ShowWindow(toplevelWnd, SW_RESTORE);
+ }
+ ::SetFocus(mWnd);
+ }
+ return NS_OK;
+}
+
+
+/**************************************************************
+ *
+ * SECTION: Bounds
+ *
+ * GetBounds, GetClientBounds, GetScreenBounds,
+ * GetRestoredBounds, GetClientOffset
+ * SetDrawsInTitlebar, 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.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+
+ // popup window bounds' are in screen coordinates, not relative to parent
+ // window
+ if (mWindowType == eWindowType_popup) {
+ rect.x = r.left;
+ rect.y = 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.x = r.left;
+ rect.y = r.top;
+ return rect;
+}
+
+// Get this component dimension
+LayoutDeviceIntRect
+nsWindow::GetClientBounds()
+{
+ if (!mWnd) {
+ return LayoutDeviceIntRect(0, 0, 0, 0);
+ }
+
+ RECT r;
+ VERIFY(::GetClientRect(mWnd, &r));
+
+ LayoutDeviceIntRect bounds = GetBounds();
+ LayoutDeviceIntRect rect;
+ rect.MoveTo(bounds.TopLeft() + GetClientOffset());
+ rect.width = r.right - r.left;
+ rect.height = 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));
+
+ LayoutDeviceIntRect rect;
+ rect.x = r.left;
+ rect.y = r.top;
+ rect.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+ return rect;
+}
+
+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 - r1.left, pt.y - r1.top);
+}
+
+void
+nsWindow::SetDrawsInTitlebar(bool aState)
+{
+ nsWindow * window = GetTopLevelWindow(true);
+ if (window && window != this) {
+ return window->SetDrawsInTitlebar(aState);
+ }
+
+ if (aState) {
+ // top, right, bottom, left for nsIntMargin
+ LayoutDeviceIntMargin margins(0, -1, -1, -1);
+ SetNonClientMargins(margins);
+ }
+ else {
+ LayoutDeviceIntMargin margins(-1, -1, -1, -1);
+ SetNonClientMargins(margins);
+ }
+}
+
+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);
+ nsIntRect evRect(WinUtils::ToIntRect(clientRc));
+ OnResize(evRect);
+
+ // 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 GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr;
+
+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;
+
+ if (!sGetWindowInfoPtrStub) {
+ sUser32Intercept.Init("user32.dll");
+ if (!sUser32Intercept.AddHook("GetWindowInfo", reinterpret_cast<intptr_t>(GetWindowInfoHook),
+ (void**) &sGetWindowInfoPtrStub))
+ return;
+ }
+ // Update our internally tracked caption status
+ SetPropW(mWnd, kManageWindowInfoProperty,
+ reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1));
+}
+
+/**
+ * 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(int32_t aSizeMode, bool aReflowWindow)
+{
+ if (!mCustomNonClient)
+ return false;
+
+ if (aSizeMode == -1) {
+ aSizeMode = mSizeMode;
+ }
+
+ bool hasCaption = (mBorderStyle
+ & (eBorderStyle_all
+ | eBorderStyle_title
+ | eBorderStyle_menu
+ | eBorderStyle_default));
+
+ // 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
+ // `GetSystemMetrics(SM_CYFRAME)`
+ mCaptionHeight = GetSystemMetrics(SM_CYFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CYCAPTION)
+ + GetSystemMetrics(SM_CXPADDEDBORDER)
+ : 0);
+
+ // 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
+ // `GetSystemMetrics(SM_CXFRAME)`
+ mHorResizeMargin = GetSystemMetrics(SM_CXFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 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
+ // `GetSystemMetrics(SM_CYFRAME)`
+ mVertResizeMargin = GetSystemMetrics(SM_CYFRAME)
+ + (hasCaption ? GetSystemMetrics(SM_CXPADDEDBORDER) : 0);
+
+ if (aSizeMode == nsSizeMode_Minimized) {
+ // Use default frame size for minimized windows
+ mNonClientOffset.top = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+ mNonClientOffset.bottom = 0;
+ } else if (aSizeMode == 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 (aSizeMode == nsSizeMode_Maximized) {
+ // Remove the default frame from the top of our maximized window. This
+ // makes the whole caption part of our client area, allowing us to draw
+ // in the whole caption area. Use default frame size on left, right, and
+ // bottom. The reason this works is that, for maximized windows,
+ // Windows positions them so that their frames fall off the screen.
+ // This gives the illusion of windows having no frames when they are
+ // maximized. If we try to mess with the frame sizes by setting these
+ // offsets to positive values, our client area will fall off the screen.
+ mNonClientOffset.top = mCaptionHeight;
+ mNonClientOffset.bottom = 0;
+ mNonClientOffset.left = 0;
+ mNonClientOffset.right = 0;
+
+ APPBARDATA appBarData;
+ appBarData.cbSize = sizeof(appBarData);
+ UINT taskbarState = SHAppBarMessage(ABM_GETSTATE, &appBarData);
+ if (ABS_AUTOHIDE & taskbarState) {
+ UINT edge = -1;
+ appBarData.hWnd = FindWindow(L"Shell_TrayWnd", nullptr);
+ if (appBarData.hWnd) {
+ HMONITOR taskbarMonitor = ::MonitorFromWindow(appBarData.hWnd,
+ MONITOR_DEFAULTTOPRIMARY);
+ HMONITOR windowMonitor = ::MonitorFromWindow(mWnd,
+ MONITOR_DEFAULTTONEAREST);
+ if (taskbarMonitor == windowMonitor) {
+ SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData);
+ edge = appBarData.uEdge;
+ }
+ }
+
+ if (ABE_LEFT == edge) {
+ mNonClientOffset.left -= 1;
+ } else if (ABE_RIGHT == edge) {
+ mNonClientOffset.right -= 1;
+ } else if (ABE_BOTTOM == edge || ABE_TOP == edge) {
+ mNonClientOffset.bottom -= 1;
+ }
+ }
+ } else {
+ bool glass = nsUXThemeData::CheckForCompositor();
+
+ // 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 && glass) {
+ mNonClientOffset.top = std::min(mCaptionHeight, mNonClientMargins.top);
+ } else if (mNonClientMargins.top == 0) {
+ mNonClientOffset.top = mCaptionHeight;
+ } else {
+ mNonClientOffset.top = 0;
+ }
+
+ if (mNonClientMargins.bottom > 0 && glass) {
+ mNonClientOffset.bottom = std::min(mVertResizeMargin, mNonClientMargins.bottom);
+ } else if (mNonClientMargins.bottom == 0) {
+ mNonClientOffset.bottom = mVertResizeMargin;
+ } else {
+ mNonClientOffset.bottom = 0;
+ }
+
+ if (mNonClientMargins.left > 0 && glass) {
+ mNonClientOffset.left = std::min(mHorResizeMargin, mNonClientMargins.left);
+ } else if (mNonClientMargins.left == 0) {
+ mNonClientOffset.left = mHorResizeMargin;
+ } else {
+ mNonClientOffset.left = 0;
+ }
+
+ if (mNonClientMargins.right > 0 && glass) {
+ mNonClientOffset.right = std::min(mHorResizeMargin, mNonClientMargins.right);
+ } else if (mNonClientMargins.right == 0) {
+ mNonClientOffset.right = mHorResizeMargin;
+ } else {
+ mNonClientOffset.right = 0;
+ }
+ }
+
+ if (aReflowWindow) {
+ // Force a reflow of content based on the new client
+ // dimensions.
+ ResetLayout();
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::SetNonClientMargins(LayoutDeviceIntMargin &margins)
+{
+ if (!mIsTopWidgetWindow ||
+ mBorderStyle == eBorderStyle_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::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 -= mHorResizeMargin;
+ rect.left += mVertResizeMargin;
+ 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);
+}
+
+HRGN
+nsWindow::ExcludeNonClientFromPaintRegion(HRGN aRegion)
+{
+ RECT rect;
+ HRGN rgn = nullptr;
+ if (aRegion == (HRGN)1) { // undocumented value indicating a full refresh
+ GetWindowRect(mWnd, &rect);
+ rgn = CreateRectRgnIndirect(&rect);
+ } else {
+ rgn = aRegion;
+ }
+ GetClientRect(mWnd, &rect);
+ MapWindowPoints(mWnd, nullptr, (LPPOINT)&rect, 2);
+ HRGN nonClientRgn = CreateRectRgnIndirect(&rect);
+ CombineRgn(rgn, rgn, nonClientRgn, RGN_DIFF);
+ DeleteObject(nonClientRgn);
+ return rgn;
+}
+
+/**************************************************************
+ *
+ * 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
+NS_IMETHODIMP nsWindow::SetCursor(nsCursor aCursor)
+{
+ // Only change cursor if it's changing
+
+ //XXX mCursor isn't always right. Scrollbars and others change it, too.
+ //XXX If we want this optimization we need a better way to do it.
+ //if (aCursor != mCursor) {
+ HCURSOR newCursor = nullptr;
+
+ switch (aCursor) {
+ case eCursor_select:
+ newCursor = ::LoadCursor(nullptr, IDC_IBEAM);
+ break;
+
+ case eCursor_wait:
+ newCursor = ::LoadCursor(nullptr, IDC_WAIT);
+ break;
+
+ case eCursor_hyperlink:
+ {
+ newCursor = ::LoadCursor(nullptr, IDC_HAND);
+ break;
+ }
+
+ case eCursor_standard:
+ case eCursor_context_menu: // XXX See bug 258960.
+ newCursor = ::LoadCursor(nullptr, IDC_ARROW);
+ break;
+
+ case eCursor_n_resize:
+ case eCursor_s_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENS);
+ break;
+
+ case eCursor_w_resize:
+ case eCursor_e_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEWE);
+ break;
+
+ case eCursor_nw_resize:
+ case eCursor_se_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE);
+ break;
+
+ case eCursor_ne_resize:
+ case eCursor_sw_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENESW);
+ break;
+
+ case eCursor_crosshair:
+ newCursor = ::LoadCursor(nullptr, IDC_CROSS);
+ break;
+
+ case eCursor_move:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEALL);
+ break;
+
+ case eCursor_help:
+ newCursor = ::LoadCursor(nullptr, IDC_HELP);
+ break;
+
+ case eCursor_copy: // CSS3
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY));
+ break;
+
+ case eCursor_alias:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS));
+ break;
+
+ case eCursor_cell:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL));
+ break;
+
+ case eCursor_grab:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB));
+ break;
+
+ case eCursor_grabbing:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRABBING));
+ break;
+
+ case eCursor_spinning:
+ newCursor = ::LoadCursor(nullptr, IDC_APPSTARTING);
+ break;
+
+ case eCursor_zoom_in:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN));
+ break;
+
+ case eCursor_zoom_out:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMOUT));
+ break;
+
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ newCursor = ::LoadCursor(nullptr, IDC_NO);
+ break;
+
+ case eCursor_col_resize:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COLRESIZE));
+ break;
+
+ case eCursor_row_resize:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ROWRESIZE));
+ break;
+
+ case eCursor_vertical_text:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_VERTICALTEXT));
+ break;
+
+ case eCursor_all_scroll:
+ // XXX not 100% appropriate perhaps
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEALL);
+ break;
+
+ case eCursor_nesw_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENESW);
+ break;
+
+ case eCursor_nwse_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENWSE);
+ break;
+
+ case eCursor_ns_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZENS);
+ break;
+
+ case eCursor_ew_resize:
+ newCursor = ::LoadCursor(nullptr, IDC_SIZEWE);
+ break;
+
+ case eCursor_none:
+ newCursor = ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE));
+ break;
+
+ default:
+ NS_ERROR("Invalid cursor type");
+ break;
+ }
+
+ if (nullptr != newCursor) {
+ mCursor = aCursor;
+ HCURSOR oldCursor = ::SetCursor(newCursor);
+
+ if (sHCursor == oldCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ if (sHCursor != nullptr)
+ ::DestroyIcon(sHCursor);
+ sHCursor = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Setting the actual cursor
+NS_IMETHODIMP nsWindow::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ if (sCursorImgContainer == aCursor && sHCursor) {
+ ::SetCursor(sHCursor);
+ return NS_OK;
+ }
+
+ int32_t width;
+ int32_t height;
+
+ nsresult rv;
+ rv = aCursor->GetWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCursor->GetHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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 (width > 128 || height > 128)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ HCURSOR cursor;
+ double scale = GetDefaultScale().scale;
+ IntSize size = RoundedToInt(Size(width * scale, height * scale));
+ rv = nsWindowGfx::CreateIcon(aCursor, true, aHotspotX, aHotspotY, size, &cursor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCursor = nsCursor(-1);
+ ::SetCursor(cursor);
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aCursor;
+ NS_ADDREF(sCursorImgContainer);
+
+ if (sHCursor != nullptr)
+ ::DestroyIcon(sHCursor);
+ sHCursor = cursor;
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::Get/SetTransparencyMode
+ *
+ * Manage the transparency mode of the top-level window
+ * containing this widget.
+ *
+ **************************************************************/
+
+#ifdef MOZ_XUL
+nsTransparencyMode nsWindow::GetTransparencyMode()
+{
+ return GetTopLevelWindow(true)->GetWindowTranslucencyInner();
+}
+
+void nsWindow::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ GetTopLevelWindow(true)->SetWindowTranslucencyInner(aMode);
+}
+
+void nsWindow::UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion)
+{
+ if (!HasGlass() || GetParent())
+ return;
+
+ // If there is no opaque region or hidechrome=true, set margins
+ // to support a full sheet of glass. Comments in MSDN indicate
+ // all values must be set to -1 to get a full sheet of glass.
+ MARGINS margins = { -1, -1, -1, -1 };
+ if (!aOpaqueRegion.IsEmpty()) {
+ LayoutDeviceIntRect pluginBounds;
+ for (nsIWidget* child = GetFirstChild(); child; child = child->GetNextSibling()) {
+ if (child->IsPlugin()) {
+ // Collect the bounds of all plugins for GetLargestRectangle.
+ LayoutDeviceIntRect childBounds = child->GetBounds();
+ pluginBounds.UnionRect(pluginBounds, childBounds);
+ }
+ }
+
+ LayoutDeviceIntRect clientBounds = GetClientBounds();
+
+ // Find the largest rectangle and use that to calculate the inset. Our top
+ // priority is to include the bounds of all plugins.
+ LayoutDeviceIntRect largest =
+ aOpaqueRegion.GetLargestRectangle(pluginBounds);
+ margins.cxLeftWidth = largest.x;
+ margins.cxRightWidth = clientBounds.width - largest.XMost();
+ margins.cyBottomHeight = clientBounds.height - largest.YMost();
+ if (mCustomNonClient) {
+ // The minimum glass height must be the caption buttons height,
+ // otherwise the buttons are drawn incorrectly.
+ largest.y = std::max<uint32_t>(largest.y,
+ nsUXThemeData::sCommandButtons[CMDBUTTONIDX_BUTTONBOX].cy);
+ }
+ margins.cyTopHeight = largest.y;
+ }
+
+ // Only update glass area if there are changes
+ if (memcmp(&mGlassMargins, &margins, sizeof mGlassMargins)) {
+ mGlassMargins = margins;
+ UpdateGlass();
+ }
+}
+
+/**************************************************************
+*
+* 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;
+ }
+}
+
+void nsWindow::UpdateGlass()
+{
+ MARGINS margins = mGlassMargins;
+
+ // DWMNCRP_USEWINDOWSTYLE - The non-client rendering area is
+ // rendered based on the window style.
+ // DWMNCRP_ENABLED - The non-client area rendering is
+ // enabled; the window style is ignored.
+ DWMNCRENDERINGPOLICY policy = DWMNCRP_USEWINDOWSTYLE;
+ switch (mTransparencyMode) {
+ case eTransparencyBorderlessGlass:
+ // Only adjust if there is some opaque rectangle
+ if (margins.cxLeftWidth >= 0) {
+ margins.cxLeftWidth += kGlassMarginAdjustment;
+ margins.cyTopHeight += kGlassMarginAdjustment;
+ margins.cxRightWidth += kGlassMarginAdjustment;
+ margins.cyBottomHeight += kGlassMarginAdjustment;
+ }
+ // Fall through
+ case eTransparencyGlass:
+ policy = DWMNCRP_ENABLED;
+ break;
+ default:
+ break;
+ }
+
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("glass margins: left:%d top:%d right:%d bottom:%d\n",
+ margins.cxLeftWidth, margins.cyTopHeight,
+ margins.cxRightWidth, margins.cyBottomHeight));
+
+ // Extends the window frame behind the client area
+ if (nsUXThemeData::CheckForCompositor()) {
+ WinUtils::dwmExtendFrameIntoClientAreaPtr(mWnd, &margins);
+ WinUtils::dwmSetWindowAttributePtr(mWnd, DWMWA_NCRENDERING_POLICY, &policy, sizeof policy);
+ }
+}
+#endif
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::HideWindowChrome
+ *
+ * Show or hide window chrome.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP 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 NS_ERROR_FAILURE;
+ }
+
+ if (mHideChrome == aShouldHide)
+ return NS_OK;
+
+ 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);
+
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsWindow::Invalidate
+ *
+ * Invalidate an area of the client for painting.
+ *
+ **************************************************************/
+
+// Invalidate this component visible area
+NS_IMETHODIMP nsWindow::Invalidate(bool aEraseBackground,
+ bool aUpdateNCArea,
+ bool aIncludeChildren)
+{
+ if (!mWnd) {
+ return NS_OK;
+ }
+
+#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));
+ return NS_OK;
+}
+
+// Invalidate this component visible area
+NS_IMETHODIMP 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.x + aRect.width;
+ rect.bottom = aRect.y + aRect.height;
+
+ VERIFY(::InvalidateRect(mWnd, &rect, FALSE));
+ }
+ return NS_OK;
+}
+
+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
+{
+ nsIntRect 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)
+{
+ // We don't support fullscreen transition when composition is not
+ // enabled, which could make the transition broken and annoying.
+ // See bug 1184201.
+ if (!nsUXThemeData::CheckForCompositor()) {
+ return false;
+ }
+
+ FullscreenTransitionInitData initData;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ int32_t x, y, width, height;
+ screen->GetRectDisplayPix(&x, &y, &width, &height);
+ MOZ_ASSERT(BoundsUseDesktopPixels(),
+ "Should only be called on top-level window");
+ double scale = GetDesktopToDeviceScale().scale; // XXX or GetDefaultScale() ?
+ initData.mBounds.x = NSToIntRound(x * scale);
+ initData.mBounds.y = NSToIntRound(y * scale);
+ initData.mBounds.width = NSToIntRound(width * scale);
+ initData.mBounds.height = NSToIntRound(height * scale);
+
+ // 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);
+}
+
+nsresult
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
+{
+ // taskbarInfo will be nullptr pre Windows 7 until Bug 680227 is resolved.
+ nsCOMPtr<nsIWinTaskbar> taskbarInfo =
+ do_GetService(NS_TASKBAR_CONTRACTID);
+
+ mFullscreenMode = aFullScreen;
+ if (aFullScreen) {
+ if (mSizeMode == nsSizeMode_Fullscreen)
+ return NS_OK;
+ mOldSizeMode = mSizeMode;
+ SetSizeMode(nsSizeMode_Fullscreen);
+
+ // Notify the taskbar that we will be entering full screen mode.
+ if (taskbarInfo) {
+ taskbarInfo->PrepareFullScreenHWND(mWnd, TRUE);
+ }
+ } else {
+ if (mSizeMode != nsSizeMode_Fullscreen)
+ return NS_OK;
+ SetSizeMode(mOldSizeMode);
+ }
+
+ // If we are going fullscreen, the window size continues to change
+ // and the window will be reflow again then.
+ UpdateNonClientMargins(mSizeMode, /* Reflow */ !aFullScreen);
+
+ // Will call hide chrome, reposition window. Note this will
+ // also cache dimensions for restoration, so it should only
+ // be called once per fullscreen request.
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen, aTargetScreen);
+
+ if (mIsVisible && !aFullScreen && mOldSizeMode == nsSizeMode_Normal) {
+ // Ensure the window exiting fullscreen get activated. Window
+ // activation might be bypassed in SetSizeMode.
+ DispatchFocusToTopLevelWindow(true);
+ }
+
+ // Notify the taskbar that we have exited full screen mode.
+ if (!aFullScreen && taskbarInfo) {
+ taskbarInfo->PrepareFullScreenHWND(mWnd, FALSE);
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+
+ // Send a eMouseEnterIntoWidget event since Windows has already sent
+ // a WM_MOUSELEAVE that caused us to send a eMouseExitFromWidget event.
+ if (aFullScreen && !sCurrentWindow) {
+ sCurrentWindow = this;
+ LPARAM pos = sCurrentWindow->lParamToClient(sMouseExitlParamScreen);
+ sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
+ sMouseExitwParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ }
+
+ 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_TMP_WINDOW:
+ return (void*)::CreateWindowExW(mIsRTL ? WS_EX_LAYOUTRTL : 0,
+ GetWindowClass(),
+ L"",
+ WS_CHILD,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ mWnd,
+ nullptr,
+ nsToolkit::mDllInstance,
+ nullptr);
+ case NS_NATIVE_PLUGIN_ID:
+ case NS_NATIVE_PLUGIN_PORT:
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_WINDOW:
+ return (void*)mWnd;
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ return (void*) WinUtils::GetTopLevelHWND(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;
+ }
+ MOZ_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;
+}
+
+static void
+SetChildStyleAndParent(HWND aChildWindow, HWND aParentWindow)
+{
+ // Make sure the window is styled to be a child window.
+ LONG_PTR style = GetWindowLongPtr(aChildWindow, GWL_STYLE);
+ style |= WS_CHILD;
+ style &= ~WS_POPUP;
+ SetWindowLongPtr(aChildWindow, GWL_STYLE, style);
+
+ // Do the reparenting. Note that this call will probably cause a sync native
+ // message to the process that owns the child window.
+ ::SetParent(aChildWindow, aParentWindow);
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ switch (aDataType) {
+ case NS_NATIVE_CHILD_WINDOW:
+ SetChildStyleAndParent(reinterpret_cast<HWND>(aVal), mWnd);
+ break;
+ case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW:
+ SetChildStyleAndParent(reinterpret_cast<HWND>(aVal),
+ WinUtils::GetTopLevelHWND(mWnd));
+ break;
+ default:
+ NS_ERROR("SetNativeData called with unsupported data type.");
+ }
+}
+
+// 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:
+ case NS_NATIVE_PLUGIN_PORT:
+ break;
+ default:
+ break;
+ }
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::SetTitle
+ *
+ * Set the main windows title text.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP 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.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP nsWindow::SetIcon(const nsAString& aIconSpec)
+{
+ // Assume the given string is a local identifier for an icon file.
+
+ nsCOMPtr<nsIFile> iconFile;
+ ResolveIconName(aIconSpec, NS_LITERAL_STRING(".ico"),
+ getter_AddRefs(iconFile));
+ if (!iconFile)
+ return NS_OK; // not an error if icon is not found
+
+ 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) {
+ HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)bigIcon);
+ if (icon)
+ ::DestroyIcon(icon);
+ mIconBig = 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) {
+ HICON icon = (HICON) ::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)smallIcon);
+ if (icon)
+ ::DestroyIcon(icon);
+ mIconSmall = 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
+ return NS_OK;
+}
+
+/**************************************************************
+ *
+ * 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);
+}
+
+LayoutDeviceIntSize
+nsWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize)
+{
+ if (mWindowType == eWindowType_popup && !IsPopupWithTitleBar())
+ return aClientSize;
+
+ // just use (200, 200) as the position
+ RECT r;
+ r.left = 200;
+ r.top = 200;
+ r.right = 200 + aClientSize.width;
+ r.bottom = 200 + aClientSize.height;
+ ::AdjustWindowRectEx(&r, WindowStyle(), false, WindowExStyle());
+
+ return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::EnableDragDrop
+ *
+ * Enables/Disables drag and drop of files on this widget.
+ *
+ **************************************************************/
+
+void
+nsWindow::EnableDragDrop(bool aEnable)
+{
+ NS_ASSERTION(mWnd, "nsWindow::EnableDragDrop() called after Destroy()");
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aEnable) {
+ if (!mNativeDragTarget) {
+ mNativeDragTarget = new nsNativeDragTarget(this);
+ mNativeDragTarget->AddRef();
+ if (SUCCEEDED(::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget,
+ TRUE, FALSE))) {
+ ::RegisterDragDrop(mWnd, (LPDROPTARGET)mNativeDragTarget);
+ }
+ }
+ } else {
+ if (mWnd && mNativeDragTarget) {
+ ::RevokeDragDrop(mWnd);
+ ::CoLockObjectExternal((LPUNKNOWN)mNativeDragTarget, FALSE, TRUE);
+ 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(nsIRollupListener* aListener, bool aDoCapture)
+{
+ if (aDoCapture) {
+ gRollupListener = aListener;
+ if (!sMsgFilterHook && !sCallProcHook && !sCallMouseHook) {
+ RegisterSpecialDropdownHooks();
+ }
+ sProcessHook = true;
+ } else {
+ gRollupListener = nullptr;
+ 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.
+NS_IMETHODIMP
+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::GetLayerManager
+ *
+ * Get the layer manager associated with this widget.
+ *
+ **************************************************************/
+
+LayerManager*
+nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ RECT windowRect;
+ ::GetClientRect(mWnd, &windowRect);
+
+ // Try OMTC first.
+ if (!mLayerManager && ShouldUseOffMainThreadCompositing()) {
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+
+ // e10s uses the parameter to pass in the shadow manager from the TabChild
+ // so we don't expect to see it there since this doesn't support e10s.
+ NS_ASSERTION(aShadowManager == nullptr, "Async Compositor not supported with e10s");
+ CreateCompositor();
+ }
+
+ if (!mLayerManager) {
+ 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.
+ CompositorWidgetInitData initData(
+ reinterpret_cast<uintptr_t>(mWnd),
+ reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)),
+ mTransparencyMode);
+ mBasicLayersSurface = new InProcessWinCompositorWidget(initData, this);
+ mCompositorWidgetDelegate = mBasicLayersSurface;
+ mLayerManager = CreateBasicLayerManager();
+ }
+
+ NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager.");
+
+ return mLayerManager;
+}
+
+/**************************************************************
+ *
+ * SECTION: nsIWidget::OnDefaultButtonLoaded
+ *
+ * Called after the dialog is loaded and it has a default button.
+ *
+ **************************************************************/
+
+NS_IMETHODIMP
+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;
+}
+
+void
+nsWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries)
+{
+ RefPtr<LayerManager> layerManager = GetLayerManager();
+ if (!layerManager) {
+ return;
+ }
+
+ nsIntRegion clearRegion;
+ if (!HasGlass() || !nsUXThemeData::CheckForCompositor()) {
+ // Make sure and clear old regions we've set previously. Note HasGlass can be false
+ // for glass desktops if the window we are rendering to doesn't make use of glass
+ // (e.g. fullscreen browsing).
+ layerManager->SetRegionToClear(clearRegion);
+ return;
+ }
+
+ // On Win10, force show the top border:
+ if (IsWin10OrLater() && mCustomNonClient && mSizeMode == nsSizeMode_Normal) {
+ RECT rect;
+ ::GetWindowRect(mWnd, &rect);
+ // We want 1 pixel of border for every whole 100% of scaling
+ double borderSize = std::min(1, RoundDown(GetDesktopToDeviceScale().scale));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(0, 0, rect.right - rect.left, borderSize));
+ }
+
+ if (!IsWin10OrLater()) {
+ for (size_t i = 0; i < aThemeGeometries.Length(); i++) {
+ if (aThemeGeometries[i].mType == nsNativeThemeWin::eThemeGeometryTypeWindowButtons) {
+ LayoutDeviceIntRect bounds = aThemeGeometries[i].mRect;
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height() - 2.0));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X() + 1.0, bounds.YMost() - 2.0, bounds.Width() - 1.0, 1.0));
+ clearRegion.Or(clearRegion, gfx::IntRect::Truncate(bounds.X() + 2.0, bounds.YMost() - 1.0, bounds.Width() - 3.0, 1.0));
+ }
+ }
+ }
+
+ layerManager->SetRegionToClear(clearRegion);
+}
+
+uint32_t
+nsWindow::GetMaxTouchPoints() const
+{
+ return WinUtils::GetMaxTouchPoints();
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** 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(messageTime, 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.
+NS_IMETHODIMP 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);
+ 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());
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event)
+{
+ nsEventStatus status;
+ DispatchEvent(event, status);
+ return ConvertStatus(status);
+}
+
+bool nsWindow::DispatchWindowEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus)
+{
+ DispatchEvent(event, aStatus);
+ return ConvertStatus(aStatus);
+}
+
+// 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()
+{
+ if (mPainting) {
+ NS_WARNING("We were asked to dispatch pending events during painting, "
+ "denying since that's unsafe.");
+ return;
+ }
+
+ // 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);
+ }
+}
+
+bool nsWindow::DispatchPluginEvent(UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam,
+ bool aDispatchPendingEvents)
+{
+ bool ret = nsWindowBase::DispatchPluginEvent(
+ WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd));
+ if (aDispatchPendingEvents && !Destroyed()) {
+ DispatchPendingEvents();
+ }
+ return ret;
+}
+
+// Deal with all sort of mouse event
+bool
+nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
+ LPARAM lParam, bool aIsContextMenuKey,
+ int16_t aButton, uint16_t aInputSource,
+ uint16_t aPointerId)
+{
+ 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) && (sLastMouseMovePoint.y == mpScreen.y)) {
+ return result;
+ }
+ sLastMouseMovePoint.x = mpScreen.x;
+ sLastMouseMovePoint.y = mpScreen.y;
+ }
+
+ if (WinUtils::GetIsMouseFromTouch(aEventMessage)) {
+ if (aEventMessage == eMouseDown) {
+ Telemetry::Accumulate(Telemetry::FX_TOUCH_USED, 1);
+ }
+
+ if (mTouchWindow) {
+ // If mTouchWindow is true, then we must have APZ enabled and be
+ // feeding it raw touch events. In that case we don't need to
+ // send touch-generated mouse events to content. The only exception is
+ // the touch-generated mouse double-click, which is used to start off the
+ // touch-based drag-and-drop gesture.
+ MOZ_ASSERT(mAPZC);
+ if (aEventMessage == eMouseDoubleClick) {
+ aEventMessage = eMouseTouchDrag;
+ } else {
+ return result;
+ }
+ }
+ }
+
+ // Since it is unclear whether a user will use the digitizer,
+ // Postpone initialization until first PEN message will be found.
+ if (nsIDOMMouseEvent::MOZ_SOURCE_PEN == aInputSource
+ // Messages should be only at topLevel window.
+ && nsWindowType::eWindowType_toplevel == mWindowType
+ // Currently this scheme is used only when pointer events is enabled.
+ && gfxPrefs::PointerEventsEnabled()) {
+ InkCollector::sInkCollector->SetTarget(mWnd);
+ InkCollector::sInkCollector->SetPointerId(aPointerId);
+ }
+
+ 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);
+ event.button = aButton;
+ event.inputSource = aInputSource;
+ event.pointerId = aPointerId;
+ // If we get here the mouse events must be from non-touch sources, so
+ // convert it to pointer events as well
+ event.convertToPointer = true;
+
+ bool insideMovementThreshold = (DeprecatedAbs(sLastMousePoint.x - eventPoint.x) < (short)::GetSystemMetrics(SM_CXDOUBLECLK)) &&
+ (DeprecatedAbs(sLastMousePoint.y - eventPoint.y) < (short)::GetSystemMetrics(SM_CYDOUBLECLK));
+
+ BYTE eventButton;
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ eventButton = VK_LBUTTON;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ eventButton = VK_MBUTTON;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ 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.button = 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 =
+ IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::eTopLevel :
+ WidgetMouseEvent::eChild;
+ 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
+
+ NPEvent pluginEvent;
+
+ switch (aEventMessage) {
+ case eMouseDown:
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONDOWN;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONDOWN;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ pluginEvent.event = WM_RBUTTONDOWN;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseUp:
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONUP;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONUP;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ pluginEvent.event = WM_RBUTTONUP;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseDoubleClick:
+ switch (aButton) {
+ case WidgetMouseEvent::eLeftButton:
+ pluginEvent.event = WM_LBUTTONDBLCLK;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ pluginEvent.event = WM_MBUTTONDBLCLK;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ pluginEvent.event = WM_RBUTTONDBLCLK;
+ break;
+ default:
+ break;
+ }
+ break;
+ case eMouseMove:
+ pluginEvent.event = WM_MOUSEMOVE;
+ break;
+ case eMouseExitFromWidget:
+ pluginEvent.event = WM_MOUSELEAVE;
+ break;
+ default:
+ pluginEvent.event = WM_NULL;
+ break;
+ }
+
+ pluginEvent.wParam = wParam; // plugins NEED raw OS event flags!
+ pluginEvent.lParam = lParam;
+
+ event.mPluginEvent.Copy(pluginEvent);
+
+ // call the event callback
+ if (mWidgetListener) {
+ if (aEventMessage == eMouseMove) {
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.x = 0;
+ rect.y = 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,
+ WidgetMouseEvent::eLeftButton,
+ aInputSource, aPointerId);
+ }
+ sCurrentWindow = this;
+ if (!mInDtor) {
+ LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam));
+ sCurrentWindow->DispatchMouseEvent(eMouseEnterIntoWidget,
+ wParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ aInputSource, aPointerId);
+ }
+ }
+ }
+ } else if (aEventMessage == eMouseExitFromWidget) {
+ sMouseExitwParam = wParam;
+ sMouseExitlParamScreen = lParamToScreen(lParam);
+ if (sCurrentWindow == this) {
+ sCurrentWindow = nullptr;
+ }
+ }
+
+ result = ConvertStatus(DispatchInputEvent(&event));
+
+ // Release the widget with NS_IF_RELEASE() just in case
+ // the context menu key code in EventListenerManager::HandleEvent()
+ // released it already.
+ return result;
+ }
+
+ return result;
+}
+
+void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate)
+{
+ if (aIsActivate)
+ sJustGotActivate = false;
+ sJustGotDeactivate = false;
+
+ // retrive the toplevel window or dialog
+ HWND curWnd = mWnd;
+ HWND toplevelWnd = nullptr;
+ while (curWnd) {
+ toplevelWnd = curWnd;
+
+ nsWindow *win = WinUtils::GetNSWindowPtr(curWnd);
+ if (win) {
+ nsWindowType wintype = win->WindowType();
+ if (wintype == eWindowType_toplevel || wintype == eWindowType_dialog)
+ break;
+ }
+
+ curWnd = ::GetParent(curWnd); // Parent or owner (if has no parent)
+ }
+
+ if (toplevelWnd) {
+ nsWindow *win = WinUtils::GetNSWindowPtr(toplevelWnd);
+ if (win && win->mWidgetListener) {
+ if (aIsActivate) {
+ win->mWidgetListener->WindowActivated();
+ } else {
+ if (!win->BlurEventsSuppressed()) {
+ win->mWidgetListener->WindowDeactivated();
+ }
+ }
+ }
+ }
+}
+
+bool nsWindow::IsTopLevelMouseExit(HWND aWnd)
+{
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+ HWND mouseWnd = ::WindowFromPoint(mp);
+
+ // 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;
+}
+
+bool nsWindow::BlurEventsSuppressed()
+{
+ // are they suppressed in this window?
+ if (mBlurSuppressLevel > 0)
+ return true;
+
+ // are they suppressed by any container widget?
+ HWND parentWnd = ::GetParent(mWnd);
+ if (parentWnd) {
+ nsWindow *parent = WinUtils::GetNSWindowPtr(parentWnd);
+ if (parent)
+ return parent->BlurEventsSuppressed();
+ }
+ return false;
+}
+
+// In some circumstances (opening dependent windows) it makes more sense
+// (and fixes a crash bug) to not blur the parent window. Called from
+// nsFilePicker.
+void nsWindow::SuppressBlurEvents(bool aSuppress)
+{
+ if (aSuppress)
+ ++mBlurSuppressLevel; // for this widget
+ else {
+ NS_ASSERTION(mBlurSuppressLevel > 0, "unbalanced blur event suppression");
+ if (mBlurSuppressLevel > 0)
+ --mBlurSuppressLevel;
+ }
+}
+
+bool nsWindow::ConvertStatus(nsEventStatus aStatus)
+{
+ return aStatus == nsEventStatus_eConsumeNoDefault;
+}
+
+/**************************************************************
+ *
+ * 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;
+ }
+ 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;
+}
+
+inline static mozilla::HangMonitor::ActivityType ActivityTypeForMessage(UINT msg)
+{
+ if ((msg >= WM_KEYFIRST && msg <= WM_IME_KEYLAST) ||
+ (msg >= WM_MOUSEFIRST && msg <= WM_MOUSELAST) ||
+ (msg >= MOZ_WM_MOUSEWHEEL_FIRST && msg <= MOZ_WM_MOUSEWHEEL_LAST) ||
+ (msg >= NS_WM_IMEFIRST && msg <= NS_WM_IMELAST)) {
+ return mozilla::HangMonitor::kUIActivity;
+ }
+
+ // This may not actually be right, but we don't want to reset the timer if
+ // we're not actually processing a UI message.
+ return mozilla::HangMonitor::kActivityUIAVail;
+}
+
+// The WndProc procedure for all nsWindows in this toolkit. This merely catches
+// 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();
+
+ HangMonitor::NotifyActivity(ActivityTypeForMessage(msg));
+
+ 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;
+}
+
+// The main windows message processing method for plugins.
+// The result means whether this method processed the native
+// event for plugin. If false, the native event should be
+// processed by the caller self.
+bool
+nsWindow::ProcessMessageForPlugin(const MSG &aMsg,
+ MSGResult& aResult)
+{
+ aResult.mResult = 0;
+ aResult.mConsumed = true;
+
+ bool eventDispatched = false;
+ switch (aMsg.message) {
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ aResult.mResult = ProcessCharMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ aResult.mResult = ProcessKeyUpMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ aResult.mResult = ProcessKeyDownMessage(aMsg, &eventDispatched);
+ break;
+
+ case WM_DEADCHAR:
+ case WM_SYSDEADCHAR:
+
+ case WM_CUT:
+ case WM_COPY:
+ case WM_PASTE:
+ case WM_CLEAR:
+ case WM_UNDO:
+ break;
+
+ default:
+ return false;
+ }
+
+ if (!eventDispatched) {
+ aResult.mConsumed = nsWindowBase::DispatchPluginEvent(aMsg);
+ }
+ if (!Destroyed()) {
+ DispatchPendingEvents();
+ }
+ return true;
+}
+
+static void ForceFontUpdate()
+{
+ // update device context font cache
+ // Dirty but easiest way:
+ // Changing nsIPrefBranch entry which triggers callbacks
+ // and flows into calling mDeviceContext->FlushFontCache()
+ // to update the font cache in all the instance of Browsers
+ static const char kPrefName[] = "font.internaluseonly.changed";
+ bool fontInternalChange =
+ Preferences::GetBool(kPrefName, false);
+ Preferences::SetBool(kPrefName, !fontInternalChange);
+}
+
+static bool CleartypeSettingChanged()
+{
+ static int currentQuality = -1;
+ BYTE quality = cairo_win32_get_system_text_quality();
+
+ if (currentQuality == quality)
+ return false;
+
+ if (currentQuality < 0) {
+ currentQuality = quality;
+ return false;
+ }
+ currentQuality = quality;
+ return true;
+}
+
+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;
+ }
+
+ if (PluginHasFocus()) {
+ MSG nativeMsg = WinUtils::InitMSG(aMessage, aWParam, aLParam, mWnd);
+ if (ProcessMessageForPlugin(nativeMsg, aResult)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// The main windows message processing method.
+bool
+nsWindow::ProcessMessage(UINT msg, WPARAM& wParam, LPARAM& lParam,
+ LRESULT *aRetValue)
+{
+#if defined(EVENT_DEBUG_OUTPUT)
+ // First param shows all events, second param indicates whether
+ // to show mouse move events. See nsWindowDbg for details.
+ PrintEvent(msg, SHOW_REPEAT_EVENTS, SHOW_MOUSEMOVE_EVENTS);
+#endif
+
+ MSGResult msgResult(aRetValue);
+ if (ExternalHandlerProcessMessage(msg, wParam, lParam, msgResult)) {
+ return (msgResult.mConsumed || !mWnd);
+ }
+
+ bool result = false; // call the default nsWindow proc
+ *aRetValue = 0;
+
+ // Glass hit testing w/custom transparent margins
+ LRESULT dwmHitResult;
+ if (mCustomNonClient &&
+ nsUXThemeData::CheckForCompositor() &&
+ /* We don't do this for win10 glass with a custom titlebar,
+ * in order to avoid the caption buttons breaking. */
+ !(IsWin10OrLater() && HasGlass()) &&
+ WinUtils::dwmDwmDefWindowProcPtr(mWnd, msg, wParam, lParam, &dwmHitResult)) {
+ *aRetValue = dwmHitResult;
+ return true;
+ }
+
+ // (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:
+ if (sCanQuit == TRI_UNKNOWN)
+ {
+ // Ask if it's ok to quit, and store the answer until we
+ // get WM_ENDSESSION signaling the round is complete.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ nsCOMPtr<nsISupportsPRBool> cancelQuit =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+ cancelQuit->SetData(false);
+ obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr);
+
+ bool abortQuit;
+ cancelQuit->GetData(&abortQuit);
+ sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE;
+ }
+ *aRetValue = sCanQuit ? TRUE : FALSE;
+ result = true;
+ break;
+
+ case WM_ENDSESSION:
+ case MOZ_WM_APP_QUIT:
+ if (msg == MOZ_WM_APP_QUIT || (wParam == TRUE && sCanQuit == TRI_TRUE))
+ {
+ // 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();
+ NS_NAMED_LITERAL_STRING(context, "shutdown-persist");
+ NS_NAMED_LITERAL_STRING(syncShutdown, "syncShutdown");
+ obsServ->NotifyObservers(nullptr, "quit-application-granted", syncShutdown.get());
+ obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr);
+ obsServ->NotifyObservers(nullptr, "quit-application", nullptr);
+ obsServ->NotifyObservers(nullptr, "profile-change-net-teardown", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-change-teardown", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context.get());
+ obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry", context.get());
+ // Then a controlled but very quick exit.
+ _exit(0);
+ }
+ sCanQuit = TRI_UNKNOWN;
+ result = true;
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ OnSysColorChanged();
+ break;
+
+ case WM_THEMECHANGED:
+ {
+ // Update non-client margin offsets
+ UpdateNonClientMargins();
+ nsUXThemeData::InitTitlebarInfo();
+ nsUXThemeData::UpdateNativeThemeInfo();
+
+ NotifyThemeChanged();
+
+ // 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 != eWindowType_invisible) {
+ break;
+ }
+
+ nsresult rv;
+ bool didChange = false;
+
+ // update the global font list
+ nsCOMPtr<nsIFontEnumerator> fontEnum = do_GetService("@mozilla.org/gfx/fontenumerator;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ fontEnum->UpdateFontList(&didChange);
+ ForceFontUpdate();
+ } //if (NS_SUCCEEDED(rv))
+ }
+ break;
+
+ case WM_SETTINGCHANGE:
+ {
+ if (IsWin10OrLater() && mWindowType == eWindowType_invisible && lParam) {
+ auto lParamString = reinterpret_cast<const wchar_t*>(lParam);
+ if (!wcscmp(lParamString, L"UserInteractionMode")) {
+ nsCOMPtr<nsIWindowsUIUtils> uiUtils(do_GetService("@mozilla.org/windows-ui-utils;1"));
+ if (uiUtils) {
+ uiUtils->UpdateTabletModeState();
+ }
+ }
+ }
+ }
+ break;
+
+ case WM_NCCALCSIZE:
+ {
+ 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));
+ double scale = WinUtils::IsPerMonitorDPIAware()
+ ? WinUtils::LogToPhysFactor(mWnd) / WinUtils::SystemScaleFactor()
+ : 1.0;
+ clientRect->top +=
+ NSToIntRound((mCaptionHeight - mNonClientOffset.top) * scale);
+ clientRect->left +=
+ NSToIntRound((mHorResizeMargin - mNonClientOffset.left) * scale);
+ clientRect->right -=
+ NSToIntRound((mHorResizeMargin - mNonClientOffset.right) * scale);
+ clientRect->bottom -=
+ NSToIntRound((mVertResizeMargin - mNonClientOffset.bottom) * scale);
+
+ result = true;
+ *aRetValue = 0;
+ }
+ break;
+ }
+
+ case WM_NCHITTEST:
+ {
+ if (mMouseTransparent) {
+ // Treat this window as transparent.
+ *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 && nsUXThemeData::CheckForCompositor()) ||
+ !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::DisableForcePresent() &&
+ gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ NS_DispatchToMainThread(NewRunnableMethod(this, &nsWindow::ForcePresent));
+ }
+
+ // let the dwm handle nc painting on glass
+ // Never allow native painting if we are on fullscreen
+ if(mSizeMode != nsSizeMode_Fullscreen &&
+ nsUXThemeData::CheckForCompositor())
+ 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:
+ {
+ /*
+ * Reset the non-client paint region so that it excludes the
+ * non-client areas we paint manually. Then call defwndproc
+ * to do the actual painting.
+ */
+
+ if (!mCustomNonClient)
+ break;
+
+ // let the dwm handle nc painting on glass
+ if(nsUXThemeData::CheckForCompositor())
+ break;
+
+ HRGN paintRgn = ExcludeNonClientFromPaintRegion((HRGN)wParam);
+ LRESULT res = CallWindowProcW(GetPrevWindowProc(), mWnd,
+ msg, (WPARAM)paintRgn, lParam);
+ if (paintRgn != (HRGN)wParam)
+ DeleteObject(paintRgn);
+ *aRetValue = res;
+ result = true;
+ }
+ 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.
+ OnDestroy();
+ result = true;
+ break;
+
+ case WM_PAINT:
+ if (CleartypeSettingChanged()) {
+ ForceFontUpdate();
+ gfxFontCache *fc = gfxFontCache::GetCache();
+ if (fc) {
+ fc->Flush();
+ }
+ }
+ *aRetValue = (int) OnPaint(nullptr, 0);
+ result = true;
+ break;
+
+ case WM_PRINTCLIENT:
+ result = OnPaint((HDC) wParam, 0);
+ 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 erase background if widget does
+ // not need auto-erasing
+ case WM_ERASEBKGND:
+ if (!AutoErase((HDC)wParam)) {
+ *aRetValue = 1;
+ result = true;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ {
+ 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.
+ LPARAM lParamScreen = lParamToScreen(lParam);
+ 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;
+ }
+
+ result = DispatchMouseEvent(eMouseMove, wParam, lParam,
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ if (userMovedMouse) {
+ DispatchPendingEvents();
+ }
+ }
+ break;
+
+ case WM_NCMOUSEMOVE:
+ // If we receive a mouse move event on non-client chrome, make sure and
+ // send an eMouseExitFromWidget event as well.
+ if (mMousePresent && !sIsInMouseCapture)
+ SendMessage(mWnd, WM_MOUSELEAVE, 0, 0);
+ break;
+
+ case WM_LBUTTONDOWN:
+ {
+ result = DispatchMouseEvent(eMouseDown, wParam, lParam,
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ {
+ result = DispatchMouseEvent(eMouseUp, wParam, lParam,
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ }
+ break;
+
+ case WM_MOUSELEAVE:
+ {
+ if (!mMousePresent)
+ break;
+ mMousePresent = false;
+
+ // 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,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ }
+ break;
+
+ case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER:
+ {
+ LPARAM pos = lParamToClient(::GetMessagePos());
+ uint16_t pointerId = InkCollector::sInkCollector->GetPointerId();
+ if (pointerId != 0) {
+ DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false,
+ WidgetMouseEvent::eLeftButton,
+ nsIDOMMouseEvent::MOZ_SOURCE_PEN, pointerId);
+ InkCollector::sInkCollector->ClearTarget();
+ InkCollector::sInkCollector->ClearPointerId();
+ }
+ }
+ 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() == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+ MOZ_ASSERT(mAPZC); // since mTouchWindow is true, APZ must be enabled
+ 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 ?
+ WidgetMouseEvent::eLeftButton :
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ 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, mSizeMode, mIsRTL, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ result = true;
+ }
+ }
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_MBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCMBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eMiddleButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_RBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, wParam,
+ lParam, false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDOWN:
+ result = DispatchMouseEvent(eMouseDown, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONUP:
+ result = DispatchMouseEvent(eMouseUp, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ break;
+
+ case WM_NCRBUTTONDBLCLK:
+ result = DispatchMouseEvent(eMouseDoubleClick, 0,
+ lParamToClient(lParam), false,
+ WidgetMouseEvent::eRightButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ 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:
+ {
+ // 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;
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "live-resize-start",
+ nullptr);
+ }
+ }
+ 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,
+ // which 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.
+ ChangedDPI();
+ ResetLayout();
+ }
+ break;
+
+ case WM_ENTERSIZEMOVE:
+ {
+ if (mResizeState == NOT_RESIZING) {
+ mResizeState = IN_SIZEMOVE;
+ }
+ break;
+ }
+
+ case WM_EXITSIZEMOVE:
+ {
+ FinishLiveResizing(NOT_RESIZING);
+
+ if (!sIsInMouseCapture) {
+ NotifySizeMoveDone();
+ }
+
+ break;
+ }
+
+ case WM_NCLBUTTONDBLCLK:
+ DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam),
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ result =
+ DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam),
+ false, WidgetMouseEvent::eLeftButton,
+ MOUSE_INPUT_SOURCE(), MOUSE_POINTERID());
+ DispatchPendingEvents();
+ 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:
+ if (mWidgetListener) {
+ int32_t fActive = LOWORD(wParam);
+
+ 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.
+ if (HIWORD(wParam))
+ DispatchFocusToTopLevelWindow(false);
+ else
+ sJustGotDeactivate = true;
+
+ if (mIsTopWidgetWindow)
+ mLastKeyboardLayout = KeyboardLayout::GetInstance()->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);
+ }
+ }
+ 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;
+
+ 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:
+ // 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);
+ }
+ break;
+
+ case WM_KILLFOCUS:
+ if (sJustGotDeactivate) {
+ DispatchFocusToTopLevelWindow(false);
+ }
+ break;
+
+ case WM_WINDOWPOSCHANGED:
+ {
+ WINDOWPOS* wp = (LPWINDOWPOS)lParam;
+ OnWindowPosChanged(wp);
+ 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
+ a11y::Accessible* rootAccessible = GetAccessible(); // Held by a11y cache
+ if (rootAccessible) {
+ IAccessible *msaaAccessible = nullptr;
+ rootAccessible->GetNativeInterface((void**)&msaaAccessible); // does an addref
+ if (msaaAccessible) {
+ *aRetValue = LresultFromObject(IID_IAccessible, wParam, msaaAccessible); // does an addref
+ msaaAccessible->Release(); // release extra addref
+ result = true; // We handled the WM_GETOBJECT message
+ }
+ }
+ }
+ }
+ break;
+#endif
+
+ case WM_SYSCOMMAND:
+ {
+ WPARAM filteredWParam = (wParam &0xFFF0);
+ // prevent Windows from trimming the working set. bug 76831
+ if (!sTrimOnMinimize && filteredWParam == SC_MINIMIZE) {
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ result = true;
+ }
+
+ if (mSizeMode == nsSizeMode_Fullscreen &&
+ filteredWParam == SC_RESTORE &&
+ GetCurrentShowCmd(mWnd) != SW_SHOWMINIMIZED) {
+ MakeFullScreen(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 &&
+ mSizeMode == nsSizeMode_Fullscreen) {
+ DisplaySystemMenu(mWnd, mSizeMode, mIsRTL,
+ MOZ_SYSCONTEXT_X_POS,
+ MOZ_SYSCONTEXT_Y_POS);
+ result = true;
+ }
+ }
+ break;
+
+ case WM_DWMCOMPOSITIONCHANGED:
+ // First, update the compositor state to latest one. All other methods
+ // should use same state as here for consistency painting.
+ nsUXThemeData::CheckForCompositor(true);
+
+ UpdateNonClientMargins();
+ BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED);
+ NotifyThemeChanged();
+ UpdateGlass();
+ Invalidate(true, true, true);
+ break;
+
+ case WM_DPICHANGED:
+ {
+ LPRECT rect = (LPRECT) lParam;
+ OnDPIChanged(rect->left, rect->top, rect->right - rect->left,
+ rect->bottom - rect->top);
+ break;
+ }
+
+ case WM_UPDATEUISTATE:
+ {
+ // If the UI state has changed, fire an event so the UI updates the
+ // keyboard cues based on the system setting and how the window was
+ // opened. For example, a dialog opened via a keyboard press on a button
+ // should enable cues, whereas the same dialog opened via a mouse click of
+ // the button should not.
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ int32_t action = LOWORD(wParam);
+ if (action == UIS_SET || action == UIS_CLEAR) {
+ int32_t flags = HIWORD(wParam);
+ UIStateChangeType showAccelerators = UIStateChangeType_NoChange;
+ UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
+ if (flags & UISF_HIDEACCEL)
+ showAccelerators = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set;
+ if (flags & UISF_HIDEFOCUS)
+ showFocusRings = (action == UIS_SET) ? UIStateChangeType_Clear : UIStateChangeType_Set;
+
+ NotifyUIStateChanged(showAccelerators, showFocusRings);
+ }
+ }
+
+ 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 != eWindowType_invisible &&
+ !IsPlugin()) {
+ // 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) {
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(nullptr, "live-resize-end", nullptr);
+ }
+ }
+ mResizeState = aNewState;
+ ForcePresent();
+}
+
+/**************************************************************
+ *
+ * SECTION: Broadcast messaging
+ *
+ * Broadcast messages to all windows.
+ *
+ **************************************************************/
+
+// Enumerate all child windows sending aMsg to each of them
+BOOL CALLBACK nsWindow::BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg)
+{
+ WNDPROC winProc = (WNDPROC)::GetWindowLongPtrW(aWnd, GWLP_WNDPROC);
+ if (winProc == &nsWindow::WindowProc) {
+ // it's one of our windows so go ahead and send a message to it
+ ::CallWindowProcW(winProc, aWnd, aMsg, 0, 0);
+ }
+ return TRUE;
+}
+
+// Enumerate all top level windows specifying that the children of each
+// top level window should be enumerated. Do *not* send the message to
+// each top level window since it is assumed that the toolkit will send
+// aMsg to them directly.
+BOOL CALLBACK nsWindow::BroadcastMsg(HWND aTopWindow, LPARAM aMsg)
+{
+ // Iterate each of aTopWindows child windows sending the aMsg
+ // to each of them.
+ ::EnumChildWindows(aTopWindow, nsWindow::BroadcastMsgToChildren, aMsg);
+ return TRUE;
+}
+
+/**************************************************************
+ *
+ * SECTION: Event processing helpers
+ *
+ * Special processing for certain event types and
+ * synthesized events.
+ *
+ **************************************************************/
+
+int32_t
+nsWindow::ClientMarginHitTestPoint(int32_t mx, int32_t my)
+{
+ if (mSizeMode == nsSizeMode_Minimized ||
+ mSizeMode == nsSizeMode_Fullscreen) {
+ return HTCLIENT;
+ }
+
+ // Calculations are done in screen coords
+ RECT winRect;
+ GetWindowRect(mWnd, &winRect);
+
+ // 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;
+
+ bool isResizable = (mBorderStyle & (eBorderStyle_all |
+ eBorderStyle_resizeh |
+ eBorderStyle_default)) > 0 ? true : false;
+ if (mSizeMode == nsSizeMode_Maximized)
+ isResizable = false;
+
+ // Ensure being accessible to borders of window. Even if contents are in
+ // this area, the area must behave as border.
+ nsIntMargin nonClientSize(std::max(mCaptionHeight - mNonClientOffset.top,
+ kResizableBorderMinSize),
+ std::max(mHorResizeMargin - mNonClientOffset.right,
+ kResizableBorderMinSize),
+ std::max(mVertResizeMargin - mNonClientOffset.bottom,
+ kResizableBorderMinSize),
+ std::max(mHorResizeMargin - mNonClientOffset.left,
+ kResizableBorderMinSize));
+
+ bool allowContentOverride = mSizeMode == nsSizeMode_Maximized ||
+ (mx >= winRect.left + nonClientSize.left &&
+ mx <= winRect.right - nonClientSize.right &&
+ my >= winRect.top + nonClientSize.top &&
+ my <= winRect.bottom - nonClientSize.bottom);
+
+ // 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.
+ nsIntMargin borderSize(std::max(nonClientSize.top, mVertResizeMargin),
+ std::max(nonClientSize.right, mHorResizeMargin),
+ std::max(nonClientSize.bottom, mVertResizeMargin),
+ std::max(nonClientSize.left, mHorResizeMargin));
+
+ bool top = false;
+ bool bottom = false;
+ bool left = false;
+ bool right = false;
+
+ if (my >= winRect.top && my < winRect.top + borderSize.top) {
+ top = true;
+ } else if (my <= winRect.bottom && my > winRect.bottom - borderSize.bottom) {
+ bottom = true;
+ }
+
+ // (the 2x case here doubles the resize area for corners)
+ int multiplier = (top || bottom) ? 2 : 1;
+ if (mx >= winRect.left &&
+ mx < winRect.left + (multiplier * borderSize.left)) {
+ left = true;
+ } else if (mx <= winRect.right &&
+ mx > winRect.right - (multiplier * borderSize.right)) {
+ right = true;
+ }
+
+ 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;
+ }
+ } else {
+ if (top)
+ testResult = HTCAPTION;
+ else if (bottom || left || right)
+ testResult = HTBORDER;
+ }
+
+ if (!sIsInMouseCapture && allowContentOverride) {
+ POINT pt = { mx, my };
+ ::ScreenToClient(mWnd, &pt);
+ if (pt.x == mCachedHitTestPoint.x && pt.y == mCachedHitTestPoint.y &&
+ TimeStamp::Now() - mCachedHitTestTime < TimeDuration::FromMilliseconds(HITTEST_CACHE_LIFETIME_MS)) {
+ return mCachedHitTestResult;
+ }
+ if (mDraggableRegion.Contains(pt.x, pt.y)) {
+ testResult = HTCAPTION;
+ } else {
+ testResult = HTCLIENT;
+ }
+ mCachedHitTestPoint = pt;
+ mCachedHitTestTime = TimeStamp::Now();
+ mCachedHitTestResult = testResult;
+ }
+
+ return testResult;
+}
+
+TimeStamp
+nsWindow::GetMessageTimeStamp(LONG aEventTime) const
+{
+ CurrentWindowsTimeGetter getCurrentTime(mWnd);
+ return TimeConverter().GetTimeStampFromSystemTime(aEventTime,
+ getCurrentTime);
+}
+
+void nsWindow::PostSleepWakeNotification(const bool aIsSleepMode)
+{
+ if (aIsSleepMode == gIsSleepMode)
+ return;
+
+ gIsSleepMode = 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);
+ return static_cast<LRESULT>(nativeKey.HandleKeyUpMessage(aEventDispatched));
+}
+
+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,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (aNativeMessage == 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};
+ }
+ ::SetCursorPos(aPoint.x, aPoint.y);
+
+ INPUT input;
+ memset(&input, 0, sizeof(input));
+
+ input.type = INPUT_MOUSE;
+ input.mi.dwFlags = aNativeMessage;
+ ::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);
+}
+
+/**************************************************************
+ *
+ * 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 == nullptr)
+ return;
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) {
+ 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
+
+ // Handle window size mode changes
+ if (wp->flags & SWP_FRAMECHANGED && mSizeMode != nsSizeMode_Fullscreen) {
+
+ // 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 (mSizeMode == nsSizeMode_Minimized && (wp->flags & SWP_NOACTIVATE))
+ return;
+
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(mWnd, &pl);
+
+ nsSizeMode previousSizeMode = mSizeMode;
+
+ // Windows has just changed the size mode of this window. The call to
+ // SizeModeChanged will trigger a call into SetSizeMode where we will
+ // set the min/max window state again or for nsSizeMode_Normal, call
+ // SetWindow with a parameter of SW_RESTORE. There's no need however as
+ // this window's mode has already changed. Updating mSizeMode here
+ // insures the SetSizeMode call is a no-op. Addresses a bug on Win7 related
+ // to window docking. (bug 489258)
+ if (pl.showCmd == SW_SHOWMAXIMIZED)
+ mSizeMode = (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized);
+ else if (pl.showCmd == SW_SHOWMINIMIZED)
+ mSizeMode = nsSizeMode_Minimized;
+ else if (mFullscreenMode)
+ mSizeMode = nsSizeMode_Fullscreen;
+ else
+ mSizeMode = nsSizeMode_Normal;
+
+ // If !sTrimOnMinimize, we minimize windows using SW_SHOWMINIMIZED (See
+ // SetSizeMode for internal calls, and WM_SYSCOMMAND for external). This
+ // prevents the working set from being trimmed but keeps the window active.
+ // After the window is minimized, we need to do some touch up work on the
+ // active window. (bugs 76831 & 499816)
+ if (!sTrimOnMinimize && nsSizeMode_Minimized == mSizeMode)
+ ActivateOtherWindowHelper(mWnd);
+
+#ifdef WINSTATE_DEBUG_OUTPUT
+ switch (mSizeMode) {
+ case nsSizeMode_Normal:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Normal\n"));
+ break;
+ case nsSizeMode_Minimized:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Minimized\n"));
+ break;
+ case nsSizeMode_Maximized:
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("*** mSizeMode: nsSizeMode_Maximized\n"));
+ break;
+ default:
+ MOZ_LOG(gWindowsLog, LogLevel::Info, ("*** mSizeMode: ??????\n"));
+ break;
+ }
+#endif
+
+ if (mWidgetListener && mSizeMode != previousSizeMode)
+ mWidgetListener->SizeModeChanged(mSizeMode);
+
+ // If window was restored, window activation was bypassed during the
+ // SetSizeMode call originating from OnWindowPosChanging to avoid saving
+ // pre-restore attributes. Force activation now to get correct attributes.
+ if (mLastSizeMode != nsSizeMode_Normal && mSizeMode == nsSizeMode_Normal)
+ DispatchFocusToTopLevelWindow(true);
+
+ mLastSizeMode = mSizeMode;
+
+ // Skip window size change events below on minimization.
+ if (mSizeMode == nsSizeMode_Minimized)
+ return;
+ }
+
+ // Handle window position changes
+ if (!(wp->flags & SWP_NOMOVE)) {
+ mBounds.x = wp->x;
+ mBounds.y = 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;
+ nsIntRect rect(wp->x, wp->y, newWidth, newHeight);
+
+ 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.width = newWidth;
+ mBounds.height = 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 a maximized window is resized, recalculate the non-client margins.
+ if (mSizeMode == nsSizeMode_Maximized) {
+ if (UpdateNonClientMargins(nsSizeMode_Maximized, true)) {
+ // gecko resize event already sent by UpdateNonClientMargins.
+ return;
+ }
+ }
+
+ // Recalculate the width and height based on the client area for gecko events.
+ if (::GetClientRect(mWnd, &r)) {
+ rect.width = r.right - r.left;
+ rect.height = r.bottom - r.top;
+ }
+
+ // Send a gecko resize event
+ OnResize(rect);
+ }
+}
+
+// static
+void nsWindow::ActivateOtherWindowHelper(HWND aWnd)
+{
+ // Find the next window that is enabled, visible, and not minimized.
+ HWND hwndBelow = ::GetNextWindow(aWnd, GW_HWNDNEXT);
+ while (hwndBelow && (!::IsWindowEnabled(hwndBelow) || !::IsWindowVisible(hwndBelow) ||
+ ::IsIconic(hwndBelow))) {
+ hwndBelow = ::GetNextWindow(hwndBelow, GW_HWNDNEXT);
+ }
+
+ // Push ourselves to the bottom of the stack, then activate the
+ // next window.
+ ::SetWindowPos(aWnd, HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
+ if (hwndBelow)
+ ::SetForegroundWindow(hwndBelow);
+
+ // Play the minimize sound while we're here, since that is also
+ // forgotten when we use SW_SHOWMINIMIZED.
+ nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
+ if (sound) {
+ sound->PlaySystemSound(NS_LITERAL_STRING("Minimize"));
+ }
+}
+
+void nsWindow::OnWindowPosChanging(LPWINDOWPOS& 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)) &&
+ mSizeMode != nsSizeMode_Fullscreen) {
+ WINDOWPLACEMENT pl;
+ pl.length = sizeof(pl);
+ ::GetWindowPlacement(mWnd, &pl);
+ nsSizeMode sizeMode;
+ if (pl.showCmd == SW_SHOWMAXIMIZED)
+ sizeMode = (mFullscreenMode ? nsSizeMode_Fullscreen : nsSizeMode_Maximized);
+ else if (pl.showCmd == SW_SHOWMINIMIZED)
+ sizeMode = nsSizeMode_Minimized;
+ else if (mFullscreenMode)
+ sizeMode = nsSizeMode_Fullscreen;
+ else
+ sizeMode = nsSizeMode_Normal;
+
+ if (mWidgetListener)
+ mWidgetListener->SizeModeChanged(sizeMode);
+
+ UpdateNonClientMargins(sizeMode, false);
+ }
+
+ // 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 == eWindowType_invisible)
+ 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/idleservice;1");
+ }
+
+ // Check that we now have the idle service.
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam)
+{
+ uint32_t cInputs = LOWORD(wParam);
+ PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
+
+ if (mGesture.GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs)) {
+ 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.mTime = ::GetMessageTime();
+ touchInput.mTimeStamp = GetMessageTimeStamp(touchInput.mTime);
+ 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.mTime = ::GetMessageTime();
+ touchEndInput.mTimeStamp = GetMessageTimeStamp(touchEndInput.mTime);
+ 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
+ /* radius, if known */
+ pInputs[i].dwFlags & TOUCHINPUTMASKF_CONTACTAREA
+ ? 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()) {
+ DispatchTouchInput(touchInput);
+ }
+ // Dispatch touch end event if we have one.
+ if (!touchEndInput.mTimeStamp.IsNull()) {
+ DispatchTouchInput(touchEndInput);
+ }
+ }
+
+ delete [] pInputs;
+ mGesture.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.button = 0;
+ wheelEvent.mTime = ::GetMessageTime();
+ wheelEvent.mTimeStamp = GetMessageTimeStamp(wheelEvent.mTime);
+ wheelEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+
+ bool endFeedback = true;
+
+ if (mGesture.PanDeltaToPixelScroll(wheelEvent)) {
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
+ (uint32_t) ScrollInputMethod::MainThreadTouch);
+ 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);
+ }
+
+ mGesture.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.button = 0;
+ event.mTime = ::GetMessageTime();
+ event.mTimeStamp = GetMessageTimeStamp(event.mTime);
+ event.inputSource = nsIDOMMouseEvent::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.
+ mGesture.CloseGestureInfoHandle((HGESTUREINFO)lParam);
+
+ return true; // Handled
+}
+
+nsresult
+nsWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ // If this is a remotely updated widget we receive clipping, position, and
+ // size information from a source other than our owner. Don't let our parent
+ // update this information.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ return NS_OK;
+ }
+
+ // XXXroc we could use BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos
+ // here, if that helps in some situations. So far I haven't seen a
+ // need.
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ nsWindow* w = static_cast<nsWindow*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this,
+ "Configured widget is not a child");
+ nsresult rv = w->SetWindowClipRegion(configuration.mClipRegion, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LayoutDeviceIntRect bounds = w->GetBounds();
+ if (bounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.x, configuration.mBounds.y,
+ configuration.mBounds.width, configuration.mBounds.height,
+ true);
+ } else if (bounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.x, configuration.mBounds.y);
+
+
+ if (gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() ||
+ GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_BASIC) {
+ // XXX - Workaround for Bug 587508. This will invalidate the part of the
+ // plugin window that might be touched by moving content somehow. The
+ // underlying problem should be found and fixed!
+ LayoutDeviceIntRegion r;
+ r.Sub(bounds, configuration.mBounds);
+ r.MoveBy(-bounds.x,
+ -bounds.y);
+ LayoutDeviceIntRect toInvalidate = r.GetBounds();
+
+ WinUtils::InvalidatePluginAsWorkaround(w, toInvalidate);
+ }
+ }
+ rv = w->SetWindowClipRegion(configuration.mClipRegion, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+static HRGN
+CreateHRGNFromArray(const nsTArray<LayoutDeviceIntRect>& aRects)
+{
+ int32_t size = sizeof(RGNDATAHEADER) + sizeof(RECT)*aRects.Length();
+ AutoTArray<uint8_t,100> buf;
+ buf.SetLength(size);
+ RGNDATA* data = reinterpret_cast<RGNDATA*>(buf.Elements());
+ RECT* rects = reinterpret_cast<RECT*>(data->Buffer);
+ data->rdh.dwSize = sizeof(data->rdh);
+ data->rdh.iType = RDH_RECTANGLES;
+ data->rdh.nCount = aRects.Length();
+ LayoutDeviceIntRect bounds;
+ for (uint32_t i = 0; i < aRects.Length(); ++i) {
+ const LayoutDeviceIntRect& r = aRects[i];
+ bounds.UnionRect(bounds, r);
+ ::SetRect(&rects[i], r.x, r.y, r.XMost(), r.YMost());
+ }
+ ::SetRect(&data->rdh.rcBound, bounds.x, bounds.y, bounds.XMost(), bounds.YMost());
+ return ::ExtCreateRegion(nullptr, buf.Length(), data);
+}
+
+nsresult
+nsWindow::SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting)
+{
+ if (IsWindowClipRegionEqual(aRects)) {
+ return NS_OK;
+ }
+
+ nsBaseWidget::SetWindowClipRegion(aRects, aIntersectWithExisting);
+
+ HRGN dest = CreateHRGNFromArray(aRects);
+ if (!dest)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (aIntersectWithExisting) {
+ HRGN current = ::CreateRectRgn(0, 0, 0, 0);
+ if (current) {
+ if (::GetWindowRgn(mWnd, current) != 0 /*ERROR*/) {
+ ::CombineRgn(dest, dest, current, RGN_AND);
+ }
+ ::DeleteObject(current);
+ }
+ }
+
+ // If a plugin is not visible, especially if it is in a background tab,
+ // it should not be able to steal keyboard focus. This code checks whether
+ // the region that the plugin is being clipped to is NULLREGION. If it is,
+ // the plugin window gets disabled.
+ if (IsPlugin()) {
+ if (NULLREGION == ::CombineRgn(dest, dest, dest, RGN_OR)) {
+ ::ShowWindow(mWnd, SW_HIDE);
+ ::EnableWindow(mWnd, FALSE);
+ } else {
+ ::EnableWindow(mWnd, TRUE);
+ ::ShowWindow(mWnd, SW_SHOW);
+ }
+ }
+ if (!::SetWindowRgn(mWnd, dest, TRUE)) {
+ ::DeleteObject(dest);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// WM_DESTROY event handler
+void nsWindow::OnDestroy()
+{
+ mOnDestroyCalled = true;
+
+ // 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;
+
+ // Unregister notifications from terminal services
+ ::WTSUnRegisterSessionNotification(mWnd);
+
+ // Free our subclass and clear |this| stored in the window props. We will no longer
+ // receive events from Windows after this point.
+ SubclassWindow(FALSE);
+
+ // 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) {
+ if ( rollupListener )
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ CaptureRollupEvents(nullptr, false);
+ }
+
+ IMEHandler::OnDestroyWindow(this);
+
+ // Free GDI window class objects
+ if (mBrush) {
+ VERIFY(::DeleteObject(mBrush));
+ mBrush = nullptr;
+ }
+
+ // Destroy any custom cursor resources.
+ if (mCursor == -1)
+ SetCursor(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(nsIntRect &aWindowRect)
+{
+ bool result = mWidgetListener ?
+ mWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height) : false;
+
+ // If there is an attached view, inform it as well as the normal widget listener.
+ if (mAttachedWidgetListener) {
+ return mAttachedWidgetListener->WindowResized(this, aWindowRect.width, aWindowRect.height);
+ }
+
+ return result;
+}
+
+bool nsWindow::OnHotKey(WPARAM wParam, LPARAM lParam)
+{
+ return true;
+}
+
+// Can be overriden. Controls auto-erase of background.
+bool nsWindow::AutoErase(HDC dc)
+{
+ return false;
+}
+
+bool
+nsWindow::IsPopup()
+{
+ return mWindowType == eWindowType_popup;
+}
+
+bool
+nsWindow::ShouldUseOffMainThreadCompositing()
+{
+ // 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
+ if (mTransparencyMode == eTransparencyTransparent) {
+ 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");
+}
+
+bool
+nsWindow::HasBogusPopupsDropShadowOnMultiMonitor() {
+ if (sHasBogusPopupsDropShadowOnMultiMonitor == TRI_UNKNOWN) {
+ // Since any change in the preferences requires a restart, this can be
+ // done just once.
+ // Check for Direct2D first.
+ sHasBogusPopupsDropShadowOnMultiMonitor =
+ gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend() ? TRI_TRUE : TRI_FALSE;
+ if (!sHasBogusPopupsDropShadowOnMultiMonitor) {
+ // Otherwise check if Direct3D 9 may be used.
+ if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING) &&
+ !gfxConfig::IsEnabled(Feature::OPENGL_COMPOSITING))
+ {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ if (gfxInfo) {
+ int32_t status;
+ nsCString discardFailureId;
+ if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS,
+ discardFailureId, &status))) {
+ if (status == nsIGfxInfo::FEATURE_STATUS_OK ||
+ gfxConfig::IsForcedOnByUser(Feature::HW_COMPOSITING))
+ {
+ sHasBogusPopupsDropShadowOnMultiMonitor = TRI_TRUE;
+ }
+ }
+ }
+ }
+ }
+ }
+ return !!sHasBogusPopupsDropShadowOnMultiMonitor;
+}
+
+void
+nsWindow::OnSysColorChanged()
+{
+ if (mWindowType == eWindowType_invisible) {
+ ::EnumThreadWindows(GetCurrentThreadId(), nsWindow::BroadcastMsg, WM_SYSCOLORCHANGE);
+ }
+ else {
+ // Note: This is sent for child windows as well as top-level windows.
+ // The Win32 toolkit normally only sends these events to top-level windows.
+ // But we cycle through all of the childwindows and send it to them as well
+ // so all presentations get notified properly.
+ // See nsWindow::GlobalMsgWindowProc.
+ NotifySysColorChanged();
+ }
+}
+
+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 == eWindowType_popup) {
+ return;
+ }
+ if (DefaultScaleOverride() > 0.0) {
+ return;
+ }
+ double oldScale = mDefaultScale;
+ mDefaultScale = -1.0; // force recomputation of scale factor
+ double newScale = GetDefaultScaleInternal();
+
+ if (mResizeState != RESIZING && mSizeMode == 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);
+ }
+ ChangedDPI();
+ ResetLayout();
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: IME management and accessibility
+ **
+ ** Handles managing IME input and accessibility.
+ **
+ **************************************************************
+ **************************************************************/
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ InputContext newInputContext = aContext;
+ IMEHandler::SetInputContext(this, newInputContext, aAction);
+ mInputContext = newInputContext;
+}
+
+NS_IMETHODIMP_(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;
+}
+
+nsIMEUpdatePreference
+nsWindow::GetIMEUpdatePreference()
+{
+ return IMEHandler::GetUpdatePreference();
+}
+
+NS_IMETHODIMP_(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::Accessible*
+nsWindow::GetAccessible()
+{
+ // If the pref was ePlatformIsDisabled, return null here, disabling a11y.
+ if (a11y::PlatformDisabledState() == a11y::ePlatformIsDisabled)
+ return nullptr;
+
+ if (mInDtor || mOnDestroyCalled || mWindowType == eWindowType_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->PresContext()->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.
+ **
+ **************************************************************
+ **************************************************************/
+
+#ifdef MOZ_XUL
+
+void nsWindow::SetWindowTranslucencyInner(nsTransparencyMode 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 == eTransparencyTransparent) {
+ // If we're switching to the use of a transparent window, hide the chrome
+ // on our parent.
+ HideWindowChrome(true);
+ } else if (mHideChrome && mTransparencyMode == eTransparencyTransparent) {
+ // 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->mSizeMode == nsSizeMode_Maximized)
+ style |= WS_MAXIMIZE;
+ else if (parent->mSizeMode == nsSizeMode_Minimized)
+ style |= WS_MINIMIZE;
+
+ if (aMode == eTransparencyTransparent)
+ exStyle |= WS_EX_LAYERED;
+ else
+ exStyle &= ~WS_EX_LAYERED;
+
+ VERIFY_WINDOW_STYLE(style);
+ ::SetWindowLongPtrW(hWnd, GWL_STYLE, style);
+ ::SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exStyle);
+
+ if (HasGlass())
+ memset(&mGlassMargins, 0, sizeof mGlassMargins);
+ mTransparencyMode = aMode;
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->UpdateTransparency(aMode);
+ }
+ UpdateGlass();
+}
+
+#endif //MOZ_XUL
+
+/**************************************************************
+ **************************************************************
+ **
+ ** 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) {
+ // If this window is windowed plugin window, the mouse events are not
+ // sent to us.
+ if (static_cast<nsWindow*>(mozWin)->IsPlugin())
+ ScheduleHookTimer(ms->hwnd, (UINT)wParam);
+ } else {
+ 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
+ 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;
+ }
+}
+
+BOOL CALLBACK nsWindow::ClearResourcesCallback(HWND aWnd, LPARAM aMsg)
+{
+ nsWindow *window = WinUtils::GetNSWindowPtr(aWnd);
+ if (window) {
+ window->ClearCachedResources();
+ }
+ return TRUE;
+}
+
+void
+nsWindow::ClearCachedResources()
+{
+ if (mLayerManager &&
+ mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ mLayerManager->ClearCachedResources();
+ }
+ ::EnumChildWindows(mWnd, nsWindow::ClearResourcesCallback, 0);
+}
+
+static bool IsDifferentThreadWindow(HWND aWnd)
+{
+ return ::GetCurrentThreadId() != ::GetWindowThreadProcessId(aWnd, nullptr);
+}
+
+// static
+bool
+nsWindow::EventIsInsideWindow(nsWindow* aWindow)
+{
+ RECT r;
+ ::GetWindowRect(aWindow->mWnd, &r);
+ DWORD pos = ::GetMessagePos();
+ POINT mp;
+ mp.x = GET_X_LPARAM(pos);
+ mp.y = GET_Y_LPARAM(pos);
+
+ // was the event inside this window?
+ return static_cast<bool>(::PtInRect(&r, mp));
+}
+
+// static
+bool
+nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener,
+ uint32_t* aPopupsToRollup)
+{
+ // 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))) {
+ // 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
+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;
+ }
+
+ 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;
+
+ 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;
+ }
+ MOZ_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() == nsIDOMMouseEvent::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) &&
+ GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ break;
+ }
+ return false;
+
+ 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:
+ break;
+
+ case WM_ACTIVATE:
+ // 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()) {
+ // 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()) {
+ return false;
+ }
+ } 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.
+ if (activeWindow) {
+ if (activeWindow->IsPopup()) {
+ return false;
+ }
+ nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd);
+ if (deactiveWindow && deactiveWindow->IsPopup()) {
+ return false;
+ }
+ }
+ } 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.
+ if (EventIsInsideWindow(popupWindow) ||
+ !GetPopupsToRollup(rollupListener, &popupsToRollup)) {
+ return false;
+ }
+ }
+ break;
+
+ case MOZ_WM_REACTIVATE:
+ // The previous active window should take back focus.
+ if (::IsWindow(reinterpret_cast<HWND>(aLParam))) {
+ ::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) {
+ 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))) {
+ 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(!mLastRollup, "mLastRollup is null");
+
+ if (nativeMessage == WM_LBUTTONDOWN) {
+ POINT pt;
+ pt.x = GET_X_LPARAM(aLParam);
+ pt.y = GET_Y_LPARAM(aLParam);
+ ::ClientToScreen(aWnd, &pt);
+ nsIntPoint pos(pt.x, pt.y);
+
+ consumeRollupEvent =
+ rollupListener->Rollup(popupsToRollup, true, &pos, &mLastRollup);
+ NS_IF_ADDREF(mLastRollup);
+ } else {
+ consumeRollupEvent =
+ rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
+ }
+
+ // 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 eWindowType_dialog:
+ case eWindowType_popup:
+ return curWindow;
+ default:
+ break;
+ }
+ }
+
+ // Retrieve the top level parent or owner window
+ nsWindow* parentWindow = curWindow->GetParentWindow(true);
+
+ if (!parentWindow)
+ return curWindow;
+
+ curWindow = parentWindow;
+ }
+}
+
+static BOOL CALLBACK gEnumWindowsProc(HWND hwnd, LPARAM lParam)
+{
+ DWORD pid;
+ ::GetWindowThreadProcessId(hwnd, &pid);
+ if (pid == GetCurrentProcessId() && ::IsWindowVisible(hwnd))
+ {
+ gWindowsVisible = true;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool nsWindow::CanTakeFocus()
+{
+ gWindowsVisible = false;
+ EnumWindows(gEnumWindowsProc, 0);
+ if (!gWindowsVisible) {
+ return true;
+ } else {
+ HWND fgWnd = ::GetForegroundWindow();
+ if (!fgWnd) {
+ return true;
+ }
+ DWORD pid;
+ GetWindowThreadProcessId(fgWnd, &pid);
+ if (pid == GetCurrentProcessId()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ const wchar_t*
+nsWindow::GetMainWindowClass()
+{
+ static const wchar_t* sMainWindowClass = nullptr;
+ if (!sMainWindowClass) {
+ nsAdoptingString className =
+ Preferences::GetString("ui.window_class_override");
+ 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);
+}
+
+void nsWindow::PickerOpen()
+{
+ mPickerDisplayCount++;
+}
+
+void nsWindow::PickerClosed()
+{
+ NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
+ if (!mPickerDisplayCount)
+ return;
+ mPickerDisplayCount--;
+ 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.
+ //
+ // 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 != eTransparencyTransparent &&
+ !(IsPopup() && DeviceManagerDx::Get()->IsWARP());
+}
+
+void
+nsWindow::SetCandidateWindowForPlugin(const CandidateWindowPosition& aPosition)
+{
+ CANDIDATEFORM form;
+ form.dwIndex = 0;
+ if (aPosition.mExcludeRect) {
+ form.dwStyle = CFS_EXCLUDE;
+ form.rcArea.left = aPosition.mRect.x;
+ form.rcArea.top = aPosition.mRect.y;
+ form.rcArea.right = aPosition.mRect.x + aPosition.mRect.width;
+ form.rcArea.bottom = aPosition.mRect.y + aPosition.mRect.height;
+ } else {
+ form.dwStyle = CFS_CANDIDATEPOS;
+ }
+ form.ptCurrentPos.x = aPosition.mPoint.x;
+ form.ptCurrentPos.y = aPosition.mPoint.y;
+
+ IMEHandler::SetCandidateWindow(this, &form);
+}
+
+void
+nsWindow::DefaultProcOfPluginEvent(const WidgetPluginEvent& aEvent)
+{
+ const NPEvent* pPluginEvent =
+ static_cast<const NPEvent*>(aEvent.mPluginEvent);
+
+ if (NS_WARN_IF(!pPluginEvent)) {
+ return;
+ }
+
+ if (!mWnd) {
+ return;
+ }
+
+ // For WM_IME_*COMPOSITION
+ IMEHandler::DefaultProcOfPluginEvent(this, pPluginEvent);
+
+ CallWindowProcW(GetPrevWindowProc(), mWnd, pPluginEvent->event,
+ pPluginEvent->wParam, pPluginEvent->lParam);
+}
+
+nsresult
+nsWindow::OnWindowedPluginKeyEvent(const NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback)
+{
+ if (NS_WARN_IF(!mWnd)) {
+ return NS_OK;
+ }
+ const WinNativeKeyEventData* eventData =
+ static_cast<const WinNativeKeyEventData*>(aKeyEventData);
+ switch (eventData->mMessage) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN: {
+ MSG mozMsg =
+ WinUtils::InitMSG(MOZ_WM_KEYDOWN, eventData->mWParam,
+ eventData->mLParam, mWnd);
+ ModifierKeyState modifierKeyState(eventData->mModifiers);
+ NativeKey nativeKey(this, mozMsg, modifierKeyState,
+ eventData->GetKeyboardLayout());
+ return nativeKey.HandleKeyDownMessage() ? NS_SUCCESS_EVENT_CONSUMED :
+ NS_OK;
+ }
+ case WM_KEYUP:
+ case WM_SYSKEYUP: {
+ MSG mozMsg =
+ WinUtils::InitMSG(MOZ_WM_KEYUP, eventData->mWParam,
+ eventData->mLParam, mWnd);
+ ModifierKeyState modifierKeyState(eventData->mModifiers);
+ NativeKey nativeKey(this, mozMsg, modifierKeyState,
+ eventData->GetKeyboardLayout());
+ return nativeKey.HandleKeyUpMessage() ? NS_SUCCESS_EVENT_CONSUMED : NS_OK;
+ }
+ default:
+ // We shouldn't consume WM_*CHAR messages here even if the preceding
+ // keydown or keyup event on the plugin is consumed. It should be
+ // managed in each plugin window rather than top level window.
+ return NS_OK;
+ }
+}
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: ChildWindow impl.
+ **
+ ** Child window overrides.
+ **
+ **************************************************************
+ **************************************************************/
+
+// return the style for a child nsWindow
+DWORD ChildWindow::WindowStyle()
+{
+ DWORD style = WS_CLIPCHILDREN | nsWindow::WindowStyle();
+ if (!(style & WS_POPUP))
+ style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
+ VERIFY_WINDOW_STYLE(style);
+ return style;
+}
+
+void
+nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
+{
+ aInitData->hWnd() = reinterpret_cast<uintptr_t>(mWnd);
+ aInitData->widgetKey() = reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this));
+ aInitData->transparencyMode() = mTransparencyMode;
+}
diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
new file mode 100644
index 0000000000..248978bd79
--- /dev/null
+++ b/widget/windows/nsWindow.h
@@ -0,0 +1,671 @@
+/* -*- 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 Window_h__
+#define Window_h__
+
+/*
+ * nsWindow - Native window management and event handling.
+ */
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "nsWindowBase.h"
+#include "nsdefs.h"
+#include "nsIdleService.h"
+#include "nsToolkit.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "gfxWindowsPlatform.h"
+#include "gfxWindowsSurface.h"
+#include "nsWindowDbg.h"
+#include "cairo.h"
+#include "nsITimer.h"
+#include "nsRegion.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "nsMargin.h"
+#include "nsRegionFwd.h"
+
+#include "nsWinGesture.h"
+#include "WinUtils.h"
+#include "WindowHook.h"
+#include "TaskbarWindowPreview.h"
+
+#ifdef ACCESSIBILITY
+#include "oleacc.h"
+#include "mozilla/a11y/Accessible.h"
+#endif
+
+#include "nsUXThemeData.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIIdleServiceInternal.h"
+
+#include "IMMHandler.h"
+
+/**
+ * Forward class definitions
+ */
+
+class nsNativeDragTarget;
+class nsIRollupListener;
+class imgIContainer;
+
+namespace mozilla {
+namespace widget {
+class NativeKey;
+class WinCompositorWidget;
+struct MSGResult;
+} // namespace widget
+} // namespacw mozilla;
+
+/**
+ * Native WIN32 window wrapper.
+ */
+
+class nsWindow : public nsWindowBase
+{
+ typedef mozilla::TimeStamp TimeStamp;
+ typedef mozilla::TimeDuration TimeDuration;
+ typedef mozilla::widget::WindowHook WindowHook;
+ typedef mozilla::widget::TaskbarWindowPreview TaskbarWindowPreview;
+ typedef mozilla::widget::NativeKey NativeKey;
+ typedef mozilla::widget::MSGResult MSGResult;
+ typedef mozilla::widget::IMEContext IMEContext;
+
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ friend class nsWindowGfx;
+
+ // nsWindowBase
+ virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr) override;
+ virtual WidgetEventTime CurrentMessageWidgetEventTime() const override;
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) override;
+ virtual bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent) override;
+ virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) override;
+ virtual bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent) override;
+ virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) override;
+ virtual bool IsTopLevelWidget() override { return mIsTopWidgetWindow; }
+
+ using nsWindowBase::DispatchPluginEvent;
+
+ // nsIWidget interface
+ using nsWindowBase::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+ virtual void Destroy() override;
+ NS_IMETHOD SetParent(nsIWidget *aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+ double GetDefaultScaleInternal() final;
+ int32_t LogToPhys(double aValue) final;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final
+ {
+ if (mozilla::widget::WinUtils::IsPerMonitorDPIAware()) {
+ return mozilla::DesktopToLayoutDeviceScale(1.0);
+ } else {
+ return mozilla::DesktopToLayoutDeviceScale(GetDefaultScaleInternal());
+ }
+ }
+
+ NS_IMETHOD Show(bool bState) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ virtual const SizeConstraints GetSizeConstraints() override;
+ NS_IMETHOD Move(double aX, double aY) override;
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD BeginResizeDrag(mozilla::WidgetGUIEvent* aEvent,
+ int32_t aHorizontal,
+ int32_t aVertical) override;
+ virtual void PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, bool aActivate) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ NS_IMETHOD SetFocus(bool aRaise) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual MOZ_MUST_USE nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ void SetBackgroundColor(const nscolor& aColor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aScreen = nullptr) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+ NS_IMETHOD Invalidate(bool aEraseBackground = false,
+ bool aUpdateNCArea = false,
+ bool aIncludeChildren = false);
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect);
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ virtual void FreeNativeData(void * data, uint32_t aDataType) override;
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+ NS_IMETHOD SetIcon(const nsAString& aIconSpec) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual LayoutDeviceIntSize ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) override;
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ virtual void EnableDragDrop(bool aEnable) override;
+ virtual void CaptureMouse(bool aCapture) override;
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) override;
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+ NS_IMETHOD OnDefaultButtonLoaded(const LayoutDeviceIntRect& aButtonRect) override;
+ virtual nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver) override;
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override
+ { return SynthesizeNativeMouseEvent(aPoint, MOUSEEVENTF_MOVE, 0, aObserver); }
+
+ virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver) override;
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+#ifdef MOZ_XUL
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void UpdateOpaqueRegion(const LayoutDeviceIntRegion& aOpaqueRegion) override;
+#endif // MOZ_XUL
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+ NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override;
+ void SetDrawsInTitlebar(bool aState) override;
+ virtual void UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) override;
+
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+ virtual uint32_t GetMaxTouchPoints() const override;
+
+ /**
+ * Event helpers
+ */
+ virtual bool DispatchMouseEvent(
+ mozilla::EventMessage aEventMessage,
+ WPARAM wParam,
+ LPARAM lParam,
+ bool aIsContextMenuKey = false,
+ int16_t aButton =
+ mozilla::WidgetMouseEvent::eLeftButton,
+ uint16_t aInputSource =
+ nsIDOMMouseEvent::MOZ_SOURCE_MOUSE,
+ uint16_t aPointerId = 0);
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus);
+ void DispatchPendingEvents();
+ bool DispatchPluginEvent(UINT aMessage,
+ WPARAM aWParam,
+ LPARAM aLParam,
+ bool aDispatchPendingEvents);
+
+ void SuppressBlurEvents(bool aSuppress); // Called from nsFilePicker
+ bool BlurEventsSuppressed();
+#ifdef ACCESSIBILITY
+ /**
+ * Return an accessible associated with the window.
+ */
+ mozilla::a11y::Accessible* GetAccessible();
+#endif // ACCESSIBILITY
+
+ /**
+ * Window utilities
+ */
+ nsWindow* GetTopLevelWindow(bool aStopOnDialogOrPopup);
+ WNDPROC GetPrevWindowProc() { return mPrevWndProc; }
+ WindowHook& GetWindowHook() { return mWindowHook; }
+ nsWindow* GetParentWindow(bool aIncludeOwner);
+ // Get an array of all nsWindow*s on the main thread.
+ typedef void (WindowEnumCallback)(nsWindow*);
+ static void EnumAllWindows(WindowEnumCallback aCallback);
+
+ /**
+ * Misc.
+ */
+ virtual bool AutoErase(HDC dc);
+ bool WidgetTypeSupportsAcceleration() override;
+
+ void ForcePresent();
+
+ /**
+ * 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); }
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ // Open file picker tracking
+ void PickerOpen();
+ void PickerClosed();
+
+ bool const DestroyCalled() { return mDestroyCalled; }
+
+ bool IsPopup();
+ virtual bool ShouldUseOffMainThreadCompositing() override;
+
+ const IMEContext& DefaultIMC() const { return mDefaultIMC; }
+
+ virtual void SetCandidateWindowForPlugin(
+ const mozilla::widget::CandidateWindowPosition&
+ aPosition) override;
+ virtual void DefaultProcOfPluginEvent(
+ const mozilla::WidgetPluginEvent& aEvent) override;
+ virtual nsresult OnWindowedPluginKeyEvent(
+ const mozilla::NativeEventData& aKeyEventData,
+ nsIKeyEventInPluginCallback* aCallback) override;
+
+ void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override;
+ bool IsTouchWindow() const { return mTouchWindow; }
+
+protected:
+ virtual ~nsWindow();
+
+ virtual void WindowUsesOMTC() override;
+ virtual void RegisterTouchWindow() override;
+
+ // 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
+ };
+
+ /**
+ * 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 BroadcastMsgToChildren(HWND aWnd, LPARAM aMsg);
+ static BOOL CALLBACK BroadcastMsg(HWND aTopWindow, LPARAM aMsg);
+ 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 );
+ static BOOL CALLBACK ClearResourcesCallback(HWND aChild, LPARAM aParam);
+ static BOOL CALLBACK EnumAllChildWindProc(HWND aWnd, LPARAM aParam);
+ static BOOL CALLBACK EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam);
+
+ /**
+ * Window utilities
+ */
+ LPARAM lParamToScreen(LPARAM lParam);
+ LPARAM lParamToClient(LPARAM lParam);
+ virtual void SubclassWindow(BOOL bState);
+ bool CanTakeFocus();
+ bool UpdateNonClientMargins(int32_t aSizeMode = -1, bool aReflowWindow = true);
+ void UpdateGetWindowInfoCaptionStatus(bool aActiveCaption);
+ void ResetLayout();
+ void InvalidateNonClientRegion();
+ HRGN ExcludeNonClientFromPaintRegion(HRGN aRegion);
+ static const wchar_t* GetMainWindowClass();
+ bool HasGlass() const {
+ return mTransparencyMode == eTransparencyGlass ||
+ mTransparencyMode == eTransparencyBorderlessGlass;
+ }
+ HWND GetOwnerWnd() const
+ {
+ return ::GetWindow(mWnd, GW_OWNER);
+ }
+ bool IsOwnerForegroundWindow() const
+ {
+ HWND owner = GetOwnerWnd();
+ return owner && owner == ::GetForegroundWindow();
+ }
+ bool IsPopup() const
+ {
+ return mWindowType == eWindowType_popup;
+ }
+
+
+ /**
+ * Event processing helpers
+ */
+ void DispatchFocusToTopLevelWindow(bool aIsActivate);
+ bool DispatchStandardEvent(mozilla::EventMessage aMsg);
+ void RelayMouseEvent(UINT aMsg, WPARAM wParam, LPARAM lParam);
+ virtual bool ProcessMessage(UINT msg, WPARAM &wParam,
+ LPARAM &lParam, LRESULT *aRetValue);
+ bool ExternalHandlerProcessMessage(
+ UINT aMessage, WPARAM& aWParam,
+ LPARAM& aLParam, MSGResult& aResult);
+ bool ProcessMessageForPlugin(const MSG &aMsg,
+ 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);
+ // Convert nsEventStatus value to a windows boolean
+ static bool ConvertStatus(nsEventStatus aStatus);
+ static void PostSleepWakeNotification(const bool aIsSleepMode);
+ int32_t ClientMarginHitTestPoint(int32_t mx, int32_t my);
+ TimeStamp GetMessageTimeStamp(LONG aEventTime) const;
+ static void UpdateFirstEventTime(DWORD aEventTime);
+ void FinishLiveResizing(ResizeState aNewState);
+
+ /**
+ * Event handlers
+ */
+ virtual void OnDestroy() override;
+ virtual bool OnResize(nsIntRect &aWindowRect);
+ bool OnGesture(WPARAM wParam, LPARAM lParam);
+ bool OnTouch(WPARAM wParam, LPARAM lParam);
+ bool OnHotKey(WPARAM wParam, LPARAM lParam);
+ bool OnPaint(HDC aDC, uint32_t aNestingLevel);
+ void OnWindowPosChanged(WINDOWPOS* wp);
+ void OnWindowPosChanging(LPWINDOWPOS& info);
+ void OnSysColorChanged();
+ void OnDPIChanged(int32_t x, int32_t y,
+ int32_t width, int32_t height);
+
+ /**
+ * 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);
+ const wchar_t* GetWindowClass() const;
+ const wchar_t* GetWindowPopupClass() const;
+ virtual DWORD WindowStyle();
+ DWORD WindowExStyle();
+
+ // This method registers the given window class, and returns the class name.
+ const wchar_t* RegisterWindowClass(const wchar_t* aClassName,
+ UINT aExtraStyle,
+ LPWSTR aIconID) const;
+
+ /**
+ * XP and Vista theming support for windows with rounded edges
+ */
+ void ClearThemeRegion();
+ void SetThemeRegion();
+
+ /**
+ * Popup hooks
+ */
+ static void ScheduleHookTimer(HWND aWnd, UINT aMsgId);
+ static void RegisterSpecialDropdownHooks();
+ static void UnregisterSpecialDropdownHooks();
+ static bool GetPopupsToRollup(nsIRollupListener* aRollupListener,
+ uint32_t* aPopupsToRollup);
+ static bool NeedsToHandleNCActivateDelayed(HWND aWnd);
+ static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, LPARAM inLParam, LRESULT* outResult);
+
+ /**
+ * Window transparency helpers
+ */
+#ifdef MOZ_XUL
+private:
+ void SetWindowTranslucencyInner(nsTransparencyMode aMode);
+ nsTransparencyMode GetWindowTranslucencyInner() const { return mTransparencyMode; }
+ void UpdateGlass();
+protected:
+#endif // MOZ_XUL
+
+ static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult);
+ void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam);
+
+ /**
+ * Misc.
+ */
+ void StopFlashing();
+ static bool IsTopLevelMouseExit(HWND aWnd);
+ virtual nsresult SetWindowClipRegion(const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ nsIntRegion GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC);
+ static void ActivateOtherWindowHelper(HWND aWnd);
+ void ClearCachedResources();
+ nsIWidgetListener* GetPaintListener();
+
+ already_AddRefed<SourceSurface> CreateScrollSnapshot() override;
+
+ struct ScrollSnapshot
+ {
+ RefPtr<gfxWindowsSurface> surface;
+ bool surfaceHasSnapshot = false;
+ RECT clip;
+ };
+
+ ScrollSnapshot* EnsureSnapshotSurface(ScrollSnapshot& aSnapshotData,
+ const mozilla::gfx::IntSize& aSize);
+
+ ScrollSnapshot mFullSnapshot;
+ ScrollSnapshot mPartialSnapshot;
+ ScrollSnapshot* mCurrentSnapshot = nullptr;
+
+ already_AddRefed<SourceSurface>
+ GetFallbackScrollSnapshot(const RECT& aRequiredClip);
+
+protected:
+ nsCOMPtr<nsIWidget> mParent;
+ nsIntSize mLastSize;
+ nsIntPoint mLastPoint;
+ HWND mWnd;
+ HWND mTransitionWnd;
+ WNDPROC mPrevWndProc;
+ HBRUSH mBrush;
+ IMEContext mDefaultIMC;
+ bool mIsTopWidgetWindow;
+ bool mInDtor;
+ bool mIsVisible;
+ bool mUnicodeWidget;
+ bool mPainting;
+ bool mTouchWindow;
+ bool mDisplayPanFeedback;
+ bool mHideChrome;
+ bool mIsRTL;
+ bool mFullscreenMode;
+ bool mMousePresent;
+ bool mDestroyCalled;
+ uint32_t mBlurSuppressLevel;
+ DWORD_PTR mOldStyle;
+ DWORD_PTR mOldExStyle;
+ nsNativeDragTarget* mNativeDragTarget;
+ HKL mLastKeyboardLayout;
+ nsSizeMode mOldSizeMode;
+ nsSizeMode mLastSizeMode;
+ WindowHook mWindowHook;
+ uint32_t mPickerDisplayCount;
+ HICON mIconSmall;
+ HICON mIconBig;
+ static bool sDropShadowEnabled;
+ static uint32_t sInstanceCount;
+ static TriStateBool sCanQuit;
+ static nsWindow* sCurrentWindow;
+ static BOOL sIsOleInitialized;
+ static HCURSOR sHCursor;
+ static imgIContainer* sCursorImgContainer;
+ static bool sSwitchKeyboardLayout;
+ static bool sJustGotDeactivate;
+ static bool sJustGotActivate;
+ static bool sIsInMouseCapture;
+ static int sTrimOnMinimize;
+
+ // Always use the helper method to read this property. See bug 603793.
+ static TriStateBool sHasBogusPopupsDropShadowOnMultiMonitor;
+ static bool HasBogusPopupsDropShadowOnMultiMonitor();
+
+ // 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;
+
+ // Indicates custom frames are enabled
+ bool mCustomNonClient;
+ // Cached copy of L&F's resize border
+ int32_t mHorResizeMargin;
+ int32_t mVertResizeMargin;
+ // Height of the caption plus border
+ int32_t mCaptionHeight;
+
+ double mDefaultScale;
+
+ nsCOMPtr<nsIIdleServiceInternal> mIdleService;
+
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+
+ // Hook Data Memebers 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;
+
+ // Mouse Clicks - static variable definitions for figuring
+ // out 1 - 3 Clicks.
+ static POINT sLastMousePoint;
+ static POINT sLastMouseMovePoint;
+ static LONG sLastMouseDownTime;
+ static LONG sLastClickCount;
+ static BYTE sLastMouseButton;
+
+ // Graphics
+ HDC mPaintDC; // only set during painting
+
+ LayoutDeviceIntRect mLastPaintBounds;
+
+ ResizeState mResizeState;
+
+ // Transparency
+#ifdef MOZ_XUL
+ nsTransparencyMode mTransparencyMode;
+ nsIntRegion mPossiblyTransparentRegion;
+ MARGINS mGlassMargins;
+#endif // MOZ_XUL
+
+ // Win7 Gesture processing and management
+ nsWinGesture mGesture;
+
+ // Weak ref to the nsITaskbarWindowPreview associated with this window
+ nsWeakPtr mTaskbarPreview;
+ // True if the taskbar (possibly through the tab preview) tells us that the
+ // icon has been created on the taskbar.
+ bool mHasTaskbarIconBeenCreated;
+
+ // Indicates that mouse events should be ignored and pass through to the
+ // window below. This is currently only used for popups.
+ bool mMouseTransparent;
+
+ // Whether we're in the process of sending a WM_SETTEXT ourselves
+ bool mSendingSetText;
+
+ // 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
+ POINT mCachedHitTestPoint;
+ TimeStamp mCachedHitTestTime;
+ int32_t mCachedHitTestResult;
+
+ RefPtr<mozilla::widget::WinCompositorWidget> mBasicLayersSurface;
+
+ static bool sNeedsToInitMouseWheelSettings;
+ static void InitMouseWheelScrollData();
+
+ double mSizeConstraintsScale; // scale in effect when setting constraints
+
+ // Used to remember the wParam (i.e. currently pressed modifier keys)
+ // and lParam (i.e. last mouse position) in screen coordinates from
+ // the previous mouse-exit. Static since it is not
+ // associated with a particular widget (since we exited the widget).
+ static WPARAM sMouseExitwParam;
+ static LPARAM sMouseExitlParamScreen;
+};
+
+/**
+ * A child window is a window with different style.
+ */
+class ChildWindow : public nsWindow {
+
+public:
+ ChildWindow() {}
+
+protected:
+ virtual DWORD WindowStyle();
+};
+
+#endif // Window_h__
diff --git a/widget/windows/nsWindowBase.cpp b/widget/windows/nsWindowBase.cpp
new file mode 100644
index 0000000000..a374b36359
--- /dev/null
+++ b/widget/windows/nsWindowBase.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "nsWindowBase.h"
+
+#include "mozilla/MiscEvents.h"
+#include "KeyboardLayout.h"
+#include "WinUtils.h"
+#include "npapi.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static const wchar_t kUser32LibName[] = L"user32.dll";
+bool nsWindowBase::sTouchInjectInitialized = false;
+InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr;
+
+bool
+nsWindowBase::DispatchPluginEvent(const MSG& aMsg)
+{
+ if (!ShouldDispatchPluginEvent()) {
+ return false;
+ }
+ WidgetPluginEvent pluginEvent(true, ePluginInputEvent, this);
+ LayoutDeviceIntPoint point(0, 0);
+ InitEvent(pluginEvent, &point);
+ NPEvent npEvent;
+ npEvent.event = aMsg.message;
+ npEvent.wParam = aMsg.wParam;
+ npEvent.lParam = aMsg.lParam;
+ pluginEvent.mPluginEvent.Copy(npEvent);
+ pluginEvent.mRetargetToFocusedDocument = true;
+ return DispatchWindowEvent(&pluginEvent);
+}
+
+bool
+nsWindowBase::ShouldDispatchPluginEvent()
+{
+ return PluginHasFocus();
+}
+
+// static
+bool
+nsWindowBase::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
+nsWindowBase::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;
+ memset(&info, 0, sizeof(POINTER_TOUCH_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;
+
+ if (!sInjectTouchFuncPtr(1, &info)) {
+ WinUtils::Log("InjectTouchInput failure. GetLastError=%d", GetLastError());
+ return false;
+ }
+ return true;
+}
+
+void nsWindowBase::ChangedDPI()
+{
+ if (mWidgetListener) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+ mWidgetListener->UIResolutionChanged();
+ }
+}
+
+nsresult
+nsWindowBase::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ nsIWidget::TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (gfxPrefs::APZTestFailsWithNativeInjection() || !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.mTime, time.mTimeStamp,
+ aPointerId, aPointerState, pointInWindow, aPointerPressure,
+ aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+ }
+
+ bool hover = aPointerState & TOUCH_HOVER;
+ bool contact = aPointerState & TOUCH_CONTACT;
+ bool remove = aPointerState & TOUCH_REMOVE;
+ bool cancel = aPointerState & TOUCH_CANCEL;
+
+ // 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
+ PointerInfo* info = mActivePointers.Get(aPointerId);
+
+ // We know about this pointer, send an update
+ if (info) {
+ POINTER_FLAGS 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;
+ // Remove the pointer from our tracking list. This is nsAutPtr wrapped,
+ // so shouldn't leak.
+ mActivePointers.Remove(aPointerId);
+ }
+
+ if (cancel) {
+ flags |= POINTER_FLAG_CANCELED;
+ }
+
+ return !InjectTouchPoint(aPointerId, aPoint, flags,
+ pressure, aPointerOrientation) ?
+ NS_ERROR_UNEXPECTED : NS_OK;
+ }
+
+ // Missing init state, error out
+ if (remove || cancel) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Create a new pointer
+ info = new PointerInfo(aPointerId, aPoint);
+
+ POINTER_FLAGS flags = POINTER_FLAG_INRANGE;
+ if (contact) {
+ flags |= POINTER_FLAG_INCONTACT|POINTER_FLAG_DOWN;
+ }
+
+ mActivePointers.Put(aPointerId, info);
+ return !InjectTouchPoint(aPointerId, aPoint, flags,
+ pressure, aPointerOrientation) ?
+ NS_ERROR_UNEXPECTED : NS_OK;
+}
+
+nsresult
+nsWindowBase::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()) {
+ nsAutoPtr<PointerInfo>& info = iter.Data();
+ InjectTouchPoint(info.get()->mPointerId, info.get()->mPosition,
+ POINTER_FLAG_CANCELED);
+ iter.Remove();
+ }
+
+ nsBaseWidget::ClearNativeTouchSequence(nullptr);
+
+ return NS_OK;
+}
+
+bool
+nsWindowBase::HandleAppCommandMsg(const MSG& aAppCommandMsg,
+ LRESULT *aRetValue)
+{
+ ModifierKeyState modKeyState;
+ NativeKey nativeKey(this, aAppCommandMsg, modKeyState);
+ bool consumed = nativeKey.HandleAppCommandMessage();
+ *aRetValue = consumed ? 1 : 0;
+ return consumed;
+}
diff --git a/widget/windows/nsWindowBase.h b/widget/windows/nsWindowBase.h
new file mode 100644
index 0000000000..405f5b359e
--- /dev/null
+++ b/widget/windows/nsWindowBase.h
@@ -0,0 +1,145 @@
+/* -*- 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 nsWindowBase_h_
+#define nsWindowBase_h_
+
+#include "mozilla/EventForwards.h"
+#include "nsBaseWidget.h"
+#include "nsClassHashtable.h"
+
+#include <windows.h>
+#include "touchinjection_sdk80.h"
+
+/*
+ * nsWindowBase - Base class of common methods other classes need to access
+ * in both win32 and winrt window classes.
+ */
+class nsWindowBase : public nsBaseWidget
+{
+public:
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+
+ /*
+ * Return the HWND or null for this widget.
+ */
+ virtual HWND GetWindowHandle() final {
+ return static_cast<HWND>(GetNativeData(NS_NATIVE_WINDOW));
+ }
+
+ /*
+ * Return the parent window, if it exists.
+ */
+ virtual nsWindowBase* GetParentWindowBase(bool aIncludeOwner) = 0;
+
+ /*
+ * Return true if this is a top level widget.
+ */
+ virtual bool IsTopLevelWidget() = 0;
+
+ /*
+ * Init a standard gecko event for this widget.
+ * @param aEvent the event to initialize.
+ * @param aPoint message position in physical coordinates.
+ */
+ virtual void InitEvent(mozilla::WidgetGUIEvent& aEvent,
+ LayoutDeviceIntPoint* aPoint = nullptr) = 0;
+
+ /*
+ * Returns WidgetEventTime instance which is initialized with current message
+ * time.
+ */
+ virtual WidgetEventTime CurrentMessageWidgetEventTime() const = 0;
+
+ /*
+ * Dispatch a gecko event for this widget.
+ * Returns true if it's consumed. Otherwise, false.
+ */
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent) = 0;
+
+ /*
+ * 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.
+ */
+ virtual bool DispatchKeyboardEvent(mozilla::WidgetKeyboardEvent* aEvent) = 0;
+
+ /*
+ * 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.
+ */
+ virtual bool DispatchWheelEvent(mozilla::WidgetWheelEvent* aEvent) = 0;
+
+ /*
+ * 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.
+ */
+ virtual bool DispatchContentCommandEvent(mozilla::WidgetContentCommandEvent* aEvent) = 0;
+
+ /*
+ * Default dispatch of a plugin event.
+ */
+ virtual bool DispatchPluginEvent(const MSG& aMsg);
+
+ /*
+ * Returns true if this should dispatch a plugin event.
+ */
+ bool ShouldDispatchPluginEvent();
+
+ /*
+ * Touch input injection apis
+ */
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+ virtual nsresult ClearNativeTouchSequence(nsIObserver* aObserver) override;
+
+ /*
+ * WM_APPCOMMAND common handler.
+ * Sends events via NativeKey::HandleAppCommandMessage().
+ */
+ virtual bool HandleAppCommandMsg(const MSG& aAppCommandMsg,
+ LRESULT *aRetValue);
+
+protected:
+ virtual int32_t LogToPhys(double aValue) = 0;
+ void ChangedDPI();
+
+ static bool InitTouchInjection();
+ bool InjectTouchPoint(uint32_t aId, LayoutDeviceIntPoint& aPoint,
+ POINTER_FLAGS aFlags, uint32_t aPressure = 1024,
+ uint32_t aOrientation = 90);
+
+ class PointerInfo
+ {
+ public:
+ PointerInfo(int32_t aPointerId, LayoutDeviceIntPoint& aPoint) :
+ mPointerId(aPointerId),
+ mPosition(aPoint)
+ {
+ }
+
+ int32_t mPointerId;
+ LayoutDeviceIntPoint mPosition;
+ };
+
+ nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers;
+ static bool sTouchInjectInitialized;
+ static InjectTouchInputPtr sInjectTouchFuncPtr;
+
+ // 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;
+protected:
+ InputContext mInputContext;
+};
+
+#endif // nsWindowBase_h_
diff --git a/widget/windows/nsWindowDbg.cpp b/widget/windows/nsWindowDbg.cpp
new file mode 100644
index 0000000000..d5bfcddd9f
--- /dev/null
+++ b/widget/windows/nsWindowDbg.cpp
@@ -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/. */
+
+/*
+ * nsWindowDbg - Debug related utilities for nsWindow.
+ */
+
+#include "mozilla/Logging.h"
+#include "nsWindowDbg.h"
+#include "WinUtils.h"
+
+using namespace mozilla::widget;
+
+extern mozilla::LazyLogModule gWindowsLog;
+
+#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 long gLastEventMsg = 0;
+
+void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves)
+{
+ int inx = 0;
+ while (gAllEvents[inx].mId != msg && gAllEvents[inx].mStr != nullptr) {
+ inx++;
+ }
+ if (aShowAllEvents || (!aShowAllEvents && gLastEventMsg != (long)msg)) {
+ if (aShowMouseMoves || (!aShowMouseMoves && msg != 0x0020 && msg != 0x0200 && msg != 0x0084)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Info,
+ ("%6d - 0x%04X %s\n", gEventCounter++, msg,
+ gAllEvents[inx].mStr ? gAllEvents[inx].mStr : "Unknown"));
+ gLastEventMsg = msg;
+ }
+ }
+}
+
+#ifdef DEBUG
+void DDError(const char *msg, HRESULT hr)
+{
+ /*XXX make nicer */
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ ("direct draw 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..6607699527
--- /dev/null
+++ b/widget/windows/nsWindowDbg.h
@@ -0,0 +1,61 @@
+/* -*- 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"
+
+// Enabled main event loop debug event output
+//#define EVENT_DEBUG_OUTPUT
+
+// 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
+
+// Main event loop debug output flags
+#if defined(EVENT_DEBUG_OUTPUT)
+#define SHOW_REPEAT_EVENTS true
+#define SHOW_MOUSEMOVE_EVENTS false
+#endif // defined(EVENT_DEBUG_OUTPUT)
+
+void PrintEvent(UINT msg, bool aShowAllEvents, bool aShowMouseMoves);
+
+#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..48a9356d28
--- /dev/null
+++ b/widget/windows/nsWindowDefs.h
@@ -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/. */
+
+#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: enums
+ *
+ **************************************************************/
+
+// nsWindow::sCanQuit
+typedef enum
+{
+ TRI_UNKNOWN = -1,
+ TRI_FALSE = 0,
+ TRI_TRUE = 1
+} TriStateBool;
+
+/**************************************************************
+ *
+ * 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.
+ */
+const uint32_t kMaxClassNameLength = 40;
+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 kClassNameTemp[] = L"MozillaTempWindowClass";
+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)
+ {
+ }
+};
+
+#if (WINVER < 0x0600)
+struct TITLEBARINFOEX
+{
+ DWORD cbSize;
+ RECT rcTitleBar;
+ DWORD rgstate[CCHILDREN_TITLEBAR + 1];
+ RECT rgrect[CCHILDREN_TITLEBAR + 1];
+};
+#endif
+
+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;
+
+ 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..a88631f89e
--- /dev/null
+++ b/widget/windows/nsWindowGfx.cpp
@@ -0,0 +1,686 @@
+/* -*- 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 "mozilla/plugins/PluginInstanceParent.h"
+using mozilla::plugins::PluginInstanceParent;
+
+#include "nsWindowGfx.h"
+#include "nsAppRunner.h"
+#include <windows.h>
+#include "gfxEnv.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "gfxWindowsSurface.h"
+#include "gfxWindowsPlatform.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 "prmem.h"
+#include "WinUtils.h"
+#include "nsIWidgetListener.h"
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+#include "nsIXULRuntime.h"
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "ClientLayerManager.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;
+
+/**************************************************************
+ **************************************************************
+ **
+ ** BLOCK: Variables
+ **
+ ** nsWindow Class static initializations and global variables.
+ **
+ **************************************************************
+ **************************************************************/
+
+/**************************************************************
+ *
+ * SECTION: nsWindow statics
+ *
+ **************************************************************/
+
+static UniquePtr<uint8_t[]> sSharedSurfaceData;
+static IntSize sSharedSurfaceSize;
+
+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
+nsIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint,
+ PAINTSTRUCT ps, HDC aDC)
+{
+ if (aForceFullRepaint) {
+ RECT paintRect;
+ ::GetClientRect(mWnd, &paintRect);
+ return nsIntRegion(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);
+ }
+ nsIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn));
+ ::DeleteObject(paintRgn);
+ return rgn;
+ }
+ return nsIntRegion(WinUtils::ToIntRect(ps.rcPaint));
+}
+
+#define WORDSSIZE(x) ((x).width * (x).height)
+static bool
+EnsureSharedSurfaceSize(IntSize size)
+{
+ IntSize screenSize;
+ screenSize.height = GetSystemMetrics(SM_CYSCREEN);
+ screenSize.width = GetSystemMetrics(SM_CXSCREEN);
+
+ if (WORDSSIZE(screenSize) > WORDSSIZE(size))
+ size = screenSize;
+
+ if (WORDSSIZE(screenSize) < WORDSSIZE(size))
+ NS_WARNING("Trying to create a shared surface larger than the screen");
+
+ if (!sSharedSurfaceData || (WORDSSIZE(size) > WORDSSIZE(sSharedSurfaceSize))) {
+ sSharedSurfaceSize = size;
+ sSharedSurfaceData =
+ MakeUniqueFallible<uint8_t[]>(WORDSSIZE(sSharedSurfaceSize) * 4);
+ }
+
+ return !sSharedSurfaceData;
+}
+
+nsIWidgetListener* nsWindow::GetPaintListener()
+{
+ if (mDestroyCalled)
+ return nullptr;
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+void nsWindow::ForcePresent()
+{
+ if (mResizeState != RESIZING) {
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ remoteRenderer->SendForcePresent();
+ }
+ }
+}
+
+bool nsWindow::OnPaint(HDC aDC, uint32_t aNestingLevel)
+{
+ // We never have reentrant paint events, except when we're running our RPC
+ // windows event spin loop. If we don't trap for this, we'll try to paint,
+ // but view manager will refuse to paint the surface, resulting is black
+ // flashes on the plugin rendering surface.
+ if (mozilla::ipc::MessageChannel::IsSpinLoopActive() && mPainting)
+ return false;
+
+ DeviceResetReason resetReason = DeviceResetReason::OK;
+ if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset(&resetReason)) {
+
+ gfxCriticalNote << "(nsWindow) Detected device reset: " << (int)resetReason;
+
+ gfxWindowsPlatform::GetPlatform()->UpdateRenderMode();
+ EnumAllWindows([] (nsWindow* aWindow) -> void {
+ aWindow->OnRenderingDeviceReset();
+ });
+
+ gfxCriticalNote << "(nsWindow) Finished device reset.";
+
+ return false;
+ }
+
+ // After we CallUpdateWindow to the child, occasionally a WM_PAINT message
+ // is posted to the parent event loop with an empty update rect. Do a
+ // dummy paint so that Windows stops dispatching WM_PAINT in an inifinite
+ // loop. See bug 543788.
+ if (IsPlugin()) {
+ RECT updateRect;
+ if (!GetUpdateRect(mWnd, &updateRect, FALSE) ||
+ (updateRect.left == updateRect.right &&
+ updateRect.top == updateRect.bottom)) {
+ PAINTSTRUCT ps;
+ BeginPaint(mWnd, &ps);
+ EndPaint(mWnd, &ps);
+ return true;
+ }
+
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ // Fire off an async request to the plugin to paint its window
+ mozilla::dom::ContentParent::SendAsyncUpdate(this);
+ ValidateRect(mWnd, nullptr);
+ return true;
+ }
+
+ PluginInstanceParent* instance = reinterpret_cast<PluginInstanceParent*>(
+ ::GetPropW(mWnd, L"PluginInstanceParentProperty"));
+ if (instance) {
+ Unused << instance->CallUpdateWindow();
+ } else {
+ // We should never get here since in-process plugins should have
+ // subclassed our HWND and handled WM_PAINT, but in some cases that
+ // could fail. Return without asserting since it's not our fault.
+ NS_WARNING("Plugin failed to subclass our window");
+ }
+
+ ValidateRect(mWnd, nullptr);
+ return true;
+ }
+
+ ClientLayerManager *clientLayerManager = GetLayerManager()->AsClientLayerManager();
+
+ if (clientLayerManager && !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.
+ clientLayerManager->Composite();
+ }
+ mLastPaintBounds = mBounds;
+
+ PAINTSTRUCT ps;
+
+#ifdef MOZ_XUL
+ if (!aDC && (eTransparencyTransparent == mTransparencyMode))
+ {
+ // 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.
+ // 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().
+ aDC = mCompositorWidgetDelegate->GetTransparentDC();
+ }
+#endif
+
+ mPainting = true;
+
+#ifdef WIDGET_DEBUG_OUTPUT
+ HRGN debugPaintFlashRegion = nullptr;
+ HDC debugPaintFlashDC = nullptr;
+
+ if (debug_WantPaintFlashing())
+ {
+ debugPaintFlashRegion = ::CreateRectRgn(0, 0, 0, 0);
+ ::GetUpdateRgn(mWnd, debugPaintFlashRegion, TRUE);
+ debugPaintFlashDC = ::GetDC(mWnd);
+ }
+#endif // WIDGET_DEBUG_OUTPUT
+
+ HDC hDC = aDC ? aDC : (::BeginPaint(mWnd, &ps));
+ mPaintDC = hDC;
+
+#ifdef MOZ_XUL
+ bool forceRepaint = aDC || (eTransparencyTransparent == mTransparencyMode);
+#else
+ bool forceRepaint = nullptr != aDC;
+#endif
+ nsIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC);
+
+ if (clientLayerManager) {
+ // 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.
+ clientLayerManager->SetNeedsComposite(true);
+ clientLayerManager->SendInvalidRegion(region);
+ }
+
+ 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 (clientLayerManager && clientLayerManager->NeedsComposite()) {
+ clientLayerManager->Composite();
+ clientLayerManager->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,
+ "noname",
+ (int32_t) mWnd);
+#endif // WIDGET_DEBUG_OUTPUT
+
+ switch (GetLayerManager()->GetBackendType()) {
+ case LayersBackend::LAYERS_BASIC:
+ {
+ RefPtr<gfxASurface> targetSurface;
+
+#if defined(MOZ_XUL)
+ // don't support transparency for non-GDI rendering, for now
+ if (eTransparencyTransparent == mTransparencyMode) {
+ targetSurface = mBasicLayersSurface->EnsureTransparentSurface();
+ }
+#endif
+
+ RefPtr<gfxWindowsSurface> targetSurfaceWin;
+ if (!targetSurface)
+ {
+ uint32_t flags = (mTransparencyMode == eTransparencyOpaque) ? 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::GetPlatform()->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;
+#ifdef MOZ_XUL
+ switch (mTransparencyMode) {
+ case eTransparencyGlass:
+ case eTransparencyBorderlessGlass:
+ default:
+ // If we're not doing translucency, then double buffer
+ doubleBuffering = mozilla::layers::BufferMode::BUFFERED;
+ break;
+ case eTransparencyTransparent:
+ // 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;
+ }
+#else
+ doubleBuffering = mozilla::layers::BufferMode::BUFFERED;
+#endif
+
+ RefPtr<gfxContext> thebesContext = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(thebesContext); // already checked draw target above
+
+ {
+ AutoLayerManagerSetup
+ setupLayerManager(this, thebesContext, doubleBuffering);
+ result = listener->PaintWindow(
+ this, LayoutDeviceIntRegion::FromUnknownRegion(region));
+ }
+
+#ifdef MOZ_XUL
+ if (eTransparencyTransparent == 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();
+ }
+#endif
+ }
+ break;
+ case LayersBackend::LAYERS_CLIENT:
+ {
+ result = listener->PaintWindow(
+ this, LayoutDeviceIntRegion::FromUnknownRegion(region));
+ if (!gfxEnv::DisableForcePresent() && gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &nsWindow::ForcePresent);
+ NS_DispatchToMainThread(event);
+ }
+ }
+ break;
+ default:
+ NS_ERROR("Unknown layers backend used!");
+ break;
+ }
+ }
+
+ if (!aDC) {
+ ::EndPaint(mWnd, &ps);
+ }
+
+ mPaintDC = nullptr;
+ mLastPaintEndTime = TimeStamp::Now();
+
+#if defined(WIDGET_DEBUG_OUTPUT)
+ if (debug_WantPaintFlashing())
+ {
+ // Only flash paint events which have not ignored the paint message.
+ // Those that ignore the paint message aren't painting anything so there
+ // is only the overhead of the dispatching the paint event.
+ if (result) {
+ ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion);
+ PR_Sleep(PR_MillisecondsToInterval(30));
+ ::InvertRgn(debugPaintFlashDC, debugPaintFlashRegion);
+ PR_Sleep(PR_MillisecondsToInterval(30));
+ }
+ ::ReleaseDC(mWnd, debugPaintFlashDC);
+ ::DeleteObject(debugPaintFlashRegion);
+ }
+#endif // WIDGET_DEBUG_OUTPUT
+
+ mPainting = false;
+
+ // Re-get the listener since painting may have killed it.
+ listener = GetPaintListener();
+ if (listener)
+ listener->DidPaintWindow();
+
+ if (aNestingLevel == 0 && ::GetUpdateRect(mWnd, nullptr, false)) {
+ OnPaint(aDC, 1);
+ }
+
+ return result;
+}
+
+IntSize 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 IntSize(width, height);
+}
+
+nsresult nsWindowGfx::CreateIcon(imgIContainer *aContainer,
+ bool aIsCursor,
+ uint32_t aHotspotX,
+ uint32_t aHotspotY,
+ IntSize aScaledSize,
+ HICON *aIcon) {
+
+ 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);
+ 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);
+ PR_Free(a1data);
+
+ ICONINFO info = {0};
+ info.fIcon = !aIsCursor;
+ info.xHotspot = aHotspotX;
+ info.yHotspot = aHotspotY;
+ 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*)PR_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..3f4d763621
--- /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::gfx::IntSize GetIconMetrics(IconSizeType aSizeType);
+ static nsresult CreateIcon(imgIContainer *aContainer, bool aIsCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY,
+ mozilla::gfx::IntSize 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/nsdefs.h b/widget/windows/nsdefs.h
new file mode 100644
index 0000000000..4e0c1c3973
--- /dev/null
+++ b/widget/windows/nsdefs.h
@@ -0,0 +1,45 @@
+/* -*- 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
+
+// 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..45258ab2be
--- /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/TestWinDND.cpp b/widget/windows/tests/TestWinDND.cpp
new file mode 100644
index 0000000000..096633b098
--- /dev/null
+++ b/widget/windows/tests/TestWinDND.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/. */
+#define MOZILLA_INTERNAL_API
+
+#include <ole2.h>
+#include <shlobj.h>
+
+#include "TestHarness.h"
+#include "nsArray.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIFileURL.h"
+#include "nsITransferable.h"
+#include "nsClipboard.h"
+#include "nsDataObjCollection.h"
+
+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 != true) {
+ fail("Received data is not Unicode");
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsString s;
+ unsigned long offset = 0;
+ while (1) {
+ 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,
+ 0);
+ return rv;
+}
+
+nsresult GetTransferableText(nsCOMPtr<nsITransferable>& pTransferable)
+{
+ nsresult rv;
+ NS_NAMED_LITERAL_STRING(mozString, "Mozilla can drag and drop");
+ 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/unicode", genericWrapper,
+ mozString.Length() * sizeof(char16_t));
+ return rv;
+}
+
+nsresult GetTransferableTextTwo(nsCOMPtr<nsITransferable>& pTransferable)
+{
+ nsresult rv;
+ NS_NAMED_LITERAL_STRING(mozString, " twice over");
+ 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/unicode", genericWrapper,
+ mozString.Length() * sizeof(char16_t));
+ 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, 0);
+ 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;
+}
+
+int main(int argc, char** argv)
+{
+ ScopedXPCOM xpcom("Test Windows Drag and Drop");
+
+ nsCOMPtr<nsIFile> file;
+ file = xpcom.GetProfileDirectory();
+ 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!");
+
+ return gFailCount;
+}
diff --git a/widget/windows/tests/moz.build b/widget/windows/tests/moz.build
new file mode 100644
index 0000000000..28919c271d
--- /dev/null
+++ b/widget/windows/tests/moz.build
@@ -0,0 +1,6 @@
+# -*- 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/.
+
diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h
new file mode 100644
index 0000000000..e93f62089b
--- /dev/null
+++ b/widget/windows/touchinjection_sdk80.h
@@ -0,0 +1,107 @@
+/* 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
+
+// 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 {
+ PT_POINTER = 0x00000001, // Generic pointer
+ PT_TOUCH = 0x00000002, // Touch
+ PT_PEN = 0x00000003, // Pen
+ PT_MOUSE = 0x00000004, // Mouse
+};
+
+typedef DWORD POINTER_INPUT_TYPE;
+typedef UINT32 POINTER_FLAGS;
+
+typedef enum {
+ 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,
+} POINTER_BUTTON_CHANGE_TYPE;
+
+typedef struct {
+ 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;
+} POINTER_INFO;
+
+typedef UINT32 TOUCH_FLAGS;
+typedef UINT32 TOUCH_MASK;
+
+typedef struct {
+ POINTER_INFO pointerInfo;
+ TOUCH_FLAGS touchFlags;
+ TOUCH_MASK touchMask;
+ RECT rcContact;
+ RECT rcContactRaw;
+ UINT32 orientation;
+ UINT32 pressure;
+} POINTER_TOUCH_INFO;
+
+#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)
+
+typedef BOOL (WINAPI* InitializeTouchInjectionPtr)(UINT32 maxCount, DWORD dwMode);
+typedef BOOL (WINAPI* InjectTouchInputPtr)(UINT32 count, CONST POINTER_TOUCH_INFO *info);
+
+#endif // touchinjection_sdk80_h \ No newline at end of file
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