diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /widget/windows | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows')
175 files changed, 70789 insertions, 0 deletions
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp new file mode 100644 index 0000000000..0975aa065e --- /dev/null +++ b/widget/windows/AudioSession.cpp @@ -0,0 +1,443 @@ +/* -*- 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 "AudioSession.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/WindowsVersion.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(); + nsresult CommitAudioSessionData(); + + 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; + // Guards the IAudioSessionControl + mozilla::Mutex mMutex; + + 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() : mMutex("AudioSessionControl") { + 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("brandFullName", 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; + } + + MutexAutoLock lock(mMutex); + hr = manager->GetAudioSessionControl(&GUID_NULL, 0, + getter_AddRefs(mAudioSessionControl)); + + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + + // Increments refcount of 'this'. + hr = mAudioSessionControl->RegisterAudioSessionNotification(this); + if (FAILED(hr)) { + StopInternal(); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod("AudioSession::CommitAudioSessionData", this, + &AudioSession::CommitAudioSessionData); + NS_DispatchToMainThread(runnable); + + mState = STARTED; + + return NS_OK; +} + +void SpawnASCReleaseThread(RefPtr<IAudioSessionControl>&& aASC) { + // Fake moving to the other thread by circumventing the ref count. + // (RefPtrs don't play well with C++11 lambdas and we don't want to use + // XPCOM here.) + IAudioSessionControl* rawPtr = nullptr; + aASC.forget(&rawPtr); + MOZ_ASSERT(rawPtr); + PRThread* thread = PR_CreateThread( + PR_USER_THREAD, + [](void* aRawPtr) { + NS_SetCurrentThreadName("AudioASCReleaser"); + static_cast<IAudioSessionControl*>(aRawPtr)->Release(); + }, + rawPtr, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0); + if (!thread) { + // We can't make a thread so just destroy the IAudioSessionControl here. + rawPtr->Release(); + } +} + +void AudioSession::StopInternal() { + mMutex.AssertCurrentThreadOwns(); + + if (mAudioSessionControl && (mState == STARTED || mState == STOPPED)) { + // Decrement refcount of 'this' + mAudioSessionControl->UnregisterAudioSessionNotification(this); + } + + if (mAudioSessionControl) { + // Avoid hanging when destroying AudioSessionControl. We do that by + // moving the AudioSessionControl to a worker thread (that we never + // 'join') for destruction. + SpawnASCReleaseThread(std::move(mAudioSessionControl)); + } +} + +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); + + MutexAutoLock lock(mMutex); + 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; +} + +nsresult AudioSession::CommitAudioSessionData() { + MutexAutoLock lock(mMutex); + + if (!mAudioSessionControl) { + // Stop() was called before we had a chance to do this. + return NS_OK; + } + + HRESULT 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; + } + + 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("widget::AudioSession::OnSessionDisconnectedInternal", + this, &AudioSession::OnSessionDisconnectedInternal); + NS_DispatchToMainThread(runnable); + return S_OK; +} + +nsresult AudioSession::OnSessionDisconnectedInternal() { + // 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); + + { + // We need to release the mutex before we call Start(). + MutexAutoLock lock(mMutex); + + if (!mAudioSessionControl) return NS_OK; + + 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..f715ec5458 --- /dev/null +++ b/widget/windows/AudioSession.h @@ -0,0 +1,28 @@ +/* -*- 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..9adfac64bd --- /dev/null +++ b/widget/windows/CompositorWidgetChild.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 "CompositorWidgetChild.h" +#include "mozilla/Unused.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsBaseWidget.h" +#include "VsyncDispatcher.h" +#include "gfxPlatform.h" +#include "RemoteBackbuffer.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetChild::CompositorWidgetChild( + RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver, + const CompositorWidgetInitData& aInitData) + : mVsyncDispatcher(aVsyncDispatcher), + mVsyncObserver(aVsyncObserver), + mCompositorWnd(nullptr), + mWnd(reinterpret_cast<HWND>( + aInitData.get_WinCompositorWidgetInitData().hWnd())), + mTransparencyMode( + aInitData.get_WinCompositorWidgetInitData().transparencyMode()), + mRemoteBackbufferProvider() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!gfxPlatform::IsHeadless()); + MOZ_ASSERT(mWnd && ::IsWindow(mWnd)); +} + +CompositorWidgetChild::~CompositorWidgetChild() {} + +bool CompositorWidgetChild::Initialize() { + mRemoteBackbufferProvider = std::make_unique<remote_backbuffer::Provider>(); + if (!mRemoteBackbufferProvider->Initialize(mWnd, OtherPid(), + mTransparencyMode)) { + return false; + } + + auto maybeRemoteHandles = mRemoteBackbufferProvider->CreateRemoteHandles(); + if (!maybeRemoteHandles) { + return false; + } + + Unused << SendInitialize(*maybeRemoteHandles); + + return true; +} + +void CompositorWidgetChild::EnterPresentLock() { + Unused << SendEnterPresentLock(); +} + +void CompositorWidgetChild::LeavePresentLock() { + Unused << SendLeavePresentLock(); +} + +void CompositorWidgetChild::OnDestroyWindow() {} + +bool CompositorWidgetChild::OnWindowResize(const LayoutDeviceIntSize& aSize) { + return true; +} + +void CompositorWidgetChild::OnWindowModeChange(nsSizeMode aSizeMode) {} + +void CompositorWidgetChild::UpdateTransparency(nsTransparencyMode aMode) { + mTransparencyMode = aMode; + mRemoteBackbufferProvider->UpdateTransparencyMode(aMode); + Unused << SendUpdateTransparency(aMode); +} + +void CompositorWidgetChild::ClearTransparentWindow() { + Unused << SendClearTransparentWindow(); +} + +mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() { + mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() { + mVsyncDispatcher->SetCompositorVsyncObserver(nullptr); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetChild::RecvUpdateCompositorWnd( + const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd, + UpdateCompositorWndResolver&& aResolve) { + HWND parentWnd = reinterpret_cast<HWND>(aParentWnd); + if (mWnd == parentWnd) { + mCompositorWnd = reinterpret_cast<HWND>(aCompositorWnd); + ::SetParent(mCompositorWnd, mWnd); + aResolve(true); + } else { + aResolve(false); + gfxCriticalNote << "Parent winow does not match"; + MOZ_ASSERT_UNREACHABLE("unexpected to happen"); + } + + return IPC_OK(); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/CompositorWidgetChild.h b/widget/windows/CompositorWidgetChild.h new file mode 100644 index 0000000000..2de686b706 --- /dev/null +++ b/widget/windows/CompositorWidgetChild.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_CompositorWidgetChild_h +#define widget_windows_CompositorWidgetChild_h + +#include "WinCompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetChild.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" + +namespace mozilla { +class CompositorVsyncDispatcher; + +namespace widget { + +namespace remote_backbuffer { +class Provider; +} + +class CompositorWidgetChild final : public PCompositorWidgetChild, + public PlatformCompositorWidgetDelegate { + public: + CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver, + const CompositorWidgetInitData& aInitData); + ~CompositorWidgetChild() override; + + bool Initialize(); + + void EnterPresentLock() override; + void LeavePresentLock() override; + void OnDestroyWindow() override; + bool OnWindowResize(const LayoutDeviceIntSize& aSize) override; + void OnWindowModeChange(nsSizeMode aSizeMode) override; + void UpdateTransparency(nsTransparencyMode aMode) override; + void ClearTransparentWindow() override; + + mozilla::ipc::IPCResult RecvObserveVsync() override; + mozilla::ipc::IPCResult RecvUnobserveVsync() override; + mozilla::ipc::IPCResult RecvUpdateCompositorWnd( + const WindowsHandle& aCompositorWnd, const WindowsHandle& aParentWnd, + UpdateCompositorWndResolver&& aResolve) override; + + private: + RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher; + RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver; + HWND mCompositorWnd; + + HWND mWnd; + nsTransparencyMode mTransparencyMode; + + std::unique_ptr<remote_backbuffer::Provider> mRemoteBackbufferProvider; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_CompositorWidgetChild_h diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp new file mode 100644 index 0000000000..308f9cfa0e --- /dev/null +++ b/widget/windows/CompositorWidgetParent.cpp @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CompositorWidgetParent.h" + +#include "mozilla/Unused.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsWindow.h" +#include "VsyncDispatcher.h" +#include "WinCompositorWindowThread.h" +#include "VRShMem.h" +#include "RemoteBackbuffer.h" + +#include <ddraw.h> + +namespace mozilla { +namespace widget { + +using namespace mozilla::gfx; +using namespace mozilla; + +CompositorWidgetParent::CompositorWidgetParent( + const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions) + : WinCompositorWidget(aInitData.get_WinCompositorWidgetInitData(), + aOptions), + mWnd(reinterpret_cast<HWND>( + aInitData.get_WinCompositorWidgetInitData().hWnd())), + mTransparencyMode( + aInitData.get_WinCompositorWidgetInitData().transparencyMode()), + mRemoteBackbufferClient() { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(mWnd && ::IsWindow(mWnd)); +} + +CompositorWidgetParent::~CompositorWidgetParent() {} + +bool CompositorWidgetParent::Initialize( + const RemoteBackbufferHandles& aRemoteHandles) { + mRemoteBackbufferClient = std::make_unique<remote_backbuffer::Client>(); + if (!mRemoteBackbufferClient->Initialize(aRemoteHandles)) { + return false; + } + + return true; +} + +bool CompositorWidgetParent::PreRender(WidgetRenderingContext* aContext) { + // This can block waiting for WM_SETTEXT to finish + // Using PreRender is unnecessarily pessimistic because + // we technically only need to block during the present call + // not all of compositor rendering + mPresentLock.Enter(); + return true; +} + +void CompositorWidgetParent::PostRender(WidgetRenderingContext* aContext) { + mPresentLock.Leave(); +} + +LayoutDeviceIntSize CompositorWidgetParent::GetClientSize() { + RECT r; + if (!::GetClientRect(mWnd, &r)) { + return LayoutDeviceIntSize(); + } + return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top); +} + +already_AddRefed<gfx::DrawTarget> +CompositorWidgetParent::StartRemoteDrawingInRegion( + LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) { + MOZ_ASSERT(mRemoteBackbufferClient); + MOZ_ASSERT(aBufferMode); + + // Because we use remote backbuffering, there is no need to use a local + // backbuffer too. + (*aBufferMode) = layers::BufferMode::BUFFER_NONE; + + return mRemoteBackbufferClient->BorrowDrawTarget(); +} + +void CompositorWidgetParent::EndRemoteDrawingInRegion( + gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) { + Unused << mRemoteBackbufferClient->PresentDrawTarget( + aInvalidRegion.ToUnknownRegion()); +} + +bool CompositorWidgetParent::NeedsToDeferEndRemoteDrawing() { return false; } + +already_AddRefed<gfx::DrawTarget> +CompositorWidgetParent::GetBackBufferDrawTarget(gfx::DrawTarget* aScreenTarget, + const gfx::IntRect& aRect, + bool* aOutIsCleared) { + MOZ_CRASH( + "Unexpected call to GetBackBufferDrawTarget() with remote " + "backbuffering in use"); +} + +already_AddRefed<gfx::SourceSurface> +CompositorWidgetParent::EndBackBufferDrawing() { + MOZ_CRASH( + "Unexpected call to EndBackBufferDrawing() with remote " + "backbuffering in use"); +} + +bool CompositorWidgetParent::InitCompositor(layers::Compositor* aCompositor) { + if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) { + DeviceManagerDx::Get()->InitializeDirectDraw(); + } + return true; +} + +bool CompositorWidgetParent::HasGlass() const { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread() || + wr::RenderThread::IsInRenderThread()); + + nsTransparencyMode transparencyMode = mTransparencyMode; + return transparencyMode == eTransparencyGlass || + transparencyMode == eTransparencyBorderlessGlass; +} + +bool CompositorWidgetParent::IsHidden() const { return ::IsIconic(mWnd); } + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvInitialize( + const RemoteBackbufferHandles& aRemoteHandles) { + Unused << Initialize(aRemoteHandles); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvEnterPresentLock() { + mPresentLock.Enter(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvLeavePresentLock() { + mPresentLock.Leave(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvUpdateTransparency( + const nsTransparencyMode& aMode) { + mTransparencyMode = aMode; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvClearTransparentWindow() { + gfx::CriticalSectionAutoEnter lock(&mPresentLock); + + RefPtr<DrawTarget> drawTarget = mRemoteBackbufferClient->BorrowDrawTarget(); + if (!drawTarget) { + return IPC_OK(); + } + + IntSize size = drawTarget->GetSize(); + if (size.IsEmpty()) { + return IPC_OK(); + } + + drawTarget->ClearRect(Rect(0, 0, size.width, size.height)); + + Unused << mRemoteBackbufferClient->PresentDrawTarget( + IntRect(0, 0, size.width, size.height)); + + return IPC_OK(); +} + +nsIWidget* CompositorWidgetParent::RealWidget() { return nullptr; } + +void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) { + if (aObserver) { + Unused << SendObserveVsync(); + } else { + Unused << SendUnobserveVsync(); + } + mVsyncObserver = aObserver; +} + +RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + return mVsyncObserver; +} + +void CompositorWidgetParent::UpdateCompositorWnd(const HWND aCompositorWnd, + const HWND aParentWnd) { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(mRootLayerTreeID.isSome()); + + RefPtr<CompositorWidgetParent> self = this; + SendUpdateCompositorWnd(reinterpret_cast<WindowsHandle>(aCompositorWnd), + reinterpret_cast<WindowsHandle>(aParentWnd)) + ->Then( + layers::CompositorThread(), __func__, + [self](const bool& aSuccess) { + if (aSuccess && self->mRootLayerTreeID.isSome()) { + self->mSetParentCompleted = true; + // Schedule composition after ::SetParent() call in parent + // process. + layers::CompositorBridgeParent::ScheduleForcedComposition( + self->mRootLayerTreeID.ref()); + } + }, + [self](const mozilla::ipc::ResponseRejectReason&) {}); +} + +void CompositorWidgetParent::SetRootLayerTreeID( + const layers::LayersId& aRootLayerTreeId) { + mRootLayerTreeID = Some(aRootLayerTreeId); +} + +void CompositorWidgetParent::ActorDestroy(ActorDestroyReason aWhy) {} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/CompositorWidgetParent.h b/widget/windows/CompositorWidgetParent.h new file mode 100644 index 0000000000..a4da4e1192 --- /dev/null +++ b/widget/windows/CompositorWidgetParent.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 widget_windows_CompositorWidgetParent_h +#define widget_windows_CompositorWidgetParent_h + +#include "WinCompositorWidget.h" +#include "mozilla/Maybe.h" +#include "mozilla/widget/PCompositorWidgetParent.h" + +namespace mozilla { +namespace widget { + +namespace remote_backbuffer { +class Client; +} + +class CompositorWidgetParent final : public PCompositorWidgetParent, + public WinCompositorWidget { + public: + explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions); + ~CompositorWidgetParent() override; + + bool Initialize(const RemoteBackbufferHandles& aRemoteHandles); + + bool PreRender(WidgetRenderingContext*) override; + void PostRender(WidgetRenderingContext*) override; + already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion( + LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) override; + void EndRemoteDrawingInRegion( + gfx::DrawTarget* aDrawTarget, + const LayoutDeviceIntRegion& aInvalidRegion) override; + bool NeedsToDeferEndRemoteDrawing() override; + LayoutDeviceIntSize GetClientSize() override; + already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget( + gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect, + bool* aOutIsCleared) override; + already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override; + bool InitCompositor(layers::Compositor* aCompositor) override; + bool IsHidden() const override; + + bool HasGlass() const override; + + mozilla::ipc::IPCResult RecvInitialize( + const RemoteBackbufferHandles& aRemoteHandles) override; + mozilla::ipc::IPCResult RecvEnterPresentLock() override; + mozilla::ipc::IPCResult RecvLeavePresentLock() override; + mozilla::ipc::IPCResult RecvUpdateTransparency( + const nsTransparencyMode& aMode) override; + mozilla::ipc::IPCResult RecvClearTransparentWindow() override; + void ActorDestroy(ActorDestroyReason aWhy) override; + + nsIWidget* RealWidget() override; + void ObserveVsync(VsyncObserver* aObserver) override; + RefPtr<VsyncObserver> GetVsyncObserver() const override; + + // PlatformCompositorWidgetDelegate Overrides + void UpdateCompositorWnd(const HWND aCompositorWnd, + const HWND aParentWnd) override; + void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override; + + private: + RefPtr<VsyncObserver> mVsyncObserver; + Maybe<layers::LayersId> mRootLayerTreeID; + + HWND mWnd; + + gfx::CriticalSection mPresentLock; + + // Transparency handling. + mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed> + mTransparencyMode; + + std::unique_ptr<remote_backbuffer::Client> mRemoteBackbufferClient; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_CompositorWidgetParent_h diff --git a/widget/windows/DirectManipulationOwner.cpp b/widget/windows/DirectManipulationOwner.cpp new file mode 100644 index 0000000000..b43148152f --- /dev/null +++ b/widget/windows/DirectManipulationOwner.cpp @@ -0,0 +1,718 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DirectManipulationOwner.h" +#include "nsWindow.h" +#include "WinModifierKeyState.h" +#include "InputData.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/VsyncDispatcher.h" + +#if !defined(__MINGW32__) && !defined(__MINGW64__) + +// Direct Manipulation is only defined for Win8 and newer. +# if defined(_WIN32_WINNT) +# undef _WIN32_WINNT +# define _WIN32_WINNT _WIN32_WINNT_WIN8 +# endif // defined(_WIN32_WINNT) +# if defined(NTDDI_VERSION) +# undef NTDDI_VERSION +# define NTDDI_VERSION NTDDI_WIN8 +# endif // defined(NTDDI_VERSION) + +# include "directmanipulation.h" + +#endif // !defined(__MINGW32__) && !defined(__MINGW64__) + +namespace mozilla { +namespace widget { + +#if !defined(__MINGW32__) && !defined(__MINGW64__) + +class DManipEventHandler : public IDirectManipulationViewportEventHandler, + public IDirectManipulationInteractionEventHandler { + public: + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + + friend class DirectManipulationOwner; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler) + + STDMETHODIMP QueryInterface(REFIID, void**) override; + + friend class DirectManipulationOwner; + + explicit DManipEventHandler(nsWindow* aWindow, + DirectManipulationOwner* aOwner, + const LayoutDeviceIntRect& aBounds); + + HRESULT STDMETHODCALLTYPE OnViewportStatusChanged( + IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) override; + + HRESULT STDMETHODCALLTYPE + OnViewportUpdated(IDirectManipulationViewport* viewport) override; + + HRESULT STDMETHODCALLTYPE + OnContentUpdated(IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) override; + + HRESULT STDMETHODCALLTYPE + OnInteraction(IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) override; + + void Update(); + + class VObserver final : public mozilla::VsyncObserver { + public: + bool NotifyVsync(const mozilla::VsyncEvent& aVsync) override { + if (mOwner) { + mOwner->Update(); + } + return true; + } + explicit VObserver(DManipEventHandler* aOwner) : mOwner(aOwner) {} + + void ClearOwner() { mOwner = nullptr; } + + private: + virtual ~VObserver() {} + DManipEventHandler* mOwner; + }; + + enum class State { eNone, ePanning, eInertia, ePinching }; + void TransitionToState(State aNewState); + + enum class Phase { eStart, eMiddle, eEnd }; + // Return value indicates if we sent an event or not and hence if we should + // update mLastScale. (We only want to send pinch events if the computed + // deltaY for the corresponding WidgetWheelEvent would be non-zero.) + bool SendPinch(Phase aPhase, float aScale); + void SendPan(Phase aPhase, float x, float y, bool aIsInertia); + + private: + virtual ~DManipEventHandler() = default; + + nsWindow* mWindow; + DirectManipulationOwner* mOwner; + RefPtr<VObserver> mObserver; + float mLastScale; + float mLastXOffset; + float mLastYOffset; + LayoutDeviceIntRect mBounds; + bool mShouldSendPanStart; + bool mShouldSendPinchStart; + State mState = State::eNone; +}; + +DManipEventHandler::DManipEventHandler(nsWindow* aWindow, + DirectManipulationOwner* aOwner, + const LayoutDeviceIntRect& aBounds) + : mWindow(aWindow), + mOwner(aOwner), + mLastScale(1.f), + mLastXOffset(0.f), + mLastYOffset(0.f), + mBounds(aBounds), + mShouldSendPanStart(false), + mShouldSendPinchStart(false) {} + +STDMETHODIMP +DManipEventHandler::QueryInterface(REFIID iid, void** ppv) { + const IID IID_IDirectManipulationViewportEventHandler = + __uuidof(IDirectManipulationViewportEventHandler); + const IID IID_IDirectManipulationInteractionEventHandler = + __uuidof(IDirectManipulationInteractionEventHandler); + + if ((IID_IUnknown == iid) || + (IID_IDirectManipulationViewportEventHandler == iid)) { + *ppv = static_cast<IDirectManipulationViewportEventHandler*>(this); + AddRef(); + return S_OK; + } + if (IID_IDirectManipulationInteractionEventHandler == iid) { + *ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +HRESULT +DManipEventHandler::OnViewportStatusChanged( + IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) { + if (current == previous) { + return S_OK; + } + + if (current == DIRECTMANIPULATION_INERTIA) { + if (previous != DIRECTMANIPULATION_RUNNING || mState != State::ePanning) { + // xxx transition to none? + return S_OK; + } + + TransitionToState(State::eInertia); + } + + if (current == DIRECTMANIPULATION_RUNNING) { + // INERTIA -> RUNNING, should start a new sequence. + if (previous == DIRECTMANIPULATION_INERTIA) { + TransitionToState(State::eNone); + } + } + + if (current != DIRECTMANIPULATION_ENABLED && + current != DIRECTMANIPULATION_READY) { + return S_OK; + } + + // A session has ended, reset the transform. + if (mLastScale != 1.f || mLastXOffset != 0.f || mLastYOffset != 0.f) { + HRESULT hr = + viewport->ZoomToRect(0, 0, mBounds.width, mBounds.height, false); + if (!SUCCEEDED(hr)) { + NS_WARNING("ZoomToRect failed"); + } + } + mLastScale = 1.f; + mLastXOffset = 0.f; + mLastYOffset = 0.f; + + TransitionToState(State::eNone); + + return S_OK; +} + +HRESULT +DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) { + return S_OK; +} + +void DManipEventHandler::TransitionToState(State aNewState) { + if (mState == aNewState) { + return; + } + + State prevState = mState; + mState = aNewState; + + // End the previous sequence. + switch (prevState) { + case State::ePanning: { + // ePanning -> eNone, ePinching: PanEnd + // ePanning -> eInertia: we don't want to end the current scroll sequence. + if (aNewState != State::eInertia) { + SendPan(Phase::eEnd, 0.f, 0.f, false); + } + break; + } + case State::eInertia: { + // eInertia -> *: MomentumEnd + SendPan(Phase::eEnd, 0.f, 0.f, true); + break; + } + case State::ePinching: { + MOZ_ASSERT(aNewState == State::eNone); + // ePinching -> eNone: PinchEnd. ePinching should only transition to + // eNone. + SendPinch(Phase::eEnd, 0.f); + break; + } + case State::eNone: { + // eNone -> *: no cleanup is needed. + break; + } + default: + MOZ_ASSERT(false); + } + + // Start the new sequence. + switch (aNewState) { + case State::ePanning: { + // eInertia, eNone -> ePanning: PanStart. + // We're being called from OnContentUpdated, it has the coords we need to + // pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we + // return OnContentUpdated will check it and call SendPan(Phase::eStart). + mShouldSendPanStart = true; + break; + } + case State::eInertia: { + // Only ePanning can transition to eInertia. + MOZ_ASSERT(prevState == State::ePanning); + SendPan(Phase::eStart, 0.f, 0.f, true); + break; + } + case State::ePinching: { + // * -> ePinching: PinchStart. + // Pinch gesture may begin with some scroll events. + // We're being called from OnContentUpdated, it has the scale we need to + // pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when + // we return OnContentUpdated will check it and call + // SendPinch(Phase::eStart). + mShouldSendPinchStart = true; + break; + } + case State::eNone: { + // * -> eNone: only cleanup is needed. + break; + } + default: + MOZ_ASSERT(false); + } +} + +HRESULT +DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) { + float transform[6]; + HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform)); + if (!SUCCEEDED(hr)) { + NS_WARNING("GetContentTransform failed"); + return S_OK; + } + + float windowScale = mWindow ? mWindow->GetDefaultScale().scale : 1.f; + + float scale = transform[0]; + float xoffset = transform[4] * windowScale; + float yoffset = transform[5] * windowScale; + + // Not different from last time. + if (FuzzyEqualsMultiplicative(scale, mLastScale) && xoffset == mLastXOffset && + yoffset == mLastYOffset) { + return S_OK; + } + + // Consider this is a Scroll when scale factor equals 1.0. + if (FuzzyEqualsMultiplicative(scale, 1.f)) { + if (mState == State::eNone || mState == State::eInertia) { + TransitionToState(State::ePanning); + } + } else { + // Pinch gesture may begin with some scroll events. + TransitionToState(State::ePinching); + } + + if (mState == State::ePanning || mState == State::eInertia) { + // Accumulate the offset (by not updating mLastX/YOffset) until we have at + // least one pixel both before and after scaling by the window scale. + float dx = std::abs(mLastXOffset - xoffset); + float dy = std::abs(mLastYOffset - yoffset); + float minDelta = std::max(1.f, windowScale); + if (dx < minDelta && dy < minDelta) { + return S_OK; + } + } + + bool updateLastScale = true; + if (mState == State::ePanning) { + if (mShouldSendPanStart) { + SendPan(Phase::eStart, mLastXOffset - xoffset, mLastYOffset - yoffset, + false); + mShouldSendPanStart = false; + } else { + SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset, + false); + } + } else if (mState == State::eInertia) { + SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset, + true); + } else if (mState == State::ePinching) { + if (mShouldSendPinchStart) { + updateLastScale = SendPinch(Phase::eStart, scale); + // If we get here our current scale is not fuzzy equal to the previous + // scale, so SendPinch should return true. + MOZ_ASSERT(updateLastScale); + mShouldSendPinchStart = false; + } else { + updateLastScale = SendPinch(Phase::eMiddle, scale); + } + } + + if (updateLastScale) { + mLastScale = scale; + } + mLastXOffset = xoffset; + mLastYOffset = yoffset; + + return S_OK; +} + +HRESULT +DManipEventHandler::OnInteraction( + IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) { + if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) { + if (!mObserver) { + mObserver = new VObserver(this); + } + + gfxWindowsPlatform::GetPlatform()->GetHardwareVsync()->AddGenericObserver( + mObserver); + } + + if (mObserver && interaction == DIRECTMANIPULATION_INTERACTION_END) { + gfxWindowsPlatform::GetPlatform() + ->GetHardwareVsync() + ->RemoveGenericObserver(mObserver); + } + + return S_OK; +} + +void DManipEventHandler::Update() { + if (mOwner) { + mOwner->Update(); + } +} + +#endif // !defined(__MINGW32__) && !defined(__MINGW64__) + +void DirectManipulationOwner::Update() { +#if !defined(__MINGW32__) && !defined(__MINGW64__) + if (mDmUpdateManager) { + mDmUpdateManager->Update(nullptr); + } +#endif +} + +DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow) + : mWindow(aWindow) {} + +DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); } + +#if !defined(__MINGW32__) && !defined(__MINGW64__) + +bool DManipEventHandler::SendPinch(Phase aPhase, float aScale) { + if (!mWindow) { + return false; + } + + if (aScale == mLastScale && aPhase != Phase::eEnd) { + return false; + } + + PinchGestureInput::PinchGestureType pinchGestureType = + PinchGestureInput::PINCHGESTURE_SCALE; + switch (aPhase) { + case Phase::eStart: + pinchGestureType = PinchGestureInput::PINCHGESTURE_START; + break; + case Phase::eMiddle: + pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE; + break; + case Phase::eEnd: + pinchGestureType = PinchGestureInput::PINCHGESTURE_END; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + + PRIntervalTime eventIntervalTime = PR_IntervalNow(); + TimeStamp eventTimeStamp = TimeStamp::Now(); + + ModifierKeyState modifierKeyState; + Modifiers mods = modifierKeyState.GetModifiers(); + + ExternalPoint screenOffset = ViewAs<ExternalPixel>( + mWindow->WidgetToScreenOffset(), + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); + + POINT cursor_pos; + ::GetCursorPos(&cursor_pos); + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + ::ScreenToClient(wnd, &cursor_pos); + ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y}; + + PinchGestureInput event{pinchGestureType, + PinchGestureInput::TRACKPAD, + eventIntervalTime, + eventTimeStamp, + screenOffset, + position, + 100.0 * ((aPhase == Phase::eEnd) ? 1.f : aScale), + 100.0 * ((aPhase == Phase::eEnd) ? 1.f : mLastScale), + mods}; + + double deltaY = event.ComputeDeltaY(mWindow); + if (deltaY == 0.0) { + return false; + } + gfx::IntPoint lineOrPageDelta = PinchGestureInput::GetIntegerDeltaForEvent( + (aPhase == Phase::eStart), 0, deltaY); + event.mLineOrPageDeltaY = lineOrPageDelta.y; + + mWindow->SendAnAPZEvent(event); + + return true; +} + +void DManipEventHandler::SendPan(Phase aPhase, float x, float y, + bool aIsInertia) { + if (!mWindow) { + return; + } + + PanGestureInput::PanGestureType panGestureType = + PanGestureInput::PANGESTURE_PAN; + if (aIsInertia) { + switch (aPhase) { + case Phase::eStart: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMSTART; + break; + case Phase::eMiddle: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMPAN; + break; + case Phase::eEnd: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMEND; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + } else { + switch (aPhase) { + case Phase::eStart: + panGestureType = PanGestureInput::PANGESTURE_START; + break; + case Phase::eMiddle: + panGestureType = PanGestureInput::PANGESTURE_PAN; + break; + case Phase::eEnd: + panGestureType = PanGestureInput::PANGESTURE_END; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + } + + PRIntervalTime eventIntervalTime = PR_IntervalNow(); + TimeStamp eventTimeStamp = TimeStamp::Now(); + + ModifierKeyState modifierKeyState; + Modifiers mods = modifierKeyState.GetModifiers(); + + POINT cursor_pos; + ::GetCursorPos(&cursor_pos); + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + ::ScreenToClient(wnd, &cursor_pos); + ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y}; + + PanGestureInput event{panGestureType, eventIntervalTime, eventTimeStamp, + position, ScreenPoint(x, y), mods}; + + gfx::IntPoint lineOrPageDelta = + PanGestureInput::GetIntegerDeltaForEvent((aPhase == Phase::eStart), x, y); + event.mLineOrPageDeltaX = lineOrPageDelta.x; + event.mLineOrPageDeltaY = lineOrPageDelta.y; + + mWindow->SendAnAPZEvent(event); +} + +#endif // !defined(__MINGW32__) && !defined(__MINGW64__) + +void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) { +#if !defined(__MINGW32__) && !defined(__MINGW64__) + HRESULT hr = CoCreateInstance( + CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER, + IID_IDirectManipulationManager, getter_AddRefs(mDmManager)); + if (!SUCCEEDED(hr)) { + NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed"); + mDmManager = nullptr; + return; + } + + hr = mDmManager->GetUpdateManager(IID_IDirectManipulationUpdateManager, + getter_AddRefs(mDmUpdateManager)); + if (!SUCCEEDED(hr)) { + NS_WARNING("GetUpdateManager failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + return; + } + + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + + hr = mDmManager->CreateViewport(nullptr, wnd, IID_IDirectManipulationViewport, + getter_AddRefs(mDmViewport)); + if (!SUCCEEDED(hr)) { + NS_WARNING("CreateViewport failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + DIRECTMANIPULATION_CONFIGURATION configuration = + DIRECTMANIPULATION_CONFIGURATION_INTERACTION | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA | + DIRECTMANIPULATION_CONFIGURATION_RAILS_X | + DIRECTMANIPULATION_CONFIGURATION_RAILS_Y; + if (StaticPrefs::apz_allow_zooming()) { + configuration |= DIRECTMANIPULATION_CONFIGURATION_SCALING; + } + + hr = mDmViewport->ActivateConfiguration(configuration); + if (!SUCCEEDED(hr)) { + NS_WARNING("ActivateConfiguration failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + hr = mDmViewport->SetViewportOptions( + DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportOptions failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + mDmHandler = new DManipEventHandler(mWindow, this, aBounds); + + hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(), + &mDmViewportHandlerCookie); + if (!SUCCEEDED(hr)) { + NS_WARNING("AddEventHandler failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + RECT rect = {0, 0, aBounds.Width(), aBounds.Height()}; + hr = mDmViewport->SetViewportRect(&rect); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportRect failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmManager->Activate(wnd); + if (!SUCCEEDED(hr)) { + NS_WARNING("manager Activate failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmViewport->Enable(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Enable failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmUpdateManager->Update(nullptr); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmUpdateManager->Update failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } +#endif // !defined(__MINGW32__) && !defined(__MINGW64__) +} + +void DirectManipulationOwner::ResizeViewport( + const LayoutDeviceIntRect& aBounds) { +#if !defined(__MINGW32__) && !defined(__MINGW64__) + if (mDmHandler) { + mDmHandler->mBounds = aBounds; + } + + if (mDmViewport) { + RECT rect = {0, 0, aBounds.Width(), aBounds.Height()}; + HRESULT hr = mDmViewport->SetViewportRect(&rect); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportRect failed"); + } + } +#endif +} + +void DirectManipulationOwner::Destroy() { +#if !defined(__MINGW32__) && !defined(__MINGW64__) + if (mDmHandler) { + mDmHandler->mWindow = nullptr; + mDmHandler->mOwner = nullptr; + if (mDmHandler->mObserver) { + gfxWindowsPlatform::GetPlatform() + ->GetHardwareVsync() + ->RemoveGenericObserver(mDmHandler->mObserver); + mDmHandler->mObserver->ClearOwner(); + mDmHandler->mObserver = nullptr; + } + } + + HRESULT hr; + if (mDmViewport) { + hr = mDmViewport->Stop(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Stop() failed"); + } + + hr = mDmViewport->Disable(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Disable() failed"); + } + + hr = mDmViewport->RemoveEventHandler(mDmViewportHandlerCookie); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->RemoveEventHandler() failed"); + } + + hr = mDmViewport->Abandon(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Abandon() failed"); + } + } + + if (mWindow) { + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + + if (mDmManager) { + hr = mDmManager->Deactivate(wnd); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmManager->Deactivate() failed"); + } + } + } + + mDmHandler = nullptr; + mDmViewport = nullptr; + mDmUpdateManager = nullptr; + mDmManager = nullptr; +#endif // !defined(__MINGW32__) && !defined(__MINGW64__) + mWindow = nullptr; +} + +void DirectManipulationOwner::SetContact(UINT aContactId) { +#if !defined(__MINGW32__) && !defined(__MINGW64__) + if (mDmViewport) { + mDmViewport->SetContact(aContactId); + } +#endif +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/DirectManipulationOwner.h b/widget/windows/DirectManipulationOwner.h new file mode 100644 index 0000000000..dd78dcd099 --- /dev/null +++ b/widget/windows/DirectManipulationOwner.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 DirectManipulationOwner_h__ +#define DirectManipulationOwner_h__ + +#include <windows.h> +#include "Units.h" + +class nsWindow; +class IDirectManipulationManager; +class IDirectManipulationUpdateManager; +class IDirectManipulationViewport; + +namespace mozilla { +namespace widget { + +class DManipEventHandler; + +class DirectManipulationOwner { + public: + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + + explicit DirectManipulationOwner(nsWindow* aWindow); + ~DirectManipulationOwner(); + void Init(const LayoutDeviceIntRect& aBounds); + void ResizeViewport(const LayoutDeviceIntRect& aBounds); + void Destroy(); + + void SetContact(UINT aContactId); + + void Update(); + + private: + nsWindow* mWindow; +#if !defined(__MINGW32__) && !defined(__MINGW64__) + DWORD mDmViewportHandlerCookie; + RefPtr<IDirectManipulationManager> mDmManager; + RefPtr<IDirectManipulationUpdateManager> mDmUpdateManager; + RefPtr<IDirectManipulationViewport> mDmViewport; + RefPtr<DManipEventHandler> mDmHandler; +#endif +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef DirectManipulationOwner_h__ diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp new file mode 100644 index 0000000000..bf2b083729 --- /dev/null +++ b/widget/windows/GfxInfo.cpp @@ -0,0 +1,2058 @@ +/* -*- 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 <devguid.h> // for GUID_DEVCLASS_BATTERY +#include <setupapi.h> // for SetupDi* +#include <winioctl.h> // for IOCTL_* +#include <batclass.h> // for BATTERY_* +#include "gfxConfig.h" +#include "gfxWindowsPlatform.h" +#include "GfxInfo.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 "mozilla/SSE.h" +#include "nsExceptionHandler.h" +#include "nsPrintfCString.h" +#include "jsapi.h" +#include <intrin.h> + +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +#ifdef DEBUG +NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug) +#endif + +GfxInfo::GfxInfo() + : mWindowsVersion(0), + mWindowsBuildNumber(0), + mActiveGPUIndex(0), + mHasDualGPU(false), + mHasBattery(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; +} + +NS_IMETHODIMP +GfxInfo::GetHasBattery(bool* aHasBattery) { + *aHasBattery = mHasBattery; + return NS_OK; +} + +int32_t GfxInfo::GetMaxRefreshRate(bool* aMixed) { + int32_t maxRefreshRate = -1; + if (aMixed) { + *aMixed = false; + } + + for (auto displayInfo : mDisplayInfo) { + int32_t refreshRate = int32_t(displayInfo.mRefreshRate); + if (aMixed && maxRefreshRate > 0 && maxRefreshRate != refreshRate) { + *aMixed = true; + } + maxRefreshRate = std::max(maxRefreshRate, refreshRate); + } + return maxRefreshRate; +} + +NS_IMETHODIMP +GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) { + *aEmbeddedInFirefoxReality = gfxVars::FxREmbedded(); + return NS_OK; +} + +#define PIXEL_STRUCT_RGB 1 +#define PIXEL_STRUCT_BGR 2 + +NS_IMETHODIMP +GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) { + nsTArray<ClearTypeParameterInfo> clearTypeParams; + + gfxWindowsPlatform::GetPlatform()->GetCleartypeParams(clearTypeParams); + uint32_t d, numDisplays = clearTypeParams.Length(); + bool displayNames = (numDisplays > 1); + bool foundData = false; + nsString outStr; + + for (d = 0; d < numDisplays; d++) { + ClearTypeParameterInfo& params = clearTypeParams[d]; + + if (displayNames) { + outStr.AppendPrintf("%S [ ", 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; +} + +NS_IMETHODIMP +GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, + uint32_t& destValue, int type) { + MOZ_ASSERT(type == REG_DWORD || type == REG_QWORD); + HKEY key; + DWORD dwcbData; + DWORD dValue; + DWORD resultType; + LONG result; + nsresult retval = NS_OK; + + result = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key); + if (result != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + switch (type) { + case REG_DWORD: { + // We only use this for vram size + dwcbData = sizeof(dValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)&dValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_DWORD) { + destValue = (uint32_t)(dValue / 1024 / 1024); + } else { + retval = NS_ERROR_FAILURE; + } + break; + } + case REG_QWORD: { + // We only use this for vram size + LONGLONG qValue; + dwcbData = sizeof(qValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)&qValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_QWORD) { + destValue = (uint32_t)(qValue / 1024 / 1024); + } else { + retval = NS_ERROR_FAILURE; + } + break; + } + } + RegCloseKey(key); + + return retval; +} + +static nsresult GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, + nsAString& destString, int type) { + MOZ_ASSERT(type == REG_MULTI_SZ); + + HKEY key; + DWORD dwcbData; + DWORD resultType; + LONG result; + nsresult retval = NS_OK; + + result = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key); + if (result != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // A chain of null-separated strings; we convert the nulls to spaces + WCHAR wCharValue[1024]; + dwcbData = sizeof(wCharValue); + + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + (LPBYTE)wCharValue, &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) { + // This bit here could probably be cleaner. + bool isValid = false; + + DWORD strLen = dwcbData / sizeof(wCharValue[0]); + for (DWORD i = 0; i < strLen; i++) { + if (wCharValue[i] == '\0') { + if (i < strLen - 1 && wCharValue[i + 1] == '\0') { + isValid = true; + break; + } else { + wCharValue[i] = ' '; + } + } + } + + // ensure wCharValue is null terminated + wCharValue[strLen - 1] = '\0'; + + if (isValid) destString = wCharValue; + + } else { + retval = NS_ERROR_FAILURE; + } + + RegCloseKey(key); + + return retval; +} + +static nsresult GetKeyValues(const WCHAR* keyLocation, const WCHAR* keyName, + nsTArray<nsString>& destStrings) { + // First ask for the size of the value + DWORD size; + LONG rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName, + RRF_RT_REG_MULTI_SZ, nullptr, nullptr, &size); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // Create a buffer with the proper size and retrieve the value + WCHAR* wCharValue = new WCHAR[size / sizeof(WCHAR)]; + rv = RegGetValueW(HKEY_LOCAL_MACHINE, keyLocation, keyName, + RRF_RT_REG_MULTI_SZ, nullptr, (LPBYTE)wCharValue, &size); + if (rv != ERROR_SUCCESS) { + delete[] wCharValue; + return NS_ERROR_FAILURE; + } + + // The value is a sequence of null-terminated strings, usually terminated by + // an empty string (\0). RegGetValue ensures that the value is properly + // terminated with a null character. + DWORD i = 0; + DWORD strLen = size / sizeof(WCHAR); + while (i < strLen) { + nsString value(wCharValue + i); + if (!value.IsEmpty()) { + destStrings.AppendElement(value); + } + i += value.Length() + 1; + } + delete[] wCharValue; + + return NS_OK; +} + +// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD +// this function is used to extract the id's out of it +uint32_t ParseIDFromDeviceID(const nsAString& key, const 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); + } + if (id.Equals(L"QCOM", nsCaseInsensitiveStringComparator)) { + // String format assumptions are broken, so use a Qualcomm PCI Vendor ID + // for now. See also GfxDriverInfo::GetDeviceVendor. + return 0x5143; + } + nsresult err; + return id.ToInteger(&err, 16); +} + +// OS version in 16.16 major/minor form +// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx +enum { + kWindowsUnknown = 0, + kWindows7 = 0x60001, + kWindows8 = 0x60002, + kWindows8_1 = 0x60003, + kWindows10 = 0xA0000 +}; + +static bool HasBattery() { + // Helper classes to manage lifetimes of Windows structs. + class MOZ_STACK_CLASS HDevInfoHolder final { + public: + explicit HDevInfoHolder(HDEVINFO aHandle) : mHandle(aHandle) {} + + ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle); } + + private: + HDEVINFO mHandle; + }; + + class MOZ_STACK_CLASS HandleHolder final { + public: + explicit HandleHolder(HANDLE aHandle) : mHandle(aHandle) {} + + ~HandleHolder() { ::CloseHandle(mHandle); } + + private: + HANDLE mHandle; + }; + + HDEVINFO hdev = + ::SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY, nullptr, nullptr, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hdev == INVALID_HANDLE_VALUE) { + return true; + } + + HDevInfoHolder hdevHolder(hdev); + + DWORD i = 0; + SP_DEVICE_INTERFACE_DATA did = {0}; + did.cbSize = sizeof(did); + + while (::SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVCLASS_BATTERY, i, + &did)) { + DWORD bufferSize = 0; + ::SetupDiGetDeviceInterfaceDetail(hdev, &did, nullptr, 0, &bufferSize, + nullptr); + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return true; + } + + UniquePtr<uint8_t[]> buffer(new (std::nothrow) uint8_t[bufferSize]); + if (!buffer) { + return true; + } + + PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = + reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buffer.get()); + pdidd->cbSize = sizeof(*pdidd); + if (!::SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, bufferSize, + &bufferSize, nullptr)) { + return true; + } + + HANDLE hbat = ::CreateFile(pdidd->DevicePath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hbat == INVALID_HANDLE_VALUE) { + return true; + } + + HandleHolder hbatHolder(hbat); + + BATTERY_QUERY_INFORMATION bqi = {0}; + DWORD dwWait = 0; + DWORD dwOut; + + // We need the tag to query the information below. + if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_TAG, &dwWait, + sizeof(dwWait), &bqi.BatteryTag, + sizeof(bqi.BatteryTag), &dwOut, nullptr) || + !bqi.BatteryTag) { + return true; + } + + BATTERY_INFORMATION bi = {0}; + bqi.InformationLevel = BatteryInformation; + + if (!::DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, + sizeof(bqi), &bi, sizeof(bi), &dwOut, nullptr)) { + return true; + } + + // If a battery intended for general use (i.e. system use) is not a UPS + // (i.e. short term), then we know for certain we have a battery. + if ((bi.Capabilities & BATTERY_SYSTEM_BATTERY) && + !(bi.Capabilities & BATTERY_IS_SHORT_TERM)) { + return true; + } + + // Otherwise we check the next battery. + ++i; + } + + // If we fail to enumerate because there are no more batteries to check, then + // we can safely say there are indeed no system batteries. + return ::GetLastError() != ERROR_NO_MORE_ITEMS; +} + +/* Other interesting places for info: + * IDXGIAdapter::GetDesc() + * IDirectDraw7::GetAvailableVidMem() + * e->GetAvailableTextureMem() + * */ + +#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\" +nsresult GfxInfo::Init() { + nsresult rv = GfxInfoBase::Init(); + mHasBattery = HasBattery(); + + DISPLAY_DEVICEW displayDevice; + displayDevice.cb = sizeof(displayDevice); + int deviceIndex = 0; + + const char* spoofedWindowsVersion = + PR_GetEnv("MOZ_GFX_SPOOF_WINDOWS_VERSION"); + if (spoofedWindowsVersion) { + PR_sscanf(spoofedWindowsVersion, "%x,%u", &mWindowsVersion, + &mWindowsBuildNumber); + } else { + OSVERSIONINFO vinfo; + vinfo.dwOSVersionInfoSize = sizeof(vinfo); +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4996) +#endif + if (!GetVersionEx(&vinfo)) { +#ifdef _MSC_VER +# pragma warning(pop) +#endif + mWindowsVersion = kWindowsUnknown; + } else { + mWindowsVersion = + int32_t(vinfo.dwMajorVersion << 16) + vinfo.dwMinorVersion; + mWindowsBuildNumber = vinfo.dwBuildNumber; + } + } + + mDeviceKeyDebug = u"PrimarySearch"_ns; + + while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) { + if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) { + mDeviceKeyDebug = u"NullSearch"_ns; + break; + } + deviceIndex++; + } + + // make sure the string is nullptr terminated + if (wcsnlen(displayDevice.DeviceKey, ArrayLength(displayDevice.DeviceKey)) == + ArrayLength(displayDevice.DeviceKey)) { + // we did not find a nullptr + return rv; + } + + mDeviceKeyDebug = displayDevice.DeviceKey; + + /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */ + /* check that DeviceKey begins with DEVICE_KEY_PREFIX */ + /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need + * to compare case insenstively */ + /* If the device key is empty, we are most likely in a remote desktop + * environment. In this case we set the devicekey to an empty string so + * it can be handled later. + */ + if (displayDevice.DeviceKey[0] != '\0') { + if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, + ArrayLength(DEVICE_KEY_PREFIX) - 1) != 0) { + return rv; + } + + // chop off DEVICE_KEY_PREFIX + mDeviceKey[0] = + displayDevice.DeviceKey + ArrayLength(DEVICE_KEY_PREFIX) - 1; + } else { + mDeviceKey[0].Truncate(); + } + + mDeviceID[0] = displayDevice.DeviceID; + mDeviceString[0] = displayDevice.DeviceString; + + // On Windows 8 and Server 2012 hosts, we want to not block RDP + // sessions from attempting hardware acceleration. RemoteFX + // provides features and functionaltiy that can give a good D3D10 + + // D2D + DirectWrite experience emulated via a software GPU. + // + // Unfortunately, the Device ID is nullptr, and we can't enumerate + // it using the setup infrastructure (SetupDiGetClassDevsW below + // will return INVALID_HANDLE_VALUE). + UINT flags = DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES; + if (mWindowsVersion >= kWindows8 && mDeviceID[0].Length() == 0 && + mDeviceString[0].EqualsLiteral("RDPUDD Chained DD")) { + WCHAR sysdir[255]; + UINT len = GetSystemDirectory(sysdir, sizeof(sysdir)); + if (len < sizeof(sysdir)) { + nsString rdpudd(sysdir); + rdpudd.AppendLiteral("\\rdpudd.dll"); + gfxWindowsPlatform::GetDLLVersion(rdpudd.BeginReading(), + mDriverVersion[0]); + mDriverDate[0].AssignLiteral("01-01-1970"); + + // 0x1414 is Microsoft; 0xfefe is an invented (and unused) code + mDeviceID[0].AssignLiteral("PCI\\VEN_1414&DEV_FEFE&SUBSYS_00000000"); + flags |= DIGCF_DEVICEINTERFACE; + } + } + + /* create a device information set composed of the current display device */ + HDEVINFO devinfo = + SetupDiGetClassDevsW(nullptr, mDeviceID[0].get(), nullptr, flags); + + if (devinfo != INVALID_HANDLE_VALUE) { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + + devinfoData.cbSize = sizeof(devinfoData); + constexpr auto driverKeyPre = + u"System\\CurrentControlSet\\Control\\Class\\"_ns; + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, &devinfoData, SPDRP_DRIVER, + nullptr, (PBYTE)value, + sizeof(value), nullptr)) { + nsAutoString driverKey(driverKeyPre); + driverKey += value; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey.get(), 0, + KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + /* we've found the driver we're looking for */ + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result == ERROR_SUCCESS) { + mDriverVersion[0] = value; + } else { + // If the entry wasn't found, assume the worst (0.0.0.0). + mDriverVersion[0].AssignLiteral("0.0.0.0"); + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result == ERROR_SUCCESS) { + mDriverDate[0] = value; + } else { + // Again, assume the worst + mDriverDate[0].AssignLiteral("01-01-1970"); + } + RegCloseKey(key); + break; + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + + // It is convenient to have these as integers + uint32_t adapterVendorID[2] = {0, 0}; + uint32_t adapterDeviceID[2] = {0, 0}; + uint32_t adapterSubsysID[2] = {0, 0}; + + adapterVendorID[0] = ParseIDFromDeviceID(mDeviceID[0], "VEN_", 4); + adapterDeviceID[0] = ParseIDFromDeviceID(mDeviceID[0], "&DEV_", 4); + adapterSubsysID[0] = ParseIDFromDeviceID(mDeviceID[0], "&SUBSYS_", 8); + + // Sometimes we don't get the valid device using this method. For now, + // allow zero vendor or device as valid, as long as the other value is + // non-zero. + bool foundValidDevice = (adapterVendorID[0] != 0 || adapterDeviceID[0] != 0); + + // We now check for second display adapter. If we didn't find the valid + // device using the original approach, we will try the alternative. + + // Device interface class for display adapters. + CLSID GUID_DISPLAY_DEVICE_ARRIVAL; + HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}", + &GUID_DISPLAY_DEVICE_ARRIVAL); + if (hresult == NOERROR) { + devinfo = + SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, nullptr, nullptr, + DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); + + if (devinfo != INVALID_HANDLE_VALUE) { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + devinfoData.cbSize = sizeof(devinfoData); + + nsAutoString adapterDriver2; + nsAutoString deviceID2; + nsAutoString driverVersion2; + nsAutoString driverDate2; + + constexpr auto driverKeyPre = + u"System\\CurrentControlSet\\Control\\Class\\"_ns; + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW( + devinfo, &devinfoData, SPDRP_DRIVER, nullptr, (PBYTE)value, + sizeof(value), nullptr)) { + nsAutoString driverKey2(driverKeyPre); + driverKey2 += value; + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverKey2.get(), 0, + KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr, + nullptr, (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + continue; + } + deviceID2 = value; + adapterVendorID[1] = ParseIDFromDeviceID(deviceID2, "VEN_", 4); + adapterDeviceID[1] = ParseIDFromDeviceID(deviceID2, "&DEV_", 4); + // Skip the devices we already considered, as well as any + // "zero" ones. + if ((adapterVendorID[0] == adapterVendorID[1] && + adapterDeviceID[0] == adapterDeviceID[1]) || + (adapterVendorID[1] == 0 && adapterDeviceID[1] == 0)) { + RegCloseKey(key); + continue; + } + + // If this device is missing driver information, it is unlikely to + // be a real display adapter. + if (NS_FAILED(GetKeyValue(driverKey2.get(), + L"InstalledDisplayDrivers", + adapterDriver2, REG_MULTI_SZ))) { + RegCloseKey(key); + continue; + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + continue; + } + driverVersion2 = value; + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + continue; + } + driverDate2 = value; + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"Device Description", nullptr, + nullptr, (LPBYTE)value, &dwcbData); + if (result != ERROR_SUCCESS) { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr, + (LPBYTE)value, &dwcbData); + } + RegCloseKey(key); + if (result == ERROR_SUCCESS) { + // If we didn't find a valid device with the original method + // take this one, and continue looking for the second GPU. + if (!foundValidDevice) { + foundValidDevice = true; + adapterVendorID[0] = adapterVendorID[1]; + adapterDeviceID[0] = adapterDeviceID[1]; + mDeviceString[0] = value; + mDeviceID[0] = deviceID2; + mDeviceKey[0] = driverKey2; + mDriverVersion[0] = driverVersion2; + mDriverDate[0] = driverDate2; + adapterSubsysID[0] = + ParseIDFromDeviceID(mDeviceID[0], "&SUBSYS_", 8); + continue; + } + + mHasDualGPU = true; + mDeviceString[1] = value; + mDeviceID[1] = deviceID2; + mDeviceKey[1] = driverKey2; + mDriverVersion[1] = driverVersion2; + mDriverDate[1] = driverDate2; + adapterSubsysID[1] = + ParseIDFromDeviceID(mDeviceID[1], "&SUBSYS_", 8); + mAdapterVendorID[1].AppendPrintf("0x%04x", adapterVendorID[1]); + mAdapterDeviceID[1].AppendPrintf("0x%04x", adapterDeviceID[1]); + mAdapterSubsysID[1].AppendPrintf("%08x", adapterSubsysID[1]); + break; + } + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + } + + mAdapterVendorID[0].AppendPrintf("0x%04x", adapterVendorID[0]); + mAdapterDeviceID[0].AppendPrintf("0x%04x", adapterDeviceID[0]); + mAdapterSubsysID[0].AppendPrintf("%08x", adapterSubsysID[0]); + + // Sometimes, the enumeration is not quite right and the two adapters + // end up being swapped. Actually enumerate the adapters that come + // back from the DXGI factory to check, and tag the second as active + // if found. + if (mHasDualGPU) { + nsModuleHandle dxgiModule(LoadLibrarySystem32(L"dxgi.dll")); + decltype(CreateDXGIFactory)* createDXGIFactory = + (decltype(CreateDXGIFactory)*)GetProcAddress(dxgiModule, + "CreateDXGIFactory"); + + if (createDXGIFactory) { + RefPtr<IDXGIFactory> factory = nullptr; + createDXGIFactory(__uuidof(IDXGIFactory), (void**)(&factory)); + if (factory) { + RefPtr<IDXGIAdapter> adapter; + if (SUCCEEDED(factory->EnumAdapters(0, getter_AddRefs(adapter)))) { + DXGI_ADAPTER_DESC desc; + PodZero(&desc); + if (SUCCEEDED(adapter->GetDesc(&desc))) { + if (desc.VendorId != adapterVendorID[0] && + desc.DeviceId != adapterDeviceID[0] && + desc.VendorId == adapterVendorID[1] && + desc.DeviceId == adapterDeviceID[1]) { + mActiveGPUIndex = 1; + } + } + } + } + } + } + + mHasDriverVersionMismatch = false; + if (mAdapterVendorID[mActiveGPUIndex] == + GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel)) { + // we've had big crashers (bugs 590373 and 595364) apparently correlated + // with bad Intel driver installations where the DriverVersion reported + // by the registry was not the version of the DLL. + + // Note that these start without the .dll extension but eventually gain it. + bool is64bitApp = sizeof(void*) == 8; + nsAutoString dllFileName(is64bitApp ? u"igd10umd64" : u"igd10umd32"); + nsAutoString dllFileName2(is64bitApp ? u"igd10iumd64" : u"igd10iumd32"); + + nsString dllVersion, dllVersion2; + uint64_t dllNumericVersion = 0, dllNumericVersion2 = 0, + driverNumericVersion = 0, knownSafeMismatchVersion = 0; + + // Only parse the DLL version for those found in the driver list + nsAutoString eligibleDLLs; + if (NS_SUCCEEDED(GetAdapterDriver(eligibleDLLs))) { + if (FindInReadable(dllFileName, eligibleDLLs)) { + dllFileName += u".dll"_ns; + gfxWindowsPlatform::GetDLLVersion(dllFileName.get(), dllVersion); + ParseDriverVersion(dllVersion, &dllNumericVersion); + } + if (FindInReadable(dllFileName2, eligibleDLLs)) { + dllFileName2 += u".dll"_ns; + gfxWindowsPlatform::GetDLLVersion(dllFileName2.get(), dllVersion2); + ParseDriverVersion(dllVersion2, &dllNumericVersion2); + } + } + + // Sometimes the DLL is not in the System32 nor SysWOW64 directories. But + // UserModeDriverName (or UserModeDriverNameWow, if available) might provide + // the full path to the DLL in some DriverStore FileRepository. + if (dllNumericVersion == 0 && dllNumericVersion2 == 0) { + nsTArray<nsString> eligibleDLLpaths; + const WCHAR* keyLocation = mDeviceKey[mActiveGPUIndex].get(); + GetKeyValues(keyLocation, L"UserModeDriverName", eligibleDLLpaths); + GetKeyValues(keyLocation, L"UserModeDriverNameWow", eligibleDLLpaths); + size_t length = eligibleDLLpaths.Length(); + for (size_t i = 0; + i < length && dllNumericVersion == 0 && dllNumericVersion2 == 0; + ++i) { + if (FindInReadable(dllFileName, eligibleDLLpaths[i])) { + gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(), + dllVersion); + ParseDriverVersion(dllVersion, &dllNumericVersion); + } else if (FindInReadable(dllFileName2, eligibleDLLpaths[i])) { + gfxWindowsPlatform::GetDLLVersion(eligibleDLLpaths[i].get(), + dllVersion2); + ParseDriverVersion(dllVersion2, &dllNumericVersion2); + } + } + } + + ParseDriverVersion(mDriverVersion[mActiveGPUIndex], &driverNumericVersion); + ParseDriverVersion(u"9.17.10.0"_ns, &knownSafeMismatchVersion); + + // If there's a driver version mismatch, consider this harmful only when + // the driver version is less than knownSafeMismatchVersion. See the + // above comment about crashes with old mismatches. If the GetDllVersion + // call fails, we are not calling it a mismatch. + if ((dllNumericVersion != 0 && dllNumericVersion != driverNumericVersion) || + (dllNumericVersion2 != 0 && + dllNumericVersion2 != driverNumericVersion)) { + if (driverNumericVersion < knownSafeMismatchVersion || + std::max(dllNumericVersion, dllNumericVersion2) < + knownSafeMismatchVersion) { + mHasDriverVersionMismatch = true; + gfxCriticalNoteOnce + << "Mismatched driver versions between the registry " + << NS_ConvertUTF16toUTF8(mDriverVersion[mActiveGPUIndex]).get() + << " and DLL(s) " << NS_ConvertUTF16toUTF8(dllVersion).get() << ", " + << NS_ConvertUTF16toUTF8(dllVersion2).get() << " reported."; + } + } else if (dllNumericVersion == 0 && dllNumericVersion2 == 0) { + // Leave it as an asserting error for now, to see if we can find + // a system that exhibits this kind of a problem internally. + gfxCriticalErrorOnce() + << "Potential driver version mismatch ignored due to missing DLLs " + << NS_ConvertUTF16toUTF8(dllFileName).get() + << " v=" << NS_ConvertUTF16toUTF8(dllVersion).get() << " and " + << NS_ConvertUTF16toUTF8(dllFileName2).get() + << " v=" << NS_ConvertUTF16toUTF8(dllVersion2).get(); + } + } + + // Get monitor information + 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; + } + + DisplayInfo displayInfo; + + displayInfo.mScreenWidth = mode.dmPelsWidth; + displayInfo.mScreenHeight = mode.dmPelsHeight; + displayInfo.mRefreshRate = mode.dmDisplayFrequency; + displayInfo.mIsPseudoDisplay = + !!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER); + + mDisplayInfo.AppendElement(displayInfo); + } + + const char* spoofedDriverVersionString = + PR_GetEnv("MOZ_GFX_SPOOF_DRIVER_VERSION"); + if (spoofedDriverVersionString) { + mDriverVersion[mActiveGPUIndex].AssignASCII(spoofedDriverVersionString); + } + + const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_VENDOR_ID"); + if (spoofedVendor) { + mAdapterVendorID[mActiveGPUIndex].AssignASCII(spoofedVendor); + } + + const char* spoofedDevice = PR_GetEnv("MOZ_GFX_SPOOF_DEVICE_ID"); + if (spoofedDevice) { + mAdapterDeviceID[mActiveGPUIndex].AssignASCII(spoofedDevice); + } + + AddCrashReportAnnotations(); + + return rv; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) { + aAdapterDescription = mDeviceString[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) { + aAdapterDescription = mDeviceString[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) { + uint32_t result = 0; + if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(), + L"HardwareInformation.qwMemorySize", result, + REG_QWORD)) || + result == 0) { + if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(), + L"HardwareInformation.MemorySize", result, + REG_DWORD))) { + result = 0; + } + } + *aAdapterRAM = result; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) { + uint32_t result = 0; + if (mHasDualGPU) { + if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(), + L"HardwareInformation.qwMemorySize", result, + REG_QWORD)) || + result == 0) { + if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(), + L"HardwareInformation.MemorySize", result, + REG_DWORD))) { + result = 0; + } + } + } + *aAdapterRAM = result; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) { + if (NS_FAILED(GetKeyValue(mDeviceKey[mActiveGPUIndex].get(), + L"InstalledDisplayDrivers", aAdapterDriver, + REG_MULTI_SZ))) + aAdapterDriver = L"Unknown"; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) { + if (!mHasDualGPU) { + aAdapterDriver.Truncate(); + } else if (NS_FAILED(GetKeyValue(mDeviceKey[1 - mActiveGPUIndex].get(), + L"InstalledDisplayDrivers", aAdapterDriver, + REG_MULTI_SZ))) { + aAdapterDriver = L"Unknown"; + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) { + aAdapterDriverVendor.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) { + aAdapterDriverVersion = mDriverVersion[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) { + aAdapterDriverDate = mDriverDate[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) { + aAdapterDriverVendor.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) { + aAdapterDriverVersion = mDriverVersion[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) { + aAdapterDriverDate = mDriverDate[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) { + aAdapterVendorID = mAdapterVendorID[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) { + aAdapterVendorID = mAdapterVendorID[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) { + aAdapterDeviceID = mAdapterDeviceID[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) { + aAdapterDeviceID = mAdapterDeviceID[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) { + aAdapterSubsysID = mAdapterSubsysID[mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) { + aAdapterSubsysID = mAdapterSubsysID[1 - mActiveGPUIndex]; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) { + // This is never the case, as the active GPU ends up being + // the first one. It should probably be removed. + *aIsGPU2Active = false; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) { + for (auto displayInfo : mDisplayInfo) { + nsString value; + value.AppendPrintf("%dx%d@%dHz %s", displayInfo.mScreenWidth, + displayInfo.mScreenHeight, displayInfo.mRefreshRate, + displayInfo.mIsPseudoDisplay ? "Pseudo Display" : ""); + + aDisplayInfo.AppendElement(value); + } + + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) { + for (auto displayInfo : mDisplayInfo) { + aDisplayWidth.AppendElement((uint32_t)displayInfo.mScreenWidth); + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) { + for (auto displayInfo : mDisplayInfo) { + aDisplayHeight.AppendElement((uint32_t)displayInfo.mScreenHeight); + } + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* Cisco's VPN software can cause corruption of the floating point state. + * Make a note of this in our crash reports so that some weird crashes + * make more sense */ +static void CheckForCiscoVPN() { + LONG result; + HKEY key; + /* This will give false positives, but hopefully no false negatives */ + result = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Cisco Systems\\VPN Client", + 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) { + RegCloseKey(key); + CrashReporter::AppendAppNotesToCrashReport("Cisco VPN\n"_ns); + } +} + +void GfxInfo::AddCrashReportAnnotations() { + CheckForCiscoVPN(); + + if (mHasDriverVersionMismatch) { + CrashReporter::AppendAppNotesToCrashReport("DriverVersionMismatch\n"_ns); + } + + nsString deviceID, vendorID, driverVersion, subsysID; + nsCString narrowDeviceID, narrowVendorID, narrowDriverVersion, narrowSubsysID; + + GetAdapterDeviceID(deviceID); + CopyUTF16toUTF8(deviceID, narrowDeviceID); + GetAdapterVendorID(vendorID); + CopyUTF16toUTF8(vendorID, narrowVendorID); + GetAdapterDriverVersion(driverVersion); + CopyUTF16toUTF8(driverVersion, narrowDriverVersion); + GetAdapterSubsysID(subsysID); + CopyUTF16toUTF8(subsysID, narrowSubsysID); + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID, + narrowVendorID); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID, + narrowDeviceID); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::AdapterDriverVersion, narrowDriverVersion); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterSubsysID, + narrowSubsysID); + + /* Add an App Note, this contains extra information. */ + nsAutoCString note; + + // TODO: We should probably convert this into a proper annotation + if (vendorID == GfxDriverInfo::GetDeviceVendor(DeviceVendor::All)) { + /* if we didn't find a valid vendorID lets append the mDeviceID string to + * try to find out why */ + LossyAppendUTF16toASCII(mDeviceID[mActiveGPUIndex], note); + note.AppendLiteral(", "); + LossyAppendUTF16toASCII(mDeviceKeyDebug, note); + } + note.AppendLiteral("\n"); + + if (mHasDualGPU) { + nsString deviceID2, vendorID2, subsysID2; + nsAutoString adapterDriverVersionString2; + nsCString narrowDeviceID2, narrowVendorID2, narrowSubsysID2; + + // Make a slight difference between the two cases so that we + // can see it in the crash reports. It may come in handy. + if (mActiveGPUIndex == 1) { + note.AppendLiteral("Has dual GPUs. GPU-#2: "); + } else { + note.AppendLiteral("Has dual GPUs. GPU #2: "); + } + GetAdapterDeviceID2(deviceID2); + CopyUTF16toUTF8(deviceID2, narrowDeviceID2); + GetAdapterVendorID2(vendorID2); + CopyUTF16toUTF8(vendorID2, narrowVendorID2); + GetAdapterDriverVersion2(adapterDriverVersionString2); + GetAdapterSubsysID(subsysID2); + CopyUTF16toUTF8(subsysID2, narrowSubsysID2); + note.AppendLiteral("AdapterVendorID2: "); + note.Append(narrowVendorID2); + note.AppendLiteral(", AdapterDeviceID2: "); + note.Append(narrowDeviceID2); + note.AppendLiteral(", AdapterSubsysID2: "); + note.Append(narrowSubsysID2); + note.AppendLiteral(", AdapterDriverVersion2: "); + note.Append(NS_LossyConvertUTF16toASCII(adapterDriverVersionString2)); + } + CrashReporter::AppendAppNotesToCrashReport(note); +} + +static OperatingSystem WindowsVersionToOperatingSystem( + int32_t aWindowsVersion) { + switch (aWindowsVersion) { + case kWindows7: + return OperatingSystem::Windows7; + case kWindows8: + return OperatingSystem::Windows8; + case kWindows8_1: + return OperatingSystem::Windows8_1; + case kWindows10: + return OperatingSystem::Windows10; + case kWindowsUnknown: + default: + return OperatingSystem::Unknown; + } +} + +static bool OnlyAllowFeatureOnWhitelistedVendor(int32_t aFeature) { + switch (aFeature) { + // The GPU process doesn't need hardware acceleration and can run on + // devices that we normally block from not being on our whitelist. + case nsIGfxInfo::FEATURE_GPU_PROCESS: + // We can mostly assume that ANGLE will work + case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE: + // Software WebRender is our Basic compositor replacement. It needs to + // always work. + case nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE: + return false; + default: + return true; + } +} + +// Return true if the CPU supports AVX, but the operating system does not. +#if defined(_M_X64) +static inline bool DetectBrokenAVX() { + int regs[4]; + __cpuid(regs, 0); + if (regs[0] == 0) { + // Level not supported. + return false; + } + + __cpuid(regs, 1); + + const unsigned AVX = 1u << 28; + const unsigned XSAVE = 1u << 26; + if ((regs[2] & (AVX | XSAVE)) != (AVX | XSAVE)) { + // AVX is not supported on this CPU. + return false; + } + + const unsigned OSXSAVE = 1u << 27; + if ((regs[2] & OSXSAVE) != OSXSAVE) { + // AVX is supported, but the OS didn't enable it. + // This can be forced via bcdedit /set xsavedisable 1. + return true; + } + + const unsigned AVX_CTRL_BITS = (1 << 1) | (1 << 2); + return (xgetbv(0) & AVX_CTRL_BITS) != AVX_CTRL_BITS; +} +#endif + +const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() { + if (!sDriverInfo->Length()) { + /* + * It should be noted here that more specialized rules on certain features + * should be inserted -before- more generalized restriction. As the first + * match for feature/OS/device found in the list will be used for the final + * blocklisting call. + */ + + /* + * NVIDIA entries + */ + /* + * The last 5 digit of the NVIDIA driver version maps to the version that + * NVIDIA uses. The minor version (15, 16, 17) corresponds roughtly to the + * OS (Vista, Win7, Win7) but they show up in smaller numbers across all + * OS versions (perhaps due to OS upgrades). So we want to support + * October 2009+ drivers across all these minor versions. + * + * 187.45 (late October 2009) and earlier contain a bug which can cause us + * to crash on shutdown. + */ + APPEND_TO_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::NvidiaAll, + GfxDriverInfo::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::Windows7, DeviceFamily::NvidiaAll, + 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::Windows7, DeviceFamily::NvidiaAll, + 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, DeviceFamily::AtiAll, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN, V(8, 56, 1, 15), "FEATURE_FAILURE_AMD1", "8.56.1.15"); + + // Bug 1099252 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::AtiAll, + GfxDriverInfo::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, DeviceFamily::AtiAll, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(8, 783, 2, 2000), "FEATURE_FAILURE_BUG_1118695"); + + // Bug 1587155 + // + // There are a several reports of strange rendering corruptions with this + // driver version, with and without webrender. We weren't able to + // reproduce these problems, but the users were able to update their + // drivers and it went away. So just to be safe, let's blocklist all + // gpu use with this particular (very old) driver, restricted + // to Win10 since we only have reports from that platform. + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows10, DeviceFamily::AtiAll, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_EQUAL, V(22, 19, 162, 4), "FEATURE_FAILURE_BUG_1587155"); + + // Bug 1198815 + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(15, 200, 0, 0), V(15, 200, 1062, 1004), "FEATURE_FAILURE_BUG_1198815", + "15.200.0.0-15.200.1062.1004"); + + // Bug 1267970 + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows10, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(15, 200, 0, 0), V(15, 301, 2301, 1002), "FEATURE_FAILURE_BUG_1267970", + "15.200.0.0-15.301.2301.1002"); + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows10, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(16, 100, 0, 0), V(16, 300, 2311, 0), "FEATURE_FAILURE_BUG_1267970", + "16.100.0.0-16.300.2311.0"); + + /* + * Bug 783517 - crashes in AMD driver on Windows 8 + */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows8, DeviceFamily::AtiAll, + GfxDriverInfo::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.*.*"); + + /* + * Bug 1599981 - crashes in AMD driver on Windows 10 + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows10, DeviceFamily::RadeonCaicos, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, + V(15, 301, 1901, 0), "FEATURE_FAILURE_BUG_1599981"); + + /* OpenGL on any ATI/AMD hardware is discouraged + * See: + * bug 619773 - WebGL: Crash with blue screen : "NMI: Parity Check / Memory + * Parity Error" bugs 584403, 584404, 620924 - crashes in atioglxx + * + many complaints about incorrect rendering + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_OGL_ATI_DIS"); + +/* + * Intel entries + */ + +/* The driver versions used here come from bug 594877. They might not + * be particularly relevant anymore. + */ +#define IMPLEMENT_INTEL_DRIVER_BLOCKLIST(winVer, devFamily, driverVer, ruleId) \ + APPEND_TO_DRIVER_BLOCKLIST2(winVer, devFamily, GfxDriverInfo::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, devFamily, nsIGfxInfo::FEATURE_DIRECT2D, \ + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, \ + DRIVER_BUILD_ID_LESS_THAN, driverVer, ruleId) + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, + DeviceFamily::IntelGMA500, 2026, + "FEATURE_FAILURE_594877_7"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D( + OperatingSystem::Windows7, DeviceFamily::IntelGMA900, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_594877_8"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, + DeviceFamily::IntelGMA950, 1930, + "FEATURE_FAILURE_594877_9"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, + DeviceFamily::IntelGMA3150, 2117, + "FEATURE_FAILURE_594877_10"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D(OperatingSystem::Windows7, + DeviceFamily::IntelGMAX3000, 1930, + "FEATURE_FAILURE_594877_11"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST_D2D( + OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge, + 2202, "FEATURE_FAILURE_594877_12"); + + /* Disable Direct2D on Intel GMAX4500 devices because of rendering + * corruption discovered in bug 1180379. These seems to affect even the most + * recent drivers. We're black listing all of the devices to be safe even + * though we've only confirmed the issue on the G45 + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelGMAX4500HD, + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_1180379"); + + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMA500, V(5, 0, 0, 2026), + "FEATURE_FAILURE_INTEL_16"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMA900, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_INTEL_17"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMA950, + V(8, 15, 10, 1930), "FEATURE_FAILURE_INTEL_18"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMA3150, + V(8, 14, 10, 1972), "FEATURE_FAILURE_INTEL_19"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMAX3000, + V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_20"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD, + V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_21"); + IMPLEMENT_INTEL_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge, + V(7, 15, 10, 1666), "FEATURE_FAILURE_INTEL_22"); + + // Bug 1074378 + APPEND_TO_DRIVER_BLOCKLIST( + OperatingSystem::Windows7, DeviceFamily::IntelGMAX4500HD, + GfxDriverInfo::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, DeviceFamily::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, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_INTEL_OGL_DIS"); + + /** + * Disable acceleration on Intel HD 3000 for graphics drivers + * <= 8.15.10.2321. See bug 1018278 and bug 1060736. + */ + APPEND_TO_DRIVER_BLOCKLIST( + OperatingSystem::Windows, DeviceFamily::IntelSandyBridge, + GfxDriverInfo::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 Haswell for graphics drivers build id <= + * 4578. See bug 1432610 + */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + DeviceFamily::IntelHaswell, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4578, + "FEATURE_FAILURE_BUG_1432610"); + + /* Disable D2D on Win7 on Intel HD Graphics on driver <= 8.15.10.2302 + * See bug 806786 + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::IntelMobileHDGraphics, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, + V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_806786"); + + /* Disable D2D on Win8 on Intel HD Graphics on driver <= 8.15.10.2302 + * See bug 804144 and 863683 + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows8, DeviceFamily::IntelMobileHDGraphics, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, + V(8, 15, 10, 2302), "FEATURE_FAILURE_BUG_804144"); + + /* Disable D2D on Win7 on Intel HD Graphics on driver == 8.15.10.2418 + * See bug 1433790 + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::IntelHDGraphicsToSandyBridge, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL, + V(8, 15, 10, 2418), "FEATURE_FAILURE_BUG_1433790"); + + /* Disable D3D11 layers on Intel G41 express graphics and Intel GM965, Intel + * X3100, for causing device resets. See bug 1116812. + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::Bug1116812, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1116812"); + + /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared + * handle for textures. See bug 1207665. Additionally block D2D so we don't + * accidentally use WARP. + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::Bug1207665, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1207665_1"); + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::Bug1207665, + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_BUG_1207665_2"); + + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows10, DeviceFamily::QualcommAll, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_QUALCOMM"); + + // Bug 1548410. Disable hardware accelerated video decoding on + // Qualcomm drivers used on Windows on ARM64 which are known to + // cause BSOD's and output suprious green frames while decoding video. + // Bug 1592826 expands the blocklist. + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows10, DeviceFamily::QualcommAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, + V(25, 18, 10440, 0), "FEATURE_FAILURE_BUG_1592826"); + + /* Disable D2D on AMD Catalyst 14.4 until 14.6 + * See bug 984488 + */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_DIRECT2D, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BETWEEN_INCLUSIVE_START, V(14, 1, 0, 0), V(14, 2, 0, 0), + "FEATURE_FAILURE_BUG_984488_1", "ATI Catalyst 14.6+"); + + /* Disable D3D9 layers on NVIDIA 6100/6150/6200 series due to glitches + * whilst scrolling. See bugs: 612007, 644787 & 645872. + */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::NvidiaBlockD3D9Layers, + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_612007"); + + /* Microsoft RemoteFX; blocked less than 6.2.0.0 */ + APPEND_TO_DRIVER_BLOCKLIST( + OperatingSystem::Windows, DeviceFamily::MicrosoftAll, + GfxDriverInfo::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, DeviceFamily::Nvidia310M, + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_BUG_1008759"); + + /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows10, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL, + V(15, 200, 1006, 0), "FEATURE_FAILURE_BUG_1139503"); + + /* Bug 1213107: D3D9 crashes with ATI cards on Windows 7. */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows7, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_1", + "Radeon driver > 8.862.6.5000"); + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows7, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(8, 861, 0, 0), V(8, 862, 6, 5000), "FEATURE_FAILURE_BUG_1213107_2", + "Radeon driver > 8.862.6.5000"); + + /* This may not be needed at all */ + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::Bug1155608, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, + V(8, 15, 10, 2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING"); + + /* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */ + APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, DeviceFamily::IntelAll, + 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, DeviceFamily::Nvidia8800GTS, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL, + V(9, 18, 13, 4052), "FEATURE_FAILURE_BUG_1203199_2"); + + /* Bug 1137716: XXX this should really check for the matching Intel piece as + * well. Unfortunately, we don't have the infrastructure to do that */ + APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2( + OperatingSystem::Windows7, DeviceFamily::Bug1137716, + GfxDriverInfo::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, DeviceFamily::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, DeviceFamily::IntelGMAX4500HD, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_BUG_1336710"); + + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::WindowsXP, DeviceFamily::IntelHDGraphicsToSandyBridge, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_FAILURE_BUG_1336710"); + + /* Bug 1304360: Graphical artifacts with D3D9 on Windows 7. */ + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows7, + DeviceFamily::IntelGMAX3000, + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 1749, + "FEATURE_FAILURE_INTEL_W7_D3D9_LAYERS"); + +#if defined(_M_X64) + if (DetectBrokenAVX()) { + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1403353"); + } +#endif + + //////////////////////////////////// + // WebGL + + // Older than 5-15-2016 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, V(16, 200, 1010, 1002), "WEBGL_NATIVE_GL_OLD_AMD"); + + // Older than 11-18-2015 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_BUILD_ID_LESS_THAN, 4331, "WEBGL_NATIVE_GL_OLD_INTEL"); + + // Older than 2-23-2016 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::NvidiaAll, + nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_DISCOURAGED, + DRIVER_LESS_THAN, V(10, 18, 13, 6200), "WEBGL_NATIVE_GL_OLD_NVIDIA"); + + //////////////////////////////////// + // FEATURE_DX_INTEROP2 + + // All AMD. + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_DX_INTEROP2, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "DX_INTEROP2_AMD_CRASH"); + + //////////////////////////////////// + // FEATURE_D3D11_KEYED_MUTEX + + // bug 1359416 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge, + nsIGfxInfo::FEATURE_D3D11_KEYED_MUTEX, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, + GfxDriverInfo::allDriverVersions, "FEATURE_FAILURE_BUG_1359416"); + + // bug 1419264 + APPEND_TO_DRIVER_BLOCKLIST_RANGE( + OperatingSystem::Windows7, DeviceFamily::NvidiaAll, + nsIGfxInfo::FEATURE_ADVANCED_LAYERS, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_BETWEEN_INCLUSIVE, + V(23, 21, 13, 8569), V(23, 21, 13, 9135), "FEATURE_FAILURE_BUG_1419264", + "Windows 10"); + + // Bug 1447141, for causing device creation crashes. + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::Bug1447141, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_EQUAL, V(15, 201, 2201, 0), "FEATURE_FAILURE_BUG_1447141_1"); + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows7, DeviceFamily::Bug1447141, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_EQUAL, V(15, 201, 1701, 0), "FEATURE_FAILURE_BUG_1447141_1"); + + // bug 1457758 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::NvidiaAll, + GfxDriverInfo::allFeatures, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_EQUAL, V(24, 21, 13, 9731), "FEATURE_FAILURE_BUG_1457758"); + + //////////////////////////////////// + // FEATURE_DX_NV12 + + // Bug 1437334 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelHDGraphicsToSandyBridge, + nsIGfxInfo::FEATURE_DX_NV12, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 4459, + "FEATURE_BLOCKED_DRIVER_VERSION"); + + //////////////////////////////////// + // FEATURE_DX_P010 + + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::NvidiaAll, + nsIGfxInfo::FEATURE_DX_P010, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_UNQUALIFIED_P010_NVIDIA"); + + //////////////////////////////////// + // FEATURE_WEBRENDER + + // Block some specific Nvidia cards for being too low-powered. + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::NvidiaBlockWebRender, + nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions, + "FEATURE_UNQUALIFIED_WEBRENDER_NVIDIA_BLOCKED"); + + // Block 8.56.1.15/16 + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_WEBRENDER, + nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + DRIVER_LESS_THAN_OR_EQUAL, V(8, 56, 1, 16), + "CRASHY_DRIVERS_BUG_1678808"); + + //////////////////////////////////// + // FEATURE_WEBRENDER - ALLOWLIST + APPEND_TO_DRIVER_BLOCKLIST2_EXT( + OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All, + DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All, + DeviceFamily::AmdR600, nsIGfxInfo::FEATURE_WEBRENDER, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_AMD_R600"); + + APPEND_TO_DRIVER_BLOCKLIST2_EXT( + OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All, + DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All, + DeviceFamily::AtiRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_DESKTOP_AMD"); + + APPEND_TO_DRIVER_BLOCKLIST2_EXT( + OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All, + DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All, + DeviceFamily::NvidiaRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_NV"); + + APPEND_TO_DRIVER_BLOCKLIST2_EXT( + OperatingSystem::Windows, ScreenSizeStatus::All, BatteryStatus::All, + DesktopEnvironment::All, WindowProtocol::All, DriverVendor::All, + DeviceFamily::IntelRolloutWebRender, nsIGfxInfo::FEATURE_WEBRENDER, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_INTEL"); + + //////////////////////////////////// + // FEATURE_WEBRENDER_COMPOSITOR + +#ifndef EARLY_BETA_OR_EARLIER + // See also bug 161687 + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_EQUAL, V(24, 20, 100, 6293), + "FEATURE_FAILURE_BUG_1602511"); + + APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Windows, DeviceFamily::AtiAll, + nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN_OR_EQUAL, V(8, 17, 10, 1129), + "FEATURE_FAILURE_CHROME_BUG_800950"); +#endif + + // WebRender is unable to use scissored clears in some cases + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelAll, + nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1603515"); + + //////////////////////////////////// + // FEATURE_WEBRENDER_SOFTWARE + + // TODO(aosmond): Bug 1678044 - wdspec tests ignore enable/disable-webrender + // Once the test infrastructure is fixed, we can remove this blocklist rule + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::AmazonAll, + nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1678044"); + + //////////////////////////////////// + // FEATURE_WEBRENDER_SOFTWARE - ALLOWLIST +#ifdef EARLY_BETA_OR_EARLIER +# if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__amd64__) + APPEND_TO_DRIVER_BLOCKLIST2_EXT( + OperatingSystem::Windows, ScreenSizeStatus::SmallAndMedium, + BatteryStatus::All, DesktopEnvironment::All, WindowProtocol::All, + DriverVendor::All, DeviceFamily::All, + nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE, + nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_ROLLOUT_NIGHTLY_SOFTWARE_WR_S_M_SCRN"); +# endif +#endif + } + return *sDriverInfo; +} + +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; + + if (sShutdownOccurred) { + return NS_OK; + } + + // Don't evaluate special cases if we're checking the downloaded blocklist. + if (!aDriverInfo.Length()) { + nsAutoString adapterVendorID; + nsAutoString adapterDeviceID; + nsAutoString adapterDriverVersionString; + if (NS_FAILED(GetAdapterVendorID(adapterVendorID)) || + NS_FAILED(GetAdapterDeviceID(adapterDeviceID)) || + NS_FAILED(GetAdapterDriverVersion(adapterDriverVersionString))) { + aFailureId = "FEATURE_FAILURE_GET_ADAPTER"; + *aStatus = FEATURE_BLOCKED_DEVICE; + return NS_OK; + } + + if (OnlyAllowFeatureOnWhitelistedVendor(aFeature) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel), + nsCaseInsensitiveStringComparator) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA), + nsCaseInsensitiveStringComparator) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI), + nsCaseInsensitiveStringComparator) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::Microsoft), + nsCaseInsensitiveStringComparator) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::Parallels), + nsCaseInsensitiveStringComparator) && + !adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::Qualcomm), + nsCaseInsensitiveStringComparator) && + // FIXME - these special hex values are currently used in xpcshell tests + // introduced by bug 625160 patch 8/8. Maybe these tests need to be + // adjusted now that we're only whitelisting intel/ati/nvidia. + !adapterVendorID.LowerCaseEqualsLiteral("0xabcd") && + !adapterVendorID.LowerCaseEqualsLiteral("0xdcba") && + !adapterVendorID.LowerCaseEqualsLiteral("0xabab") && + !adapterVendorID.LowerCaseEqualsLiteral("0xdcdc")) { + if (adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::MicrosoftHyperV), + nsCaseInsensitiveStringComparator) || + adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::VMWare), + nsCaseInsensitiveStringComparator) || + adapterVendorID.Equals( + GfxDriverInfo::GetDeviceVendor(DeviceVendor::VirtualBox), + nsCaseInsensitiveStringComparator)) { + aFailureId = "FEATURE_FAILURE_VM_VENDOR"; + } else if (adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor( + DeviceVendor::MicrosoftBasic), + nsCaseInsensitiveStringComparator)) { + aFailureId = "FEATURE_FAILURE_MICROSOFT_BASIC_VENDOR"; + } else if (adapterVendorID.IsEmpty()) { + aFailureId = "FEATURE_FAILURE_EMPTY_DEVICE_VENDOR"; + } else { + aFailureId = "FEATURE_FAILURE_UNKNOWN_DEVICE_VENDOR"; + } + *aStatus = FEATURE_BLOCKED_DEVICE; + return NS_OK; + } + + uint64_t driverVersion; + if (!ParseDriverVersion(adapterDriverVersionString, &driverVersion)) { + aFailureId = "FEATURE_FAILURE_PARSE_DRIVER"; + *aStatus = FEATURE_BLOCKED_DRIVER_VERSION; + return NS_OK; + } + + 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 (auto displayInfo : mDisplayInfo) { + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted<JS::Value> screenWidth(aCx, + JS::Int32Value(displayInfo.mScreenWidth)); + JS_SetProperty(aCx, obj, "screenWidth", screenWidth); + + JS::Rooted<JS::Value> screenHeight( + aCx, JS::Int32Value(displayInfo.mScreenHeight)); + JS_SetProperty(aCx, obj, "screenHeight", screenHeight); + + JS::Rooted<JS::Value> refreshRate(aCx, + JS::Int32Value(displayInfo.mRefreshRate)); + JS_SetProperty(aCx, obj, "refreshRate", refreshRate); + + JS::Rooted<JS::Value> pseudoDisplay( + aCx, JS::BooleanValue(displayInfo.mIsPseudoDisplay)); + 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::FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING); + if (!InitFeatureObject(aCx, aObj, "d3d11", d3d11, &obj)) { + return; + } + if (d3d11.GetValue() == gfx::FeatureStatus::Available) { + DeviceManagerDx* dm = DeviceManagerDx::Get(); + JS::Rooted<JS::Value> val(aCx, + JS::Int32Value(dm->GetCompositorFeatureLevel())); + JS_SetProperty(aCx, obj, "version", val); + + val = JS::BooleanValue(dm->IsWARP()); + JS_SetProperty(aCx, obj, "warp", val); + + val = JS::BooleanValue(dm->TextureSharingWorks()); + JS_SetProperty(aCx, obj, "textureSharing", val); + + bool blocklisted = false; + if (nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo()) { + int32_t status; + nsCString discardFailureId; + if (SUCCEEDED( + gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + discardFailureId, &status))) { + blocklisted = (status != nsIGfxInfo::FEATURE_STATUS_OK); + } + } + + val = JS::BooleanValue(blocklisted); + JS_SetProperty(aCx, obj, "blocklisted", val); + } + + gfx::FeatureState& d2d = gfxConfig::GetFeature(Feature::DIRECT2D); + if (!InitFeatureObject(aCx, aObj, "d2d", d2d, &obj)) { + return; + } + { + const char* version = "1.1"; + JS::Rooted<JSString*> str(aCx, JS_NewStringCopyZ(aCx, version)); + JS::Rooted<JS::Value> val(aCx, JS::StringValue(str)); + JS_SetProperty(aCx, obj, "version", val); + } +} + +#ifdef DEBUG + +// Implement nsIGfxInfoDebug + +NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) { + mAdapterVendorID[mActiveGPUIndex] = aVendorID; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) { + mAdapterDeviceID[mActiveGPUIndex] = aDeviceID; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) { + mDriverVersion[mActiveGPUIndex] = aDriverVersion; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) { + mWindowsVersion = aVersion; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; } + +#endif diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h new file mode 100644 index 0000000000..b2c02162fe --- /dev/null +++ b/widget/windows/GfxInfo.h @@ -0,0 +1,114 @@ +/* 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 GetEmbeddedInFirefoxReality( + bool* aEmbeddedInFirefoxReality) override; + NS_IMETHOD GetHasBattery(bool* aHasBattery) override; + NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override; + NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override; + NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override; + NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override; + NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override; + NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override; + NS_IMETHOD GetAdapterDriverVersion2( + nsAString& aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override; + NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override; + NS_IMETHOD GetDisplayInfo(nsTArray<nsString>& aDisplayInfo) override; + NS_IMETHOD GetDisplayWidth(nsTArray<uint32_t>& aDisplayWidth) override; + NS_IMETHOD GetDisplayHeight(nsTArray<uint32_t>& aDisplayHeight) override; + NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override; + using GfxInfoBase::GetFeatureStatus; + using GfxInfoBase::GetFeatureSuggestedDriverVersion; + + nsresult Init() override; + NS_IMETHOD_(int32_t) GetMaxRefreshRate(bool* aMixed) override; + + uint32_t OperatingSystemVersion() override { return mWindowsVersion; } + uint32_t OperatingSystemBuild() override { return mWindowsBuildNumber; } + + 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: + struct DisplayInfo { + uint32_t mScreenWidth; + uint32_t mScreenHeight; + uint32_t mRefreshRate; + bool mIsPseudoDisplay; + }; + + private: + void AddCrashReportAnnotations(); + + nsString mDeviceString[2]; + nsString mDeviceID[2]; + nsString mDriverVersion[2]; + nsString mDriverDate[2]; + nsString mDeviceKey[2]; + nsString mDeviceKeyDebug; + nsString mAdapterVendorID[2]; + nsString mAdapterDeviceID[2]; + nsString mAdapterSubsysID[2]; + uint32_t mWindowsVersion; + uint32_t mWindowsBuildNumber; + uint32_t mActiveGPUIndex; // This must be 0 or 1 + nsTArray<DisplayInfo> mDisplayInfo; + bool mHasDualGPU; + bool mHasDriverVersionMismatch; + bool mHasBattery; +}; + +} // 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..8fe7138609 --- /dev/null +++ b/widget/windows/IEnumFE.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IEnumFE.h" +#include "nsAlgorithm.h" +#include <algorithm> + +CEnumFormatEtc::CEnumFormatEtc() : mRefCnt(0), mCurrentIdx(0) {} + +// Constructor used by Clone() +CEnumFormatEtc::CEnumFormatEtc(nsTArray<FormatEtc>& aArray) + : mRefCnt(0), mCurrentIdx(0) { + // a deep copy, calls FormatEtc's copy constructor on each + mFormatList.AppendElements(aArray); +} + +CEnumFormatEtc::~CEnumFormatEtc() {} + +/* IUnknown impl. */ + +STDMETHODIMP +CEnumFormatEtc::QueryInterface(REFIID riid, LPVOID* ppv) { + *ppv = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEnumFORMATETC)) + *ppv = (LPVOID)this; + + if (*ppv == nullptr) return E_NOINTERFACE; + + // AddRef any interface we'll return. + ((LPUNKNOWN)*ppv)->AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) +CEnumFormatEtc::AddRef() { + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "CEnumFormatEtc", sizeof(*this)); + return mRefCnt; +} + +STDMETHODIMP_(ULONG) +CEnumFormatEtc::Release() { + uint32_t refReturn; + + refReturn = --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "CEnumFormatEtc"); + + if (mRefCnt == 0) delete this; + + return refReturn; +} + +/* IEnumFORMATETC impl. */ + +STDMETHODIMP +CEnumFormatEtc::Next(ULONG aMaxToFetch, FORMATETC* aResult, + ULONG* aNumFetched) { + // If the method retrieves the number of items requested, the return + // value is S_OK. Otherwise, it is S_FALSE. + + if (aNumFetched) *aNumFetched = 0; + + // aNumFetched can be null if aMaxToFetch is 1 + if (!aNumFetched && aMaxToFetch > 1) return S_FALSE; + + if (!aResult) return S_FALSE; + + // We're done walking the list + if (mCurrentIdx >= mFormatList.Length()) return S_FALSE; + + uint32_t left = mFormatList.Length() - mCurrentIdx; + + if (!aMaxToFetch) return S_FALSE; + + uint32_t count = std::min(static_cast<uint32_t>(aMaxToFetch), left); + + uint32_t idx = 0; + while (count > 0) { + // Copy out to aResult + mFormatList[mCurrentIdx++].CopyOut(&aResult[idx++]); + count--; + } + + if (aNumFetched) *aNumFetched = idx; + + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Skip(ULONG aSkipNum) { + // If the method skips the number of items requested, the return value is + // S_OK. Otherwise, it is S_FALSE. + + if ((mCurrentIdx + aSkipNum) >= mFormatList.Length()) return S_FALSE; + + mCurrentIdx += aSkipNum; + + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Reset(void) { + mCurrentIdx = 0; + return S_OK; +} + +STDMETHODIMP +CEnumFormatEtc::Clone(LPENUMFORMATETC* aResult) { + // Must return a new IEnumFORMATETC interface with the same iterative state. + + if (!aResult) return E_INVALIDARG; + + CEnumFormatEtc* pEnumObj = new CEnumFormatEtc(mFormatList); + + if (!pEnumObj) return E_OUTOFMEMORY; + + pEnumObj->AddRef(); + pEnumObj->SetIndex(mCurrentIdx); + + *aResult = pEnumObj; + + return S_OK; +} + +/* utils */ + +void CEnumFormatEtc::AddFormatEtc(LPFORMATETC aFormat) { + if (!aFormat) return; + FormatEtc* etc = mFormatList.AppendElement(); + // Make a copy of aFormat + if (etc) etc->CopyIn(aFormat); +} + +/* private */ + +void CEnumFormatEtc::SetIndex(uint32_t aIdx) { mCurrentIdx = aIdx; } diff --git a/widget/windows/IEnumFE.h b/widget/windows/IEnumFE.h new file mode 100644 index 0000000000..b8cb6ad9d0 --- /dev/null +++ b/widget/windows/IEnumFE.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IEnumeFE_h__ +#define IEnumeFE_h__ + +/* + * CEnumFormatEtc - implements IEnumFORMATETC + */ + +#include <ole2.h> + +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +// FORMATETC container +class FormatEtc { + public: + FormatEtc() { memset(&mFormat, 0, sizeof(FORMATETC)); } + FormatEtc(const FormatEtc& copy) { CopyIn(©.mFormat); } + ~FormatEtc() { + if (mFormat.ptd) CoTaskMemFree(mFormat.ptd); + } + + void CopyIn(const FORMATETC* aSrc) { + if (!aSrc) { + memset(&mFormat, 0, sizeof(FORMATETC)); + return; + } + mFormat = *aSrc; + if (aSrc->ptd) { + mFormat.ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + *(mFormat.ptd) = *(aSrc->ptd); + } + } + + void CopyOut(LPFORMATETC aDest) { + if (!aDest) return; + *aDest = mFormat; + if (mFormat.ptd) { + aDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + *(aDest->ptd) = *(mFormat.ptd); + } + } + + private: + FORMATETC mFormat; +}; + +/* + * CEnumFormatEtc is created within IDataObject::EnumFormatEtc. This object + * lives on its own, that is, QueryInterface only knows IUnknown and + * IEnumFormatEtc, nothing more. We still use an outer unknown for reference + * counting, because as long as this enumerator lives, the data object should + * live, thereby keeping the application up. + */ + +class CEnumFormatEtc final : public IEnumFORMATETC { + public: + explicit CEnumFormatEtc(nsTArray<FormatEtc>& aArray); + CEnumFormatEtc(); + ~CEnumFormatEtc(); + + // IUnknown impl. + STDMETHODIMP QueryInterface(REFIID riid, LPVOID* ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumFORMATETC impl. + STDMETHODIMP Next(ULONG aMaxToFetch, FORMATETC* aResult, ULONG* aNumFetched); + STDMETHODIMP Skip(ULONG aSkipNum); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(LPENUMFORMATETC* aResult); // Addrefs + + // Utils + void AddFormatEtc(LPFORMATETC aFormat); + + private: + nsTArray<FormatEtc> mFormatList; // Formats + ULONG mRefCnt; // Object reference count + ULONG mCurrentIdx; // Current element + + void SetIndex(uint32_t aIdx); +}; + +#endif //_IENUMFE_H_ diff --git a/widget/windows/IMMHandler.cpp b/widget/windows/IMMHandler.cpp new file mode 100644 index 0000000000..338c3a64fd --- /dev/null +++ b/widget/windows/IMMHandler.cpp @@ -0,0 +1,2384 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "IMMHandler.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "WinIMEHandler.h" +#include "WinUtils.h" +#include "KeyboardLayout.h" +#include <algorithm> + +#include "mozilla/CheckedInt.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/TextEvents.h" +#include "mozilla/WindowsVersion.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: + explicit GetIMEGeneralPropertyName(DWORD aFlags) { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & IME_PROP_AT_CARET) { + AppendLiteral("IME_PROP_AT_CARET"); + } + if (aFlags & IME_PROP_SPECIAL_UI) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_SPECIAL_UI"); + } + if (aFlags & IME_PROP_CANDLIST_START_FROM_1) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_CANDLIST_START_FROM_1"); + } + if (aFlags & IME_PROP_UNICODE) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_UNICODE"); + } + if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT"); + } + if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) { + HandleSeparator(*this); + AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY"); + } + } + virtual ~GetIMEGeneralPropertyName() {} +}; + +class GetIMEUIPropertyName : public nsAutoCString { + public: + explicit GetIMEUIPropertyName(DWORD aFlags) { + if (!aFlags) { + AppendLiteral("no flags"); + return; + } + if (aFlags & UI_CAP_2700) { + AppendLiteral("UI_CAP_2700"); + } + if (aFlags & UI_CAP_ROT90) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROT90"); + } + if (aFlags & UI_CAP_ROTANY) { + HandleSeparator(*this); + AppendLiteral("UI_CAP_ROTANY"); + } + } + virtual ~GetIMEUIPropertyName() {} +}; + +class GetWritingModeName : public nsAutoCString { + public: + explicit GetWritingModeName(const WritingMode& aWritingMode) { + if (!aWritingMode.IsVertical()) { + AssignLiteral("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + AssignLiteral("Vertical (LR)"); + return; + } + AssignLiteral("Vertical (RL)"); + } + virtual ~GetWritingModeName() {} +}; + +class GetReconvertStringLog : public nsAutoCString { + public: + explicit GetReconvertStringLog(RECONVERTSTRING* aReconv) { + AssignLiteral("{ dwSize="); + AppendInt(static_cast<uint32_t>(aReconv->dwSize)); + AppendLiteral(", dwVersion="); + AppendInt(static_cast<uint32_t>(aReconv->dwVersion)); + AppendLiteral(", dwStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwStrLen)); + AppendLiteral(", dwStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwStrOffset)); + AppendLiteral(", dwCompStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwCompStrLen)); + AppendLiteral(", dwCompStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwCompStrOffset)); + AppendLiteral(", dwTargetStrLen="); + AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrLen)); + AppendLiteral(", dwTargetStrOffset="); + AppendInt(static_cast<uint32_t>(aReconv->dwTargetStrOffset)); + AppendLiteral(", result str=\""); + if (aReconv->dwStrLen) { + char16_t* strStart = reinterpret_cast<char16_t*>( + reinterpret_cast<char*>(aReconv) + aReconv->dwStrOffset); + nsDependentString str(strStart, aReconv->dwStrLen); + Append(NS_ConvertUTF16toUTF8(str)); + } + AppendLiteral("\" }"); + } + virtual ~GetReconvertStringLog() {} +}; + +namespace mozilla { +namespace widget { + +static IMMHandler* gIMMHandler = nullptr; + +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; + +#define IMPL_IS_IME_ACTIVE(aReadableName, aActualName) \ + bool IMMHandler::Is##aReadableName##Active() { \ + return sIMEName.Equals(aActualName); \ + } + +IMPL_IS_IME_ACTIVE(ATOK2006, u"ATOK 2006") +IMPL_IS_IME_ACTIVE(ATOK2007, u"ATOK 2007") +IMPL_IS_IME_ACTIVE(ATOK2008, u"ATOK 2008") +IMPL_IS_IME_ACTIVE(ATOK2009, u"ATOK 2009") +IMPL_IS_IME_ACTIVE(ATOK2010, u"ATOK 2010") +// NOTE: Even on Windows for en-US, the name of Google Japanese Input is +// written in Japanese. +IMPL_IS_IME_ACTIVE(GoogleJapaneseInput, + u"Google \x65E5\x672C\x8A9E\x5165\x529B " + u"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB") +IMPL_IS_IME_ACTIVE(Japanist2003, u"Japanist 2003") + +#undef IMPL_IS_IME_ACTIVE + +// static +bool IMMHandler::IsActiveIMEInBlockList() { + if (sIMEName.IsEmpty()) { + return false; + } +#ifdef _WIN64 + // ATOK started to be TIP of TSF since 2011. Older than it, i.e., ATOK 2010 + // and earlier have a lot of problems even for daily use. Perhaps, the + // reason is Win 8 has a lot of changes around IMM-IME support and TSF, + // and ATOK 2010 is released earlier than Win 8. + // ATOK 2006 crashes while converting a word with candidate window. + // ATOK 2007 doesn't paint and resize suggest window and candidate window + // correctly (showing white window or too big window). + // ATOK 2008 and ATOK 2009 crash when user just opens their open state. + // ATOK 2010 isn't installable newly on Win 7 or later, but we have a lot of + // crash reports. + if (IsWin8OrLater() && + (IsATOK2006Active() || IsATOK2007Active() || IsATOK2008Active() || + IsATOK2009Active() || IsATOK2010Active())) { + return true; + } +#endif // #ifdef _WIN64 + return false; +} + +// static +void IMMHandler::EnsureHandlerInstance() { + if (!gIMMHandler) { + gIMMHandler = new IMMHandler(); + } +} + +// static +void IMMHandler::Initialize() { + if (!sWM_MSIME_MOUSE) { + sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE); + } + sAssumeVerticalWritingModeNotSupported = Preferences::GetBool( + "intl.imm.vertical_writing.always_assume_not_supported", false); + InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0)); +} + +// static +void IMMHandler::Terminate() { + if (!gIMMHandler) return; + delete gIMMHandler; + gIMMHandler = nullptr; +} + +// static +bool IMMHandler::IsComposingOnOurEditor() { + return gIMMHandler && gIMMHandler->mIsComposing; +} + +// static +bool IMMHandler::IsComposingWindow(nsWindow* aWindow) { + return gIMMHandler && gIMMHandler->mComposingWindow == aWindow; +} + +// static +bool IMMHandler::IsTopLevelWindowOfComposition(nsWindow* aWindow) { + if (!gIMMHandler || !gIMMHandler->mComposingWindow) { + return false; + } + HWND wnd = gIMMHandler->mComposingWindow->GetWindowHandle(); + return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle(); +} + +// static +bool IMMHandler::ShouldDrawCompositionStringOurselves() { + // If current IME has special UI or its composition window should not + // positioned to caret position, we should now draw composition string + // ourselves. + return !(sIMEProperty & IME_PROP_SPECIAL_UI) && + (sIMEProperty & IME_PROP_AT_CARET); +} + +// static +bool IMMHandler::IsVerticalWritingSupported() { + // Even if IME claims that they support vertical writing mode but it may not + // support vertical writing mode for its candidate window. + if (sAssumeVerticalWritingModeNotSupported) { + return false; + } + // Google Japanese Input doesn't support vertical writing mode. We should + // return false if it's active IME. + if (IsGoogleJapaneseInputActive()) { + return false; + } + return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY)); +} + +// static +void IMMHandler::InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout) { + UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0); + if (IMENameLength) { + // Add room for the terminating null character + sIMEName.SetLength(++IMENameLength); + IMENameLength = + ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.get(), IMENameLength); + // Adjust the length to ignore the terminating null character + sIMEName.SetLength(IMENameLength); + } else { + sIMEName.Truncate(); + } + + WORD langID = LOWORD(aKeyboardLayout); + ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT), + LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, + (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR)); + sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY); + sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI); + + // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API. + // For hacking some bugs of some TIP, we should set an IME name from the + // pref. + if (sCodePage == 932 && sIMEName.IsEmpty()) { + Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as", + sIMEName); + } + + // Whether the IME supports vertical writing mode might be changed or + // some IMEs may need specific font for their UI. Therefore, we should + // update composition font forcibly here. + if (aWindow) { + MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true); + } + + MOZ_LOG(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 +IMENotificationRequests IMMHandler::GetIMENotificationRequests() { + return IMENotificationRequests( + IMENotificationRequests::NOTIFY_POSITION_CHANGE | + IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR); +} + +// used for checking the lParam of WM_IME_COMPOSITION +#define IS_COMPOSING_LPARAM(lParam) \ + ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS)) +#define IS_COMMITTING_LPARAM(lParam) ((lParam)&GCS_RESULTSTR) +// Some IMEs (e.g., the standard IME for Korean) don't have caret position, +// then, we should not set caret position to compositionchange event. +#define NO_IME_CARET -1 + +IMMHandler::IMMHandler() + : mComposingWindow(nullptr), + mCursorPosition(NO_IME_CARET), + mCompositionStart(0), + mIsComposing(false) { + MOZ_LOG(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", + GetBoolName(aFocus), aWindow, GetBoolName(sHasFocus), + GetBoolName(IsComposingWindow(aWindow)), + GetBoolName(aWindow->Destroyed()))); + + if (!aFocus) { + IMEHandler::MaybeDestroyNativeCaret(); + if (IsComposingWindow(aWindow) && aWindow->Destroyed()) { + CancelComposition(aWindow); + } + } + if (gIMMHandler) { + gIMMHandler->mSelection.Clear(); + } + sHasFocus = aFocus; +} + +// static +void IMMHandler::OnUpdateComposition(nsWindow* aWindow) { + if (!gIMMHandler) { + return; + } + + IMEContext context(aWindow); + gIMMHandler->SetIMERelatedWindowsPos(aWindow, context); +} + +// static +void IMMHandler::OnSelectionChange(nsWindow* aWindow, + const IMENotification& aIMENotification, + bool aIsIMMActive) { + if (!aIMENotification.mSelectionChangeData.mCausedByComposition && + aIsIMMActive) { + MaybeAdjustCompositionFont( + aWindow, aIMENotification.mSelectionChangeData.GetWritingMode()); + } + // MaybeAdjustCompositionFont() may create gIMMHandler. So, check it + // after a call of MaybeAdjustCompositionFont(). + if (gIMMHandler) { + gIMMHandler->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. + + aResult.mResult = 0; + switch (msg) { + case WM_INPUTLANGCHANGE: + return ProcessInputLangChangeMessage(aWindow, wParam, lParam, aResult); + case WM_IME_STARTCOMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEStartComposition(aWindow, aResult); + case WM_IME_COMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEComposition(aWindow, wParam, lParam, aResult); + case WM_IME_ENDCOMPOSITION: + EnsureHandlerInstance(); + return gIMMHandler->OnIMEEndComposition(aWindow, aResult); + case WM_IME_CHAR: + return OnIMEChar(aWindow, wParam, lParam, aResult); + case WM_IME_NOTIFY: + return OnIMENotify(aWindow, wParam, lParam, aResult); + case WM_IME_REQUEST: + EnsureHandlerInstance(); + return gIMMHandler->OnIMERequest(aWindow, wParam, lParam, aResult); + case WM_IME_SELECT: + return OnIMESelect(aWindow, wParam, lParam, aResult); + case WM_IME_SETCONTEXT: + return OnIMESetContext(aWindow, wParam, lParam, aResult); + case WM_KEYDOWN: + return OnKeyDownEvent(aWindow, wParam, lParam, aResult); + case WM_CHAR: + if (!gIMMHandler) { + return false; + } + return gIMMHandler->OnChar(aWindow, wParam, lParam, aResult); + default: + return false; + }; +} + +/**************************************************************************** + * message handlers + ****************************************************************************/ + +void IMMHandler::OnInputLangChange(nsWindow* aWindow, WPARAM wParam, + LPARAM lParam, MSGResult& aResult) { + MOZ_LOG(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; +} + +/**************************************************************************** + * others + ****************************************************************************/ + +TextEventDispatcher* IMMHandler::GetTextEventDispatcherFor(nsWindow* aWindow) { + return aWindow == mComposingWindow && mDispatcher + ? mDispatcher.get() + : aWindow->GetTextEventDispatcher(); +} + +void IMMHandler::HandleStartComposition(nsWindow* aWindow, + const IMEContext& aContext) { + MOZ_ASSERT(!mIsComposing, + "HandleStartComposition is called but mIsComposing is TRUE"); + + 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() : "")); + + IMEHandler::MaybeDestroyNativeCaret(); + + 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 queryEditorRectEvent(true, eQueryEditorRect, aWindow); + aWindow->InitEvent(queryEditorRectEvent); + DispatchEvent(aWindow, queryEditorRectEvent); + if (NS_WARN_IF(queryEditorRectEvent.Failed())) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleQueryCharPosition, eQueryEditorRect failed")); + ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument); + } else { + LayoutDeviceIntRect editorRectInWindow = queryEditorRectEvent.mReply->mRect; + nsWindow* window = !!queryEditorRectEvent.mReply->mFocusedWidget + ? static_cast<nsWindow*>( + queryEditorRectEvent.mReply->mFocusedWidget) + : aWindow; + LayoutDeviceIntRect editorRectInScreen; + ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen); + ::SetRect(&pCharPosition->rcDocument, editorRectInScreen.X(), + editorRectInScreen.Y(), editorRectInScreen.XMost(), + editorRectInScreen.YMost()); + } + + *oResult = TRUE; + + MOZ_LOG(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 queryTextContentEvent(true, eQueryTextContent, + aWindow); + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); + aWindow->InitEvent(queryTextContentEvent, &point); + DispatchEvent(aWindow, queryTextContentEvent); + if (NS_WARN_IF(queryTextContentEvent.Failed())) { + MOZ_LOG(gIMMLog, LogLevel::Error, + ("HandleDocumentFeed, FAILED, due to eQueryTextContent failure")); + return false; + } + + nsAutoString str(queryTextContentEvent.mReply->DataRef()); + if (targetOffset > static_cast<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 nsString& aStr, UINT aCodePage, + nsACString& aANSIStr) { + int len = ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), + aStr.Length(), nullptr, 0, nullptr, nullptr); + NS_ENSURE_TRUE(len >= 0, false); + + if (!aANSIStr.SetLength(len, mozilla::fallible)) { + MOZ_LOG(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 queryTextRectEvent(true, eQueryTextRect, aWindow); + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + queryTextRectEvent.InitForQueryTextRect(aOffset, 1, options); + aWindow->InitEvent(queryTextRectEvent, &point); + DispatchEvent(aWindow, queryTextRectEvent); + if (queryTextRectEvent.Succeeded()) { + aCharRect = queryTextRectEvent.mReply->mRect; + if (aWritingMode) { + *aWritingMode = queryTextRectEvent.mReply->WritingModeRef(); + } + MOZ_LOG( + gIMMLog, LogLevel::Debug, + ("GetCharacterRectOfSelectedTextAt, Succeeded, aOffset=%u, " + "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "queryTextRectEvent={ mReply=%s }", + aOffset, aCharRect.X(), aCharRect.Y(), aCharRect.Width(), + aCharRect.Height(), ToString(queryTextRectEvent.mReply).c_str())); + return true; + } + } + + return GetCaretRect(aWindow, aCharRect, aWritingMode); +} + +bool IMMHandler::GetCaretRect(nsWindow* aWindow, + LayoutDeviceIntRect& aCaretRect, + WritingMode* aWritingMode) { + LayoutDeviceIntPoint point(0, 0); + + WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow); + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + queryCaretRectEvent.InitForQueryCaretRect(0, options); + aWindow->InitEvent(queryCaretRectEvent, &point); + DispatchEvent(aWindow, queryCaretRectEvent); + if (queryCaretRectEvent.Failed()) { + MOZ_LOG(gIMMLog, LogLevel::Info, + ("GetCaretRect, FAILED, due to eQueryCaretRect failure")); + return false; + } + aCaretRect = queryCaretRectEvent.mReply->mRect; + if (aWritingMode) { + *aWritingMode = queryCaretRectEvent.mReply->WritingModeRef(); + } + MOZ_LOG(gIMMLog, LogLevel::Info, + ("GetCaretRect, SUCCEEDED, " + "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, " + "queryCaretRectEvent={ mReply=%s }", + aCaretRect.X(), aCaretRect.Y(), aCaretRect.Width(), + aCaretRect.Height(), ToString(queryCaretRectEvent.mReply).c_str())); + return true; +} + +bool IMMHandler::SetIMERelatedWindowsPos(nsWindow* aWindow, + const IMEContext& aContext) { + // Get first character rect of current a normal selected text or a composing + // string. + WritingMode writingMode; + LayoutDeviceIntRect firstSelectedCharRectRelativeToWindow; + bool ret = GetCharacterRectOfSelectedTextAt( + aWindow, 0, firstSelectedCharRectRelativeToWindow, &writingMode); + NS_ENSURE_TRUE(ret, false); + nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); + LayoutDeviceIntRect firstSelectedCharRect; + ResolveIMECaretPos(toplevelWindow, firstSelectedCharRectRelativeToWindow, + aWindow, firstSelectedCharRect); + + // Set native caret size/position to our caret. Some IMEs honor it. E.g., + // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified + // Chinese) on XP. But if a11y module is handling native caret, we shouldn't + // touch it. + if (!IMEHandler::IsA11yHandlingNativeCaret()) { + LayoutDeviceIntRect caretRect(firstSelectedCharRect), + caretRectRelativeToWindow; + if (GetCaretRect(aWindow, caretRectRelativeToWindow)) { + ResolveIMECaretPos(toplevelWindow, caretRectRelativeToWindow, aWindow, + caretRect); + } else { + NS_WARNING("failed to get caret rect"); + caretRect.SetWidth(1); + } + IMEHandler::CreateNativeCaret(aWindow, caretRect); + } + + if (ShouldDrawCompositionStringOurselves()) { + MOZ_LOG(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::ResolveIMECaretPos(nsIWidget* aReferenceWidget, + LayoutDeviceIntRect& aCursorRect, + nsIWidget* aNewOriginWidget, + LayoutDeviceIntRect& aOutRect) { + aOutRect = aCursorRect; + + if (aReferenceWidget == aNewOriginWidget) return; + + if (aReferenceWidget) + aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffset()); + + if (aNewOriginWidget) + aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffset()); +} + +static void SetHorizontalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) { + aLogFont.lfEscapement = aLogFont.lfOrientation = 0; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) { + memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System")); + return; + } + memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length()] = 0; +} + +static void SetVerticalFontToLogFont(const nsAString& aFontFace, + LOGFONTW& aLogFont) { + aLogFont.lfEscapement = aLogFont.lfOrientation = 2700; + if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) { + memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System")); + return; + } + aLogFont.lfFaceName[0] = '@'; + memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(), + aFontFace.Length() * sizeof(wchar_t)); + aLogFont.lfFaceName[aFontFace.Length() + 1] = 0; +} + +void IMMHandler::AdjustCompositionFont(nsWindow* aWindow, + const IMEContext& aContext, + const WritingMode& aWritingMode, + bool aForceUpdate) { + // An instance of IMMHandler is destroyed when active IME is changed. + // Therefore, we need to store the information which are set to the IM + // context to static variables since IM context is never recreated. + static bool sCompositionFontsInitialized = false; + static nsString sCompositionFont; + static bool sCompositionFontPrefDone = false; + if (!sCompositionFontPrefDone) { + sCompositionFontPrefDone = true; + Preferences::GetString("intl.imm.composition_font", sCompositionFont); + } + + // If composition font is customized by pref, we need to modify the + // composition font of the IME context at first time even if the writing mode + // is horizontal. + bool setCompositionFontForcibly = + aForceUpdate || + (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty()); + + static WritingMode sCurrentWritingMode; + static nsString sCurrentIMEName; + if (!setCompositionFontForcibly && + sWritingModeOfCompositionFont == aWritingMode && + sCurrentIMEName == sIMEName) { + // Nothing to do if writing mode isn't being changed. + return; + } + + // Decide composition fonts for both horizontal writing mode and vertical + // writing mode. If the font isn't specified by the pref, use default + // font which is already set to the IM context. And also in vertical writing + // mode, insert '@' to the start of the font. + if (!sCompositionFontsInitialized) { + sCompositionFontsInitialized = true; + // sCompositionFontH must not start with '@' and its length is less than + // LF_FACESIZE since it needs to end with null terminating character. + if (sCompositionFont.IsEmpty() || + sCompositionFont.Length() > LF_FACESIZE - 1 || + sCompositionFont[0] == '@') { + LOGFONTW defaultLogFont; + if (NS_WARN_IF( + !::ImmGetCompositionFont(aContext.get(), &defaultLogFont))) { + MOZ_LOG(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"; + Preferences::GetString(kCompositionFontForJapanist2003, + sCompositionFontForJapanist2003); + // If the font name is not specified properly, let's use + // "MS PGothic" instead. + if (sCompositionFontForJapanist2003.IsEmpty() || + sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 || + sCompositionFontForJapanist2003[0] == '@') { + sCompositionFontForJapanist2003.AssignLiteral("MS PGothic"); + } + } + + sWritingModeOfCompositionFont = aWritingMode; + sCurrentIMEName = sIMEName; + + LOGFONTW logFont; + memset(&logFont, 0, sizeof(logFont)); + if (!::ImmGetCompositionFont(aContext.get(), &logFont)) { + MOZ_LOG(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 (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 MouseButton::ePrimary: + button = IMEMOUSE_LDOWN; + break; + case MouseButton::eMiddle: + button = IMEMOUSE_MDOWN; + break; + case MouseButton::eSecondary: + button = IMEMOUSE_RDOWN; + break; + default: + return NS_OK; + } + + // calcurate positioning and offset + // char : JCH1|JCH2|JCH3 + // offset: 0011 1122 2233 + // positioning: 2301 2301 2301 + 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; + } +} + +/****************************************************************************** + * 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 querySelectedTextEvent(true, eQuerySelectedText, + aWindow); + LayoutDeviceIntPoint point(0, 0); + aWindow->InitEvent(querySelectedTextEvent, &point); + DispatchEvent(aWindow, querySelectedTextEvent); + if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) { + 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; + } + + MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome()); + mOffset = querySelectedTextEvent.mReply->StartOffset(); + mString = querySelectedTextEvent.mReply->DataRef(); + mWritingMode = querySelectedTextEvent.mReply->WritingModeRef(); + mIsValid = true; + + MOZ_LOG(gIMMLog, LogLevel::Info, + ("Selection::Init, querySelectedTextEvent={ mReply=%s }", + ToString(querySelectedTextEvent.mReply).c_str())); + + 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..54182c0e2e --- /dev/null +++ b/widget/windows/IMMHandler.h @@ -0,0 +1,436 @@ +/* -*- 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 IMENotificationRequests GetIMENotificationRequests(); + + // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by + // IME. Otherwise, NS_OK. + static nsresult OnMouseButtonEvent(nsWindow* aWindow, + const IMENotification& aIMENotification); + +#define DECL_IS_IME_ACTIVE(aReadableName) \ + static bool Is##aReadableName##Active(); + + // Japanese IMEs + DECL_IS_IME_ACTIVE(ATOK2006) + DECL_IS_IME_ACTIVE(ATOK2007) + DECL_IS_IME_ACTIVE(ATOK2008) + DECL_IS_IME_ACTIVE(ATOK2009) + DECL_IS_IME_ACTIVE(ATOK2010) + DECL_IS_IME_ACTIVE(GoogleJapaneseInput) + DECL_IS_IME_ACTIVE(Japanist2003) + +#undef DECL_IS_IME_ACTIVE + + /** + * IsActiveIMEInBlockList() returns true if we know active keyboard layout's + * IME has some crash bugs or something which make some damage to us. When + * this returns true, IMC shouldn't be associated with any windows. + */ + static bool IsActiveIMEInBlockList(); + + protected: + static void EnsureHandlerInstance(); + + static bool IsComposingOnOurEditor(); + static bool IsComposingWindow(nsWindow* aWindow); + + static bool ShouldDrawCompositionStringOurselves(); + static bool IsVerticalWritingSupported(); + // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE. + static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout); + static UINT GetKeyboardCodePage(); + + /** + * Checks whether the window is top level window of the composing window. + * In this method, the top level window means in all windows, not only in all + * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. + */ + static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); + + static bool ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam, + LPARAM lParam, MSGResult& aResult); + + IMMHandler(); + ~IMMHandler(); + + // On*() methods return true if the caller of message handler shouldn't do + // anything anymore. Otherwise, false. + static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult); + bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult); + bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + // These message handlers don't use instance members, we should not create + // the instance by the messages. So, they should be static. + static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult); + static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + // The result of Handle* method mean "Processed" when it's TRUE. + void HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext); + bool HandleComposition(nsWindow* aWindow, const IMEContext& aContext, + LPARAM lParam); + // If aCommitString is null, this commits composition with the latest + // dispatched data. Otherwise, commits composition with the value. + void HandleEndComposition(nsWindow* aWindow, + const nsAString* aCommitString = nullptr); + bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult); + bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam, + LRESULT* oResult); + bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult); + + /** + * When a window's IME context is activating but we have composition on + * another window, we should commit our composition because IME context is + * shared by all our windows (including plug-ins). + * @param aWindow is a new activated window. + * If aWindow is our composing window, this method does nothing. + * Otherwise, this commits the composition on the previous window. + * If this method did commit a composition, this returns TRUE. + */ + bool CommitCompositionOnPreviousWindow(nsWindow* aWindow); + + /** + * ResolveIMECaretPos + * Convert the caret rect of a composition event to another widget's + * coordinate system. + * + * @param aReferenceWidget The origin widget of aCursorRect. + * Typically, this is mReferenceWidget of the + * composing events. If the aCursorRect is in screen + * coordinates, set nullptr. + * @param aCursorRect The cursor rect. + * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If + * this is nullptr, aOutRect will be in screen + * coordinates. + * @param aOutRect The converted cursor rect. + */ + void ResolveIMECaretPos(nsIWidget* aReferenceWidget, + mozilla::LayoutDeviceIntRect& aCursorRect, + nsIWidget* aNewOriginWidget, + mozilla::LayoutDeviceIntRect& aOutRect); + + bool ConvertToANSIString(const nsString& aStr, UINT aCodePage, + nsACString& aANSIStr); + + bool SetIMERelatedWindowsPos(nsWindow* aWindow, const IMEContext& aContext); + /** + * GetCharacterRectOfSelectedTextAt() returns character rect of the offset + * from the selection start or the start of composition string if there is + * a composition. + * + * @param aWindow The window which has focus. + * @param aOffset Offset from the selection start or the start of + * composition string when there is a composition. + * This must be in the selection range or + * the composition string. + * @param aCharRect The result. + * @param aWritingMode The writing mode of current selection. When this + * is nullptr, this assumes that the selection is in + * horizontal writing mode. + * @return true if this succeeded to retrieve the rect. + * Otherwise, false. + */ + bool GetCharacterRectOfSelectedTextAt( + nsWindow* aWindow, uint32_t aOffset, + mozilla::LayoutDeviceIntRect& aCharRect, + mozilla::WritingMode* aWritingMode = nullptr); + /** + * GetCaretRect() returns caret rect at current selection start. + * + * @param aWindow The window which has focus. + * @param aCaretRect The result. + * @param aWritingMode The writing mode of current selection. When this + * is nullptr, this assumes that the selection is in + * horizontal writing mode. + * @return true if this succeeded to retrieve the rect. + * Otherwise, false. + */ + bool GetCaretRect(nsWindow* aWindow, mozilla::LayoutDeviceIntRect& aCaretRect, + mozilla::WritingMode* aWritingMode = nullptr); + void GetCompositionString(const IMEContext& aContext, DWORD aIndex, + nsAString& aCompositionString) const; + + /** + * AdjustCompositionFont() makes IME vertical writing mode if it's supported. + * If aForceUpdate is true, it will update composition font even if writing + * mode isn't being changed. + */ + void AdjustCompositionFont(nsWindow* aWindow, const IMEContext& aContext, + const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + + /** + * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the + * locale of active IME is CJK. Note that this creates an instance even + * when there is no composition but the locale is CJK. + */ + static void MaybeAdjustCompositionFont( + nsWindow* aWindow, const mozilla::WritingMode& aWritingMode, + bool aForceUpdate = false); + + /** + * Get the current target clause of composition string. + * If there are one or more characters whose attribute is ATTR_TARGET_*, + * this returns the first character's offset and its length. + * Otherwise, e.g., the all characters are ATTR_INPUT, this returns + * the composition string range because the all is the current target. + * + * aLength can be null (default), but aOffset must not be null. + * + * The aOffset value is offset in the contents. So, when you need offset + * in the composition string, you need to subtract mCompositionStart from it. + */ + bool GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength = nullptr); + + /** + * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet. + */ + static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent); + + /** + * DispatchCompositionChangeEvent() dispatches eCompositionChange event + * with clause information (it'll be retrieved by CreateTextRangeArray()). + * I.e., this should be called only during composing. If a composition is + * being committed, only HandleCompositionEnd() should be called. + * + * @param aWindow The window which has the composition. + * @param aContext Native IME context which has the composition. + */ + void DispatchCompositionChangeEvent(nsWindow* aWindow, + const IMEContext& aContext); + + nsresult EnsureClauseArray(int32_t aCount); + nsresult EnsureAttributeArray(int32_t aCount); + + /** + * When WM_IME_CHAR is received and passed to DefWindowProc, we need to + * record the messages. In other words, we should record the messages + * when we receive WM_IME_CHAR on windowless plug-in (if we have focus, + * we always eat them). When focus is moved from a windowless plug-in to + * our window during composition, WM_IME_CHAR messages were received when + * the plug-in has focus. However, WM_CHAR messages are received after the + * plug-in lost focus. So, we need to ignore the WM_CHAR messages because + * they make unexpected text input events on us. + */ + nsTArray<MSG> mPassedIMEChar; + + bool IsIMECharRecordsEmpty() { return mPassedIMEChar.IsEmpty(); } + void ResetIMECharRecords() { mPassedIMEChar.Clear(); } + void DequeueIMECharRecords(WPARAM& wParam, LPARAM& lParam) { + MSG msg = mPassedIMEChar.ElementAt(0); + wParam = msg.wParam; + lParam = msg.lParam; + mPassedIMEChar.RemoveElementAt(0); + } + void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) { + MSG msg; + msg.wParam = wParam; + msg.lParam = lParam; + mPassedIMEChar.AppendElement(msg); + } + + TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow); + + nsWindow* mComposingWindow; + RefPtr<TextEventDispatcher> mDispatcher; + nsString mCompositionString; + nsTArray<uint32_t> mClauseArray; + nsTArray<uint8_t> mAttributeArray; + + int32_t mCursorPosition; + uint32_t mCompositionStart; + + 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; + + static mozilla::WritingMode sWritingModeOfCompositionFont; + static nsString sIMEName; + static UINT sCodePage; + static DWORD sIMEProperty; + static DWORD sIMEUIProperty; + static bool sAssumeVerticalWritingModeNotSupported; + static bool sHasFocus; +}; + +} // namespace widget +} // namespace mozilla + +#endif // IMMHandler_h_ diff --git a/widget/windows/IconLoaderHelperWin.cpp b/widget/windows/IconLoaderHelperWin.cpp new file mode 100644 index 0000000000..a6be500fe4 --- /dev/null +++ b/widget/windows/IconLoaderHelperWin.cpp @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +/* + * Retrieves and displays icons in native menu items on Windows. + */ + +#include "gfxPlatform.h" +#include "imgIContainer.h" +#include "imgLoader.h" +#include "imgRequestProxy.h" +#include "mozilla/dom/Document.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsNameSpaceManager.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "nsToolkit.h" +#include "nsWindowGfx.h" +#include "IconLoaderHelperWin.h" + +using namespace mozilla; + +using mozilla::gfx::SourceSurface; +using mozilla::widget::IconLoader; +using mozilla::widget::IconLoaderListenerWin; + +namespace mozilla::widget { + +NS_IMPL_CYCLE_COLLECTION(IconLoaderHelperWin, mLoadListener) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IconLoaderHelperWin) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(IconLoaderHelperWin) +NS_IMPL_CYCLE_COLLECTING_RELEASE(IconLoaderHelperWin) + +IconLoaderHelperWin::IconLoaderHelperWin(IconLoaderListenerWin* aListener) + : mLoadListener(aListener) { + MOZ_ASSERT(aListener); + MOZ_ASSERT(NS_IsMainThread()); +} + +IconLoaderHelperWin::~IconLoaderHelperWin() { Destroy(); } + +nsresult IconLoaderHelperWin::OnComplete(imgIContainer* aImage, + const nsIntRect& aRect) { + NS_ENSURE_ARG_POINTER(aImage); + + nsresult rv = nsWindowGfx::CreateIcon( + aImage, false, LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), + &mNativeIconImage); + NS_ENSURE_SUCCESS(rv, rv); + + mLoadListener->OnComplete(); + return NS_OK; +} + +HICON IconLoaderHelperWin::GetNativeIconImage() { + if (mNativeIconImage) { + return mNativeIconImage; + } + return ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION); +} + +void IconLoaderHelperWin::Destroy() { + if (mNativeIconImage) { + ::DestroyIcon(mNativeIconImage); + mNativeIconImage = nullptr; + } + if (mLoadListener) { + mLoadListener = nullptr; + } +} + +} // namespace mozilla::widget diff --git a/widget/windows/IconLoaderHelperWin.h b/widget/windows/IconLoaderHelperWin.h new file mode 100644 index 0000000000..94eba152e4 --- /dev/null +++ b/widget/windows/IconLoaderHelperWin.h @@ -0,0 +1,70 @@ +/* -*- 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_IconLoaderHelperWin_h +#define mozilla_widget_IconLoaderHelperWin_h + +#include "mozilla/widget/IconLoader.h" +#include "nsISupports.h" + +namespace mozilla::widget { + +/** + * Classes that want to hear about when icons load should subclass + * IconLoaderListenerWin, and implement the OnComplete() method, + * which will be called once the load of the icon has completed. + */ +class IconLoaderListenerWin : public nsISupports { + public: + IconLoaderListenerWin() = default; + + // IconLoaderListenerWin needs to implement nsISupports in order for its + // subclasses to participate in cycle collection. + + virtual nsresult OnComplete() = 0; + + protected: + virtual ~IconLoaderListenerWin() = default; +}; + +/** + * This is a Helper used with mozilla::widget::IconLoader that implements the + * Windows-specific functionality for converting a loaded icon into an HICON. + */ +class IconLoaderHelperWin final : public mozilla::widget::IconLoader::Helper { + public: + explicit IconLoaderHelperWin( + mozilla::widget::IconLoaderListenerWin* aLoadListener); + + // IconLoaderHelperWin needs to implement nsISupports in order for its + // subclasses to participate in cycle collection. + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(IconLoaderHelperWin) + + nsresult OnComplete(imgIContainer* aImage, const nsIntRect& aRect) override; + + /** + * IconLoaderHelperWin will default the HICON returned by GetNativeIconImage + * to the application icon. Once the load of the icon by IconLoader has + * completed, GetNativeIconImage will return the loaded icon. + * + * Note that IconLoaderHelperWin owns this HICON. If you don't need it to hold + * onto the HICON anymore, call Destroy on it to deallocate. The + * IconLoaderHelperWin destructor will also deallocate the HICON if necessary. + */ + HICON GetNativeIconImage(); + void Destroy(); + + protected: + ~IconLoaderHelperWin(); + + private: + RefPtr<mozilla::widget::IconLoaderListenerWin> mLoadListener; + HICON mNativeIconImage; +}; + +} // namespace mozilla::widget + +#endif // mozilla_widget_IconLoaderHelperWin_h diff --git a/widget/windows/InProcessWinCompositorWidget.cpp b/widget/windows/InProcessWinCompositorWidget.cpp new file mode 100644 index 0000000000..c65a203a40 --- /dev/null +++ b/widget/windows/InProcessWinCompositorWidget.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InProcessWinCompositorWidget.h" + +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "gfxPlatform.h" +#include "HeadlessCompositorWidget.h" +#include "HeadlessWidget.h" +#include "nsWindow.h" +#include "VsyncDispatcher.h" +#include "WinCompositorWindowThread.h" +#include "VRShMem.h" + +#include <ddraw.h> + +namespace mozilla { +namespace widget { + +using namespace mozilla::gfx; +using namespace mozilla; + +/* static */ +RefPtr<CompositorWidget> CompositorWidget::CreateLocal( + const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, nsIWidget* aWidget) { + if (aInitData.type() == + CompositorWidgetInitData::THeadlessCompositorWidgetInitData) { + return new HeadlessCompositorWidget( + aInitData.get_HeadlessCompositorWidgetInitData(), aOptions, + static_cast<HeadlessWidget*>(aWidget)); + } else { + return new InProcessWinCompositorWidget( + aInitData.get_WinCompositorWidgetInitData(), aOptions, + static_cast<nsWindow*>(aWidget)); + } +} + +InProcessWinCompositorWidget::InProcessWinCompositorWidget( + const WinCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, nsWindow* aWindow) + : WinCompositorWidget(aInitData, aOptions), + mWindow(aWindow), + mWnd(reinterpret_cast<HWND>(aInitData.hWnd())), + mTransparentSurfaceLock("mTransparentSurfaceLock"), + mTransparencyMode(aInitData.transparencyMode()), + mMemoryDC(nullptr), + mCompositeDC(nullptr), + mLockedBackBufferData(nullptr) { + MOZ_ASSERT(mWindow); + MOZ_ASSERT(mWnd && ::IsWindow(mWnd)); + + // mNotDeferEndRemoteDrawing is set on the main thread during init, + // but is only accessed after on the compositor thread. + mNotDeferEndRemoteDrawing = + StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 || + gfxPlatform::IsInLayoutAsapMode() || gfxPlatform::ForceSoftwareVsync(); +} + +void InProcessWinCompositorWidget::OnDestroyWindow() { + EnterPresentLock(); + MutexAutoLock lock(mTransparentSurfaceLock); + mTransparentSurface = nullptr; + mMemoryDC = nullptr; + LeavePresentLock(); +} + +bool InProcessWinCompositorWidget::OnWindowResize( + const LayoutDeviceIntSize& aSize) { + return true; +} + +void InProcessWinCompositorWidget::OnWindowModeChange(nsSizeMode aSizeMode) {} + +bool InProcessWinCompositorWidget::PreRender(WidgetRenderingContext* aContext) { + // This can block waiting for WM_SETTEXT to finish + // Using PreRender is unnecessarily pessimistic because + // we technically only need to block during the present call + // not all of compositor rendering + mPresentLock.Enter(); + return true; +} + +void InProcessWinCompositorWidget::PostRender( + WidgetRenderingContext* aContext) { + mPresentLock.Leave(); +} + +LayoutDeviceIntSize InProcessWinCompositorWidget::GetClientSize() { + RECT r; + if (!::GetClientRect(mWnd, &r)) { + return LayoutDeviceIntSize(); + } + return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top); +} + +already_AddRefed<gfx::DrawTarget> +InProcessWinCompositorWidget::StartRemoteDrawing() { + MutexAutoLock lock(mTransparentSurfaceLock); + + MOZ_ASSERT(!mCompositeDC); + + RefPtr<gfxASurface> surf; + if (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; + } + + RefPtr<DrawTarget> dt = + mozilla::gfx::Factory::CreateDrawTargetForCairoSurface( + surf->CairoSurface(), size); + if (dt) { + mCompositeDC = dc; + } else { + FreeWindowSurface(dc); + } + + return dt.forget(); +} + +void InProcessWinCompositorWidget::EndRemoteDrawing() { + MOZ_ASSERT(!mLockedBackBufferData); + + if (mTransparencyMode == eTransparencyTransparent) { + MOZ_ASSERT(mTransparentSurface); + RedrawTransparentWindow(); + } + if (mCompositeDC) { + FreeWindowSurface(mCompositeDC); + } + mCompositeDC = nullptr; +} + +bool InProcessWinCompositorWidget::NeedsToDeferEndRemoteDrawing() { + if (mNotDeferEndRemoteDrawing) { + return false; + } + + IDirectDraw7* ddraw = DeviceManagerDx::Get()->GetDirectDraw(); + if (!ddraw) { + return false; + } + + DWORD scanLine = 0; + int height = ::GetSystemMetrics(SM_CYSCREEN); + HRESULT ret = ddraw->GetScanLine(&scanLine); + if (ret == DDERR_VERTICALBLANKINPROGRESS) { + scanLine = 0; + } else if (ret != DD_OK) { + return false; + } + + // Check if there is a risk of tearing with GDI. + if (static_cast<int>(scanLine) > height / 2) { + // No need to defer. + return false; + } + + return true; +} + +already_AddRefed<gfx::DrawTarget> +InProcessWinCompositorWidget::GetBackBufferDrawTarget( + gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect, + bool* aOutIsCleared) { + MOZ_ASSERT(!mLockedBackBufferData); + + RefPtr<gfx::DrawTarget> target = CompositorWidget::GetBackBufferDrawTarget( + aScreenTarget, aRect, aOutIsCleared); + if (!target) { + return nullptr; + } + + MOZ_ASSERT(target->GetBackendType() == BackendType::CAIRO); + + uint8_t* destData; + IntSize destSize; + int32_t destStride; + SurfaceFormat destFormat; + if (!target->LockBits(&destData, &destSize, &destStride, &destFormat)) { + // LockBits is not supported. Use original DrawTarget. + return target.forget(); + } + + RefPtr<gfx::DrawTarget> dataTarget = Factory::CreateDrawTargetForData( + BackendType::CAIRO, destData, destSize, destStride, destFormat); + mLockedBackBufferData = destData; + + return dataTarget.forget(); +} + +already_AddRefed<gfx::SourceSurface> +InProcessWinCompositorWidget::EndBackBufferDrawing() { + if (mLockedBackBufferData) { + MOZ_ASSERT(mLastBackBuffer); + mLastBackBuffer->ReleaseBits(mLockedBackBufferData); + mLockedBackBufferData = nullptr; + } + return CompositorWidget::EndBackBufferDrawing(); +} + +bool InProcessWinCompositorWidget::InitCompositor( + layers::Compositor* aCompositor) { + if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) { + DeviceManagerDx::Get()->InitializeDirectDraw(); + } + return true; +} + +void InProcessWinCompositorWidget::EnterPresentLock() { mPresentLock.Enter(); } + +void InProcessWinCompositorWidget::LeavePresentLock() { mPresentLock.Leave(); } + +RefPtr<gfxASurface> InProcessWinCompositorWidget::EnsureTransparentSurface() { + mTransparentSurfaceLock.AssertCurrentThreadOwns(); + 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 InProcessWinCompositorWidget::CreateTransparentSurface( + const gfx::IntSize& aSize) { + mTransparentSurfaceLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mTransparentSurface && !mMemoryDC); + RefPtr<gfxWindowsSurface> surface = + new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32); + mTransparentSurface = surface; + mMemoryDC = surface->GetDC(); +} + +void InProcessWinCompositorWidget::UpdateTransparency( + nsTransparencyMode aMode) { + EnterPresentLock(); + MutexAutoLock lock(mTransparentSurfaceLock); + if (mTransparencyMode == aMode) { + return; + } + + mTransparencyMode = aMode; + mTransparentSurface = nullptr; + mMemoryDC = nullptr; + + if (mTransparencyMode == eTransparencyTransparent) { + EnsureTransparentSurface(); + } + LeavePresentLock(); +} + +bool InProcessWinCompositorWidget::HasGlass() const { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread() || + wr::RenderThread::IsInRenderThread()); + + nsTransparencyMode transparencyMode = mTransparencyMode; + return transparencyMode == eTransparencyGlass || + transparencyMode == eTransparencyBorderlessGlass; +} + +void InProcessWinCompositorWidget::ClearTransparentWindow() { + EnterPresentLock(); + MutexAutoLock lock(mTransparentSurfaceLock); + if (!mTransparentSurface) { + return; + } + + EnsureTransparentSurface(); + + IntSize size = mTransparentSurface->GetSize(); + if (!size.IsEmpty()) { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::CreateDrawTargetForSurface(mTransparentSurface, size); + if (!drawTarget) { + return; + } + drawTarget->ClearRect(Rect(0, 0, size.width, size.height)); + RedrawTransparentWindow(); + } + LeavePresentLock(); +} + +bool InProcessWinCompositorWidget::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 InProcessWinCompositorWidget::GetWindowSurface() { + return eTransparencyTransparent == mTransparencyMode ? mMemoryDC + : ::GetDC(mWnd); +} + +void InProcessWinCompositorWidget::FreeWindowSurface(HDC dc) { + if (eTransparencyTransparent != mTransparencyMode) ::ReleaseDC(mWnd, dc); +} + +bool InProcessWinCompositorWidget::IsHidden() const { return ::IsIconic(mWnd); } + +nsIWidget* InProcessWinCompositorWidget::RealWidget() { return mWindow; } + +void InProcessWinCompositorWidget::ObserveVsync(VsyncObserver* aObserver) { + if (RefPtr<CompositorVsyncDispatcher> cvd = + mWindow->GetCompositorVsyncDispatcher()) { + cvd->SetCompositorVsyncObserver(aObserver); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/InProcessWinCompositorWidget.h b/widget/windows/InProcessWinCompositorWidget.h new file mode 100644 index 0000000000..25ce85eadc --- /dev/null +++ b/widget/windows/InProcessWinCompositorWidget.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_InProcessWinCompositorWidget_h +#define widget_windows_InProcessWinCompositorWidget_h + +#include "WinCompositorWidget.h" + +class nsWindow; + +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 PlatformCompositorWidgetDelegate { + public: + InProcessWinCompositorWidget(const WinCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, + nsWindow* aWindow); + + bool PreRender(WidgetRenderingContext*) override; + void PostRender(WidgetRenderingContext*) override; + already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override; + void EndRemoteDrawing() override; + bool NeedsToDeferEndRemoteDrawing() override; + LayoutDeviceIntSize GetClientSize() override; + already_AddRefed<gfx::DrawTarget> GetBackBufferDrawTarget( + gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect, + bool* aOutIsCleared) override; + already_AddRefed<gfx::SourceSurface> EndBackBufferDrawing() override; + bool InitCompositor(layers::Compositor* aCompositor) override; + CompositorWidgetDelegate* AsDelegate() override { return this; } + bool IsHidden() const override; + + // PlatformCompositorWidgetDelegate Overrides + + void EnterPresentLock() override; + void LeavePresentLock() override; + void OnDestroyWindow() override; + bool OnWindowResize(const LayoutDeviceIntSize& aSize) override; + void OnWindowModeChange(nsSizeMode aSizeMode) override; + void UpdateTransparency(nsTransparencyMode aMode) override; + void ClearTransparentWindow() override; + + bool RedrawTransparentWindow(); + + // Ensure that a transparent surface exists, then return it. + RefPtr<gfxASurface> EnsureTransparentSurface(); + + HDC GetTransparentDC() const { return mMemoryDC; } + + mozilla::Mutex& GetTransparentSurfaceLock() { + return mTransparentSurfaceLock; + } + + bool HasGlass() const override; + + void ObserveVsync(VsyncObserver* aObserver) override; + nsIWidget* RealWidget() override; + + void UpdateCompositorWnd(const HWND aCompositorWnd, + const HWND aParentWnd) override {} + void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) override {} + + private: + HDC GetWindowSurface(); + void FreeWindowSurface(HDC dc); + + void CreateTransparentSurface(const gfx::IntSize& aSize); + + nsWindow* mWindow; + + HWND mWnd; + + gfx::CriticalSection mPresentLock; + + // Transparency handling. + mozilla::Mutex mTransparentSurfaceLock; + mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed> + 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_InProcessWinCompositorWidget_h diff --git a/widget/windows/InkCollector.cpp b/widget/windows/InkCollector.cpp new file mode 100644 index 0000000000..4c2dfbf387 --- /dev/null +++ b/widget/windows/InkCollector.cpp @@ -0,0 +1,245 @@ +/* -*- 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); + + // Set AutoRedraw to false to prevent repainting the ink when the window is + // invalidated. + mInkCollector->put_AutoRedraw(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..f13bfbf570 --- /dev/null +++ b/widget/windows/InkCollector.h @@ -0,0 +1,101 @@ +/* -*- 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 final : 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; + + ~InkCollectorEvent() = default; +}; + +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 mozilla::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/InputDeviceUtils.cpp b/widget/windows/InputDeviceUtils.cpp new file mode 100644 index 0000000000..3636b93d6a --- /dev/null +++ b/widget/windows/InputDeviceUtils.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InputDeviceUtils.h" + +#define INITGUID +#include <dbt.h> +#include <hidclass.h> +#include <ntddmou.h> +#include <setupapi.h> + +namespace mozilla { +namespace widget { + +HDEVNOTIFY +InputDeviceUtils::RegisterNotification(HWND aHwnd) { + DEV_BROADCAST_DEVICEINTERFACE filter = {}; + + filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); + filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + // Some touchsreen devices are not GUID_DEVINTERFACE_MOUSE, so here we use + // GUID_DEVINTERFACE_HID instead. + filter.dbcc_classguid = GUID_DEVINTERFACE_HID; + return RegisterDeviceNotification(aHwnd, &filter, + DEVICE_NOTIFY_WINDOW_HANDLE); +} + +void InputDeviceUtils::UnregisterNotification(HDEVNOTIFY aHandle) { + if (!aHandle) { + return; + } + UnregisterDeviceNotification(aHandle); +} + +DWORD +InputDeviceUtils::CountMouseDevices() { + HDEVINFO hdev = + SetupDiGetClassDevs(&GUID_DEVINTERFACE_MOUSE, nullptr, nullptr, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (hdev == INVALID_HANDLE_VALUE) { + return 0; + } + + DWORD count = 0; + SP_INTERFACE_DEVICE_DATA info = {}; + info.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA); + while (SetupDiEnumDeviceInterfaces(hdev, nullptr, &GUID_DEVINTERFACE_MOUSE, + count, &info)) { + if (info.Flags & SPINT_ACTIVE) { + count++; + } + } + SetupDiDestroyDeviceInfoList(hdev); + return count; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/InputDeviceUtils.h b/widget/windows/InputDeviceUtils.h new file mode 100644 index 0000000000..d16698a03a --- /dev/null +++ b/widget/windows/InputDeviceUtils.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_InputDeviceUtils_h__ +#define mozilla_widget_InputDeviceUtils_h__ + +#include <windows.h> + +namespace mozilla { +namespace widget { + +class InputDeviceUtils { + public: + static HDEVNOTIFY RegisterNotification(HWND aHwnd); + static void UnregisterNotification(HDEVNOTIFY aHandle); + + // Returns the number of mouse type devices connected to this system. + static DWORD CountMouseDevices(); +}; + +} // namespace widget +} // namespace mozilla +#endif // mozilla_widget_InputDeviceUtils_h__ diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp new file mode 100644 index 0000000000..fa14dd7cb5 --- /dev/null +++ b/widget/windows/JumpListBuilder.cpp @@ -0,0 +1,635 @@ +/* -*- 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 "nsWidgetsCID.h" +#include "WinTaskbar.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Preferences.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "mozilla/LazyIdleThread.h" +#include "nsIObserverService.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/mscom/ApartmentRegion.h" +#include "mozilla/mscom/EnsureMTA.h" + +#include <shellapi.h> +#include "WinUtils.h" + +using mozilla::dom::Promise; + +// The amount of time, in milliseconds, that our IO thread will stay alive after +// the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +namespace mozilla { +namespace widget { + +// defined in WinTaskbar.cpp +extern const wchar_t* gMozillaJumpListIDGeneric; + +Atomic<bool> 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" + +namespace detail { + +class DoneCommitListBuildCallback final : public nsIRunnable { + NS_DECL_THREADSAFE_ISUPPORTS + + public: + DoneCommitListBuildCallback(nsIJumpListCommittedCallback* aCallback, + JumpListBuilder* aBuilder) + : mCallback(aCallback), mBuilder(aBuilder), mResult(false) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + if (mCallback) { + Unused << mCallback->Done(mResult); + } + // Ensure we are releasing on the main thread. + Destroy(); + return NS_OK; + } + + void SetResult(bool aResult) { mResult = aResult; } + + private: + ~DoneCommitListBuildCallback() { + // Destructor does not always call on the main thread. + MOZ_ASSERT(!mCallback); + MOZ_ASSERT(!mBuilder); + } + + void Destroy() { + MOZ_ASSERT(NS_IsMainThread()); + mCallback = nullptr; + mBuilder = nullptr; + } + + // These two references MUST be released on the main thread. + RefPtr<nsIJumpListCommittedCallback> mCallback; + RefPtr<JumpListBuilder> mBuilder; + bool mResult; +}; + +NS_IMPL_ISUPPORTS(DoneCommitListBuildCallback, nsIRunnable); + +} // namespace detail + +JumpListBuilder::JumpListBuilder() + : mMaxItems(0), mHasCommit(false), mMonitor("JumpListBuilderMonitor") { + MOZ_ASSERT(NS_IsMainThread()); + + // Instantiate mJumpListMgr in the multithreaded apartment so that proxied + // calls on that object do not need to interact with the main thread's message + // pump. + mscom::EnsureMTA([this]() { + RefPtr<ICustomDestinationList> jumpListMgr; + HRESULT hr = ::CoCreateInstance( + CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, + IID_ICustomDestinationList, getter_AddRefs(jumpListMgr)); + if (FAILED(hr)) { + return; + } + + // Since we are accessing mJumpListMgr across different threads + // (ie, different apartments), mJumpListMgr must be an agile reference. + mJumpListMgr = jumpListMgr; + }); + + if (!mJumpListMgr) { + return; + } + + // Make a lazy thread for any IO + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List"_ns, + 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); + } + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return; + } + + // GetAppUserModelID can only be called once we're back on the main thread. + nsString modelId; + if (mozilla::widget::WinTaskbar::GetAppUserModelID(modelId)) { + jumpListMgr->SetAppID(modelId.get()); + } +} + +JumpListBuilder::~JumpListBuilder() { + Preferences::RemoveObserver(this, kPrefTaskbarEnabled); +} + +NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) { + *aAvailable = false; + + ReentrantMonitorAutoEnter lock(mMonitor); + 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) { + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE; + + *aMaxItems = 0; + + if (sBuildingList) { + *aMaxItems = mMaxItems; + return NS_OK; + } + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return NS_ERROR_UNEXPECTED; + } + + IObjectArray* objArray; + if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { + *aMaxItems = mMaxItems; + + if (objArray) objArray->Release(); + + jumpListMgr->AbortList(); + } + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::InitListBuild(JSContext* aCx, + Promise** aPromise) { + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!globalObject)) { + return NS_ERROR_FAILURE; + } + + ErrorResult result; + RefPtr<Promise> promise = Promise::Create(globalObject, result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>( + "InitListBuild", this, &JumpListBuilder::DoInitListBuild, promise); + nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + promise.forget(aPromise); + return NS_OK; +} + +void JumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) { + // Since we're invoking COM interfaces to talk to the shell on a background + // thread, we need to be running inside a multithreaded apartment. + mscom::MTARegion mta; + MOZ_ASSERT(mta.IsValid()); + + ReentrantMonitorAutoEnter lock(mMonitor); + MOZ_ASSERT(mJumpListMgr); + + if (sBuildingList) { + AbortListBuild(); + } + + HRESULT hr = E_UNEXPECTED; + auto errorHandler = MakeScopeExit([&aPromise, &hr]() { + if (SUCCEEDED(hr)) { + return; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "InitListBuildReject", [promise = std::move(aPromise)]() { + promise->MaybeReject(NS_ERROR_FAILURE); + })); + }); + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return; + } + + nsTArray<nsString> urisToRemove; + RefPtr<IObjectArray> objArray; + hr = jumpListMgr->BeginList( + &mMaxItems, + IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray)))); + if (FAILED(hr)) { + return; + } + + // The returned objArray of removed items are for manually removed items. + // This does not return items which are removed because they were previously + // part of the jump list but are no longer part of the jump list. + sBuildingList = true; + RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "InitListBuildResolve", [urisToRemove = std::move(urisToRemove), + promise = std::move(aPromise)]() { + promise->MaybeResolve(urisToRemove); + })); +} + +// Ensures that we have no old ICO files left in the jump list cache +nsresult 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<nsIDirectoryEnumerator> entries; + rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through each directory entry and remove all ICO files found + do { + nsCOMPtr<nsIFile> currFile; + if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile) + break; + + nsAutoString path; + if (NS_FAILED(currFile->GetPath(path))) continue; + + if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { + // Check if the cached ICO file exists + bool exists; + if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue; + + // We found an ICO file that exists, so we should remove it + currFile->Remove(false); + } + } while (true); + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray* items, + const nsAString& catName, + bool* _retval) { + nsresult rv; + + *_retval = false; + + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE; + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return NS_ERROR_UNEXPECTED; + } + + 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 = jumpListMgr->AddUserTasks(pArray); + if (SUCCEEDED(hr)) *_retval = true; + return NS_OK; + } break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: { + if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT))) + *_retval = true; + return NS_OK; + } break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: { + if (SUCCEEDED(jumpListMgr->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 = jumpListMgr->AppendCategory( + reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray); + if (SUCCEEDED(hr)) *_retval = true; + + // Get rid of the old icons + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; + } break; + } + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::AbortListBuild() { + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE; + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return NS_ERROR_UNEXPECTED; + } + + jumpListMgr->AbortList(); + sBuildingList = false; + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::CommitListBuild( + nsIJumpListCommittedCallback* aCallback) { + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE; + + // Also holds a strong reference to this to prevent use-after-free. + RefPtr<detail::DoneCommitListBuildCallback> callback = + new detail::DoneCommitListBuildCallback(aCallback, this); + + // The builder has a strong reference in the callback already, so we do not + // need to do it for this runnable again. + RefPtr<nsIRunnable> event = + NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>( + "JumpListBuilder::DoCommitListBuild", this, + &JumpListBuilder::DoCommitListBuild, std::move(callback)); + Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void JumpListBuilder::DoCommitListBuild( + RefPtr<detail::DoneCommitListBuildCallback> aCallback) { + // Since we're invoking COM interfaces to talk to the shell on a background + // thread, we need to be running inside a multithreaded apartment. + mscom::MTARegion mta; + MOZ_ASSERT(mta.IsValid()); + + ReentrantMonitorAutoEnter lock(mMonitor); + MOZ_ASSERT(mJumpListMgr); + MOZ_ASSERT(aCallback); + + HRESULT hr = E_UNEXPECTED; + auto onExit = MakeScopeExit([&hr, &aCallback]() { + // XXX We might want some specific error data here. + aCallback->SetResult(SUCCEEDED(hr)); + Unused << NS_DispatchToMainThread(aCallback); + }); + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return; + } + + hr = jumpListMgr->CommitList(); + sBuildingList = false; + + if (SUCCEEDED(hr)) { + mHasCommit = true; + } +} + +NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) { + *_retval = false; + + ReentrantMonitorAutoEnter lock(mMonitor); + if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE; + + if (sBuildingList) { + AbortListBuild(); + } + + nsAutoString uid; + if (!WinTaskbar::GetAppUserModelID(uid)) return NS_OK; + + RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr; + if (!jumpListMgr) { + return NS_ERROR_UNEXPECTED; + } + + if (SUCCEEDED(jumpListMgr->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; +} + +// RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to +// avoid unnecessary extra XPCOM incantations. For each object in the input +// array, if it's an IShellLinkW, this deletes the cached icon and adds the +// url param to a list of URLs to be removed from the places database. +void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs( + IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) { + MOZ_ASSERT(!NS_IsMainThread()); + + // Early return here just in case some versions of Windows don't populate this + if (!aObjArray) { + return; + } + + uint32_t count = 0; + aObjArray->GetCount(&count); + + for (uint32_t idx = 0; idx < count; idx++) { + RefPtr<IShellLinkW> pLink; + + if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW, + static_cast<void**>(getter_AddRefs(pLink))))) { + continue; + } + + wchar_t buf[MAX_PATH]; + HRESULT hres = pLink->GetArguments(buf, MAX_PATH); + if (SUCCEEDED(hres)) { + LPWSTR* arglist; + int32_t numArgs; + + arglist = ::CommandLineToArgvW(buf, &numArgs); + if (arglist && numArgs > 0) { + nsString spec(arglist[0]); + aURISpecs.AppendElement(std::move(spec)); + ::LocalFree(arglist); + } + } + + int iconIdx = 0; + hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx); + if (SUCCEEDED(hres)) { + nsDependentString spec(buf); + DeleteIconFromDisk(spec); + } + } +} + +void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) { + MOZ_ASSERT(!NS_IsMainThread()); + + // Check that we aren't deleting some arbitrary file that is not an icon + if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) { + // Construct the parent path of the passed in path + nsCOMPtr<nsIFile> icoFile; + nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + icoFile->Remove(false); + } +} + +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(); + // Clear out mJumpListMgr, as MSCOM services won't be available soon. + ReentrantMonitorAutoEnter lock(mMonitor); + mJumpListMgr = nullptr; + } else if (strcmp(aTopic, "nsPref:changed") == 0 && + nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { + bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); + if (!enabled) { + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + } + } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) { + // Delete JumpListCache icons from Disk, if any. + nsCOMPtr<nsIRunnable> event = + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/JumpListBuilder.h b/widget/windows/JumpListBuilder.h new file mode 100644 index 0000000000..ce6e71b305 --- /dev/null +++ b/widget/windows/JumpListBuilder.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "nsIJumpListBuilder.h" +#include "nsIJumpListItem.h" +#include "JumpListItem.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/mscom/AgileReference.h" +#include "mozilla/ReentrantMonitor.h" + +namespace mozilla { +namespace widget { + +namespace detail { +class DoneCommitListBuildCallback; +} // namespace detail + +class JumpListBuilder : public nsIJumpListBuilder, public nsIObserver { + virtual ~JumpListBuilder(); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIJUMPLISTBUILDER + NS_DECL_NSIOBSERVER + + JumpListBuilder(); + + protected: + static Atomic<bool> sBuildingList; + + private: + mscom::AgileReference mJumpListMgr; + uint32_t mMaxItems; + bool mHasCommit; + nsCOMPtr<nsIThread> mIOThread; + ReentrantMonitor mMonitor; + + bool IsSeparator(nsCOMPtr<nsIJumpListItem>& item); + void RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray* aObjArray, + nsTArray<nsString>& aURISpecs); + void DeleteIconFromDisk(const nsAString& aPath); + nsresult RemoveIconCacheForAllItems(); + void DoCommitListBuild(RefPtr<detail::DoneCommitListBuildCallback> aCallback); + void DoInitListBuild(RefPtr<dom::Promise>&& aPromise); + + friend class WinTaskbar; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __JumpListBuilder_h__ */ diff --git a/widget/windows/JumpListItem.cpp b/widget/windows/JumpListItem.cpp new file mode 100644 index 0000000000..941a479203 --- /dev/null +++ b/widget/windows/JumpListItem.cpp @@ -0,0 +1,575 @@ +/* -*- 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_INTERFACE_MAP_BEGIN(JumpListSeparator) + NS_INTERFACE_MAP_ENTRY(nsIJumpListSeparator) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase) +NS_INTERFACE_MAP_END +NS_IMPL_ADDREF(JumpListSeparator) +NS_IMPL_RELEASE(JumpListSeparator) + +NS_INTERFACE_MAP_BEGIN(JumpListLink) + NS_INTERFACE_MAP_ENTRY(nsIJumpListLink) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase) +NS_INTERFACE_MAP_END +NS_IMPL_ADDREF(JumpListLink) +NS_IMPL_RELEASE(JumpListLink) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut) + NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJumpListShortcut) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut) +NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp) + +NS_IMETHODIMP JumpListItemBase::GetType(int16_t* aType) { + NS_ENSURE_ARG_POINTER(aType); + + *aType = mItemType; + + return NS_OK; +} + +NS_IMETHODIMP JumpListItemBase::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; + 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(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; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/JumpListItem.h b/widget/windows/JumpListItem.h new file mode 100644 index 0000000000..67d9d50d50 --- /dev/null +++ b/widget/windows/JumpListItem.h @@ -0,0 +1,131 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsICryptoHash.h" +#include "nsString.h" +#include "nsCycleCollectionParticipant.h" + +class nsIThread; + +namespace mozilla { +namespace widget { + +class JumpListItemBase : public nsIJumpListItem { + public: + JumpListItemBase() : mItemType(nsIJumpListItem::JUMPLIST_ITEM_EMPTY) {} + + explicit JumpListItemBase(int32_t type) : mItemType(type) {} + + NS_DECL_NSIJUMPLISTITEM + + static const char kJumpListCacheDir[]; + + protected: + virtual ~JumpListItemBase() {} + + short Type() { return mItemType; } + short mItemType; +}; + +class JumpListItem : public JumpListItemBase { + ~JumpListItem() {} + + public: + using JumpListItemBase::JumpListItemBase; + + NS_DECL_ISUPPORTS +}; + +class JumpListSeparator : public JumpListItemBase, public nsIJumpListSeparator { + ~JumpListSeparator() {} + + public: + JumpListSeparator() + : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) {} + + NS_DECL_ISUPPORTS + NS_FORWARD_NSIJUMPLISTITEM(JumpListItemBase::) + + static nsresult GetSeparator(RefPtr<IShellLinkW>& aShellLink); +}; + +class JumpListLink : public JumpListItemBase, public nsIJumpListLink { + ~JumpListLink() {} + + public: + JumpListLink() : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_LINK) {} + + NS_DECL_ISUPPORTS + NS_IMETHOD GetType(int16_t* aType) override { + return JumpListItemBase::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 JumpListItemBase, public nsIJumpListShortcut { + ~JumpListShortcut() {} + + public: + JumpListShortcut() + : JumpListItemBase(nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {} + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JumpListShortcut, JumpListItemBase) + NS_IMETHOD GetType(int16_t* aType) override { + return JumpListItemBase::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..b08d9c8dc6 --- /dev/null +++ b/widget/windows/KeyboardLayout.cpp @@ -0,0 +1,5418 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Logging.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TextEvents.h" + +#include "nsAlgorithm.h" +#include "nsExceptionHandler.h" +#include "nsGkAtoms.h" +#include "nsIUserIdleServiceInternal.h" +#include "nsIWindowsRegKey.h" +#include "nsMemory.h" +#include "nsPrintfCString.h" +#include "nsQuickSort.h" +#include "nsReadableUtils.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* const kVirtualKeyName[] = { + "NULL", + "VK_LBUTTON", + "VK_RBUTTON", + "VK_CANCEL", + "VK_MBUTTON", + "VK_XBUTTON1", + "VK_XBUTTON2", + "0x07", + "VK_BACK", + "VK_TAB", + "0x0A", + "0x0B", + "VK_CLEAR", + "VK_RETURN", + "0x0E", + "0x0F", + + "VK_SHIFT", + "VK_CONTROL", + "VK_MENU", + "VK_PAUSE", + "VK_CAPITAL", + "VK_KANA, VK_HANGUL", + "0x16", + "VK_JUNJA", + "VK_FINAL", + "VK_HANJA, VK_KANJI", + "0x1A", + "VK_ESCAPE", + "VK_CONVERT", + "VK_NONCONVERT", + "VK_ACCEPT", + "VK_MODECHANGE", + + "VK_SPACE", + "VK_PRIOR", + "VK_NEXT", + "VK_END", + "VK_HOME", + "VK_LEFT", + "VK_UP", + "VK_RIGHT", + "VK_DOWN", + "VK_SELECT", + "VK_PRINT", + "VK_EXECUTE", + "VK_SNAPSHOT", + "VK_INSERT", + "VK_DELETE", + "VK_HELP", + + "VK_0", + "VK_1", + "VK_2", + "VK_3", + "VK_4", + "VK_5", + "VK_6", + "VK_7", + "VK_8", + "VK_9", + "0x3A", + "0x3B", + "0x3C", + "0x3D", + "0x3E", + "0x3F", + + "0x40", + "VK_A", + "VK_B", + "VK_C", + "VK_D", + "VK_E", + "VK_F", + "VK_G", + "VK_H", + "VK_I", + "VK_J", + "VK_K", + "VK_L", + "VK_M", + "VK_N", + "VK_O", + + "VK_P", + "VK_Q", + "VK_R", + "VK_S", + "VK_T", + "VK_U", + "VK_V", + "VK_W", + "VK_X", + "VK_Y", + "VK_Z", + "VK_LWIN", + "VK_RWIN", + "VK_APPS", + "0x5E", + "VK_SLEEP", + + "VK_NUMPAD0", + "VK_NUMPAD1", + "VK_NUMPAD2", + "VK_NUMPAD3", + "VK_NUMPAD4", + "VK_NUMPAD5", + "VK_NUMPAD6", + "VK_NUMPAD7", + "VK_NUMPAD8", + "VK_NUMPAD9", + "VK_MULTIPLY", + "VK_ADD", + "VK_SEPARATOR", + "VK_SUBTRACT", + "VK_DECIMAL", + "VK_DIVIDE", + + "VK_F1", + "VK_F2", + "VK_F3", + "VK_F4", + "VK_F5", + "VK_F6", + "VK_F7", + "VK_F8", + "VK_F9", + "VK_F10", + "VK_F11", + "VK_F12", + "VK_F13", + "VK_F14", + "VK_F15", + "VK_F16", + + "VK_F17", + "VK_F18", + "VK_F19", + "VK_F20", + "VK_F21", + "VK_F22", + "VK_F23", + "VK_F24", + "0x88", + "0x89", + "0x8A", + "0x8B", + "0x8C", + "0x8D", + "0x8E", + "0x8F", + + "VK_NUMLOCK", + "VK_SCROLL", + "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO", + "VK_OEM_FJ_MASSHOU", + "VK_OEM_FJ_TOUROKU", + "VK_OEM_FJ_LOYA", + "VK_OEM_FJ_ROYA", + "0x97", + "0x98", + "0x99", + "0x9A", + "0x9B", + "0x9C", + "0x9D", + "0x9E", + "0x9F", + + "VK_LSHIFT", + "VK_RSHIFT", + "VK_LCONTROL", + "VK_RCONTROL", + "VK_LMENU", + "VK_RMENU", + "VK_BROWSER_BACK", + "VK_BROWSER_FORWARD", + "VK_BROWSER_REFRESH", + "VK_BROWSER_STOP", + "VK_BROWSER_SEARCH", + "VK_BROWSER_FAVORITES", + "VK_BROWSER_HOME", + "VK_VOLUME_MUTE", + "VK_VOLUME_DOWN", + "VK_VOLUME_UP", + + "VK_MEDIA_NEXT_TRACK", + "VK_MEDIA_PREV_TRACK", + "VK_MEDIA_STOP", + "VK_MEDIA_PLAY_PAUSE", + "VK_LAUNCH_MAIL", + "VK_LAUNCH_MEDIA_SELECT", + "VK_LAUNCH_APP1", + "VK_LAUNCH_APP2", + "0xB8", + "0xB9", + "VK_OEM_1", + "VK_OEM_PLUS", + "VK_OEM_COMMA", + "VK_OEM_MINUS", + "VK_OEM_PERIOD", + "VK_OEM_2", + + "VK_OEM_3", + "VK_ABNT_C1", + "VK_ABNT_C2", + "0xC3", + "0xC4", + "0xC5", + "0xC6", + "0xC7", + "0xC8", + "0xC9", + "0xCA", + "0xCB", + "0xCC", + "0xCD", + "0xCE", + "0xCF", + + "0xD0", + "0xD1", + "0xD2", + "0xD3", + "0xD4", + "0xD5", + "0xD6", + "0xD7", + "0xD8", + "0xD9", + "0xDA", + "VK_OEM_4", + "VK_OEM_5", + "VK_OEM_6", + "VK_OEM_7", + "VK_OEM_8", + + "0xE0", + "VK_OEM_AX", + "VK_OEM_102", + "VK_ICO_HELP", + "VK_ICO_00", + "VK_PROCESSKEY", + "VK_ICO_CLEAR", + "VK_PACKET", + "0xE8", + "VK_OEM_RESET", + "VK_OEM_JUMP", + "VK_OEM_PA1", + "VK_OEM_PA2", + "VK_OEM_PA3", + "VK_OEM_WSCTRL", + "VK_OEM_CUSEL", + + "VK_OEM_ATTN", + "VK_OEM_FINISH", + "VK_OEM_COPY", + "VK_OEM_AUTO", + "VK_OEM_ENLW", + "VK_OEM_BACKTAB", + "VK_ATTN", + "VK_CRSEL", + "VK_EXSEL", + "VK_EREOF", + "VK_PLAY", + "VK_ZOOM", + "VK_NONAME", + "VK_PA1", + "VK_OEM_CLEAR", + "0xFF"}; + +static_assert(sizeof(kVirtualKeyName) / sizeof(const char*) == 0x100, + "The virtual key name must be defined just 256 keys"); + +static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; } + +static const nsCString GetCharacterCodeName(WPARAM aCharCode) { + switch (aCharCode) { + case 0x0000: + return "NULL (0x0000)"_ns; + case 0x0008: + return "BACKSPACE (0x0008)"_ns; + case 0x0009: + return "CHARACTER TABULATION (0x0009)"_ns; + case 0x000A: + return "LINE FEED (0x000A)"_ns; + case 0x000B: + return "LINE TABULATION (0x000B)"_ns; + case 0x000C: + return "FORM FEED (0x000C)"_ns; + case 0x000D: + return "CARRIAGE RETURN (0x000D)"_ns; + case 0x0018: + return "CANCEL (0x0018)"_ns; + case 0x001B: + return "ESCAPE (0x001B)"_ns; + case 0x0020: + return "SPACE (0x0020)"_ns; + case 0x007F: + return "DELETE (0x007F)"_ns; + case 0x00A0: + return "NO-BREAK SPACE (0x00A0)"_ns; + case 0x00AD: + return "SOFT HYPHEN (0x00AD)"_ns; + case 0x2000: + return "EN QUAD (0x2000)"_ns; + case 0x2001: + return "EM QUAD (0x2001)"_ns; + case 0x2002: + return "EN SPACE (0x2002)"_ns; + case 0x2003: + return "EM SPACE (0x2003)"_ns; + case 0x2004: + return "THREE-PER-EM SPACE (0x2004)"_ns; + case 0x2005: + return "FOUR-PER-EM SPACE (0x2005)"_ns; + case 0x2006: + return "SIX-PER-EM SPACE (0x2006)"_ns; + case 0x2007: + return "FIGURE SPACE (0x2007)"_ns; + case 0x2008: + return "PUNCTUATION SPACE (0x2008)"_ns; + case 0x2009: + return "THIN SPACE (0x2009)"_ns; + case 0x200A: + return "HAIR SPACE (0x200A)"_ns; + case 0x200B: + return "ZERO WIDTH SPACE (0x200B)"_ns; + case 0x200C: + return "ZERO WIDTH NON-JOINER (0x200C)"_ns; + case 0x200D: + return "ZERO WIDTH JOINER (0x200D)"_ns; + case 0x200E: + return "LEFT-TO-RIGHT MARK (0x200E)"_ns; + case 0x200F: + return "RIGHT-TO-LEFT MARK (0x200F)"_ns; + case 0x2029: + return "PARAGRAPH SEPARATOR (0x2029)"_ns; + case 0x202A: + return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns; + case 0x202B: + return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns; + case 0x202D: + return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns; + case 0x202E: + return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns; + case 0x202F: + return "NARROW NO-BREAK SPACE (0x202F)"_ns; + case 0x205F: + return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns; + case 0x2060: + return "WORD JOINER (0x2060)"_ns; + case 0x2066: + return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns; + case 0x2067: + return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns; + case 0x3000: + return "IDEOGRAPHIC SPACE (0x3000)"_ns; + case 0xFEFF: + return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns; + default: { + if (aCharCode < ' ' || (aCharCode >= 0x80 && aCharCode < 0xA0)) { + return nsPrintfCString("control (0x%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 eKeyLocationLeft: + return "KEY_LOCATION_LEFT"_ns; + case eKeyLocationRight: + return "KEY_LOCATION_RIGHT"_ns; + case eKeyLocationStandard: + return "KEY_LOCATION_STANDARD"_ns; + case eKeyLocationNumpad: + return "KEY_LOCATION_NUMPAD"_ns; + default: + return nsPrintfCString("Unknown (0x%04X)", aLocation); + } +} + +static const nsCString GetCharacterCodeNames(const char16_t* aChars, + uint32_t aLength) { + if (!aLength) { + return ""_ns; + } + nsCString result; + result.AssignLiteral("\""); + StringJoinAppend(result, ", "_ns, Span{aChars, aLength}, + [](nsACString& dest, const char16_t charValue) { + dest.Append(GetCharacterCodeName(charValue)); + }); + result.AppendLiteral("\""); + return result; +} + +static const nsCString GetCharacterCodeNames( + const UniCharsAndModifiers& aUniCharsAndModifiers) { + if (aUniCharsAndModifiers.IsEmpty()) { + return ""_ns; + } + nsCString result; + result.AssignLiteral("\""); + StringJoinAppend(result, ", "_ns, Span{aUniCharsAndModifiers.ToString()}, + [](nsACString& dest, const char16_t charValue) { + dest.Append(GetCharacterCodeName(charValue)); + }); + result.AppendLiteral("\""); + return result; +} + +class MOZ_STACK_CLASS GetShiftStateName final : public nsAutoCString { + public: + explicit GetShiftStateName(VirtualKey::ShiftState aShiftState) { + if (!aShiftState) { + AssignLiteral("none"); + return; + } + if (aShiftState & VirtualKey::STATE_SHIFT) { + AssignLiteral("Shift"); + aShiftState &= ~VirtualKey::STATE_SHIFT; + } + if (aShiftState & VirtualKey::STATE_CONTROL) { + MaybeAppendSeparator(); + AssignLiteral("Ctrl"); + aShiftState &= ~VirtualKey::STATE_CONTROL; + } + if (aShiftState & VirtualKey::STATE_ALT) { + MaybeAppendSeparator(); + AssignLiteral("Alt"); + aShiftState &= ~VirtualKey::STATE_ALT; + } + if (aShiftState & VirtualKey::STATE_CAPSLOCK) { + MaybeAppendSeparator(); + AssignLiteral("CapsLock"); + aShiftState &= ~VirtualKey::STATE_CAPSLOCK; + } + MOZ_ASSERT(!aShiftState); + } + + private: + void MaybeAppendSeparator() { + if (!IsEmpty()) { + AppendLiteral(" | "); + } + } +}; + +static const nsCString GetMessageName(UINT aMessage) { + switch (aMessage) { + case WM_NULL: + return "WM_NULL"_ns; + case WM_KEYDOWN: + return "WM_KEYDOWN"_ns; + case WM_KEYUP: + return "WM_KEYUP"_ns; + case WM_SYSKEYDOWN: + return "WM_SYSKEYDOWN"_ns; + case WM_SYSKEYUP: + return "WM_SYSKEYUP"_ns; + case WM_CHAR: + return "WM_CHAR"_ns; + case WM_UNICHAR: + return "WM_UNICHAR"_ns; + case WM_SYSCHAR: + return "WM_SYSCHAR"_ns; + case WM_DEADCHAR: + return "WM_DEADCHAR"_ns; + case WM_SYSDEADCHAR: + return "WM_SYSDEADCHAR"_ns; + case MOZ_WM_KEYDOWN: + return "MOZ_WM_KEYDOWN"_ns; + case MOZ_WM_KEYUP: + return "MOZ_WM_KEYUP"_ns; + case WM_APPCOMMAND: + return "WM_APPCOMMAND"_ns; + case WM_QUIT: + return "WM_QUIT"_ns; + default: + return nsPrintfCString("Unknown Message (0x%04X)", aMessage); + } +} + +static const nsCString GetVirtualKeyCodeName(WPARAM aVK) { + if (aVK >= ArrayLength(kVirtualKeyName)) { + return nsPrintfCString("Invalid (0x%08X)", aVK); + } + return nsCString(kVirtualKeyName[aVK]); +} + +static const nsCString GetAppCommandName(WPARAM aCommand) { + switch (aCommand) { + case APPCOMMAND_BASS_BOOST: + return "APPCOMMAND_BASS_BOOST"_ns; + case APPCOMMAND_BASS_DOWN: + return "APPCOMMAND_BASS_DOWN"_ns; + case APPCOMMAND_BASS_UP: + return "APPCOMMAND_BASS_UP"_ns; + case APPCOMMAND_BROWSER_BACKWARD: + return "APPCOMMAND_BROWSER_BACKWARD"_ns; + case APPCOMMAND_BROWSER_FAVORITES: + return "APPCOMMAND_BROWSER_FAVORITES"_ns; + case APPCOMMAND_BROWSER_FORWARD: + return "APPCOMMAND_BROWSER_FORWARD"_ns; + case APPCOMMAND_BROWSER_HOME: + return "APPCOMMAND_BROWSER_HOME"_ns; + case APPCOMMAND_BROWSER_REFRESH: + return "APPCOMMAND_BROWSER_REFRESH"_ns; + case APPCOMMAND_BROWSER_SEARCH: + return "APPCOMMAND_BROWSER_SEARCH"_ns; + case APPCOMMAND_BROWSER_STOP: + return "APPCOMMAND_BROWSER_STOP"_ns; + case APPCOMMAND_CLOSE: + return "APPCOMMAND_CLOSE"_ns; + case APPCOMMAND_COPY: + return "APPCOMMAND_COPY"_ns; + case APPCOMMAND_CORRECTION_LIST: + return "APPCOMMAND_CORRECTION_LIST"_ns; + case APPCOMMAND_CUT: + return "APPCOMMAND_CUT"_ns; + case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE: + return "APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE"_ns; + case APPCOMMAND_FIND: + return "APPCOMMAND_FIND"_ns; + case APPCOMMAND_FORWARD_MAIL: + return "APPCOMMAND_FORWARD_MAIL"_ns; + case APPCOMMAND_HELP: + return "APPCOMMAND_HELP"_ns; + case APPCOMMAND_LAUNCH_APP1: + return "APPCOMMAND_LAUNCH_APP1"_ns; + case APPCOMMAND_LAUNCH_APP2: + return "APPCOMMAND_LAUNCH_APP2"_ns; + case APPCOMMAND_LAUNCH_MAIL: + return "APPCOMMAND_LAUNCH_MAIL"_ns; + case APPCOMMAND_LAUNCH_MEDIA_SELECT: + return "APPCOMMAND_LAUNCH_MEDIA_SELECT"_ns; + case APPCOMMAND_MEDIA_CHANNEL_DOWN: + return "APPCOMMAND_MEDIA_CHANNEL_DOWN"_ns; + case APPCOMMAND_MEDIA_CHANNEL_UP: + return "APPCOMMAND_MEDIA_CHANNEL_UP"_ns; + case APPCOMMAND_MEDIA_FAST_FORWARD: + return "APPCOMMAND_MEDIA_FAST_FORWARD"_ns; + case APPCOMMAND_MEDIA_NEXTTRACK: + return "APPCOMMAND_MEDIA_NEXTTRACK"_ns; + case APPCOMMAND_MEDIA_PAUSE: + return "APPCOMMAND_MEDIA_PAUSE"_ns; + case APPCOMMAND_MEDIA_PLAY: + return "APPCOMMAND_MEDIA_PLAY"_ns; + case APPCOMMAND_MEDIA_PLAY_PAUSE: + return "APPCOMMAND_MEDIA_PLAY_PAUSE"_ns; + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + return "APPCOMMAND_MEDIA_PREVIOUSTRACK"_ns; + case APPCOMMAND_MEDIA_RECORD: + return "APPCOMMAND_MEDIA_RECORD"_ns; + case APPCOMMAND_MEDIA_REWIND: + return "APPCOMMAND_MEDIA_REWIND"_ns; + case APPCOMMAND_MEDIA_STOP: + return "APPCOMMAND_MEDIA_STOP"_ns; + case APPCOMMAND_MIC_ON_OFF_TOGGLE: + return "APPCOMMAND_MIC_ON_OFF_TOGGLE"_ns; + case APPCOMMAND_MICROPHONE_VOLUME_DOWN: + return "APPCOMMAND_MICROPHONE_VOLUME_DOWN"_ns; + case APPCOMMAND_MICROPHONE_VOLUME_MUTE: + return "APPCOMMAND_MICROPHONE_VOLUME_MUTE"_ns; + case APPCOMMAND_MICROPHONE_VOLUME_UP: + return "APPCOMMAND_MICROPHONE_VOLUME_UP"_ns; + case APPCOMMAND_NEW: + return "APPCOMMAND_NEW"_ns; + case APPCOMMAND_OPEN: + return "APPCOMMAND_OPEN"_ns; + case APPCOMMAND_PASTE: + return "APPCOMMAND_PASTE"_ns; + case APPCOMMAND_PRINT: + return "APPCOMMAND_PRINT"_ns; + case APPCOMMAND_REDO: + return "APPCOMMAND_REDO"_ns; + case APPCOMMAND_REPLY_TO_MAIL: + return "APPCOMMAND_REPLY_TO_MAIL"_ns; + case APPCOMMAND_SAVE: + return "APPCOMMAND_SAVE"_ns; + case APPCOMMAND_SEND_MAIL: + return "APPCOMMAND_SEND_MAIL"_ns; + case APPCOMMAND_SPELL_CHECK: + return "APPCOMMAND_SPELL_CHECK"_ns; + case APPCOMMAND_TREBLE_DOWN: + return "APPCOMMAND_TREBLE_DOWN"_ns; + case APPCOMMAND_TREBLE_UP: + return "APPCOMMAND_TREBLE_UP"_ns; + case APPCOMMAND_UNDO: + return "APPCOMMAND_UNDO"_ns; + case APPCOMMAND_VOLUME_DOWN: + return "APPCOMMAND_VOLUME_DOWN"_ns; + case APPCOMMAND_VOLUME_MUTE: + return "APPCOMMAND_VOLUME_MUTE"_ns; + case APPCOMMAND_VOLUME_UP: + return "APPCOMMAND_VOLUME_UP"_ns; + default: + return nsPrintfCString("Unknown app command (0x%08X)", aCommand); + } +} + +static const nsCString GetAppCommandDeviceName(LPARAM aDevice) { + switch (aDevice) { + case FAPPCOMMAND_KEY: + return "FAPPCOMMAND_KEY"_ns; + case FAPPCOMMAND_MOUSE: + return "FAPPCOMMAND_MOUSE"_ns; + case FAPPCOMMAND_OEM: + return "FAPPCOMMAND_OEM"_ns; + default: + return nsPrintfCString("Unknown app command device (0x%04X)", aDevice); + } +}; + +class MOZ_STACK_CLASS GetAppCommandKeysName final : public nsAutoCString { + public: + explicit GetAppCommandKeysName(WPARAM aKeys) { + if (aKeys & MK_CONTROL) { + AppendLiteral("MK_CONTROL"); + aKeys &= ~MK_CONTROL; + } + if (aKeys & MK_LBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_LBUTTON"); + aKeys &= ~MK_LBUTTON; + } + if (aKeys & MK_MBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_MBUTTON"); + aKeys &= ~MK_MBUTTON; + } + if (aKeys & MK_RBUTTON) { + MaybeAppendSeparator(); + AppendLiteral("MK_RBUTTON"); + aKeys &= ~MK_RBUTTON; + } + if (aKeys & MK_SHIFT) { + MaybeAppendSeparator(); + AppendLiteral("MK_SHIFT"); + aKeys &= ~MK_SHIFT; + } + if (aKeys & MK_XBUTTON1) { + MaybeAppendSeparator(); + AppendLiteral("MK_XBUTTON1"); + aKeys &= ~MK_XBUTTON1; + } + if (aKeys & MK_XBUTTON2) { + MaybeAppendSeparator(); + AppendLiteral("MK_XBUTTON2"); + aKeys &= ~MK_XBUTTON2; + } + if (aKeys) { + MaybeAppendSeparator(); + AppendPrintf("Unknown Flags (0x%04X)", aKeys); + } + if (IsEmpty()) { + AssignLiteral("none (0x0000)"); + } + } + + private: + void MaybeAppendSeparator() { + if (!IsEmpty()) { + AppendLiteral(" | "); + } + } +}; + +static const nsCString ToString(const MSG& aMSG) { + nsCString result; + result.AssignLiteral("{ message="); + result.Append(GetMessageName(aMSG.message).get()); + result.AppendLiteral(", "); + switch (aMSG.message) { + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + 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; + } + nsCString result; + result.AssignLiteral("{ "); + result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(0))); + for (size_t i = 1; i < aUniCharsAndModifiers.Length(); ++i) { + if (aUniCharsAndModifiers.ModifiersAt(i - 1) != + aUniCharsAndModifiers.ModifiersAt(i)) { + result.AppendLiteral(" ["); + result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(0))); + result.AppendLiteral("]"); + } + result.AppendLiteral(", "); + result.Append(GetCharacterCodeName(aUniCharsAndModifiers.CharAt(i))); + } + result.AppendLiteral(" ["); + uint32_t lastIndex = aUniCharsAndModifiers.Length() - 1; + result.Append(GetModifiersName(aUniCharsAndModifiers.ModifiersAt(lastIndex))); + result.AppendLiteral("] }"); + return result; +} + +const nsCString ToString(const ModifierKeyState& aModifierKeyState) { + nsCString result; + result.AssignLiteral("{ "); + result.Append(GetModifiersName(aModifierKeyState.GetModifiers()).get()); + result.AppendLiteral(" }"); + return result; +} + +// Unique id counter associated with a keydown / keypress events. Used in +// identifing keypress events for removal from async event dispatch queue +// in metrofx after preventDefault is called on keydown events. +static uint32_t sUniqueKeyEventId = 0; + +/***************************************************************************** + * mozilla::widget::ModifierKeyState + *****************************************************************************/ + +ModifierKeyState::ModifierKeyState() { Update(); } + +ModifierKeyState::ModifierKeyState(Modifiers aModifiers) + : mModifiers(aModifiers) { + MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()), + "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set " + "if MODIFIER_ALTGRAPH is set"); +} + +void ModifierKeyState::Update() { + mModifiers = 0; + if (IS_VK_DOWN(VK_SHIFT)) { + mModifiers |= MODIFIER_SHIFT; + } + // If AltGr key (i.e., VK_RMENU on some keyboard layout) is pressed, only + // MODIFIER_ALTGRAPH should be set. Otherwise, i.e., if both Ctrl and Alt + // keys are pressed to emulate AltGr key, MODIFIER_CONTROL and MODIFIER_ALT + // keys should be set separately. + if (KeyboardLayout::GetInstance()->HasAltGr() && IS_VK_DOWN(VK_RMENU)) { + mModifiers |= MODIFIER_ALTGRAPH; + } else { + if (IS_VK_DOWN(VK_CONTROL)) { + mModifiers |= MODIFIER_CONTROL; + } + if (IS_VK_DOWN(VK_MENU)) { + mModifiers |= MODIFIER_ALT; + } + } + if (IS_VK_DOWN(VK_LWIN) || IS_VK_DOWN(VK_RWIN)) { + mModifiers |= MODIFIER_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; + } +} + +void ModifierKeyState::Unset(Modifiers aRemovingModifiers) { + mModifiers &= ~aRemovingModifiers; +} + +void ModifierKeyState::Set(Modifiers aAddingModifiers) { + mModifiers |= aAddingModifiers; + MOZ_ASSERT(!(mModifiers & MODIFIER_ALTGRAPH) || (!IsControl() && !IsAlt()), + "Neither MODIFIER_CONTROL nor MODIFIER_ALT should be set " + "if MODIFIER_ALTGRAPH is set"); +} + +void ModifierKeyState::InitInputEvent(WidgetInputEvent& aInputEvent) const { + aInputEvent.mModifiers = mModifiers; + + switch (aInputEvent.mClass) { + case eMouseEventClass: + case eMouseScrollEventClass: + case eWheelEventClass: + case eDragEventClass: + case eSimpleGestureEventClass: + InitMouseEvent(aInputEvent); + break; + default: + break; + } +} + +void ModifierKeyState::InitMouseEvent(WidgetInputEvent& aMouseEvent) const { + NS_ASSERTION(aMouseEvent.mClass == eMouseEventClass || + aMouseEvent.mClass == eWheelEventClass || + aMouseEvent.mClass == eDragEventClass || + aMouseEvent.mClass == eSimpleGestureEventClass, + "called with non-mouse event"); + + WidgetMouseEventBase& mouseEvent = *aMouseEvent.AsMouseEventBase(); + mouseEvent.mButtons = 0; + if (::GetKeyState(VK_LBUTTON) < 0) { + mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag; + } + if (::GetKeyState(VK_RBUTTON) < 0) { + mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag; + } + if (::GetKeyState(VK_MBUTTON) < 0) { + mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag; + } + if (::GetKeyState(VK_XBUTTON1) < 0) { + mouseEvent.mButtons |= MouseButtonsFlag::e4thFlag; + } + if (::GetKeyState(VK_XBUTTON2) < 0) { + mouseEvent.mButtons |= MouseButtonsFlag::e5thFlag; + } +} + +bool ModifierKeyState::IsShift() const { + return (mModifiers & MODIFIER_SHIFT) != 0; +} + +bool ModifierKeyState::IsControl() const { + return (mModifiers & MODIFIER_CONTROL) != 0; +} + +bool ModifierKeyState::IsAlt() const { + return (mModifiers & MODIFIER_ALT) != 0; +} + +bool ModifierKeyState::IsWin() const { return (mModifiers & MODIFIER_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; +} + +/***************************************************************************** + * mozilla::widget::UniCharsAndModifiers + *****************************************************************************/ + +void UniCharsAndModifiers::Append(char16_t aUniChar, Modifiers aModifiers) { + mChars.Append(aUniChar); + mModifiers.AppendElement(aModifiers); +} + +void UniCharsAndModifiers::FillModifiers(Modifiers aModifiers) { + for (size_t i = 0; i < Length(); i++) { + mModifiers[i] = aModifiers; + } +} + +void UniCharsAndModifiers::OverwriteModifiersIfBeginsWith( + const UniCharsAndModifiers& aOther) { + if (!BeginsWith(aOther)) { + return; + } + for (size_t i = 0; i < aOther.Length(); ++i) { + mModifiers[i] = aOther.mModifiers[i]; + } +} + +bool UniCharsAndModifiers::UniCharsEqual( + const UniCharsAndModifiers& aOther) const { + return mChars.Equals(aOther.mChars); +} + +bool UniCharsAndModifiers::UniCharsCaseInsensitiveEqual( + const UniCharsAndModifiers& aOther) const { + return mChars.Equals(aOther.mChars, nsCaseInsensitiveStringComparator); +} + +bool UniCharsAndModifiers::BeginsWith( + const UniCharsAndModifiers& aOther) const { + return StringBeginsWith(mChars, aOther.mChars); +} + +UniCharsAndModifiers& UniCharsAndModifiers::operator+=( + const UniCharsAndModifiers& aOther) { + mChars.Append(aOther.mChars); + mModifiers.AppendElements(aOther.mModifiers); + return *this; +} + +UniCharsAndModifiers UniCharsAndModifiers::operator+( + const UniCharsAndModifiers& aOther) const { + UniCharsAndModifiers result(*this); + result += aOther; + return result; +} + +/***************************************************************************** + * mozilla::widget::VirtualKey + *****************************************************************************/ + +// static +VirtualKey::ShiftState VirtualKey::ModifiersToShiftState(Modifiers aModifiers) { + ShiftState state = 0; + if (aModifiers & MODIFIER_SHIFT) { + state |= STATE_SHIFT; + } + if (aModifiers & MODIFIER_ALTGRAPH) { + state |= STATE_ALTGRAPH; + } else { + if (aModifiers & MODIFIER_CONTROL) { + state |= STATE_CONTROL; + } + if (aModifiers & MODIFIER_ALT) { + state |= STATE_ALT; + } + } + if (aModifiers & MODIFIER_CAPSLOCK) { + state |= STATE_CAPSLOCK; + } + return state; +} + +// static +Modifiers VirtualKey::ShiftStateToModifiers(ShiftState aShiftState) { + Modifiers modifiers = 0; + if (aShiftState & STATE_SHIFT) { + modifiers |= MODIFIER_SHIFT; + } + if (aShiftState & STATE_ALTGRAPH) { + modifiers |= MODIFIER_ALTGRAPH; + } else { + if (aShiftState & STATE_CONTROL) { + modifiers |= MODIFIER_CONTROL; + } + if (aShiftState & STATE_ALT) { + modifiers |= MODIFIER_ALT; + } + } + if (aShiftState & STATE_CAPSLOCK) { + modifiers |= MODIFIER_CAPSLOCK; + } + return modifiers; +} + +const DeadKeyTable* VirtualKey::MatchingDeadKeyTable( + const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const { + if (!mIsDeadKey) { + return nullptr; + } + + for (ShiftState shiftState = 0; shiftState < 16; shiftState++) { + if (!IsDeadKey(shiftState)) { + continue; + } + const DeadKeyTable* dkt = mShiftStates[shiftState].DeadKey.Table; + if (dkt && dkt->IsEqual(aDeadKeyArray, aEntries)) { + return dkt; + } + } + + return nullptr; +} + +void VirtualKey::SetNormalChars(ShiftState aShiftState, const char16_t* aChars, + uint32_t aNumOfChars) { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + + SetDeadKey(aShiftState, false); + + for (uint32_t index = 0; index < aNumOfChars; index++) { + // Ignore legacy non-printable control characters + mShiftStates[aShiftState].Normal.Chars[index] = + (aChars[index] >= 0x20) ? aChars[index] : 0; + } + + uint32_t len = ArrayLength(mShiftStates[aShiftState].Normal.Chars); + for (uint32_t index = aNumOfChars; index < len; index++) { + mShiftStates[aShiftState].Normal.Chars[index] = 0; + } +} + +void VirtualKey::SetDeadChar(ShiftState aShiftState, char16_t aDeadChar) { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + + SetDeadKey(aShiftState, true); + + mShiftStates[aShiftState].DeadKey.DeadChar = aDeadChar; + mShiftStates[aShiftState].DeadKey.Table = nullptr; +} + +UniCharsAndModifiers VirtualKey::GetUniChars(ShiftState aShiftState) const { + UniCharsAndModifiers result = GetNativeUniChars(aShiftState); + + const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState); + if (!(kShiftStateIndex & STATE_CONTROL_ALT)) { + // If neither Alt nor Ctrl key is pressed, just return stored data + // for the key. + return result; + } + + if (result.IsEmpty()) { + // If Alt and/or Control are pressed and the key produces no + // character, return characters which is produced by the key without + // Alt and Control, and return given modifiers as is. + result = GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT); + result.FillModifiers(ShiftStateToModifiers(aShiftState)); + return result; + } + + if (IsAltGrIndex(kShiftStateIndex)) { + // If AltGr or both Ctrl and Alt are pressed and the key produces + // character(s), we need to clear MODIFIER_ALT and MODIFIER_CONTROL + // since TextEditor won't handle eKeyPress event whose mModifiers + // has MODIFIER_ALT or MODIFIER_CONTROL. Additionally, we need to + // use MODIFIER_ALTGRAPH when a key produces character(s) with + // AltGr or both Ctrl and Alt on Windows. See following spec issue: + // <https://github.com/w3c/uievents/issues/147> + Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); + finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + finalModifiers |= MODIFIER_ALTGRAPH; + result.FillModifiers(finalModifiers); + return result; + } + + // Otherwise, i.e., Alt or Ctrl is pressed and it produces character(s), + // check if different character(s) is produced by the key without Alt/Ctrl. + // If it produces different character, we need to consume the Alt and + // Ctrl modifier for TextEditor. Otherwise, the key does not produces the + // character actually. So, keep setting Alt and Ctrl modifiers. + UniCharsAndModifiers unmodifiedReslt = + GetNativeUniChars(kShiftStateIndex & ~STATE_CONTROL_ALT); + if (!result.UniCharsEqual(unmodifiedReslt)) { + Modifiers finalModifiers = ShiftStateToModifiers(aShiftState); + finalModifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + result.FillModifiers(finalModifiers); + } + return result; +} + +UniCharsAndModifiers VirtualKey::GetNativeUniChars( + ShiftState aShiftState) const { + const uint8_t kShiftStateIndex = ToShiftStateIndex(aShiftState); + UniCharsAndModifiers result; + Modifiers modifiers = ShiftStateToModifiers(aShiftState); + if (IsDeadKey(aShiftState)) { + result.Append(mShiftStates[kShiftStateIndex].DeadKey.DeadChar, modifiers); + return result; + } + + uint32_t len = ArrayLength(mShiftStates[kShiftStateIndex].Normal.Chars); + for (uint32_t i = 0; + i < len && mShiftStates[kShiftStateIndex].Normal.Chars[i]; i++) { + result.Append(mShiftStates[kShiftStateIndex].Normal.Chars[i], modifiers); + } + return result; +} + +// static +void VirtualKey::FillKbdState(PBYTE aKbdState, const ShiftState aShiftState) { + if (aShiftState & STATE_SHIFT) { + aKbdState[VK_SHIFT] |= 0x80; + } else { + aKbdState[VK_SHIFT] &= ~0x80; + aKbdState[VK_LSHIFT] &= ~0x80; + aKbdState[VK_RSHIFT] &= ~0x80; + } + + if (aShiftState & STATE_ALTGRAPH) { + aKbdState[VK_CONTROL] |= 0x80; + aKbdState[VK_LCONTROL] |= 0x80; + aKbdState[VK_RCONTROL] &= ~0x80; + aKbdState[VK_MENU] |= 0x80; + aKbdState[VK_LMENU] &= ~0x80; + aKbdState[VK_RMENU] |= 0x80; + } else { + if (aShiftState & STATE_CONTROL) { + aKbdState[VK_CONTROL] |= 0x80; + } else { + aKbdState[VK_CONTROL] &= ~0x80; + aKbdState[VK_LCONTROL] &= ~0x80; + aKbdState[VK_RCONTROL] &= ~0x80; + } + + if (aShiftState & STATE_ALT) { + aKbdState[VK_MENU] |= 0x80; + } else { + aKbdState[VK_MENU] &= ~0x80; + aKbdState[VK_LMENU] &= ~0x80; + aKbdState[VK_RMENU] &= ~0x80; + } + } + + if (aShiftState & STATE_CAPSLOCK) { + aKbdState[VK_CAPITAL] |= 0x01; + } else { + aKbdState[VK_CAPITAL] &= ~0x01; + } +} + +/***************************************************************************** + * mozilla::widget::NativeKey + *****************************************************************************/ + +uint8_t NativeKey::sDispatchedKeyOfAppCommand = 0; +NativeKey* NativeKey::sLatestInstance = nullptr; +const MSG NativeKey::sEmptyMSG = {}; +MSG NativeKey::sLastKeyOrCharMSG = {}; +MSG NativeKey::sLastKeyMSG = {}; + +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), + mIsRepeat(false), + mIsDeadKey(false), + mIsPrintableKey(false), + mIsSkippableInRemoteProcess(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; + sLastKeyOrCharMSG = aMessage; + } + + if (mMsg.message == WM_APPCOMMAND) { + InitWithAppCommand(); + } else { + InitWithKeyOrChar(); + } + + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::NativeKey(), mKeyboardLayout=0x%08X, " + "mFocusedWndBeforeDispatch=0x%p, mDOMKeyCode=%s, " + "mKeyNameIndex=%s, mCodeNameIndex=%s, mModKeyState=%s, " + "mVirtualKeyCode=%s, mOriginalVirtualKeyCode=%s, " + "mCommittedCharsAndModifiers=%s, mInputtingStringAndModifiers=%s, " + "mShiftedString=%s, mUnshiftedString=%s, mShiftedLatinChar=%s, " + "mUnshiftedLatinChar=%s, mScanCode=0x%04X, mIsExtended=%s, " + "mIsRepeat=%s, mIsDeadKey=%s, mIsPrintableKey=%s, " + "mIsSkippableInRemoteProcess=%s, mCharMessageHasGone=%s, " + "mIsOverridingKeyboardLayout=%s", + this, mKeyboardLayout, mFocusedWndBeforeDispatch, + GetDOMKeyCodeName(mDOMKeyCode).get(), ToString(mKeyNameIndex).get(), + ToString(mCodeNameIndex).get(), ToString(mModKeyState).get(), + GetVirtualKeyCodeName(mVirtualKeyCode).get(), + GetVirtualKeyCodeName(mOriginalVirtualKeyCode).get(), + ToString(mCommittedCharsAndModifiers).get(), + ToString(mInputtingStringAndModifiers).get(), + ToString(mShiftedString).get(), ToString(mUnshiftedString).get(), + GetCharacterCodeName(mShiftedLatinChar).get(), + GetCharacterCodeName(mUnshiftedLatinChar).get(), mScanCode, + GetBoolName(mIsExtended), GetBoolName(mIsRepeat), + GetBoolName(mIsDeadKey), GetBoolName(mIsPrintableKey), + GetBoolName(mIsSkippableInRemoteProcess), + GetBoolName(mCharMessageHasGone), + GetBoolName(mIsOverridingKeyboardLayout))); +} + +void NativeKey::InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG) { + mIsSkippableInRemoteProcess = false; + + if (!mIsRepeat) { + // If the message is not repeated key message, the event should be always + // handled in remote process even if it's too old. + return; + } + + // Keyboard utilities may send us some generated messages and such messages + // may be marked as "repeated", e.g., SendInput() calls with + // KEYEVENTF_UNICODE but without KEYEVENTF_KEYUP. However, key sequence + // comes from such utilities may be really important. For example, utilities + // may send WM_KEYDOWN for VK_BACK to remove previous character and send + // WM_KEYDOWN for VK_PACKET to insert a composite character. Therefore, we + // should check if current message and previous key message are caused by + // same physical key. If not, the message may be generated by such + // utility. + // XXX With this approach, if VK_BACK messages are generated with known + // scancode, we cannot distinguish whether coming VK_BACK message is + // actually repeated by the auto-repeat feature. Currently, we need + // this hack only for "SinhalaTamil IME" and fortunately, it generates + // VK_BACK messages with odd scancode. So, we don't need to handle + // VK_BACK specially at least for now. + + if (mCodeNameIndex == CODE_NAME_INDEX_UNKNOWN) { + // If current event is not caused by physical key operation, it may be + // caused by a keyboard utility. If so, the event shouldn't be ignored by + // BrowserChild since it want to insert the character, delete a character or + // move caret. + return; + } + + if (mOriginalVirtualKeyCode == VK_PACKET) { + // If the message is VK_PACKET, that means that a keyboard utility + // tries to insert a character. + return; + } + + switch (mMsg.message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case MOZ_WM_KEYDOWN: + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + // However, some keyboard layouts may send some keyboard messages with + // activating the bit. If we dispatch repeated keyboard events, they + // may be ignored by BrowserChild due to performance reason. So, we need + // to check if actually a physical key is repeated by the auto-repeat + // feature. + switch (aLastKeyMSG.message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case MOZ_WM_KEYDOWN: + if (aLastKeyMSG.wParam == VK_PACKET) { + // If the last message was VK_PACKET, that means that a keyboard + // utility tried to insert a character. So, current message is + // not repeated key event of the previous event. + return; + } + // Let's check whether current message and previous message are + // caused by same physical key. + mIsSkippableInRemoteProcess = + mScanCode == WinUtils::GetScanCode(aLastKeyMSG.lParam) && + mIsExtended == WinUtils::IsExtendedScanCode(aLastKeyMSG.lParam); + return; + default: + // If previous message is not a keydown, this must not be generated + // by the auto-repeat feature. + return; + } + return; + case WM_APPCOMMAND: + MOZ_ASSERT_UNREACHABLE( + "WM_APPCOMMAND should be handled in " + "InitWithAppCommand()"); + return; + default: + // keyup message shouldn't be repeated by the auto-repeat feature. + return; + } +} + +void NativeKey::InitWithKeyOrChar() { + MSG lastKeyMSG = sLastKeyMSG; + 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: { + // Modify sLastKeyMSG now since retrieving following char messages may + // cause sending another key message if odd tool hooks GetMessage(), + // PeekMessage(). + sLastKeyMSG = mMsg; + + // Note that we don't need to compute raw virtual keycode here even when + // it's VK_PROCESS (i.e., already handled by IME) because we need to + // export it as DOM_VK_PROCESS and KEY_NAME_INDEX_Process. + mOriginalVirtualKeyCode = static_cast<uint8_t>(mMsg.wParam); + + // If the key message is sent from other application like a11y tools, the + // scancode value might not be set proper value. Then, probably the value + // is 0. + // NOTE: If the virtual keycode can be caused by both non-extended key + // and extended key, the API returns the non-extended key's + // scancode. E.g., VK_LEFT causes "4" key on numpad. + if (!mScanCode && mOriginalVirtualKeyCode != VK_PACKET) { + uint16_t scanCodeEx = ComputeScanCodeExFromVirtualKeyCode(mMsg.wParam); + if (scanCodeEx) { + mScanCode = static_cast<uint8_t>(scanCodeEx & 0xFF); + uint8_t extended = static_cast<uint8_t>((scanCodeEx & 0xFF00) >> 8); + mIsExtended = (extended == 0xE0) || (extended == 0xE1); + } + } + + // Most keys are not distinguished as left or right keys. + bool isLeftRightDistinguishedKey = false; + + // mOriginalVirtualKeyCode must not distinguish left or right of + // Shift, Control or Alt. + switch (mOriginalVirtualKeyCode) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + isLeftRightDistinguishedKey = true; + break; + case VK_LSHIFT: + case VK_RSHIFT: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_SHIFT; + isLeftRightDistinguishedKey = true; + break; + case VK_LCONTROL: + case VK_RCONTROL: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_CONTROL; + isLeftRightDistinguishedKey = true; + break; + case VK_LMENU: + case VK_RMENU: + mVirtualKeyCode = mOriginalVirtualKeyCode; + mOriginalVirtualKeyCode = VK_MENU; + isLeftRightDistinguishedKey = true; + break; + } + + // If virtual keycode (left-right distinguished keycode) is already + // computed, we don't need to do anymore. + if (mVirtualKeyCode) { + break; + } + + // If the keycode doesn't have LR distinguished keycode, we just set + // mOriginalVirtualKeyCode to mVirtualKeyCode. Note that don't compute + // it from MapVirtualKeyEx() because the scan code might be wrong if + // the message is sent/posted by other application. Then, we will compute + // unexpected keycode from the scan code. + if (!isLeftRightDistinguishedKey) { + break; + } + + NS_ASSERTION(!mVirtualKeyCode, + "mVirtualKeyCode has been computed already"); + + // Otherwise, compute the virtual keycode with MapVirtualKeyEx(). + mVirtualKeyCode = ComputeVirtualKeyCodeFromScanCodeEx(); + + // Following code shouldn't be used now because we compute scancode value + // if we detect that the sender doesn't set proper scancode. + // However, the detection might fail. Therefore, let's keep using this. + switch (mOriginalVirtualKeyCode) { + case VK_CONTROL: + if (mVirtualKeyCode != VK_LCONTROL && + mVirtualKeyCode != VK_RCONTROL) { + mVirtualKeyCode = mIsExtended ? VK_RCONTROL : VK_LCONTROL; + } + break; + case VK_MENU: + if (mVirtualKeyCode != VK_LMENU && mVirtualKeyCode != VK_RMENU) { + mVirtualKeyCode = mIsExtended ? VK_RMENU : VK_LMENU; + } + break; + case VK_SHIFT: + if (mVirtualKeyCode != VK_LSHIFT && mVirtualKeyCode != VK_RSHIFT) { + // Neither left shift nor right shift is an extended key, + // let's use VK_LSHIFT for unknown mapping. + mVirtualKeyCode = VK_LSHIFT; + } + break; + default: + MOZ_CRASH("Unsupported mOriginalVirtualKeyCode"); + } + break; + } + case WM_CHAR: + case WM_UNICHAR: + case WM_SYSCHAR: + // 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::InitWithKeyOrChar(), WARNING, detecting another " + "instance is trying to remove a char message, so, this instance " + "should do nothing, mLastInstance=0x%p, mRemovingMsg=%s, " + "mReceivedMsg=%s", + this, mLastInstance, ToString(mLastInstance->mRemovingMsg).get(), + ToString(mLastInstance->mReceivedMsg).get())); + mLastInstance->mReceivedMsg = mMsg; + return; + } + + // NOTE: If other applications like a11y tools sends WM_*CHAR without + // scancode, we cannot compute virtual keycode. I.e., with such + // applications, we cannot generate proper KeyboardEvent.code value. + + mVirtualKeyCode = mOriginalVirtualKeyCode = + ComputeVirtualKeyCodeFromScanCodeEx(); + NS_ASSERTION(mVirtualKeyCode, "Failed to compute virtual keycode"); + break; + default: { + MOZ_CRASH_UNSAFE_PRINTF("Unsupported message: 0x%04X", mMsg.message); + break; + } + } + + if (!mVirtualKeyCode) { + mVirtualKeyCode = mOriginalVirtualKeyCode; + } + + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + mDOMKeyCode = + keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mVirtualKeyCode); + // Be aware, keyboard utilities can change non-printable keys to printable + // keys. In such case, we should make the key value as a printable key. + // FYI: IsFollowedByPrintableCharMessage() returns true only when it's + // handling a keydown message. + mKeyNameIndex = + IsFollowedByPrintableCharMessage() + ? KEY_NAME_INDEX_USE_STRING + : keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mVirtualKeyCode); + mCodeNameIndex = KeyboardLayout::ConvertScanCodeToCodeNameIndex( + GetScanCodeWithExtendedFlag()); + + // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key + // combination is not reserved by the system, let's consume it now. + // TODO: We cannot initialize mCommittedCharsAndModifiers for VK_PACKET + // if the message is WM_KEYUP because we don't have preceding + // WM_CHAR message. + // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events + // for a Unicode character in non-BMP because its key value looks + // broken and not good thing for our editor if only one keydown or + // keypress event's default is prevented. I guess, we should store + // key message information globally and we should wait following + // WM_KEYDOWN if following WM_CHAR is a part of a Unicode character. + if ((mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN) && + !IsReservedBySystem()) { + MSG charMsg; + while (GetFollowingCharMessage(charMsg)) { + // Although, got message shouldn't be WM_NULL in desktop apps, + // we should keep checking this. FYI: This was added for Metrofox. + if (charMsg.message == WM_NULL) { + continue; + } + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::InitWithKeyOrChar(), removed char message, %s", + this, ToString(charMsg).get())); + Unused << NS_WARN_IF(charMsg.hwnd != mMsg.hwnd); + mFollowingCharMsgs.AppendElement(charMsg); + } + } + + keyboardLayout->InitNativeKey(*this); + + // Now, we can know if the key produces character(s) or a dead key with + // AltGraph modifier. When user emulates AltGr key press with pressing + // both Ctrl and Alt and the key produces character(s) or a dead key, we + // need to replace Control and Alt state with AltGraph if the keyboard + // layout has AltGr key. + // Note that if Ctrl and/or Alt are pressed (not to emulate to press AltGr), + // we need to set actual modifiers to eKeyDown and eKeyUp. + if (MaybeEmulatingAltGraph() && + (mCommittedCharsAndModifiers.IsProducingCharsWithAltGr() || + mKeyNameIndex == KEY_NAME_INDEX_Dead)) { + mModKeyState.Unset(MODIFIER_CONTROL | MODIFIER_ALT); + mModKeyState.Set(MODIFIER_ALTGRAPH); + } + + mIsDeadKey = + (IsFollowedByDeadCharMessage() || + keyboardLayout->IsDeadKey(mOriginalVirtualKeyCode, mModKeyState)); + mIsPrintableKey = mKeyNameIndex == KEY_NAME_INDEX_USE_STRING || + KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode); + // The repeat count in mMsg.lParam isn't useful to check whether the event + // is caused by the auto-repeat feature because it's not incremented even + // if it's repeated twice or more (i.e., always 1). Therefore, we need to + // check previous key state (31th bit) instead. If it's 1, the key was down + // before the message was sent. + mIsRepeat = (mMsg.lParam & (1 << 30)) != 0; + InitIsSkippableForKeyOrChar(lastKeyMSG); + + if (IsKeyDownMessage()) { + // Compute some strings which may be inputted by the key with various + // modifier state if this key event won't cause text input actually. + // They will be used for setting mAlternativeCharCodes in the callback + // method which will be called by TextEventDispatcher. + if (!IsFollowedByPrintableCharMessage()) { + ComputeInputtingStringWithKeyboardLayout(); + } + // Remove odd char messages if there are. + RemoveFollowingOddCharMessages(); + } +} + +void NativeKey::InitCommittedCharsAndModifiersWithFollowingCharMessages() { + mCommittedCharsAndModifiers.Clear(); + // This should cause inputting text in focused editor. However, it + // ignores keypress events whose altKey or ctrlKey is true. + // Therefore, we need to remove these modifier state here. + Modifiers modifiers = mModKeyState.GetModifiers(); + if (IsFollowedByPrintableCharMessage()) { + modifiers &= ~(MODIFIER_ALT | MODIFIER_CONTROL); + if (MaybeEmulatingAltGraph()) { + modifiers |= MODIFIER_ALTGRAPH; + } + } + // NOTE: This method assumes that WM_CHAR and WM_SYSCHAR are never retrieved + // at same time. + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + // Ignore non-printable char messages. + if (!IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { + continue; + } + char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam); + mCommittedCharsAndModifiers.Append(ch, modifiers); + } +} + +NativeKey::~NativeKey() { + MOZ_LOG(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()); + // If we can map the WM_APPCOMMAND to a virtual keycode, we can trust + // the result of GetKeyboardState(). Otherwise, we dispatch both + // keydown and keyup events from WM_APPCOMMAND handler. Therefore, + // even if WM_APPCOMMAND is caused by auto key repeat, web apps receive + // a pair of DOM keydown and keyup events. I.e., KeyboardEvent.repeat + // should be never true of such keys. + // XXX Isn't the key state always true? If the key press caused this + // WM_APPCOMMAND, that means it's pressed at that time. + if (mVirtualKeyCode) { + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + ::GetKeyboardState(kbdState); + mIsSkippableInRemoteProcess = mIsRepeat = !!kbdState[mVirtualKeyCode]; + } +} + +bool NativeKey::MaybeEmulatingAltGraph() const { + return IsControl() && IsAlt() && KeyboardLayout::GetInstance()->HasAltGr(); +} + +// static +bool NativeKey::IsControlChar(char16_t aChar) { + static const char16_t U_SPACE = 0x20; + static const char16_t U_DELETE = 0x7F; + return aChar < U_SPACE || aChar == U_DELETE; +} + +bool NativeKey::IsFollowedByDeadCharMessage() const { + if (mFollowingCharMsgs.IsEmpty()) { + return false; + } + return IsDeadCharMessage(mFollowingCharMsgs[0]); +} + +bool NativeKey::IsFollowedByPrintableCharMessage() const { + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + if (IsPrintableCharMessage(mFollowingCharMsgs[i])) { + return true; + } + } + return false; +} + +bool NativeKey::IsFollowedByPrintableCharOrSysCharMessage() const { + for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) { + if (IsPrintableCharOrSysCharMessage(mFollowingCharMsgs[i])) { + return true; + } + } + return false; +} + +bool NativeKey::IsReservedBySystem() const { + // Alt+Space key is handled by OS, we shouldn't touch it. + if (mModKeyState.IsAlt() && !mModKeyState.IsControl() && + mVirtualKeyCode == VK_SPACE) { + return true; + } + + // XXX How about Alt+F4? We receive WM_SYSKEYDOWN for F4 before closing the + // window. Although, we don't prevent to close the window but the key + // event shouldn't be exposed to the web. + + return false; +} + +bool NativeKey::IsIMEDoingKakuteiUndo() const { + // Following message pattern is caused by "Kakutei-Undo" of ATOK or WXG: + // --------------------------------------------------------------------------- + // WM_KEYDOWN * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC0000001) # ATOK + // WM_IME_STARTCOMPOSITION * 1 (wParam = 0x0, lParam = 0x0) + // WM_IME_COMPOSITION * 1 (wParam = 0x0, lParam = 0x1BF) + // WM_CHAR * n (wParam = VK_BACK, lParam = 0x1) + // WM_KEYUP * 1 (wParam = VK_BACK, lParam = 0xC00E0001) + // --------------------------------------------------------------------------- + // This doesn't match usual key message pattern such as: + // WM_KEYDOWN -> WM_CHAR -> WM_KEYDOWN -> WM_CHAR -> ... -> WM_KEYUP + // See following bugs for the detail. + // https://bugzilla.mozilla.gr.jp/show_bug.cgi?id=2885 (written in Japanese) + // https://bugzilla.mozilla.org/show_bug.cgi?id=194559 (written in English) + MSG startCompositionMsg, compositionMsg, charMsg; + return WinUtils::PeekMessage(&startCompositionMsg, mMsg.hwnd, + WM_IME_STARTCOMPOSITION, WM_IME_STARTCOMPOSITION, + PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&compositionMsg, mMsg.hwnd, WM_IME_COMPOSITION, + WM_IME_COMPOSITION, PM_NOREMOVE | PM_NOYIELD) && + WinUtils::PeekMessage(&charMsg, mMsg.hwnd, WM_CHAR, WM_CHAR, + PM_NOREMOVE | PM_NOYIELD) && + startCompositionMsg.wParam == 0x0 && + startCompositionMsg.lParam == 0x0 && compositionMsg.wParam == 0x0 && + compositionMsg.lParam == 0x1BF && charMsg.wParam == VK_BACK && + charMsg.lParam == 0x1 && + startCompositionMsg.time <= compositionMsg.time && + compositionMsg.time <= charMsg.time; +} + +void NativeKey::RemoveFollowingOddCharMessages() { + MOZ_ASSERT(IsKeyDownMessage()); + + // If the keydown message is synthesized for automated tests, there is + // nothing to do here. + if (mFakeCharMsgs) { + return; + } + + // If there are some following char messages before another key message, + // there is nothing to do here. + if (!mFollowingCharMsgs.IsEmpty()) { + return; + } + + // If the handling key isn't Backspace, there is nothing to do here. + if (mOriginalVirtualKeyCode != VK_BACK) { + return; + } + + // If we don't see the odd message pattern, there is nothing to do here. + if (!IsIMEDoingKakuteiUndo()) { + return; + } + + // Otherwise, we need to remove odd WM_CHAR messages for ATOK or WXG (both + // of them are Japanese IME). + MSG msg; + while (WinUtils::PeekMessage(&msg, mMsg.hwnd, WM_CHAR, WM_CHAR, + PM_REMOVE | PM_NOYIELD)) { + if (msg.message != WM_CHAR) { + MOZ_RELEASE_ASSERT(msg.message == WM_NULL, + "Unexpected message was removed"); + continue; + } + MOZ_LOG( + 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 eKeyLocationLeft; + + case VK_RSHIFT: + case VK_RCONTROL: + case VK_RMENU: + case VK_RWIN: + return eKeyLocationRight; + + case VK_RETURN: + // XXX This code assumes that all keyboard drivers use same mapping. + return !mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad; + + case VK_INSERT: + case VK_DELETE: + case VK_END: + case VK_DOWN: + case VK_NEXT: + case VK_LEFT: + case VK_CLEAR: + case VK_RIGHT: + case VK_HOME: + case VK_UP: + case VK_PRIOR: + // XXX This code assumes that all keyboard drivers use same mapping. + return mIsExtended ? eKeyLocationStandard : eKeyLocationNumpad; + + // NumLock key isn't included due to IE9's behavior. + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + case VK_DIVIDE: + case VK_MULTIPLY: + case VK_SUBTRACT: + case VK_ADD: + // Separator key of Brazilian keyboard or JIS keyboard for Mac + case VK_ABNT_C2: + return eKeyLocationNumpad; + + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + NS_WARNING("Failed to decide the key location?"); + + default: + return eKeyLocationStandard; + } +} + +uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCode() const { + return static_cast<uint8_t>( + ::MapVirtualKeyEx(mScanCode, MAPVK_VSC_TO_VK, mKeyboardLayout)); +} + +uint8_t NativeKey::ComputeVirtualKeyCodeFromScanCodeEx() const { + // MapVirtualKeyEx() has been improved for supporting extended keys since + // Vista. When we call it for mapping a scancode of an extended key and + // a virtual keycode, we need to add 0xE000 to the scancode. + return static_cast<uint8_t>(::MapVirtualKeyEx( + GetScanCodeWithExtendedFlag(), MAPVK_VSC_TO_VK_EX, mKeyboardLayout)); +} + +uint16_t NativeKey::ComputeScanCodeExFromVirtualKeyCode( + UINT aVirtualKeyCode) const { + return static_cast<uint16_t>( + ::MapVirtualKeyEx(aVirtualKeyCode, MAPVK_VK_TO_VSC_EX, mKeyboardLayout)); +} + +char16_t NativeKey::ComputeUnicharFromScanCode() const { + return static_cast<char16_t>(::MapVirtualKeyEx( + ComputeVirtualKeyCodeFromScanCode(), MAPVK_VK_TO_CHAR, mKeyboardLayout)); +} + +nsEventStatus NativeKey::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const { + return InitKeyEvent(aKeyEvent, mModKeyState); +} + +nsEventStatus NativeKey::InitKeyEvent( + WidgetKeyboardEvent& aKeyEvent, + const ModifierKeyState& aModKeyState) const { + if (mWidget->Destroyed()) { + MOZ_CRASH("NativeKey tries to dispatch a key event on destroyed widget"); + } + + LayoutDeviceIntPoint point(0, 0); + mWidget->InitEvent(aKeyEvent, &point); + + switch (aKeyEvent.mMessage) { + case eKeyDown: + // If it was followed by a char message but it was consumed by somebody, + // we should mark it as consumed because somebody must have handled it + // and we should prevent to do "double action" for the key operation. + // However, for compatibility with older version and other browsers, + // we should dispatch the events even in the web content. + if (mCharMessageHasGone) { + aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow); + } + [[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. On the other hand, we should dispatch the events even + // in the web content for compatibility with older version and other + // browsers. + if (mOriginalVirtualKeyCode == VK_MENU && mMsg.message != WM_SYSKEYUP) { + aKeyEvent.PreventDefaultBeforeDispatch(CrossProcessForwarding::eAllow); + } + break; + case eKeyPress: + MOZ_ASSERT(!mCharMessageHasGone, + "If following char message was consumed by somebody, " + "keydown event should be consumed above"); + aKeyEvent.mUniqueId = sUniqueKeyEventId; + break; + default: + MOZ_CRASH("Invalid event message"); + } + + aKeyEvent.mIsRepeat = mIsRepeat; + aKeyEvent.mMaybeSkippableInRemoteProcess = mIsSkippableInRemoteProcess; + aKeyEvent.mKeyNameIndex = mKeyNameIndex; + if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) { + aKeyEvent.mKeyValue = mCommittedCharsAndModifiers.ToString(); + } + aKeyEvent.mCodeNameIndex = mCodeNameIndex; + MOZ_ASSERT(mCodeNameIndex != CODE_NAME_INDEX_USE_STRING); + aKeyEvent.mLocation = GetKeyLocation(); + aModKeyState.InitInputEvent(aKeyEvent); + + KeyboardLayout::NotifyIdleServiceOfUserActivity(); + + MOZ_LOG( + 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; +} + +bool NativeKey::DispatchCommandEvent(uint32_t aEventCommand) const { + RefPtr<nsAtom> command; + switch (aEventCommand) { + case APPCOMMAND_BROWSER_BACKWARD: + command = nsGkAtoms::Back; + break; + case APPCOMMAND_BROWSER_FORWARD: + command = nsGkAtoms::Forward; + break; + case APPCOMMAND_BROWSER_REFRESH: + command = nsGkAtoms::Reload; + break; + case APPCOMMAND_BROWSER_STOP: + command = nsGkAtoms::Stop; + break; + case APPCOMMAND_BROWSER_SEARCH: + command = nsGkAtoms::Search; + break; + case APPCOMMAND_BROWSER_FAVORITES: + command = nsGkAtoms::Bookmarks; + break; + case APPCOMMAND_BROWSER_HOME: + command = nsGkAtoms::Home; + break; + case APPCOMMAND_CLOSE: + command = nsGkAtoms::Close; + break; + case APPCOMMAND_FIND: + command = nsGkAtoms::Find; + break; + case APPCOMMAND_HELP: + command = nsGkAtoms::Help; + break; + case APPCOMMAND_NEW: + command = nsGkAtoms::New; + break; + case APPCOMMAND_OPEN: + command = nsGkAtoms::Open; + break; + case APPCOMMAND_PRINT: + command = nsGkAtoms::Print; + break; + case APPCOMMAND_SAVE: + command = nsGkAtoms::Save; + break; + case APPCOMMAND_FORWARD_MAIL: + command = nsGkAtoms::ForwardMail; + break; + case APPCOMMAND_REPLY_TO_MAIL: + command = nsGkAtoms::ReplyToMail; + break; + case APPCOMMAND_SEND_MAIL: + command = nsGkAtoms::SendMail; + break; + case APPCOMMAND_MEDIA_NEXTTRACK: + command = nsGkAtoms::NextTrack; + break; + case APPCOMMAND_MEDIA_PREVIOUSTRACK: + command = nsGkAtoms::PreviousTrack; + break; + case APPCOMMAND_MEDIA_STOP: + command = nsGkAtoms::MediaStop; + break; + case APPCOMMAND_MEDIA_PLAY_PAUSE: + command = nsGkAtoms::PlayPause; + break; + default: + MOZ_LOG( + sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), doesn't dispatch command " + "event", + this)); + return false; + } + WidgetCommandEvent appCommandEvent(true, command, mWidget); + + mWidget->InitEvent(appCommandEvent); + MOZ_LOG(sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), dispatching " + "%s app command event...", + this, nsAtomCString(command).get())); + bool ok = + mWidget->DispatchWindowEvent(&appCommandEvent) || mWidget->Destroyed(); + MOZ_LOG( + sNativeKeyLogger, LogLevel::Info, + ("%p NativeKey::DispatchCommandEvent(), dispatched app command event, " + "result=%s, mWidget->Destroyed()=%s", + this, GetBoolName(ok), GetBoolName(mWidget->Destroyed()))); + return ok; +} + +bool NativeKey::HandleAppCommandMessage() const { + // If the widget has gone, we should do nothing. + if (mWidget->Destroyed()) { + MOZ_LOG(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); + 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); + 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); + 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 && !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)); + 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 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) || IsEnterKeyPressCharMessage(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. + // Note that when the char is '\r', it means that the char message should + // cause "Enter" keypress event for inserting a line break. + if (IsControlCharMessage(aCharMsg) && !IsEnterKeyPressCharMessage(aCharMsg)) { + // In this case, we don't need to dispatch eKeyPress event because: + // 1. We're the only browser which dispatches "keypress" event for + // non-printable characters (Although, both Chrome and Edge dispatch + // "keypress" event for some keys accidentally. For example, "IntlRo" + // key with Ctrl of Japanese keyboard layout). + // 2. Currently, we may handle shortcut keys with "keydown" event if + // it's reserved or something. So, we shouldn't dispatch "keypress" + // event without it. + // Note that this does NOT mean we stop dispatching eKeyPress event for + // key presses causes a control character when Ctrl is pressed. In such + // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress + // instead of this method. + MOZ_LOG( + 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); + if (IsEnterKeyPressCharMessage(aCharMsg)) { + keypressEvent.mKeyCode = NS_VK_RETURN; + } else { + keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam); + } + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(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, TextEditor won't treat the keypress event as inputting a + // character. Therefore, when AltGr is pressed and the key tries to input + // a character, let's set them to false. + if (modKeyState.IsControl() && modKeyState.IsAlt() && + IsPrintableCharMessage(aCharMsg)) { + modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + } + nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState); + MOZ_LOG(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); + 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. + // Note that if the key does not produce a character with AltGr and when + // AltGr key is pressed, we don't need to dispatch eKeyPress event for it + // because AltGr shouldn't be used for a modifier for a shortcut without + // Ctrl, Alt or Win. That means that we should treat it in same path for + // Shift key. + if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() && + !mModKeyState.IsWin()) { + return false; + } + + // If the key event causes dead key event, we don't need to dispatch keypress + // event. + if (mIsDeadKey && mCommittedCharsAndModifiers.IsEmpty()) { + return false; + } + + // Even if the key is a printable key, it might cause non-printable character + // input with modifier key(s). + return mIsPrintableKey; +} + +static nsCString GetResultOfInSendMessageEx() { + DWORD ret = ::InSendMessageEx(nullptr); + if (!ret) { + return "ISMEX_NOSEND"_ns; + } + nsCString result; + if (ret & ISMEX_CALLBACK) { + result = "ISMEX_CALLBACK"; + } + if (ret & ISMEX_NOTIFY) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_NOTIFY"; + } + if (ret & ISMEX_REPLIED) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_REPLIED"; + } + if (ret & ISMEX_SEND) { + if (!result.IsEmpty()) { + result += " | "; + } + result += "ISMEX_SEND"; + } + return result; +} + +bool NativeKey::MayBeSameCharMessage(const MSG& aCharMsg1, + const MSG& aCharMsg2) const { + // NOTE: Although, we don't know when this case occurs, the scan code value + // in lParam may be changed from 0 to something. The changed value + // is different from the scan code of handling keydown message. + static const LPARAM kScanCodeMask = 0x00FF0000; + return aCharMsg1.message == aCharMsg2.message && + aCharMsg1.wParam == aCharMsg2.wParam && + (aCharMsg1.lParam & ~kScanCodeMask) == + (aCharMsg2.lParam & ~kScanCodeMask); +} + +bool NativeKey::IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, + const MSG& aKeyOrCharMsg2) const { + if (NS_WARN_IF(aKeyOrCharMsg1.message < WM_KEYFIRST) || + NS_WARN_IF(aKeyOrCharMsg1.message > WM_KEYLAST) || + NS_WARN_IF(aKeyOrCharMsg2.message < WM_KEYFIRST) || + NS_WARN_IF(aKeyOrCharMsg2.message > WM_KEYLAST)) { + return false; + } + return WinUtils::GetScanCode(aKeyOrCharMsg1.lParam) == + WinUtils::GetScanCode(aKeyOrCharMsg2.lParam) && + WinUtils::IsExtendedScanCode(aKeyOrCharMsg1.lParam) == + WinUtils::IsExtendedScanCode(aKeyOrCharMsg2.lParam); +} + +bool NativeKey::GetFollowingCharMessage(MSG& aCharMsg) { + MOZ_ASSERT(IsKeyDownMessage()); + 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) { + 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( + "\nThere is no message in any window"_ns); + } + + MOZ_CRASH("We lost the following char message"); + } + + // We're still not sure why ::PeekMessage() may return WM_NULL even with + // its first message and its last message are same message. However, + // at developing Metrofox, we met this case even with usual keyboard + // layouts. So, it might be possible in desktop application or it really + // occurs with some odd keyboard layouts which perhaps hook API. + if (removedMsg.message == WM_NULL) { + MOZ_LOG(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())); + 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( + nsLiteralCString("\nThere is no key message after unexpected char " + "message removed, ")); + } + // Another window has a key message? + if (WinUtils::PeekMessage(&nextKeyMsgInAllWindows, 0, WM_KEYFIRST, + WM_KEYLAST, PM_NOREMOVE | PM_NOYIELD)) { + nsPrintfCString info("\nNext key message in all windows: %s.", + ToString(nextKeyMsgInAllWindows).get()); + CrashReporter::AppendAppNotesToCrashReport(info); + } else { + CrashReporter::AppendAppNotesToCrashReport( + "\nThere is no key message in any windows."_ns); + } + + MOZ_CRASH("PeekMessage() removed unexpected message"); + } + MOZ_LOG( + sNativeKeyLogger, LogLevel::Error, + ("%p NativeKey::GetFollowingCharMessage(), FAILED, removed messages " + "are all WM_NULL, nextKeyMsg=%s", + this, ToString(nextKeyMsg).get())); + nsPrintfCString info( + "\nWe lost following char message! " + "\nActive keyboard layout=0x%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); + MOZ_CRASH("We lost the following char message"); + return false; +} + +void NativeKey::ComputeInputtingStringWithKeyboardLayout() { + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + + if (KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode) || + mCharMessageHasGone) { + mInputtingStringAndModifiers = mCommittedCharsAndModifiers; + } else { + mInputtingStringAndModifiers.Clear(); + } + mShiftedString.Clear(); + mUnshiftedString.Clear(); + mShiftedLatinChar = mUnshiftedLatinChar = 0; + + // XXX How about when Win key is pressed? + if (mModKeyState.IsControl() == mModKeyState.IsAlt()) { + return; + } + + // If user is inputting a Unicode character with typing Alt + Numpad + // keys, we shouldn't set alternative key codes because the key event + // shouldn't match with a mnemonic. However, we should set only + // mUnshiftedString for keeping traditional behavior at WM_SYSKEYDOWN. + // FYI: I guess that it's okay that mUnshiftedString stays empty. So, + // if its value breaks something, you must be able to just return here. + if (MaybeTypingUnicodeScalarValue()) { + if (!mCommittedCharsAndModifiers.IsEmpty()) { + MOZ_ASSERT(mMsg.message == WM_SYSKEYDOWN); + char16_t num = mCommittedCharsAndModifiers.CharAt(0); + MOZ_ASSERT(num >= '0' && num <= '9'); + mUnshiftedString.Append(num, MODIFIER_NONE); + return; + } + // If user presses a function key without NumLock or 3rd party utility + // synthesizes a function key on numpad, we should handle it as-is because + // the user's intention may be performing `Alt` + foo. + MOZ_ASSERT(!KeyboardLayout::IsPrintableCharKey(mVirtualKeyCode)); + return; + } + + ModifierKeyState capsLockState(mModKeyState.GetModifiers() & + MODIFIER_CAPSLOCK); + + mUnshiftedString = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + capsLockState.Set(MODIFIER_SHIFT); + mShiftedString = + keyboardLayout->GetUniCharsAndModifiers(mVirtualKeyCode, capsLockState); + + // The current keyboard cannot input alphabets or numerics, + // we should append them for Shortcut/Access keys. + // E.g., for Cyrillic keyboard layout. + capsLockState.Unset(MODIFIER_SHIFT); + WidgetUtils::GetLatinCharCodeForKeyCode( + mDOMKeyCode, capsLockState.GetModifiers(), &mUnshiftedLatinChar, + &mShiftedLatinChar); + + // If the mShiftedLatinChar isn't 0, the key code is NS_VK_[A-Z]. + if (mShiftedLatinChar) { + // If the produced characters of the key on current keyboard layout + // are same as computed Latin characters, we shouldn't append the + // Latin characters to alternativeCharCode. + if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) && + mShiftedLatinChar == mShiftedString.CharAt(0)) { + mShiftedLatinChar = mUnshiftedLatinChar = 0; + } + } else if (mUnshiftedLatinChar) { + // If the mShiftedLatinChar is 0, the mKeyCode doesn't produce + // alphabet character. At that time, the character may be produced + // with Shift key. E.g., on French keyboard layout, NS_VK_PERCENT + // key produces LATIN SMALL LETTER U WITH GRAVE (U+00F9) without + // Shift key but with Shift key, it produces '%'. + // If the mUnshiftedLatinChar is produced by the key on current + // keyboard layout, we shouldn't append it to alternativeCharCode. + if (mUnshiftedLatinChar == mUnshiftedString.CharAt(0) || + mUnshiftedLatinChar == mShiftedString.CharAt(0)) { + mUnshiftedLatinChar = 0; + } + } + + if (!mModKeyState.IsControl()) { + return; + } + + // If the mCharCode is not ASCII character, we should replace the + // mCharCode with ASCII character only when Ctrl is pressed. + // But don't replace the mCharCode when the mCharCode is not same as + // unmodified characters. In such case, Ctrl is sometimes used for a + // part of character inputting key combination like Shift. + uint32_t ch = + mModKeyState.IsShift() ? mShiftedLatinChar : mUnshiftedLatinChar; + if (!ch) { + return; + } + if (mInputtingStringAndModifiers.IsEmpty() || + mInputtingStringAndModifiers.UniCharsCaseInsensitiveEqual( + mModKeyState.IsShift() ? mShiftedString : mUnshiftedString)) { + mInputtingStringAndModifiers.Clear(); + mInputtingStringAndModifiers.Append(ch, mModKeyState.GetModifiers()); + } +} + +bool NativeKey::DispatchKeyPressEventsWithRetrievedCharMessages() const { + MOZ_ASSERT(IsKeyDownMessage()); + MOZ_ASSERT(IsFollowedByPrintableCharOrSysCharMessage()); + MOZ_ASSERT(!mWidget->Destroyed()); + + nsresult rv = mDispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG( + 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); + 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. + 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; +nsIUserIdleServiceInternal* 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<nsIUserIdleServiceInternal> idleService = + do_GetService("@mozilla.org/widget/useridleservice;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), + mHasAltGr(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) { + if (mIsPendingToRestoreKeyboardLayout) { + LoadLayout(::GetKeyboardLayout(0)); + } + + // If the aNativeKey is initialized with WM_CHAR, the key information + // should be discarded because mKeyValue should have the string to be + // inputted. + if (aNativeKey.mMsg.message == WM_CHAR) { + char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam); + // But don't set key value as printable key if the character is a control + // character such as 0x0D at pressing Enter key. + if (!NativeKey::IsControlChar(ch)) { + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + Modifiers modifiers = + aNativeKey.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL); + aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers); + return; + } + } + + // If the aNativeKey is in a sequence to input a Unicode character with + // Alt + numpad keys, we should just set the number as the inputting charcter. + // Note that we should compute the key value from the virtual key code + // because they may be mapped to alphabets, but they should be treated as + // Alt + [0-9] even by web apps. + // However, we shouldn't touch the key value if given virtual key code is + // not a printable key because it may be synthesized by 3rd party utility + // or just NumLock is unlocked and user tries to use shortcut key. In the + // latter case, we cannot solve the conflict issue with Alt+foo shortcut key + // and inputting a Unicode scalar value like reported to bug 1606655, though, + // I have no better idea. Perhaps, `Alt` shouldn't be used for shortcut key + // combination on Windows. + if (aNativeKey.MaybeTypingUnicodeScalarValue() && + KeyboardLayout::IsPrintableCharKey(aNativeKey.mVirtualKeyCode)) { + // If the key code value is mapped to a Numpad key, let's compute the key + // value with it. This is same behavior as Chrome. In strictly speaking, + // I think that the else block's computation is better because it seems + // that Windows does not refer virtual key code value, but we should avoid + // web-compat issues. + char16_t num = '0'; + if (aNativeKey.mVirtualKeyCode >= VK_NUMPAD0 && + aNativeKey.mVirtualKeyCode <= VK_NUMPAD9) { + num = '0' + aNativeKey.mVirtualKeyCode - VK_NUMPAD0; + } + // Otherwise, let's use fake key value for making never match with + // mnemonic. + else { + switch (aNativeKey.mScanCode) { + case 0x0052: // Numpad0 + num = '0'; + break; + case 0x004F: // Numpad1 + num = '1'; + break; + case 0x0050: // Numpad2 + num = '2'; + break; + case 0x0051: // Numpad3 + num = '3'; + break; + case 0x004B: // Numpad4 + num = '4'; + break; + case 0x004C: // Numpad5 + num = '5'; + break; + case 0x004D: // Numpad6 + num = '6'; + break; + case 0x0047: // Numpad7 + num = '7'; + break; + case 0x0048: // Numpad8 + num = '8'; + break; + case 0x0049: // Numpad9 + num = '9'; + break; + default: + MOZ_ASSERT_UNREACHABLE( + "IsTypingUnicodeScalarValue() must have returned true for wrong " + "scancode"); + break; + } + } + aNativeKey.mCommittedCharsAndModifiers.Append(num, + aNativeKey.GetModifiers()); + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + return; + } + + // When it's followed by non-dead char message(s) for printable character(s), + // aNativeKey should dispatch eKeyPress events for them rather than + // information from keyboard layout because respecting WM_(SYS)CHAR messages + // guarantees that we can always input characters which is expected by + // the user even if the user uses odd keyboard layout. + // Or, when it was followed by non-dead char message for a printable character + // but it's gone at removing the message from the queue, let's treat it + // as a key inputting empty string. + if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage() || + aNativeKey.mCharMessageHasGone) { + MOZ_ASSERT(!aNativeKey.IsCharMessage(aNativeKey.mMsg)); + if (aNativeKey.IsFollowedByPrintableCharOrSysCharMessage()) { + // Initialize mCommittedCharsAndModifiers with following char messages. + aNativeKey.InitCommittedCharsAndModifiersWithFollowingCharMessages(); + MOZ_ASSERT(!aNativeKey.mCommittedCharsAndModifiers.IsEmpty()); + + // Currently, we are doing a ugly hack to keypress events to cause + // inputting character even if Ctrl or Alt key is pressed, that is, we + // remove Ctrl and Alt modifier state from keypress event. However, for + // example, Ctrl+Space which causes ' ' of WM_CHAR message never causes + // keypress event whose ctrlKey is true. For preventing this problem, + // we should mark as not removable if Ctrl or Alt key does not cause + // changing inputting character. + if (IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode) && + (aNativeKey.IsControl() ^ aNativeKey.IsAlt())) { + ModifierKeyState state = aNativeKey.ModifierKeyStateRef(); + state.Unset(MODIFIER_ALT | MODIFIER_CONTROL); + UniCharsAndModifiers charsWithoutModifier = + GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), state); + aNativeKey.mCanIgnoreModifierStateAtKeyPress = + !charsWithoutModifier.UniCharsEqual( + aNativeKey.mCommittedCharsAndModifiers); + } + } else { + aNativeKey.mCommittedCharsAndModifiers.Clear(); + } + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING; + + // If it's not in dead key sequence, we don't need to do anymore here. + if (!IsInDeadKeySequence()) { + return; + } + + // If it's in dead key sequence and dead char is inputted as is, we need to + // set the previous modifier state which is stored when preceding dead key + // is pressed. + UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); + aNativeKey.mCommittedCharsAndModifiers.OverwriteModifiersIfBeginsWith( + deadChars); + // Finish the dead key sequence. + DeactivateDeadKeyState(); + return; + } + + // If it's a dead key, aNativeKey will be initialized by + // MaybeInitNativeKeyAsDeadKey(). + if (MaybeInitNativeKeyAsDeadKey(aNativeKey)) { + return; + } + + // If the key is not a usual printable key, KeyboardLayout class assume that + // it's not cause dead char nor printable char. Therefore, there are nothing + // to do here fore such keys (e.g., function keys). + // However, this should keep dead key state even if non-printable key is + // pressed during a dead key sequence. + if (!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode)) { + return; + } + + MOZ_ASSERT(aNativeKey.mOriginalVirtualKeyCode != VK_PACKET, + "At handling VK_PACKET, we shouldn't refer keyboard layout"); + MOZ_ASSERT( + aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING, + "Printable key's key name index must be KEY_NAME_INDEX_USE_STRING"); + + // If it's in dead key handling and the pressed key causes a composite + // character, aNativeKey will be initialized by + // MaybeInitNativeKeyWithCompositeChar(). + if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) { + return; + } + + UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey); + + // If the key press isn't related to any dead keys, initialize aNativeKey + // with the characters which should be caused by the key. + if (!IsInDeadKeySequence()) { + aNativeKey.mCommittedCharsAndModifiers = baseChars; + return; + } + + // If the key doesn't cause a composite character with preceding dead key, + // initialize aNativeKey with the dead-key character followed by current + // key's character. + UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers(); + aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars; + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } +} + +bool KeyboardLayout::MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey) { + // Only when it's not in dead key sequence, we can trust IsDeadKey() result. + if (!IsInDeadKeySequence() && !IsDeadKey(aNativeKey)) { + return false; + } + + // When keydown message is followed by a dead char message, it should be + // initialized as dead key. + bool isDeadKeyDownEvent = + aNativeKey.IsKeyDownMessage() && aNativeKey.IsFollowedByDeadCharMessage(); + + // When keyup message is received, let's check if it's one of preceding + // dead keys because keydown message order and keyup message order may be + // different. + bool isDeadKeyUpEvent = + !aNativeKey.IsKeyDownMessage() && + mActiveDeadKeys.Contains(aNativeKey.GenericVirtualKeyCode()); + + if (isDeadKeyDownEvent || isDeadKeyUpEvent) { + ActivateDeadKeyState(aNativeKey); + // Any dead key events don't generate characters. So, a dead key should + // cause only keydown event and keyup event whose KeyboardEvent.key + // values are "Dead". + aNativeKey.mCommittedCharsAndModifiers.Clear(); + aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_Dead; + return true; + } + + // At keydown message handling, we need to forget the first dead key + // because there is no guarantee coming WM_KEYUP for the second dead + // key before next WM_KEYDOWN. E.g., due to auto key repeat or pressing + // another dead key before releasing current key. Therefore, we can + // set only a character for current key for keyup event. + if (!IsInDeadKeySequence()) { + aNativeKey.mCommittedCharsAndModifiers = + GetUniCharsAndModifiers(aNativeKey); + return true; + } + + // When non-printable key event comes during a dead key sequence, that must + // be a modifier key event. So, such events shouldn't be handled as a part + // of the dead key sequence. + if (!IsDeadKey(aNativeKey)) { + return false; + } + + // FYI: Following code may run when the user doesn't input text actually + // but the key sequence is a dead key sequence. For example, + // ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this + // complicated code for now because this runs really rarely. + + // Dead key followed by another dead key may cause a composed character + // (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c'). + if (MaybeInitNativeKeyWithCompositeChar(aNativeKey)) { + return true; + } + + // Otherwise, dead key followed by another dead key causes inputting both + // character. + UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers(); + UniCharsAndModifiers newChars = GetUniCharsAndModifiers(aNativeKey); + // But keypress events should be fired for each committed character. + aNativeKey.mCommittedCharsAndModifiers = prevDeadChars + newChars; + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } + return true; +} + +bool KeyboardLayout::MaybeInitNativeKeyWithCompositeChar( + NativeKey& aNativeKey) { + if (!IsInDeadKeySequence()) { + return false; + } + + if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) { + return false; + } + + UniCharsAndModifiers baseChars = GetUniCharsAndModifiers(aNativeKey); + if (baseChars.IsEmpty() || !baseChars.CharAt(0)) { + return false; + } + + char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0)); + if (!compositeChar) { + return false; + } + + // Active dead-key and base character does produce exactly one composite + // character. + aNativeKey.mCommittedCharsAndModifiers.Append(compositeChar, + baseChars.ModifiersAt(0)); + if (aNativeKey.IsKeyDownMessage()) { + DeactivateDeadKeyState(); + } + return true; +} + +UniCharsAndModifiers KeyboardLayout::GetUniCharsAndModifiers( + uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const { + UniCharsAndModifiers result; + int32_t key = GetKeyIndex(aVirtualKey); + if (key < 0) { + return result; + } + return mVirtualKeys[key].GetUniChars(aShiftState); +} + +UniCharsAndModifiers KeyboardLayout::GetDeadUniCharsAndModifiers() const { + MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length()); + + if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { + return UniCharsAndModifiers(); + } + + UniCharsAndModifiers result; + for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) { + result += + GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]); + } + return result; +} + +char16_t KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const { + if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) { + return 0; + } + // XXX Currently, we don't support computing a composite character with + // two or more dead keys since it needs big table for supporting + // long chained dead keys. However, this should be a minor bug + // because this runs only when the latest keydown event does not cause + // WM_(SYS)CHAR messages. So, when user wants to input a character, + // this path never runs. + if (mActiveDeadKeys.Length() > 1) { + return 0; + } + int32_t key = GetKeyIndex(mActiveDeadKeys[0]); + if (key < 0) { + return 0; + } + return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar); +} + +// static +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 "No name or too long name"_ns; + } + return NS_ConvertUTF16toUTF8(buf); + } + + if (NS_WARN_IF((layout & 0xF000) != 0xF000)) { + nsCString result; + result.AppendPrintf("Odd HKL: 0x%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 ""_ns; + } + nsresult rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + nsString(kKeyboardLayouts), nsIWindowsRegKey::ACCESS_READ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ""_ns; + } + uint32_t childCount = 0; + if (NS_WARN_IF(NS_FAILED(regKey->GetChildCount(&childCount))) || + NS_WARN_IF(!childCount)) { + return ""_ns; + } + 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 ""_ns; +} + +void KeyboardLayout::LoadLayout(HKL aLayout) { + mIsPendingToRestoreKeyboardLayout = false; + + if (mKeyboardLayout == aLayout) { + return; + } + + mKeyboardLayout = aLayout; + mHasAltGr = false; + + 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); + bool isAltGr = VirtualKey::IsAltGrIndex(shiftState); + for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { + int32_t vki = GetKeyIndex(virtualKey); + if (vki < 0) { + continue; + } + NS_ASSERTION(uint32_t(vki) < ArrayLength(mVirtualKeys), "invalid index"); + char16_t uniChars[5]; + int32_t ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars, + ArrayLength(uniChars), 0, mKeyboardLayout); + // dead-key + if (ret < 0) { + shiftStatesWithDeadKeys |= (1 << shiftState); + // Repeat dead-key to deactivate it and get its character + // representation. + char16_t deadChar[2]; + ret = ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)deadChar, + ArrayLength(deadChar), 0, mKeyboardLayout); + NS_ASSERTION(ret == 2, "Expecting twice repeated dead-key character"); + mVirtualKeys[vki].SetDeadChar(shiftState, deadChar[0]); + + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Debug, + (" %s (%d): DeadChar(%s, %s) (ret=%d)", + kVirtualKeyName[virtualKey], vki, + GetShiftStateName(shiftState).get(), + GetCharacterCodeNames(deadChar, 1).get(), ret)); + } else { + if (ret == 1) { + // dead-key can pair only with exactly one base character. + shiftStatesWithBaseChars |= (1 << shiftState); + } + mVirtualKeys[vki].SetNormalChars(shiftState, uniChars, ret); + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Verbose, + (" %s (%d): NormalChar(%s, %s) (ret=%d)", + kVirtualKeyName[virtualKey], vki, + GetShiftStateName(shiftState).get(), + GetCharacterCodeNames(uniChars, ret).get(), ret)); + } + + // If the key inputs at least one character with AltGr modifier, + // check if AltGr changes inputting character. If it does, mark + // this keyboard layout has AltGr modifier actually. + if (!mHasAltGr && ret > 0 && isAltGr && + mVirtualKeys[vki].IsChangedByAltGr(shiftState)) { + mHasAltGr = true; + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info, + (" Found a key (%s) changed by AltGr: %s -> %s (%s) (ret=%d)", + kVirtualKeyName[virtualKey], + GetCharacterCodeNames( + mVirtualKeys[vki].GetNativeUniChars( + shiftState - VirtualKey::ShiftStateIndex::eAltGr)) + .get(), + GetCharacterCodeNames( + mVirtualKeys[vki].GetNativeUniChars(shiftState)) + .get(), + GetShiftStateName(shiftState).get(), ret)); + } + } + } + + // Now process each dead-key to find all its base characters and resulting + // composite characters. + for (VirtualKey::ShiftState shiftState = 0; shiftState < 16; shiftState++) { + if (!(shiftStatesWithDeadKeys & (1 << shiftState))) { + continue; + } + + VirtualKey::FillKbdState(kbdState, shiftState); + + for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) { + int32_t vki = GetKeyIndex(virtualKey); + if (vki >= 0 && mVirtualKeys[vki].IsDeadKey(shiftState)) { + 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 = MAPVK_VSC_TO_VK_EX; + 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])); + } + } + } + + MOZ_LOG(sKeyboardLayoutLogger, LogLevel::Info, + (" AltGr key is %s in %s", mHasAltGr ? "found" : "not found", + GetLayoutName(aLayout).get())); +} + +inline int32_t KeyboardLayout::GetKeyIndex(uint8_t aVirtualKey) { + // Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed + // to produce visible representation: + // 0x20 - VK_SPACE ' ' + // 0x30..0x39 '0'..'9' + // 0x41..0x5A 'A'..'Z' + // 0x60..0x69 '0'..'9' on numpad + // 0x6A - VK_MULTIPLY '*' on numpad + // 0x6B - VK_ADD '+' on numpad + // 0x6D - VK_SUBTRACT '-' on numpad + // 0x6E - VK_DECIMAL '.' on numpad + // 0x6F - VK_DIVIDE '/' on numpad + // 0x6E - VK_DECIMAL '.' + // 0xBA - VK_OEM_1 ';:' for US + // 0xBB - VK_OEM_PLUS '+' any country + // 0xBC - VK_OEM_COMMA ',' any country + // 0xBD - VK_OEM_MINUS '-' any country + // 0xBE - VK_OEM_PERIOD '.' any country + // 0xBF - VK_OEM_2 '/?' for US + // 0xC0 - VK_OEM_3 '`~' for US + // 0xC1 - VK_ABNT_C1 '/?' for Brazilian + // 0xC2 - VK_ABNT_C2 separator key on numpad (Brazilian or JIS for Mac) + // 0xDB - VK_OEM_4 '[{' for US + // 0xDC - VK_OEM_5 '\|' for US + // 0xDD - VK_OEM_6 ']}' for US + // 0xDE - VK_OEM_7 ''"' for US + // 0xDF - VK_OEM_8 + // 0xE1 - no name + // 0xE2 - VK_OEM_102 '\_' for JIS + // 0xE3 - no name + // 0xE4 - no name + + static const int8_t xlat[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + //----------------------------------------------------------------------- + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 + 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, // 30 + -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 40 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1, // 50 + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51, // 60 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 70 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 90 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // A0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57, // B0 + 58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // C0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65, // D0 + -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // E0 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // F0 + }; + + return xlat[aVirtualKey]; +} + +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) { + // Dead-key state should be activated at keydown. + if (!aNativeKey.IsKeyDownMessage()) { + return; + } + + mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode); + mDeadKeyShiftStates.AppendElement(aNativeKey.GetShiftState()); +} + +void KeyboardLayout::DeactivateDeadKeyState() { + if (mActiveDeadKeys.IsEmpty()) { + return; + } + + BYTE kbdState[256]; + memset(kbdState, 0, sizeof(kbdState)); + + // Assume that the last dead key can finish dead key sequence. + VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement()); + EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState); + mActiveDeadKeys.Clear(); + mDeadKeyShiftStates.Clear(); +} + +bool KeyboardLayout::AddDeadKeyEntry(char16_t aBaseChar, + char16_t aCompositeChar, + 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_WARNING("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, + GetCharacterCodeNames(compositeChars, 1).get(), + ret <= 0 + ? "''" + : GetCharacterCodeNames(baseChars, std::min(ret, 5)).get(), + ret)); + break; + } + default: + // 1. Unexpected dead-key. Dead-key chaining is not supported. + // 2. More than one character generated. This is not a valid + // dead-key and base character combination. + deadKeyActive = false; + MOZ_LOG( + sKeyboardLayoutLogger, LogLevel::Verbose, + (" %s -> %s (%d): Unsupport dead key type(%s) (ret=%d)", + kVirtualKeyName[aDeadKey], kVirtualKeyName[virtualKey], vki, + ret <= 0 + ? "''" + : GetCharacterCodeNames(compositeChars, std::min(ret, 5)) + .get(), + ret)); + break; + } + } + } + } + + if (deadKeyActive) { + deadKeyActive = EnsureDeadKeyActive(false, aDeadKey, aDeadKeyKbdState); + } + + 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; + + case VK_LSHIFT: + case VK_RSHIFT: + return NS_VK_SHIFT; + + case VK_LCONTROL: + case VK_RCONTROL: + return NS_VK_CONTROL; + + // Note that even if the key is AltGr, we should return NS_VK_ALT for + // compatibility with both older Gecko and the other browsers. + case VK_LMENU: + case VK_RMENU: + return NS_VK_ALT; + + // Following keycodes are not defined in our DOM keycodes. + case VK_BROWSER_BACK: + case VK_BROWSER_FORWARD: + case VK_BROWSER_REFRESH: + case VK_BROWSER_STOP: + case VK_BROWSER_SEARCH: + case VK_BROWSER_FAVORITES: + case VK_BROWSER_HOME: + case VK_MEDIA_NEXT_TRACK: + case VK_MEDIA_PREV_TRACK: + case VK_MEDIA_STOP: + case VK_MEDIA_PLAY_PAUSE: + case VK_LAUNCH_MAIL: + case VK_LAUNCH_MEDIA_SELECT: + case VK_LAUNCH_APP1: + case VK_LAUNCH_APP2: + return 0; + + // Following OEM specific virtual keycodes should pass through DOM keyCode + // for compatibility with the other browsers on Windows. + + // Following OEM specific virtual keycodes are defined for Fujitsu/OASYS. + case VK_OEM_FJ_JISHO: + case VK_OEM_FJ_MASSHOU: + case VK_OEM_FJ_TOUROKU: + case VK_OEM_FJ_LOYA: + case VK_OEM_FJ_ROYA: + // Not sure what means "ICO". + case VK_ICO_HELP: + case VK_ICO_00: + case VK_ICO_CLEAR: + // Following OEM specific virtual keycodes are defined for Nokia/Ericsson. + case VK_OEM_RESET: + case VK_OEM_JUMP: + case VK_OEM_PA1: + case VK_OEM_PA2: + case VK_OEM_PA3: + case VK_OEM_WSCTRL: + case VK_OEM_CUSEL: + case VK_OEM_ATTN: + case VK_OEM_FINISH: + case VK_OEM_COPY: + case VK_OEM_AUTO: + case VK_OEM_ENLW: + case VK_OEM_BACKTAB: + // VK_OEM_CLEAR is defined as not OEM specific, but let's pass though + // DOM keyCode like other OEM specific virtual keycodes. + case VK_OEM_CLEAR: + return uint32_t(aNativeKeyCode); + + // 0xE1 is an OEM specific virtual keycode. However, the value is already + // used in our DOM keyCode for AltGr on Linux. So, this virtual keycode + // cannot pass through DOM keyCode. + case 0xE1: + return 0; + + // Following keycodes are OEM keys which are keycodes for non-alphabet and + // non-numeric keys, we should compute each keycode of them from unshifted + // character which is inputted by each key. But if the unshifted character + // is not an ASCII character but shifted character is an ASCII character, + // we should refer it. + case VK_OEM_1: + case VK_OEM_PLUS: + case VK_OEM_COMMA: + case VK_OEM_MINUS: + case VK_OEM_PERIOD: + case VK_OEM_2: + case VK_OEM_3: + case VK_OEM_4: + case VK_OEM_5: + case VK_OEM_6: + case VK_OEM_7: + case VK_OEM_8: + case VK_OEM_102: + case VK_ABNT_C1: { + NS_ASSERTION(IsPrintableCharKey(aNativeKeyCode), + "The key must be printable"); + ModifierKeyState modKeyState(0); + UniCharsAndModifiers uniChars = + GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); + if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' || + uniChars.CharAt(0) > 0x7F) { + modKeyState.Set(MODIFIER_SHIFT); + uniChars = GetUniCharsAndModifiers(aNativeKeyCode, modKeyState); + if (uniChars.Length() != 1 || uniChars.CharAt(0) < ' ' || + uniChars.CharAt(0) > 0x7F) { + // In this case, we've returned 0 in this case for long time because + // we decided that we should avoid setting same keyCode value to 2 or + // more keys since active keyboard layout may have a key to input the + // punctuation with different key. However, setting keyCode to 0 + // makes some web applications which are aware of neither + // KeyboardEvent.key nor KeyboardEvent.code not work with Firefox + // when user selects non-ASCII capable keyboard layout such as + // Russian and Thai layout. So, let's decide keyCode value with + // major keyboard layout's key which causes the OEM keycode. + // Actually, this maps same keyCode value to 2 keys on Russian + // keyboard layout. "Period" key causes VK_OEM_PERIOD but inputs + // Yu of Cyrillic and "Slash" key causes VK_OEM_2 (same as US + // keyboard layout) but inputs "." (period of ASCII). Therefore, + // we return DOM_VK_PERIOD which is same as VK_OEM_PERIOD for + // "Period" key. On the other hand, we use same keyCode value for + // "Slash" key too because it inputs ".". + CodeNameIndex code; + switch (aNativeKeyCode) { + case VK_OEM_1: + code = CODE_NAME_INDEX_Semicolon; + break; + case VK_OEM_PLUS: + code = CODE_NAME_INDEX_Equal; + break; + case VK_OEM_COMMA: + code = CODE_NAME_INDEX_Comma; + break; + case VK_OEM_MINUS: + code = CODE_NAME_INDEX_Minus; + break; + case VK_OEM_PERIOD: + code = CODE_NAME_INDEX_Period; + break; + case VK_OEM_2: + code = CODE_NAME_INDEX_Slash; + break; + case VK_OEM_3: + code = CODE_NAME_INDEX_Backquote; + break; + case VK_OEM_4: + code = CODE_NAME_INDEX_BracketLeft; + break; + case VK_OEM_5: + code = CODE_NAME_INDEX_Backslash; + break; + case VK_OEM_6: + code = CODE_NAME_INDEX_BracketRight; + break; + case VK_OEM_7: + code = CODE_NAME_INDEX_Quote; + break; + case VK_OEM_8: + // Use keyCode value for "Backquote" key on UK keyboard layout. + code = CODE_NAME_INDEX_Backquote; + break; + case VK_OEM_102: + // Use keyCode value for "IntlBackslash" key. + code = CODE_NAME_INDEX_IntlBackslash; + break; + case VK_ABNT_C1: // "/" of ABNT. + // Use keyCode value for "IntlBackslash" key on ABNT keyboard + // layout. + code = CODE_NAME_INDEX_IntlBackslash; + break; + default: + MOZ_ASSERT_UNREACHABLE("Handle all OEM keycode values"); + return 0; + } + return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code); + } + } + return WidgetUtils::ComputeKeyCodeFromChar(uniChars.CharAt(0)); + } + + // IE sets 0xC2 to the DOM keyCode for VK_ABNT_C2. However, we're already + // using NS_VK_SEPARATOR for the separator key on Mac and Linux. Therefore, + // We should keep consistency between Gecko on all platforms rather than + // with other browsers since a lot of keyCode values are already different + // between browsers. + case VK_ABNT_C2: + return NS_VK_SEPARATOR; + + // VK_PROCESSKEY means IME already consumed the key event. + case VK_PROCESSKEY: + return NS_VK_PROCESSKEY; + // VK_PACKET is generated by SendInput() API, we don't need to + // care this message as key event. + case VK_PACKET: + return 0; + // If a key is not mapped to a virtual keycode, 0xFF is used. + case 0xFF: + NS_WARNING("The key is failed to be converted to a virtual keycode"); + return 0; + } +#ifdef DEBUG + nsPrintfCString warning( + "Unknown virtual keycode (0x%08X), please check the " + "latest MSDN document, there may be some new " + "keycodes we've never known.", + aNativeKeyCode); + NS_WARNING(warning.get()); +#endif + return 0; +} + +KeyNameIndex KeyboardLayout::ConvertNativeKeyCodeToKeyNameIndex( + uint8_t aVirtualKey) const { + if (IsPrintableCharKey(aVirtualKey) || aVirtualKey == VK_PACKET) { + return KEY_NAME_INDEX_USE_STRING; + } + + // If the keyboard layout has AltGr and AltRight key is pressed, + // return AltGraph. + if (aVirtualKey == VK_RMENU && HasAltGr()) { + return KEY_NAME_INDEX_AltGraph; + } + + switch (aVirtualKey) { +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX +#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ + case aNativeKey: \ + return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + default: + break; + } + + HKL layout = 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); + + bool isAltGrKeyPress = false; + if (aModifierFlags & nsIWidget::ALTGRAPH) { + if (!HasAltGr()) { + return NS_ERROR_INVALID_ARG; + } + // AltGr emulates ControlLeft key press and AltRight key press. + // So, we should remove those flags from aModifierFlags before + // calling WinUtils::SetupKeyModifiersSequence() to create correct + // key sequence. + // FYI: We don't support both ControlLeft and AltRight (AltGr) are + // pressed at the same time unless synthesizing key is + // VK_LCONTROL. + aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::ALT_R); + } + + uint8_t argumentKeySpecific = 0; + switch (aNativeKeyCode & 0xFF) { + case VK_SHIFT: + aModifierFlags &= ~(nsIWidget::SHIFT_L | nsIWidget::SHIFT_R); + argumentKeySpecific = VK_LSHIFT; + break; + case VK_LSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; + break; + case VK_RSHIFT: + aModifierFlags &= ~nsIWidget::SHIFT_R; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_SHIFT; + break; + case VK_CONTROL: + aModifierFlags &= ~(nsIWidget::CTRL_L | nsIWidget::CTRL_R); + argumentKeySpecific = VK_LCONTROL; + break; + case VK_LCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; + break; + case VK_RCONTROL: + aModifierFlags &= ~nsIWidget::CTRL_R; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_CONTROL; + break; + case VK_MENU: + aModifierFlags &= ~(nsIWidget::ALT_L | nsIWidget::ALT_R); + argumentKeySpecific = VK_LMENU; + break; + case VK_LMENU: + aModifierFlags &= ~nsIWidget::ALT_L; + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; + break; + case VK_RMENU: + aModifierFlags &= ~(nsIWidget::ALT_R | nsIWidget::ALTGRAPH); + argumentKeySpecific = aNativeKeyCode & 0xFF; + aNativeKeyCode = (aNativeKeyCode & 0xFFFF0000) | VK_MENU; + // If AltRight key is AltGr in the keyboard layout, let's use + // SetupKeyModifiersSequence() to emulate the native behavior + // since the same event order between keydown and keyup makes + // the following code complicated. + if (HasAltGr()) { + isAltGrKeyPress = true; + aModifierFlags &= ~nsIWidget::CTRL_L; + aModifierFlags |= nsIWidget::ALTGRAPH; + } + break; + case VK_CAPITAL: + aModifierFlags &= ~nsIWidget::CAPS_LOCK; + argumentKeySpecific = VK_CAPITAL; + break; + case VK_NUMLOCK: + aModifierFlags &= ~nsIWidget::NUM_LOCK; + argumentKeySpecific = VK_NUMLOCK; + break; + } + + AutoTArray<KeyPair, 10> keySequence; + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYDOWN); + if (!isAltGrKeyPress) { + keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); + } + + // Simulate the pressing of each modifier key and then the real key + // FYI: Each NativeKey instance here doesn't need to override keyboard layout + // since this method overrides and restores the keyboard layout. + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + uint16_t scanCode = keySequence[i].mScanCode; + kbdState[key] = 0x81; // key is down and toggled on if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0x81; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + // If scan code isn't specified explicitly, let's compute it with current + // keyboard layout. + if (!scanCode) { + scanCode = + ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); + } + LPARAM lParam = static_cast<LPARAM>(scanCode << 16); + // If the scan code is for an extended key, set extended key flag. + if ((scanCode & 0xFF00) == 0xE000) { + lParam |= 0x1000000; + } + // When AltGr key is pressed, both ControlLeft and AltRight cause + // WM_KEYDOWN messages. + bool makeSysKeyMsg = + !(aModifierFlags & nsIWidget::ALTGRAPH) && IsSysKey(key, modKeyState); + MSG keyDownMsg = + WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYDOWN : WM_KEYDOWN, key, + lParam, aWidget->GetWindowHandle()); + if (i == keySequence.Length() - 1) { + bool makeDeadCharMsg = + (IsDeadKey(key, modKeyState) && aCharacters.IsEmpty()); + nsAutoString chars(aCharacters); + if (makeDeadCharMsg) { + UniCharsAndModifiers deadChars = + GetUniCharsAndModifiers(key, modKeyState); + chars = deadChars.ToString(); + NS_ASSERTION(chars.Length() == 1, + "Dead char must be only one character"); + } + if (chars.IsEmpty()) { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } else { + AutoTArray<NativeKey::FakeCharMsg, 10> fakeCharMsgs; + for (uint32_t j = 0; j < chars.Length(); j++) { + NativeKey::FakeCharMsg* fakeCharMsg = fakeCharMsgs.AppendElement(); + fakeCharMsg->mCharCode = chars.CharAt(j); + fakeCharMsg->mScanCode = scanCode; + fakeCharMsg->mIsSysKey = makeSysKeyMsg; + fakeCharMsg->mIsDeadKey = makeDeadCharMsg; + } + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState, 0, &fakeCharMsgs); + bool dispatched; + nativeKey.HandleKeyDownMessage(&dispatched); + // If some char messages are not consumed, let's emulate the widget + // receiving the message directly. + for (uint32_t j = 1; j < fakeCharMsgs.Length(); j++) { + if (fakeCharMsgs[j].mConsumed) { + continue; + } + MSG charMsg = fakeCharMsgs[j].GetCharMsg(aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, charMsg, modKeyState); + nativeKey.HandleCharMessage(charMsg); + } + } + } else { + NativeKey nativeKey(aWidget, keyDownMsg, modKeyState); + nativeKey.HandleKeyDownMessage(); + } + } + + keySequence.Clear(); + if (!isAltGrKeyPress) { + keySequence.AppendElement(KeyPair(aNativeKeyCode, argumentKeySpecific)); + } + WinUtils::SetupKeyModifiersSequence(&keySequence, aModifierFlags, WM_KEYUP); + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + uint16_t scanCode = keySequence[i].mScanCode; + kbdState[key] = 0; // key is up and toggled off if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0; + } + ::SetKeyboardState(kbdState); + ModifierKeyState modKeyState; + // If scan code isn't specified explicitly, let's compute it with current + // keyboard layout. + if (!scanCode) { + scanCode = + ComputeScanCodeForVirtualKeyCode(keySpecific ? keySpecific : key); + } + LPARAM lParam = static_cast<LPARAM>(scanCode << 16); + // If the scan code is for an extended key, set extended key flag. + if ((scanCode & 0xFF00) == 0xE000) { + lParam |= 0x1000000; + } + // Don't use WM_SYSKEYUP for Alt keyup. + // NOTE: When AltGr was pressed, ControlLeft causes WM_SYSKEYUP normally. + bool makeSysKeyMsg = IsSysKey(key, modKeyState) && key != VK_MENU; + MSG keyUpMsg = WinUtils::InitMSG(makeSysKeyMsg ? WM_SYSKEYUP : WM_KEYUP, + key, lParam, aWidget->GetWindowHandle()); + NativeKey nativeKey(aWidget, keyUpMsg, modKeyState); + nativeKey.HandleKeyUpMessage(); + } + + // Restore old key state and layout + ::SetKeyboardState(originalKbdState); + RestoreLayout(); + + // Don't unload the layout if it's installed actually. + for (uint32_t i = 0; i < keyboardLayoutListCount; i++) { + if (keyboardLayoutList[i] == loadedLayout) { + loadedLayout = 0; + break; + } + } + if (keyboardLayoutListBuff != keyboardLayoutList) { + delete[] keyboardLayoutList; + } + if (loadedLayout) { + ::UnloadKeyboardLayout(loadedLayout); + } + return NS_OK; +} + +/***************************************************************************** + * mozilla::widget::DeadKeyTable + *****************************************************************************/ + +char16_t DeadKeyTable::GetCompositeChar(char16_t aBaseChar) const { + // Dead-key table is sorted by BaseChar in ascending order. + // Usually they are too small to use binary search. + + for (uint32_t index = 0; index < mEntries; index++) { + if (mTable[index].BaseChar == aBaseChar) { + return mTable[index].CompositeChar; + } + if (mTable[index].BaseChar > aBaseChar) { + break; + } + } + + return 0; +} + +/***************************************************************************** + * mozilla::widget::RedirectedKeyDownMessage + *****************************************************************************/ + +MSG RedirectedKeyDownMessageManager::sRedirectedKeyDownMsg; +bool RedirectedKeyDownMessageManager::sDefaultPreventedOfRedirectedMsg = false; + +// static +bool RedirectedKeyDownMessageManager::IsRedirectedMessage(const MSG& aMsg) { + return (aMsg.message == WM_KEYDOWN || aMsg.message == WM_SYSKEYDOWN) && + (sRedirectedKeyDownMsg.message == aMsg.message && + WinUtils::GetScanCode(sRedirectedKeyDownMsg.lParam) == + WinUtils::GetScanCode(aMsg.lParam)); +} + +// static +void RedirectedKeyDownMessageManager::RemoveNextCharMessage(HWND aWnd) { + MSG msg; + if (WinUtils::PeekMessage(&msg, aWnd, WM_KEYFIRST, WM_KEYLAST, + PM_NOREMOVE | PM_NOYIELD) && + (msg.message == WM_CHAR || msg.message == WM_SYSCHAR)) { + WinUtils::PeekMessage(&msg, aWnd, msg.message, msg.message, + PM_REMOVE | PM_NOYIELD); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/KeyboardLayout.h b/widget/windows/KeyboardLayout.h new file mode 100644 index 0000000000..5f2b9b6004 --- /dev/null +++ b/widget/windows/KeyboardLayout.h @@ -0,0 +1,1125 @@ +/* -*- 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 nsIUserIdleServiceInternal; + +namespace mozilla { +namespace widget { + +enum ScanCode : uint16_t { + eCapsLock = 0x003A, + eNumLock = 0xE045, + eShiftLeft = 0x002A, + eShiftRight = 0x0036, + eControlLeft = 0x001D, + eControlRight = 0xE01D, + eAltLeft = 0x0038, + eAltRight = 0xE038, +}; + +// 0: nsIWidget's native modifier flag +// 1: Virtual keycode which does not distinguish whether left or right location. +// 2: Virtual keycode which distinguishes whether left or right location. +// 3: Scan code. +static const uint32_t sModifierKeyMap[][4] = { + {nsIWidget::CAPS_LOCK, VK_CAPITAL, 0, ScanCode::eCapsLock}, + {nsIWidget::NUM_LOCK, VK_NUMLOCK, 0, ScanCode::eNumLock}, + {nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT, ScanCode::eShiftLeft}, + {nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT, ScanCode::eShiftRight}, + {nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft}, + {nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL, ScanCode::eControlRight}, + {nsIWidget::ALT_L, VK_MENU, VK_LMENU, ScanCode::eAltLeft}, + {nsIWidget::ALT_R, VK_MENU, VK_RMENU, ScanCode::eAltRight}}; + +class KeyboardLayout; + +class MOZ_STACK_CLASS UniCharsAndModifiers final { + public: + UniCharsAndModifiers() {} + UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const; + UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther); + + /** + * Append a pair of unicode character and the final modifier. + */ + void Append(char16_t aUniChar, Modifiers aModifiers); + void Clear() { + mChars.Truncate(); + mModifiers.Clear(); + } + bool IsEmpty() const { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.IsEmpty(); + } + + char16_t CharAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Length()); + return mChars[aIndex]; + } + Modifiers ModifiersAt(size_t aIndex) const { + MOZ_ASSERT(aIndex < Length()); + return mModifiers[aIndex]; + } + size_t Length() const { + MOZ_ASSERT(mChars.Length() == mModifiers.Length()); + return mChars.Length(); + } + + bool IsProducingCharsWithAltGr() const { + return !IsEmpty() && (ModifiersAt(0) & MODIFIER_ALTGRAPH) != 0; + } + + void FillModifiers(Modifiers aModifiers); + /** + * OverwriteModifiersIfBeginsWith() assigns mModifiers with aOther between + * [0] and [aOther.mLength - 1] only when mChars begins with aOther.mChars. + */ + void OverwriteModifiersIfBeginsWith(const UniCharsAndModifiers& aOther); + + bool UniCharsEqual(const UniCharsAndModifiers& aOther) const; + bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const; + bool BeginsWith(const UniCharsAndModifiers& aOther) const; + + const nsString& ToString() const { return mChars; } + + private: + nsAutoString mChars; + // 5 is enough number for normal keyboard layout handling. On Windows, + // a dead key sequence may cause inputting up to 5 characters per key press. + CopyableAutoTArray<Modifiers, 5> mModifiers; +}; + +struct DeadKeyEntry { + char16_t BaseChar; + char16_t CompositeChar; +}; + +class DeadKeyTable { + friend class KeyboardLayout; + + uint16_t mEntries; + // KeyboardLayout::AddDeadKeyTable() will allocate as many entries as + // required. It is the only way to create new DeadKeyTable instances. + DeadKeyEntry mTable[1]; + + void Init(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) { + mEntries = aEntries; + memcpy(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry)); + } + + static uint32_t SizeInBytes(uint32_t aEntries) { + return offsetof(DeadKeyTable, mTable) + aEntries * sizeof(DeadKeyEntry); + } + + public: + uint32_t Entries() const { return mEntries; } + + bool IsEqual(const DeadKeyEntry* aDeadKeyArray, uint32_t aEntries) const { + return (mEntries == aEntries && + !memcmp(mTable, aDeadKeyArray, aEntries * sizeof(DeadKeyEntry))); + } + + char16_t GetCompositeChar(char16_t aBaseChar) const; +}; + +class VirtualKey { + public: + enum ShiftStateIndex : uint8_t { + // 0 - Normal + eNormal = 0, + // 1 - Shift + eShift, + // 2 - Control + eControl, + // 3 - Control + Shift + eControlShift, + // 4 - Alt + eAlt, + // 5 - Alt + Shift + eAltShift, + // 6 - Alt + Control (AltGr) + eAltGr, + // 7 - Alt + Control + Shift (AltGr + Shift) + eAltGrShift, + // 8 - CapsLock + eWithCapsLock, + // 9 - CapsLock + Shift + eShiftWithCapsLock, + // 10 - CapsLock + Control + eControlWithCapsLock, + // 11 - CapsLock + Control + Shift + eControlShiftWithCapsLock, + // 12 - CapsLock + Alt + eAltWithCapsLock, + // 13 - CapsLock + Alt + Shift + eAltShiftWithCapsLock, + // 14 - CapsLock + Alt + Control (CapsLock + AltGr) + eAltGrWithCapsLock, + // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift) + eAltGrShiftWithCapsLock, + }; + + enum ShiftStateFlag { + STATE_SHIFT = 0x01, + STATE_CONTROL = 0x02, + STATE_ALT = 0x04, + STATE_CAPSLOCK = 0x08, + // ShiftState needs to have AltGr state separately since this is necessary + // for lossless conversion with Modifiers. + STATE_ALTGRAPH = 0x80, + // Useful to remove or check Ctrl and Alt flags. + STATE_CONTROL_ALT = STATE_CONTROL | STATE_ALT, + }; + + typedef uint8_t ShiftState; + + static ShiftState ModifiersToShiftState(Modifiers aModifiers); + static ShiftState ModifierKeyStateToShiftState( + const ModifierKeyState& aModKeyState) { + return ModifiersToShiftState(aModKeyState.GetModifiers()); + } + static Modifiers ShiftStateToModifiers(ShiftState aShiftState); + static bool IsAltGrIndex(uint8_t aIndex) { + return (aIndex & STATE_CONTROL_ALT) == STATE_CONTROL_ALT; + } + + private: + union KeyShiftState { + struct { + char16_t Chars[4]; + } Normal; + struct { + const DeadKeyTable* Table; + char16_t DeadChar; + } DeadKey; + }; + + KeyShiftState mShiftStates[16]; + uint16_t mIsDeadKey; + + static uint8_t ToShiftStateIndex(ShiftState aShiftState) { + if (!(aShiftState & STATE_ALTGRAPH)) { + MOZ_ASSERT(aShiftState <= eAltGrShiftWithCapsLock); + return static_cast<uint8_t>(aShiftState); + } + uint8_t index = aShiftState & ~STATE_ALTGRAPH; + index |= (STATE_ALT | STATE_CONTROL); + MOZ_ASSERT(index <= eAltGrShiftWithCapsLock); + return index; + } + + void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey) { + if (aIsDeadKey) { + mIsDeadKey |= 1 << ToShiftStateIndex(aShiftState); + } else { + mIsDeadKey &= ~(1 << ToShiftStateIndex(aShiftState)); + } + } + + public: + static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState); + + bool IsDeadKey(ShiftState aShiftState) const { + return (mIsDeadKey & (1 << ToShiftStateIndex(aShiftState))) != 0; + } + + /** + * IsChangedByAltGr() is useful to check if a key with AltGr produces + * different character(s) from the key without AltGr. + * Note that this is designed for checking if a keyboard layout has AltGr + * key. So, this result may not exactly correct for the key since it's + * okay to fails in some edge cases when we check all keys which produce + * character(s) in a layout. + */ + bool IsChangedByAltGr(ShiftState aShiftState) const { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + MOZ_ASSERT(IsAltGrIndex(aShiftState)); + MOZ_ASSERT(IsDeadKey(aShiftState) || + mShiftStates[aShiftState].Normal.Chars[0]); + const ShiftState kShiftStateWithoutAltGr = + aShiftState - ShiftStateIndex::eAltGr; + if (IsDeadKey(aShiftState) != IsDeadKey(kShiftStateWithoutAltGr)) { + return false; + } + if (IsDeadKey(aShiftState)) { + return mShiftStates[aShiftState].DeadKey.DeadChar != + mShiftStates[kShiftStateWithoutAltGr].DeadKey.DeadChar; + } + for (size_t i = 0; i < 4; i++) { + if (mShiftStates[aShiftState].Normal.Chars[i] != + mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) { + return true; + } + if (!mShiftStates[aShiftState].Normal.Chars[i] && + !mShiftStates[kShiftStateWithoutAltGr].Normal.Chars[i]) { + return false; + } + } + return false; + } + + void AttachDeadKeyTable(ShiftState aShiftState, + const DeadKeyTable* aDeadKeyTable) { + MOZ_ASSERT(aShiftState == ToShiftStateIndex(aShiftState)); + mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable; + } + + void SetNormalChars(ShiftState aShiftState, const char16_t* aChars, + uint32_t aNumOfChars); + void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar); + const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray, + uint32_t aEntries) const; + inline char16_t GetCompositeChar(ShiftState aShiftState, + char16_t aBaseChar) const { + return mShiftStates[ToShiftStateIndex(aShiftState)] + .DeadKey.Table->GetCompositeChar(aBaseChar); + } + + char16_t GetCompositeChar(const ModifierKeyState& aModKeyState, + char16_t aBaseChar) const { + return GetCompositeChar(ModifierKeyStateToShiftState(aModKeyState), + aBaseChar); + } + + /** + * GetNativeUniChars() returns character(s) which is produced by the + * key with given modifiers. This does NOT return proper MODIFIER_ALTGRAPH + * state because this is raw accessor of the database of this key. + */ + UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetNativeUniChars( + const ModifierKeyState& aModKeyState) const { + return GetNativeUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } + + /** + * GetUniChars() returns characters and modifiers which are not consumed + * to input the character. + * For example, if you specify Ctrl key but the key produces no character + * with Ctrl, this returns character(s) which is produced by the key + * without Ctrl. So, the result is useful to decide KeyboardEvent.key + * value. + * Another example is, if you specify Ctrl key and the key produces + * different character(s) from the case without Ctrl key, this returns + * the character(s) *without* MODIFIER_CONTROL. This modifier information + * is useful for eKeyPress since TextEditor does not treat eKeyPress events + * whose modifier includes MODIFIER_ALT and/or MODIFIER_CONTROL. + * + * @param aShiftState Modifiers which you want to retrieve + * KeyboardEvent.key value for the key with. + * If AltGr key is pressed, this should include + * STATE_ALTGRAPH and should NOT include + * STATE_ALT nor STATE_CONTROL. + * If both Alt and Ctrl are pressed to emulate + * AltGr, this should include both STATE_ALT and + * STATE_CONTROL but should NOT include + * MODIFIER_ALTGRAPH. + * Then, this returns proper modifiers when + * this key produces no character with AltGr. + */ + UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const; + UniCharsAndModifiers GetUniChars(const ModifierKeyState& aModKeyState) const { + return GetUniChars(ModifierKeyStateToShiftState(aModKeyState)); + } +}; + +class MOZ_STACK_CLASS NativeKey final { + friend class KeyboardLayout; + + public: + struct FakeCharMsg { + UINT mCharCode; + UINT mScanCode; + bool mIsSysKey; + bool mIsDeadKey; + bool mConsumed; + + FakeCharMsg() + : mCharCode(0), + mScanCode(0), + mIsSysKey(false), + mIsDeadKey(false), + mConsumed(false) {} + + MSG GetCharMsg(HWND aWnd) const { + MSG msg; + msg.hwnd = aWnd; + msg.message = mIsDeadKey && mIsSysKey ? WM_SYSDEADCHAR + : mIsDeadKey ? WM_DEADCHAR + : mIsSysKey ? WM_SYSCHAR + : WM_CHAR; + msg.wParam = static_cast<WPARAM>(mCharCode); + msg.lParam = static_cast<LPARAM>(mScanCode << 16); + msg.time = 0; + msg.pt.x = msg.pt.y = 0; + return msg; + } + }; + + NativeKey(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); + + bool IsShift() const { return mModKeyState.IsShift(); } + bool IsControl() const { return mModKeyState.IsControl(); } + bool IsAlt() const { return mModKeyState.IsAlt(); } + bool MaybeEmulatingAltGraph() const; + Modifiers GetModifiers() const { return mModKeyState.GetModifiers(); } + const ModifierKeyState& ModifierKeyStateRef() const { return mModKeyState; } + VirtualKey::ShiftState GetShiftState() const { + return VirtualKey::ModifierKeyStateToShiftState(mModKeyState); + } + + /** + * GenericVirtualKeyCode() returns virtual keycode which cannot distinguish + * position of modifier keys. E.g., VK_CONTROL for both ControlLeft and + * ControlRight. + */ + uint8_t GenericVirtualKeyCode() const { return mOriginalVirtualKeyCode; } + + /** + * SpecificVirtualKeyCode() returns virtual keycode which can distinguish + * position of modifier keys. E.g., returns VK_LCONTROL or VK_RCONTROL + * instead of VK_CONTROL. If the key message is synthesized with not + * enough information, this prefers left position's keycode. + */ + uint8_t SpecificVirtualKeyCode() const { return mVirtualKeyCode; } + + private: + NativeKey* mLastInstance; + // mRemovingMsg is set at removing a char message from + // GetFollowingCharMessage(). + MSG mRemovingMsg; + // mReceivedMsg is set when another instance starts to handle the message + // unexpectedly. + MSG mReceivedMsg; + RefPtr<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; + // mIsRepeat is true if the key message is caused by the auto-repeat + // feature. + bool mIsRepeat; + bool mIsDeadKey; + // mIsPrintableKey is true if the key may be a printable key without + // any modifier keys. Otherwise, false. + // Please note that the event may not cause any text input even if this + // is true. E.g., it might be dead key state or Ctrl key may be pressed. + bool mIsPrintableKey; + // mIsSkippableInRemoteProcess is false if the key event shouldn't be + // skipped in the remote process even if it's too old event. + bool mIsSkippableInRemoteProcess; + // mCharMessageHasGone is true if the message is a keydown message and + // it's followed by at least one char message but it's gone at removing + // from the queue. This could occur if PeekMessage() or something is + // hooked by odd tool. + bool mCharMessageHasGone; + // mIsOverridingKeyboardLayout is true if the instance temporarily overriding + // keyboard layout with specified by the constructor. + bool mIsOverridingKeyboardLayout; + // mCanIgnoreModifierStateAtKeyPress is true if it's allowed to remove + // Ctrl or Alt modifier state at dispatching eKeyPress. + bool mCanIgnoreModifierStateAtKeyPress; + + nsTArray<FakeCharMsg>* mFakeCharMsgs; + + // When a keydown event is dispatched at handling WM_APPCOMMAND, the computed + // virtual keycode is set to this. Even if we consume WM_APPCOMMAND message, + // Windows may send WM_KEYDOWN and WM_KEYUP message for them. + // At that time, we should not dispatch key events for them. + static uint8_t sDispatchedKeyOfAppCommand; + + NativeKey() { + MOZ_CRASH("The default constructor of NativeKey isn't available"); + } + + void InitWithAppCommand(); + void InitWithKeyOrChar(); + + /** + * InitIsSkippableForKeyOrChar() initializes mIsSkippableInRemoteProcess with + * mIsRepeat and previous key message information. So, this must be called + * after mIsRepeat is initialized. + */ + void InitIsSkippableForKeyOrChar(const MSG& aLastKeyMSG); + + /** + * InitCommittedCharsAndModifiersWithFollowingCharMessages() initializes + * mCommittedCharsAndModifiers with mFollowingCharMsgs and mModKeyState. + * If mFollowingCharMsgs includes non-printable char messages, they are + * ignored (skipped). + */ + void InitCommittedCharsAndModifiersWithFollowingCharMessages(); + + UINT GetScanCodeWithExtendedFlag() const; + + // The result is one of eKeyLocation*. + uint32_t GetKeyLocation() const; + + /** + * RemoveFollowingOddCharMessages() removes odd WM_CHAR messages from the + * queue when IsIMEDoingKakuteiUndo() returns true. + */ + void RemoveFollowingOddCharMessages(); + + /** + * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes + * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this + * returns true, the caller needs to be careful for processing the messages. + */ + bool IsIMEDoingKakuteiUndo() const; + + /** + * This returns true if user types a number key in numpad with Alt key + * to input a Unicode character from its scalar value. + * Note that inputting Unicode scalar value is available without NumLock. + * Therefore, this returns true even if user presses a function key on + * numpad without NumLock, but that may be intended to perform a shortcut + * key like Alt + Home. + */ + bool MaybeTypingUnicodeScalarValue() const { + return !mIsExtended && IsSysKeyDownOrKeyUpMessage() && IsAlt() && + !IsControl() && !IsShift() && + ((mScanCode >= 0x004F && mScanCode <= 0x0052) || // Numpad0-3 + (mScanCode >= 0x004B && mScanCode <= 0x004D) || // Numpad4-6 + (mScanCode >= 0x0047 && mScanCode <= 0x0049)); // Numpad7-9 + } + + bool IsKeyDownMessage() const { + return (mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN || + mMsg.message == MOZ_WM_KEYDOWN); + } + bool IsKeyUpMessage() const { + return (mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP || + mMsg.message == MOZ_WM_KEYUP); + } + bool IsSysKeyDownOrKeyUpMessage() const { + return mMsg.message == WM_SYSKEYDOWN || mMsg.message == WM_SYSKEYUP; + } + bool IsCharOrSysCharMessage(const MSG& aMSG) const { + return IsCharOrSysCharMessage(aMSG.message); + } + bool IsCharOrSysCharMessage(UINT aMessage) const { + return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR); + } + bool IsCharMessage(const MSG& aMSG) const { + return IsCharMessage(aMSG.message); + } + bool IsCharMessage(UINT aMessage) const { + return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage)); + } + bool IsDeadCharMessage(const MSG& aMSG) const { + return IsDeadCharMessage(aMSG.message); + } + bool IsDeadCharMessage(UINT aMessage) const { + return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR); + } + bool IsSysCharMessage(const MSG& aMSG) const { + return IsSysCharMessage(aMSG.message); + } + bool IsSysCharMessage(UINT aMessage) const { + return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR); + } + bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const; + bool IsSamePhysicalKeyMessage(const MSG& aKeyOrCharMsg1, + const MSG& aKeyOrCharMsg2) const; + bool IsFollowedByPrintableCharMessage() const; + bool IsFollowedByPrintableCharOrSysCharMessage() const; + bool IsFollowedByDeadCharMessage() const; + bool 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 IsEnterKeyPressCharMessage(const MSG& aMSG) const { + return aMSG.message == WM_CHAR && aMSG.wParam == '\r'; + } + bool IsPrintableCharOrSysCharMessage(const MSG& aMSG) const { + return IsCharOrSysCharMessage(aMSG) && + !IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + bool IsControlCharMessage(const MSG& aMSG) const { + return IsCharMessage(aMSG.message) && + IsControlChar(static_cast<char16_t>(aMSG.wParam)); + } + + /** + * IsReservedBySystem() returns true if the key combination is reserved by + * the system. Even if it's consumed by web apps, the message should be + * sent to next wndproc. + */ + bool IsReservedBySystem() const; + + /** + * GetFollowingCharMessage() returns following char message of handling + * keydown event. If the message is found, this method returns true. + * Otherwise, returns false. + * + * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its + * hwnd may be different window. + */ + bool GetFollowingCharMessage(MSG& aCharMsg); + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK. + */ + uint8_t ComputeVirtualKeyCodeFromScanCode() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX. + */ + uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC. + */ + uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const; + + /** + * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR. + */ + char16_t ComputeUnicharFromScanCode() const; + + /** + * Initializes the aKeyEvent with the information stored in the instance. + */ + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent, + const ModifierKeyState& aModKeyState) const; + nsEventStatus InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const; + + /** + * Dispatches a command event for aEventCommand. + * Returns true if the event is consumed. Otherwise, false. + */ + bool DispatchCommandEvent(uint32_t aEventCommand) const; + + /** + * DispatchKeyPressEventsWithRetrievedCharMessages() dispatches keypress + * event(s) with retrieved char messages. + */ + bool DispatchKeyPressEventsWithRetrievedCharMessages() const; + + /** + * DispatchKeyPressEventsWithoutCharMessage() dispatches keypress event(s) + * without char messages. So, this should be used only when there are no + * following char messages. + */ + bool DispatchKeyPressEventsWithoutCharMessage() const; + + /** + * Checkes whether the key event down message is handled without following + * WM_CHAR messages. For example, if following WM_CHAR message indicates + * control character input, the WM_CHAR message is unclear whether it's + * caused by a printable key with Ctrl or just a function key such as Enter + * or Backspace. + */ + bool NeedsToHandleWithoutFollowingCharMessages() const; + + /** + * ComputeInputtingStringWithKeyboardLayout() computes string to be inputted + * with the key and the modifier state, without shift state and with shift + * state. + */ + void ComputeInputtingStringWithKeyboardLayout(); + + /** + * IsFocusedWindowChanged() returns true if focused window is changed + * after the instance is created. + */ + bool IsFocusedWindowChanged() const { + return mFocusedWndBeforeDispatch != ::GetFocus(); + } + + /** + * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be + * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them. + * Returns true if dispatched keypress event is consumed. Otherwise, false. + */ + bool HandleCharMessage(const MSG& aCharMsg, + bool* aEventDispatched = nullptr) const; + + // Calls of PeekMessage() from NativeKey might cause nested message handling + // due to (perhaps) odd API hook. NativeKey should do nothing if given + // message is tried to be retrieved by another instance. + + /** + * sLatestInstacne is a pointer to the newest instance of NativeKey which is + * handling a key or char message(s). + */ + static NativeKey* sLatestInstance; + + static const MSG sEmptyMSG; + + static MSG sLastKeyOrCharMSG; + + static MSG sLastKeyMSG; + + static bool IsEmptyMSG(const MSG& aMSG) { + return !memcmp(&aMSG, &sEmptyMSG, sizeof(MSG)); + } + + bool IsAnotherInstanceRemovingCharMessage() const { + return mLastInstance && !IsEmptyMSG(mLastInstance->mRemovingMsg); + } + + public: + /** + * Returns last key or char MSG. If no MSG has been received yet, the result + * is empty MSG (i.e., .message is WM_NULL). + */ + static const MSG& LastKeyOrCharMSG() { return sLastKeyOrCharMSG; } +}; + +class KeyboardLayout { + public: + static KeyboardLayout* GetInstance(); + static void Shutdown(); + static HKL GetActiveLayout(); + static nsCString GetActiveLayoutName(); + static void NotifyIdleServiceOfUserActivity(); + + static bool IsPrintableCharKey(uint8_t aVirtualKey); + + /** + * HasAltGr() returns true if the keyboard layout's AltRight key is AltGr + * key. + */ + bool HasAltGr() const { return mHasAltGr; } + + /** + * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState. + * This method isn't stateful. + */ + bool IsDeadKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + bool IsDeadKey(const NativeKey& aNativeKey) const { + return IsDeadKey(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.ModifierKeyStateRef()); + } + + /** + * IsInDeadKeySequence() returns true when it's in a dead key sequence. + * It starts when a dead key is down and ends when another key down causes + * inactivating the dead key state. + */ + bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); } + + /** + * IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY* + * or WM_SYS*CHAR messages. + */ + bool IsSysKey(uint8_t aVirtualKey, + const ModifierKeyState& aModKeyState) const; + bool IsSysKey(const NativeKey& aNativeKey) const { + return IsSysKey(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.ModifierKeyStateRef()); + } + + /** + * GetUniCharsAndModifiers() returns characters which are inputted by + * aVirtualKey with aModKeyState. This method isn't stateful. + * Note that if the combination causes text input, the result's Ctrl and + * Alt key state are never active. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, const ModifierKeyState& aModKeyState) const { + VirtualKey::ShiftState shiftState = + VirtualKey::ModifierKeyStateToShiftState(aModKeyState); + return GetUniCharsAndModifiers(aVirtualKey, shiftState); + } + UniCharsAndModifiers GetUniCharsAndModifiers( + const NativeKey& aNativeKey) const { + return GetUniCharsAndModifiers(aNativeKey.GenericVirtualKeyCode(), + aNativeKey.GetShiftState()); + } + + /** + * OnLayoutChange() must be called before the first keydown message is + * received. LoadLayout() changes the keyboard state, that causes breaking + * dead key state. Therefore, we need to load the layout before the first + * keydown message. + */ + void OnLayoutChange(HKL aKeyboardLayout) { + MOZ_ASSERT(!mIsOverridden); + LoadLayout(aKeyboardLayout); + } + + /** + * OverrideLayout() loads the specified keyboard layout. + */ + void OverrideLayout(HKL aLayout) { + mIsOverridden = true; + LoadLayout(aLayout); + } + + /** + * RestoreLayout() loads the current keyboard layout of the thread. + */ + void RestoreLayout() { + mIsOverridden = false; + mIsPendingToRestoreKeyboardLayout = true; + } + + uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const; + + /** + * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for + * non-printable keys (except some special keys like space key). + */ + KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const; + + /** + * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for + * the given scan code. aScanCode can be over 0xE000 since this method + * doesn't use Windows API. + */ + static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode); + + 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 nsIUserIdleServiceInternal* 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; + bool mHasAltGr; + + 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); + 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); + + /** + * MaybeInitNativeKeyAsDeadKey() initializes aNativeKey only when aNativeKey + * is a dead key's event. + * When it's not in a dead key sequence, this activates the dead key state. + * When it's in a dead key sequence, this initializes aNativeKey with a + * composite character or a preceding dead char and a dead char which should + * be caused by aNativeKey. + * Returns true when this initializes aNativeKey. Otherwise, false. + */ + bool MaybeInitNativeKeyAsDeadKey(NativeKey& aNativeKey); + + /** + * MaybeInitNativeKeyWithCompositeChar() may initialize aNativeKey with + * proper composite character when dead key produces a composite character. + * Otherwise, just returns false. + */ + bool MaybeInitNativeKeyWithCompositeChar(NativeKey& aNativeKey); + + /** + * See the comment of GetUniCharsAndModifiers() below. + */ + UniCharsAndModifiers GetUniCharsAndModifiers( + uint8_t aVirtualKey, VirtualKey::ShiftState aShiftState) const; + + /** + * GetDeadUniCharsAndModifiers() returns dead chars which are stored in + * current dead key sequence. So, this is stateful. + */ + UniCharsAndModifiers GetDeadUniCharsAndModifiers() const; + + /** + * GetCompositeChar() returns a composite character with dead character + * caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character + * (aBaseChar). + * If the combination of the dead character and the base character doesn't + * cause a composite character, this returns 0. + */ + char16_t GetCompositeChar(char16_t aBaseChar) const; + + // NativeKey class should access InitNativeKey() directly, but it shouldn't + // be available outside of NativeKey. So, let's make NativeKey a friend + // class of this. + friend class NativeKey; +}; + +class RedirectedKeyDownMessageManager { + public: + /* + * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is + * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent() + * prevents to dispatch eKeyDown event because it has been dispatched + * before the message was redirected. However, in some cases, WM_*KEYDOWN + * message handler may not handle actually. Then, the message handler needs + * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR + * message for the redirected keydown message. AutoFlusher class is a helper + * class for doing it. This must be created in the stack. + */ + class MOZ_STACK_CLASS AutoFlusher final { + public: + AutoFlusher(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..d0db086cd8 --- /dev/null +++ b/widget/windows/LSPAnnotator.cpp @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * LSPs are evil little bits of code that are allowed to inject into our + * networking stack by Windows. Once they have wormed into our process + * they gnaw at our innards until we crash. Here we force the buggers + * into the light by recording them in our crash information. + * We do the enumeration on a thread because I'm concerned about startup perf + * on machines with several LSPs. + */ + +#include "nsICrashReporter.h" +#include "nsISupportsImpl.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsQueryObject.h" +#include "nsWindowsHelpers.h" +#include <windows.h> +#include <rpc.h> +#include <ws2spi.h> + +namespace mozilla { +namespace crashreporter { + +class LSPAnnotationGatherer : public Runnable { + ~LSPAnnotationGatherer() {} + + public: + LSPAnnotationGatherer() : Runnable("crashreporter::LSPAnnotationGatherer") {} + NS_DECL_NSIRUNNABLE + + void Annotate(); + nsCString mString; +}; + +void LSPAnnotationGatherer::Annotate() { + nsCOMPtr<nsICrashReporter> cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + bool enabled; + if (cr && NS_SUCCEEDED(cr->GetEnabled(&enabled)) && enabled) { + cr->AnnotateCrashReport("Winsock_LSP"_ns, mString); + } +} + +NS_IMETHODIMP +LSPAnnotationGatherer::Run() { + DWORD size = 0; + int err; + // Get the size of the buffer we need + if (SOCKET_ERROR != WSCEnumProtocols(nullptr, nullptr, &size, &err) || + err != WSAENOBUFS) { + // Er, what? + MOZ_ASSERT_UNREACHABLE( + "WSCEnumProtocols succeeded when it should have " + "failed"); + return NS_ERROR_FAILURE; + } + + auto byteArray = MakeUnique<char[]>(size); + WSAPROTOCOL_INFOW* providers = + reinterpret_cast<WSAPROTOCOL_INFOW*>(byteArray.get()); + + int n = WSCEnumProtocols(nullptr, providers, &size, &err); + if (n == SOCKET_ERROR) { + // Lame. We provided the right size buffer; we'll just give up now. + NS_WARNING("Could not get LSP list"); + return NS_ERROR_FAILURE; + } + + nsCString str; + for (int i = 0; i < n; i++) { + AppendUTF16toUTF8(nsDependentString(providers[i].szProtocol), str); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iVersion); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iAddressFamily); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iSocketType); + str.AppendLiteral(" : "); + str.AppendInt(providers[i].iProtocol); + str.AppendLiteral(" : "); + str.AppendPrintf("0x%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(" : "); + // Call WSCGetProviderInfo to obtain the category flags for this provider. + // When present, these flags inform Windows as to which order to chain the + // providers. + DWORD categoryInfo; + size_t categoryInfoSize = sizeof(categoryInfo); + if (!WSCGetProviderInfo(&providers[i].ProviderId, ProviderInfoLspCategories, + (PBYTE)&categoryInfo, &categoryInfoSize, 0, &err)) { + str.AppendPrintf("0x%lx", categoryInfo); + } + + str.AppendLiteral(" : "); + if (providers[i].ProtocolChain.ChainLen <= BASE_PROTOCOL) { + // If we're dealing with a catalog entry that identifies an individual + // base or layer provider, log its provider GUID. + RPC_CSTR provIdStr = nullptr; + if (UuidToStringA(&providers[i].ProviderId, &provIdStr) == RPC_S_OK) { + str.Append(reinterpret_cast<char*>(provIdStr)); + RpcStringFreeA(&provIdStr); + } + } + + if (i + 1 != n) { + str.AppendLiteral(" \n "); + } + } + + mString = str; + NS_DispatchToMainThread( + NewRunnableMethod("crashreporter::LSPAnnotationGatherer::Annotate", this, + &LSPAnnotationGatherer::Annotate)); + return NS_OK; +} + +void LSPAnnotate() { + nsCOMPtr<nsIRunnable> runnable(new LSPAnnotationGatherer()); + NS_DispatchBackgroundTask(runnable.forget()); +} + +} // namespace crashreporter +} // namespace mozilla diff --git a/widget/windows/MediaKeysEventSourceFactory.cpp b/widget/windows/MediaKeysEventSourceFactory.cpp new file mode 100644 index 0000000000..525aab19c6 --- /dev/null +++ b/widget/windows/MediaKeysEventSourceFactory.cpp @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MediaKeysEventSourceFactory.h" +#include "WindowsSMTCProvider.h" + +namespace mozilla { +namespace widget { + +mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() { +#ifndef __MINGW32__ + return new WindowsSMTCProvider(); +#else + return nullptr; // MinGW doesn't support the required Windows 8.1+ APIs +#endif +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/OSKInputPaneManager.cpp b/widget/windows/OSKInputPaneManager.cpp new file mode 100644 index 0000000000..293a84cd28 --- /dev/null +++ b/widget/windows/OSKInputPaneManager.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define NTDDI_VERSION NTDDI_WIN10_RS1 + +#include "OSKInputPaneManager.h" +#include "nsDebug.h" + +#ifndef __MINGW32__ +# include <inputpaneinterop.h> +# include <windows.ui.viewmanagement.h> +# include <wrl.h> + +using namespace ABI::Windows::UI::ViewManagement; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +#endif + +namespace mozilla { +namespace widget { + +#ifndef __MINGW32__ +static ComPtr<IInputPane2> GetInputPane(HWND aHwnd) { + ComPtr<IInputPaneInterop> inputPaneInterop; + HRESULT hr = RoGetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_ViewManagement_InputPane).Get(), + IID_PPV_ARGS(&inputPaneInterop)); + if (NS_WARN_IF(FAILED(hr))) { + return nullptr; + } + + ComPtr<IInputPane> inputPane; + hr = inputPaneInterop->GetForWindow(aHwnd, IID_PPV_ARGS(&inputPane)); + if (NS_WARN_IF(FAILED(hr))) { + return nullptr; + } + + ComPtr<IInputPane2> inputPane2; + inputPane.As(&inputPane2); + return inputPane2; +} + +# ifdef DEBUG +static bool IsInputPaneVisible(ComPtr<IInputPane2>& aInputPane2) { + ComPtr<IInputPaneControl> inputPaneControl; + aInputPane2.As(&inputPaneControl); + if (NS_WARN_IF(!inputPaneControl)) { + return false; + } + boolean visible = false; + inputPaneControl->get_Visible(&visible); + return visible; +} + +static bool IsForegroundApp() { + HWND foregroundWnd = ::GetForegroundWindow(); + if (!foregroundWnd) { + return false; + } + DWORD pid; + ::GetWindowThreadProcessId(foregroundWnd, &pid); + return pid == ::GetCurrentProcessId(); +} +# endif +#endif + +// static +void OSKInputPaneManager::ShowOnScreenKeyboard(HWND aWnd) { +#ifndef __MINGW32__ + ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd); + if (!inputPane2) { + return; + } + boolean result; + inputPane2->TryShow(&result); + NS_WARNING_ASSERTION( + result || !IsForegroundApp() || IsInputPaneVisible(inputPane2), + "IInputPane2::TryShow is failure"); +#endif +} + +// static +void OSKInputPaneManager::DismissOnScreenKeyboard(HWND aWnd) { +#ifndef __MINGW32__ + ComPtr<IInputPane2> inputPane2 = GetInputPane(aWnd); + if (!inputPane2) { + return; + } + boolean result; + inputPane2->TryHide(&result); + NS_WARNING_ASSERTION( + result || !IsForegroundApp() || !IsInputPaneVisible(inputPane2), + "IInputPane2::TryHide is failure"); +#endif +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/OSKInputPaneManager.h b/widget/windows/OSKInputPaneManager.h new file mode 100644 index 0000000000..421a5f1e02 --- /dev/null +++ b/widget/windows/OSKInputPaneManager.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_OSKInputPaneManager_h +#define mozilla_widget_OSKInputPaneManager_h + +#include <windows.h> + +namespace mozilla { +namespace widget { + +class OSKInputPaneManager final { + public: + static void ShowOnScreenKeyboard(HWND aHwnd); + static void DismissOnScreenKeyboard(HWND aHwnd); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_OSKInputPaneManager_h diff --git a/widget/windows/PCompositorWidget.ipdl b/widget/windows/PCompositorWidget.ipdl new file mode 100644 index 0000000000..a2d8db5896 --- /dev/null +++ b/widget/windows/PCompositorWidget.ipdl @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PCompositorBridge; + +include "mozilla/widget/WidgetMessageUtils.h"; + +using mozilla::gfx::IntSize from "mozilla/gfx/Point.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using nsTransparencyMode from "nsIWidget.h"; + +namespace mozilla { +namespace widget { + +struct RemoteBackbufferHandles { + WindowsHandle fileMapping; + WindowsHandle requestReadyEvent; + WindowsHandle responseReadyEvent; +}; + +sync protocol PCompositorWidget +{ + manager PCompositorBridge; + +parent: + sync Initialize(RemoteBackbufferHandles aRemoteHandles); + + sync EnterPresentLock(); + sync LeavePresentLock(); + async UpdateTransparency(nsTransparencyMode aMode); + sync ClearTransparentWindow(); + async __delete__(); + +child: + async ObserveVsync(); + async UnobserveVsync(); + async UpdateCompositorWnd(WindowsHandle aCompositorWnd, WindowsHandle aParentWnd) + returns (bool success); +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/PlatformWidgetTypes.ipdlh b/widget/windows/PlatformWidgetTypes.ipdlh new file mode 100644 index 0000000000..1b7dc66195 --- /dev/null +++ b/widget/windows/PlatformWidgetTypes.ipdlh @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/dom/TabMessageUtils.h"; +include "mozilla/widget/WidgetMessageUtils.h"; + +include HeadlessWidgetTypes; + +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using nsSizeMode from "nsIWidgetListener.h"; +using nsTransparencyMode from "nsIWidget.h"; + +namespace mozilla { +namespace widget { + +struct WinCompositorWidgetInitData +{ + WindowsHandle hWnd; + uintptr_t widgetKey; + nsTransparencyMode transparencyMode; + nsSizeMode sizeMode; +}; + +union CompositorWidgetInitData +{ + WinCompositorWidgetInitData; + HeadlessCompositorWidgetInitData; +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/RemoteBackbuffer.cpp b/widget/windows/RemoteBackbuffer.cpp new file mode 100644 index 0000000000..08b420affb --- /dev/null +++ b/widget/windows/RemoteBackbuffer.cpp @@ -0,0 +1,675 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RemoteBackbuffer.h" +#include "mozilla/Span.h" +#include <algorithm> +#include <type_traits> + +namespace mozilla { +namespace widget { +namespace remote_backbuffer { + +// This number can be adjusted as a time-memory tradeoff +constexpr uint8_t kMaxDirtyRects = 8; + +struct IpcSafeRect { + explicit IpcSafeRect(const gfx::IntRect& aRect) + : x(aRect.x), y(aRect.y), width(aRect.width), height(aRect.height) {} + int32_t x; + int32_t y; + int32_t width; + int32_t height; +}; + +enum class ResponseResult { + Unknown, + Error, + BorrowSuccess, + BorrowSameBuffer, + PresentSuccess +}; + +enum class SharedDataType { + BorrowRequest, + BorrowRequestAllowSameBuffer, + BorrowResponse, + PresentRequest, + PresentResponse +}; + +struct BorrowResponseData { + ResponseResult result; + int32_t width; + int32_t height; + HANDLE fileMapping; +}; + +struct PresentRequestData { + uint8_t lenDirtyRects; + IpcSafeRect dirtyRects[kMaxDirtyRects]; +}; + +struct PresentResponseData { + ResponseResult result; +}; + +struct SharedData { + SharedDataType dataType; + union { + BorrowResponseData borrowResponse; + PresentRequestData presentRequest; + PresentResponseData presentResponse; + } data; +}; + +static_assert(std::is_trivially_copyable<SharedData>::value && + std::is_standard_layout<SharedData>::value, + "SharedData must be safe to pass over IPC boundaries"); + +class SharedImage { + public: + SharedImage() + : mWidth(0), mHeight(0), mFileMapping(nullptr), mPixelData(nullptr) {} + + ~SharedImage() { + if (mPixelData) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mPixelData)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } + } + + bool Initialize(int32_t aWidth, int32_t aHeight) { + MOZ_ASSERT(aWidth > 0); + MOZ_ASSERT(aHeight > 0); + + mWidth = aWidth; + mHeight = aHeight; + + DWORD bufferSize = static_cast<DWORD>(mHeight * GetStride()); + + mFileMapping = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, + 0 /*sizeHigh*/, bufferSize, nullptr /*name*/); + if (!mFileMapping) { + return false; + } + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr); + + return true; + } + + bool InitializeRemote(int32_t aWidth, int32_t aHeight, HANDLE aFileMapping) { + MOZ_ASSERT(aWidth > 0); + MOZ_ASSERT(aHeight > 0); + MOZ_ASSERT(aFileMapping); + + mWidth = aWidth; + mHeight = aHeight; + mFileMapping = aFileMapping; + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mPixelData = reinterpret_cast<unsigned char*>(mappedFilePtr); + + return true; + } + + HBITMAP CreateDIBSection() { + BITMAPINFO bitmapInfo = {}; + bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader); + bitmapInfo.bmiHeader.biWidth = mWidth; + bitmapInfo.bmiHeader.biHeight = -mHeight; + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + void* dummy = nullptr; + return ::CreateDIBSection(nullptr /*paletteDC*/, &bitmapInfo, + DIB_RGB_COLORS, &dummy, mFileMapping, + 0 /*offset*/); + } + + HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) { + MOZ_ASSERT(aTargetProcessId); + + HANDLE fileMapping = nullptr; + if (!ipc::DuplicateHandle(mFileMapping, aTargetProcessId, &fileMapping, + 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) { + return nullptr; + } + return fileMapping; + } + + already_AddRefed<gfx::DrawTarget> CreateDrawTarget() { + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::CAIRO, mPixelData, IntSize(mWidth, mHeight), + GetStride(), gfx::SurfaceFormat::B8G8R8A8); + } + + void CopyPixelsFrom(const SharedImage& other) { + const unsigned char* src = other.mPixelData; + unsigned char* dst = mPixelData; + + int32_t width = std::min(mWidth, other.mWidth); + int32_t height = std::min(mHeight, other.mHeight); + + for (int32_t row = 0; row < height; ++row) { + memcpy(dst, src, static_cast<uint32_t>(width * kBytesPerPixel)); + src += other.GetStride(); + dst += GetStride(); + } + } + + int32_t GetWidth() { return mWidth; } + + int32_t GetHeight() { return mHeight; } + + SharedImage(const SharedImage&) = delete; + SharedImage(SharedImage&&) = delete; + SharedImage& operator=(const SharedImage&) = delete; + SharedImage& operator=(SharedImage&&) = delete; + + private: + static constexpr int32_t kBytesPerPixel = 4; + + int32_t GetStride() const { + // DIB requires 32-bit row alignment + return (((mWidth * kBytesPerPixel) + 3) / 4) * 4; + } + + int32_t mWidth; + int32_t mHeight; + HANDLE mFileMapping; + unsigned char* mPixelData; +}; + +class PresentableSharedImage { + public: + PresentableSharedImage() + : mSharedImage(), + mDeviceContext(nullptr), + mDIBSection(nullptr), + mSavedObject(nullptr) {} + + ~PresentableSharedImage() { + if (mSavedObject) { + MOZ_ALWAYS_TRUE(::SelectObject(mDeviceContext, mSavedObject)); + } + + if (mDIBSection) { + MOZ_ALWAYS_TRUE(::DeleteObject(mDIBSection)); + } + + if (mDeviceContext) { + MOZ_ALWAYS_TRUE(::DeleteDC(mDeviceContext)); + } + } + + bool Initialize(int32_t aWidth, int32_t aHeight) { + if (!mSharedImage.Initialize(aWidth, aHeight)) { + return false; + } + + mDeviceContext = ::CreateCompatibleDC(nullptr); + if (!mDeviceContext) { + return false; + } + + mDIBSection = mSharedImage.CreateDIBSection(); + if (!mDIBSection) { + return false; + } + + mSavedObject = ::SelectObject(mDeviceContext, mDIBSection); + if (!mSavedObject) { + return false; + } + + return true; + } + + bool PresentToWindow(HWND aWindowHandle, nsTransparencyMode aTransparencyMode, + Span<const IpcSafeRect> aDirtyRects) { + if (aTransparencyMode == eTransparencyTransparent) { + // If our window is a child window or a child-of-a-child, the window + // that needs to be updated is the top level ancestor of the tree + HWND topLevelWindow = WinUtils::GetTopLevelHWND(aWindowHandle, true); + MOZ_ASSERT(::GetWindowLongPtr(topLevelWindow, GWL_EXSTYLE) & + WS_EX_LAYERED); + + BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + SIZE winSize = {mSharedImage.GetWidth(), mSharedImage.GetHeight()}; + POINT srcPos = {0, 0}; + return !!::UpdateLayeredWindow( + topLevelWindow, nullptr /*paletteDC*/, nullptr /*newPos*/, &winSize, + mDeviceContext, &srcPos, 0 /*colorKey*/, &bf, ULW_ALPHA); + } + + IntRect sharedImageRect{0, 0, mSharedImage.GetWidth(), + mSharedImage.GetHeight()}; + + bool result = true; + + HDC windowDC = ::GetDC(aWindowHandle); + if (!windowDC) { + return false; + } + + for (auto& ipcDirtyRect : aDirtyRects) { + IntRect dirtyRect{ipcDirtyRect.x, ipcDirtyRect.y, ipcDirtyRect.width, + ipcDirtyRect.height}; + IntRect bltRect = dirtyRect.Intersect(sharedImageRect); + + if (!::BitBlt(windowDC, bltRect.x /*dstX*/, bltRect.y /*dstY*/, + bltRect.width, bltRect.height, mDeviceContext, + bltRect.x /*srcX*/, bltRect.y /*srcY*/, SRCCOPY)) { + result = false; + break; + } + } + + MOZ_ALWAYS_TRUE(::ReleaseDC(aWindowHandle, windowDC)); + + return result; + } + + HANDLE CreateRemoteFileMapping(DWORD aTargetProcessId) { + return mSharedImage.CreateRemoteFileMapping(aTargetProcessId); + } + + already_AddRefed<gfx::DrawTarget> CreateDrawTarget() { + return mSharedImage.CreateDrawTarget(); + } + + void CopyPixelsFrom(const PresentableSharedImage& other) { + mSharedImage.CopyPixelsFrom(other.mSharedImage); + } + + int32_t GetWidth() { return mSharedImage.GetWidth(); } + + int32_t GetHeight() { return mSharedImage.GetHeight(); } + + PresentableSharedImage(const PresentableSharedImage&) = delete; + PresentableSharedImage(PresentableSharedImage&&) = delete; + PresentableSharedImage& operator=(const PresentableSharedImage&) = delete; + PresentableSharedImage& operator=(PresentableSharedImage&&) = delete; + + private: + SharedImage mSharedImage; + HDC mDeviceContext; + HBITMAP mDIBSection; + HGDIOBJ mSavedObject; +}; + +Provider::Provider() + : mWindowHandle(nullptr), + mTargetProcessId(0), + mFileMapping(nullptr), + mRequestReadyEvent(nullptr), + mResponseReadyEvent(nullptr), + mSharedDataPtr(nullptr), + mStopServiceThread(false), + mServiceThread(), + mBackbuffer() {} + +Provider::~Provider() { + mBackbuffer.reset(); + + if (mServiceThread.joinable()) { + mStopServiceThread = true; + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + mServiceThread.join(); + } + + if (mSharedDataPtr) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr)); + } + + if (mResponseReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent)); + } + + if (mRequestReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } +} + +bool Provider::Initialize(HWND aWindowHandle, DWORD aTargetProcessId, + nsTransparencyMode aTransparencyMode) { + MOZ_ASSERT(aWindowHandle); + MOZ_ASSERT(aTargetProcessId); + + mWindowHandle = aWindowHandle; + mTargetProcessId = aTargetProcessId; + + mFileMapping = ::CreateFileMappingW( + INVALID_HANDLE_VALUE, nullptr /*secattr*/, PAGE_READWRITE, 0 /*sizeHigh*/, + static_cast<DWORD>(sizeof(SharedData)), nullptr /*name*/); + if (!mFileMapping) { + return false; + } + + mRequestReadyEvent = + ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/, + FALSE /*initialState*/, nullptr /*name*/); + if (!mRequestReadyEvent) { + return false; + } + + mResponseReadyEvent = + ::CreateEventW(nullptr /*secattr*/, FALSE /*manualReset*/, + FALSE /*initialState*/, nullptr /*name*/); + if (!mResponseReadyEvent) { + return false; + } + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr); + + mStopServiceThread = false; + + mServiceThread = std::thread([this] { this->ThreadMain(); }); + + mTransparencyMode = aTransparencyMode; + + return true; +} + +Maybe<RemoteBackbufferHandles> Provider::CreateRemoteHandles() { + HANDLE fileMapping = nullptr; + if (!ipc::DuplicateHandle(mFileMapping, mTargetProcessId, &fileMapping, + 0 /*desiredAccess*/, DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + HANDLE requestReadyEvent = nullptr; + if (!ipc::DuplicateHandle(mRequestReadyEvent, mTargetProcessId, + &requestReadyEvent, 0 /*desiredAccess*/, + DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + HANDLE responseReadyEvent = nullptr; + if (!ipc::DuplicateHandle(mResponseReadyEvent, mTargetProcessId, + &responseReadyEvent, 0 /*desiredAccess*/, + DUPLICATE_SAME_ACCESS)) { + return Nothing(); + } + + return Some(RemoteBackbufferHandles( + reinterpret_cast<WindowsHandle>(fileMapping), + reinterpret_cast<WindowsHandle>(requestReadyEvent), + reinterpret_cast<WindowsHandle>(responseReadyEvent))); +} + +void Provider::UpdateTransparencyMode(nsTransparencyMode aTransparencyMode) { + mTransparencyMode = aTransparencyMode; +} + +void Provider::ThreadMain() { + while (true) { + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mRequestReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mStopServiceThread) { + break; + } + + switch (mSharedDataPtr->dataType) { + case SharedDataType::BorrowRequest: + case SharedDataType::BorrowRequestAllowSameBuffer: { + BorrowResponseData responseData = {}; + + HandleBorrowRequest(&responseData, + mSharedDataPtr->dataType == + SharedDataType::BorrowRequestAllowSameBuffer); + + mSharedDataPtr->dataType = SharedDataType::BorrowResponse; + mSharedDataPtr->data.borrowResponse = responseData; + + MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent)); + + break; + } + case SharedDataType::PresentRequest: { + PresentRequestData requestData = mSharedDataPtr->data.presentRequest; + PresentResponseData responseData = {}; + + HandlePresentRequest(requestData, &responseData); + + mSharedDataPtr->dataType = SharedDataType::PresentResponse; + mSharedDataPtr->data.presentResponse = responseData; + + MOZ_ALWAYS_TRUE(::SetEvent(mResponseReadyEvent)); + + break; + } + default: + break; + }; + } +} + +void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData, + bool aAllowSameBuffer) { + MOZ_ASSERT(aResponseData); + + aResponseData->result = ResponseResult::Error; + + RECT clientRect = {}; + if (!::GetClientRect(mWindowHandle, &clientRect)) { + return; + } + + MOZ_ASSERT(clientRect.left == 0); + MOZ_ASSERT(clientRect.top == 0); + + int32_t width = clientRect.right ? clientRect.right : 1; + int32_t height = clientRect.bottom ? clientRect.bottom : 1; + + bool needNewBackbuffer = !aAllowSameBuffer || !mBackbuffer || + (mBackbuffer->GetWidth() != width) || + (mBackbuffer->GetHeight() != height); + + if (!needNewBackbuffer) { + aResponseData->result = ResponseResult::BorrowSameBuffer; + return; + } + + auto newBackbuffer = std::make_unique<PresentableSharedImage>(); + if (!newBackbuffer->Initialize(width, height)) { + return; + } + + // Preserve the contents of the old backbuffer (if it exists) + if (mBackbuffer) { + newBackbuffer->CopyPixelsFrom(*mBackbuffer); + mBackbuffer.reset(); + } + + HANDLE remoteFileMapping = + newBackbuffer->CreateRemoteFileMapping(mTargetProcessId); + if (!remoteFileMapping) { + return; + } + + aResponseData->result = ResponseResult::BorrowSuccess; + aResponseData->width = width; + aResponseData->height = height; + aResponseData->fileMapping = remoteFileMapping; + + mBackbuffer = std::move(newBackbuffer); +} + +void Provider::HandlePresentRequest(const PresentRequestData& aRequestData, + PresentResponseData* aResponseData) { + MOZ_ASSERT(aResponseData); + + Span rectSpan(aRequestData.dirtyRects, kMaxDirtyRects); + + aResponseData->result = ResponseResult::Error; + + if (!mBackbuffer) { + return; + } + + if (!mBackbuffer->PresentToWindow( + mWindowHandle, mTransparencyMode, + rectSpan.First(aRequestData.lenDirtyRects))) { + return; + } + + aResponseData->result = ResponseResult::PresentSuccess; +} + +Client::Client() + : mFileMapping(nullptr), + mRequestReadyEvent(nullptr), + mResponseReadyEvent(nullptr), + mSharedDataPtr(nullptr), + mBackbuffer() {} + +Client::~Client() { + mBackbuffer.reset(); + + if (mSharedDataPtr) { + MOZ_ALWAYS_TRUE(::UnmapViewOfFile(mSharedDataPtr)); + } + + if (mResponseReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mResponseReadyEvent)); + } + + if (mRequestReadyEvent) { + MOZ_ALWAYS_TRUE(::CloseHandle(mRequestReadyEvent)); + } + + if (mFileMapping) { + MOZ_ALWAYS_TRUE(::CloseHandle(mFileMapping)); + } +} + +bool Client::Initialize(const RemoteBackbufferHandles& aRemoteHandles) { + MOZ_ASSERT(aRemoteHandles.fileMapping()); + MOZ_ASSERT(aRemoteHandles.requestReadyEvent()); + MOZ_ASSERT(aRemoteHandles.responseReadyEvent()); + + mFileMapping = reinterpret_cast<HANDLE>(aRemoteHandles.fileMapping()); + mRequestReadyEvent = + reinterpret_cast<HANDLE>(aRemoteHandles.requestReadyEvent()); + mResponseReadyEvent = + reinterpret_cast<HANDLE>(aRemoteHandles.responseReadyEvent()); + + void* mappedFilePtr = + ::MapViewOfFile(mFileMapping, FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/, + 0 /*offsetLow*/, 0 /*bytesToMap*/); + if (!mappedFilePtr) { + return false; + } + + mSharedDataPtr = reinterpret_cast<SharedData*>(mappedFilePtr); + + return true; +} + +already_AddRefed<gfx::DrawTarget> Client::BorrowDrawTarget() { + mSharedDataPtr->dataType = mBackbuffer + ? SharedDataType::BorrowRequestAllowSameBuffer + : SharedDataType::BorrowRequest; + + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mSharedDataPtr->dataType != SharedDataType::BorrowResponse) { + return nullptr; + } + + BorrowResponseData responseData = mSharedDataPtr->data.borrowResponse; + + if ((responseData.result != ResponseResult::BorrowSameBuffer) && + (responseData.result != ResponseResult::BorrowSuccess)) { + return nullptr; + } + + if (responseData.result == ResponseResult::BorrowSuccess) { + mBackbuffer.reset(); + + auto newBackbuffer = std::make_unique<SharedImage>(); + if (!newBackbuffer->InitializeRemote(responseData.width, + responseData.height, + responseData.fileMapping)) { + return nullptr; + } + + mBackbuffer = std::move(newBackbuffer); + } + + MOZ_ASSERT(mBackbuffer); + + return mBackbuffer->CreateDrawTarget(); +} + +bool Client::PresentDrawTarget(gfx::IntRegion aDirtyRegion) { + mSharedDataPtr->dataType = SharedDataType::PresentRequest; + + // Simplify the region until it has <= kMaxDirtyRects + aDirtyRegion.SimplifyOutward(kMaxDirtyRects); + + Span rectSpan(mSharedDataPtr->data.presentRequest.dirtyRects, kMaxDirtyRects); + + uint8_t rectIndex = 0; + for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + rectSpan[rectIndex] = IpcSafeRect(iter.Get()); + ++rectIndex; + } + + mSharedDataPtr->data.presentRequest.lenDirtyRects = rectIndex; + + MOZ_ALWAYS_TRUE(::SetEvent(mRequestReadyEvent)); + MOZ_ALWAYS_TRUE(::WaitForSingleObject(mResponseReadyEvent, INFINITE) == + WAIT_OBJECT_0); + + if (mSharedDataPtr->dataType != SharedDataType::PresentResponse) { + return false; + } + + if (mSharedDataPtr->data.presentResponse.result != + ResponseResult::PresentSuccess) { + return false; + } + + return true; +} + +} // namespace remote_backbuffer +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/RemoteBackbuffer.h b/widget/windows/RemoteBackbuffer.h new file mode 100644 index 0000000000..038078d4cd --- /dev/null +++ b/widget/windows/RemoteBackbuffer.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_RemoteBackbuffer_h +#define widget_windows_RemoteBackbuffer_h + +#include "nsIWidget.h" +#include "mozilla/widget/PCompositorWidgetParent.h" +#include "mozilla/Maybe.h" +#include <thread> +#include <windows.h> + +namespace mozilla { +namespace widget { +namespace remote_backbuffer { + +struct IpcRect; +struct SharedData; +struct BorrowResponseData; +struct PresentRequestData; +struct PresentResponseData; +class SharedImage; +class PresentableSharedImage; + +class Provider { + public: + Provider(); + ~Provider(); + + bool Initialize(HWND aWindowHandle, DWORD aTargetProcessId, + nsTransparencyMode aTransparencyMode); + + Maybe<RemoteBackbufferHandles> CreateRemoteHandles(); + + void UpdateTransparencyMode(nsTransparencyMode aTransparencyMode); + + Provider(const Provider&) = delete; + Provider(Provider&&) = delete; + Provider& operator=(const Provider&) = delete; + Provider& operator=(Provider&&) = delete; + + private: + void ThreadMain(); + + void HandleBorrowRequest(BorrowResponseData* aResponseData, + bool aAllowSameBuffer); + void HandlePresentRequest(const PresentRequestData& aRequestData, + PresentResponseData* aResponseData); + + HWND mWindowHandle; + DWORD mTargetProcessId; + HANDLE mFileMapping; + HANDLE mRequestReadyEvent; + HANDLE mResponseReadyEvent; + SharedData* mSharedDataPtr; + bool mStopServiceThread; + std::thread mServiceThread; + std::unique_ptr<PresentableSharedImage> mBackbuffer; + mozilla::Atomic<nsTransparencyMode, MemoryOrdering::Relaxed> + mTransparencyMode; +}; + +class Client { + public: + Client(); + ~Client(); + + bool Initialize(const RemoteBackbufferHandles& aRemoteHandles); + + already_AddRefed<gfx::DrawTarget> BorrowDrawTarget(); + bool PresentDrawTarget(gfx::IntRegion aDirtyRegion); + + Client(const Client&) = delete; + Client(Client&&) = delete; + Client& operator=(const Client&) = delete; + Client& operator=(Client&&) = delete; + + private: + HANDLE mFileMapping; + HANDLE mRequestReadyEvent; + HANDLE mResponseReadyEvent; + SharedData* mSharedDataPtr; + std::unique_ptr<SharedImage> mBackbuffer; +}; + +} // namespace remote_backbuffer +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_RemoteBackbuffer_h diff --git a/widget/windows/ScreenHelperWin.cpp b/widget/windows/ScreenHelperWin.cpp new file mode 100644 index 0000000000..ff455b6c8c --- /dev/null +++ b/widget/windows/ScreenHelperWin.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScreenHelperWin.h" + +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include "WinUtils.h" + +static mozilla::LazyLogModule sScreenLog("WidgetScreen"); + +namespace mozilla { +namespace widget { + +BOOL CALLBACK CollectMonitors(HMONITOR aMon, HDC, LPRECT, LPARAM ioParam) { + auto screens = reinterpret_cast<nsTArray<RefPtr<Screen>>*>(ioParam); + BOOL success = FALSE; + MONITORINFOEX info; + info.cbSize = sizeof(MONITORINFOEX); + success = ::GetMonitorInfoW(aMon, &info); + if (!success) { + MOZ_LOG(sScreenLog, LogLevel::Error, ("GetMonitorInfoW failed")); + return TRUE; // continue the enumeration + } + + double scale = WinUtils::LogToPhysFactor(aMon); + DesktopToLayoutDeviceScale contentsScaleFactor; + if (WinUtils::IsPerMonitorDPIAware()) { + contentsScaleFactor.scale = 1.0; + } else { + contentsScaleFactor.scale = scale; + } + CSSToLayoutDeviceScale defaultCssScaleFactor(scale); + LayoutDeviceIntRect rect(info.rcMonitor.left, info.rcMonitor.top, + info.rcMonitor.right - info.rcMonitor.left, + info.rcMonitor.bottom - info.rcMonitor.top); + LayoutDeviceIntRect availRect(info.rcWork.left, info.rcWork.top, + info.rcWork.right - info.rcWork.left, + info.rcWork.bottom - info.rcWork.top); + + HDC hDC = CreateDC(nullptr, info.szDevice, nullptr, nullptr); + if (!hDC) { + MOZ_LOG(sScreenLog, LogLevel::Error, ("CollectMonitors CreateDC failed")); + return TRUE; + } + uint32_t pixelDepth = ::GetDeviceCaps(hDC, BITSPIXEL); + DeleteDC(hDC); + if (pixelDepth == 32) { + // If a device uses 32 bits per pixel, it's still only using 8 bits + // per color component, which is what our callers want to know. + // (Some devices report 32 and some devices report 24.) + pixelDepth = 24; + } + + float dpi = WinUtils::MonitorDPI(aMon); + MOZ_LOG(sScreenLog, LogLevel::Debug, + ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.X(), + rect.Y(), rect.Width(), rect.Height(), availRect.X(), availRect.Y(), + availRect.Width(), availRect.Height(), pixelDepth, + contentsScaleFactor.scale, defaultCssScaleFactor.scale, dpi)); + auto screen = new Screen(rect, availRect, pixelDepth, pixelDepth, + contentsScaleFactor, defaultCssScaleFactor, dpi); + if (info.dwFlags & MONITORINFOF_PRIMARY) { + // The primary monitor must be the first element of the screen list. + screens->InsertElementAt(0, std::move(screen)); + } else { + screens->AppendElement(std::move(screen)); + } + return TRUE; +} + +void ScreenHelperWin::RefreshScreens() { + MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens")); + + AutoTArray<RefPtr<Screen>, 4> screens; + BOOL result = ::EnumDisplayMonitors( + nullptr, nullptr, (MONITORENUMPROC)CollectMonitors, (LPARAM)&screens); + if (!result) { + NS_WARNING("Unable to EnumDisplayMonitors"); + } + ScreenManager& screenManager = ScreenManager::GetSingleton(); + screenManager.Refresh(std::move(screens)); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/ScreenHelperWin.h b/widget/windows/ScreenHelperWin.h new file mode 100644 index 0000000000..275014232a --- /dev/null +++ b/widget/windows/ScreenHelperWin.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_windows_ScreenHelperWin_h +#define mozilla_widget_windows_ScreenHelperWin_h + +#include "mozilla/widget/ScreenManager.h" + +namespace mozilla { +namespace widget { + +class ScreenHelperWin final : public ScreenManager::Helper { + public: + ScreenHelperWin(){}; + ~ScreenHelperWin() override {} + + static void RefreshScreens(); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_windows_ScreenHelperWin_h diff --git a/widget/windows/ScrollbarUtil.cpp b/widget/windows/ScrollbarUtil.cpp new file mode 100644 index 0000000000..4c00dd1963 --- /dev/null +++ b/widget/windows/ScrollbarUtil.cpp @@ -0,0 +1,217 @@ +/* -*- 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 "ScrollbarUtil.h" + +#include "mozilla/RelativeLuminanceUtils.h" +#include "mozilla/StaticPrefs_widget.h" +#include "nsNativeTheme.h" + +/*static*/ +bool ScrollbarUtil::IsScrollbarWidthThin(ComputedStyle* aStyle) { + auto scrollbarWidth = aStyle->StyleUIReset()->mScrollbarWidth; + return scrollbarWidth == StyleScrollbarWidth::Thin; +} + +/*static*/ +bool ScrollbarUtil::IsScrollbarWidthThin(nsIFrame* aFrame) { + ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame); + return IsScrollbarWidthThin(style); +} + +/*static*/ +ComputedStyle* ScrollbarUtil::GetCustomScrollbarStyle(nsIFrame* aFrame, + bool* aDarkScrollbar) { + ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame); + if (style->StyleUI()->HasCustomScrollbars()) { + return style; + } + bool useDarkScrollbar = !StaticPrefs::widget_disable_dark_scrollbar() && + nsNativeTheme::IsDarkBackground(aFrame); + if (useDarkScrollbar || IsScrollbarWidthThin(style)) { + if (aDarkScrollbar) { + *aDarkScrollbar = useDarkScrollbar; + } + return style; + } + return nullptr; +} + +/*static*/ +nscolor ScrollbarUtil::GetScrollbarButtonColor(nscolor aTrackColor, + EventStates aStates) { + // See numbers in GetScrollbarArrowColor. + // This function is written based on ratios between values listed there. + + bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE); + bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER); + if (!isActive && !isHover) { + return aTrackColor; + } + float luminance = RelativeLuminanceUtils::Compute(aTrackColor); + if (isActive) { + if (luminance >= 0.18f) { + luminance *= 0.134f; + } else { + luminance /= 0.134f; + luminance = std::min(luminance, 1.0f); + } + } else { + if (luminance >= 0.18f) { + luminance *= 0.805f; + } else { + luminance /= 0.805f; + } + } + return RelativeLuminanceUtils::Adjust(aTrackColor, luminance); +} + +/*static*/ +nscolor ScrollbarUtil::GetScrollbarArrowColor(nscolor aButtonColor) { + // In Windows 10 scrollbar, there are several gray colors used: + // + // State | Background (lum) | Arrow | Contrast + // -------+------------------+---------+--------- + // Normal | Gray 240 (87.1%) | Gray 96 | 5.5 + // Hover | Gray 218 (70.1%) | Black | 15.0 + // Active | Gray 96 (11.7%) | White | 6.3 + // + // Contrast value is computed based on the definition in + // https://www.w3.org/TR/WCAG20/#contrast-ratiodef + // + // This function is written based on these values. + + float luminance = RelativeLuminanceUtils::Compute(aButtonColor); + // Color with luminance larger than 0.72 has contrast ratio over 4.6 + // to color with luminance of gray 96, so this value is chosen for + // this range. It is the luminance of gray 221. + if (luminance >= 0.72) { + // ComputeRelativeLuminanceFromComponents(96). That function cannot + // be constexpr because of std::pow. + const float GRAY96_LUMINANCE = 0.117f; + return RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE); + } + // The contrast ratio of a color to black equals that to white when its + // luminance is around 0.18, with a contrast ratio ~4.6 to both sides, + // thus the value below. It's the lumanince of gray 118. + if (luminance >= 0.18) { + return NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor)); + } + return NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor)); +} + +/*static*/ +nscolor ScrollbarUtil::AdjustScrollbarFaceColor(nscolor aFaceColor, + EventStates aStates) { + // In Windows 10, scrollbar thumb has the following colors: + // + // State | Color | Luminance + // -------+----------+---------- + // Normal | Gray 205 | 61.0% + // Hover | Gray 166 | 38.1% + // Active | Gray 96 | 11.7% + // + // This function is written based on the ratios between the values. + + bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE); + bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER); + if (!isActive && !isHover) { + return aFaceColor; + } + float luminance = RelativeLuminanceUtils::Compute(aFaceColor); + if (isActive) { + if (luminance >= 0.18f) { + luminance *= 0.192f; + } else { + luminance /= 0.192f; + } + } else { + if (luminance >= 0.18f) { + luminance *= 0.625f; + } else { + luminance /= 0.625f; + } + } + return RelativeLuminanceUtils::Adjust(aFaceColor, luminance); +} + +/*static*/ +nscolor ScrollbarUtil::GetScrollbarTrackColor(nsIFrame* aFrame) { + bool darkScrollbar = false; + ComputedStyle* style = GetCustomScrollbarStyle(aFrame, &darkScrollbar); + if (style) { + const nsStyleUI* ui = style->StyleUI(); + auto* customColors = ui->mScrollbarColor.IsAuto() + ? nullptr + : &ui->mScrollbarColor.AsColors(); + if (customColors) { + return customColors->track.CalcColor(*style); + } + } + return darkScrollbar ? NS_RGBA(20, 20, 25, 77) : NS_RGB(240, 240, 240); +} + +/*static*/ +nscolor ScrollbarUtil::GetScrollbarThumbColor(nsIFrame* aFrame, + EventStates aEventStates) { + bool darkScrollbar = false; + ComputedStyle* style = GetCustomScrollbarStyle(aFrame, &darkScrollbar); + if (style) { + const nsStyleUI* ui = style->StyleUI(); + auto* customColors = ui->mScrollbarColor.IsAuto() + ? nullptr + : &ui->mScrollbarColor.AsColors(); + if (customColors) { + nscolor faceColor = customColors->thumb.CalcColor(*style); + return AdjustScrollbarFaceColor(faceColor, aEventStates); + } + } + nscolor faceColor = + darkScrollbar ? NS_RGBA(249, 249, 250, 102) : NS_RGB(205, 205, 205); + return AdjustScrollbarFaceColor(faceColor, aEventStates); +} + +/*static*/ +Maybe<nsITheme::Transparency> ScrollbarUtil::GetScrollbarPartTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) { + if (nsNativeTheme::IsWidgetScrollbarPart(aAppearance)) { + if (ComputedStyle* style = ScrollbarUtil::GetCustomScrollbarStyle(aFrame)) { + auto* ui = style->StyleUI(); + if (ui->mScrollbarColor.IsAuto() || + ui->mScrollbarColor.AsColors().track.MaybeTransparent()) { + return Some(nsITheme::eTransparent); + } + // These widgets may be thinner than the track, so we need to return + // transparent for them to make the track visible. + switch (aAppearance) { + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + return Some(nsITheme::eTransparent); + default: + break; + } + } + } + + switch (aAppearance) { + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: + case StyleAppearance::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 Some(nsITheme::eOpaque); + default: + break; + } + + return Nothing(); +} diff --git a/widget/windows/ScrollbarUtil.h b/widget/windows/ScrollbarUtil.h new file mode 100644 index 0000000000..9085653fb2 --- /dev/null +++ b/widget/windows/ScrollbarUtil.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef ScrollbarUtil_h +#define ScrollbarUtil_h + +#include "nsLayoutUtils.h" +#include "nsNativeTheme.h" + +class ScrollbarUtil { + public: + static bool IsScrollbarWidthThin(ComputedStyle* aStyle); + static bool IsScrollbarWidthThin(nsIFrame* aFrame); + + // Returns the style for custom scrollbar if the scrollbar part frame should + // use the custom drawing path, nullptr otherwise. + // + // Optionally the caller can pass a pointer to aDarkScrollbar for whether + // custom scrollbar may be drawn due to dark background. + static ComputedStyle* GetCustomScrollbarStyle(nsIFrame* aFrame, + bool* aDarkScrollbar = nullptr); + + static nscolor GetScrollbarButtonColor(nscolor aTrackColor, + EventStates aStates); + static nscolor GetScrollbarArrowColor(nscolor aButtonColor); + static nscolor AdjustScrollbarFaceColor(nscolor aFaceColor, + EventStates aStates); + static nscolor GetScrollbarTrackColor(nsIFrame* aFrame); + static nscolor GetScrollbarThumbColor(nsIFrame* aFrame, + EventStates aEventStates); + static Maybe<nsITheme::Transparency> GetScrollbarPartTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance); + + protected: + ScrollbarUtil() = default; + virtual ~ScrollbarUtil() = default; +}; + +#endif diff --git a/widget/windows/ShellHeaderOnlyUtils.h b/widget/windows/ShellHeaderOnlyUtils.h new file mode 100644 index 0000000000..11849c99cc --- /dev/null +++ b/widget/windows/ShellHeaderOnlyUtils.h @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ShellHeaderOnlyUtils_h +#define mozilla_ShellHeaderOnlyUtils_h + +#include "mozilla/WinHeaderOnlyUtils.h" + +#include <objbase.h> + +#include <exdisp.h> +#include <shldisp.h> +#include <shlobj.h> +#include <shlwapi.h> +#include <shobjidl.h> +#include <shtypes.h> +// NB: include this after shldisp.h so its macros do not conflict with COM +// interfaces defined by shldisp.h +#include <shellapi.h> +#include <type_traits> + +#include <comdef.h> +#include <comutil.h> + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +/** + * Ask the current user's Desktop to ShellExecute on our behalf, thus causing + * the resulting launched process to inherit its security priviliges from + * Explorer instead of our process. + * + * This is useful in two scenarios, in particular: + * * We are running as an elevated user and we want to start something as the + * "normal" user; + * * We are starting a process that is incompatible with our process's + * process mitigation policies. By delegating to Explorer, the child process + * will not be affected by our process mitigations. + * + * Since this communication happens over DCOM, Explorer's COM DACL governs + * whether or not we can execute against it, thus avoiding privilege escalation. + */ +inline LauncherVoidResult ShellExecuteByExplorer(const _bstr_t& aPath, + const _variant_t& aArgs, + const _variant_t& aVerb, + const _variant_t& aWorkingDir, + const _variant_t& aShowCmd) { + // NB: Explorer may be a local server, not an inproc server + RefPtr<IShellWindows> shellWindows; + HRESULT hr = ::CoCreateInstance( + CLSID_ShellWindows, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, + IID_IShellWindows, getter_AddRefs(shellWindows)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // 1. Find the shell view for the desktop. + _variant_t loc(int(CSIDL_DESKTOP)); + _variant_t empty; + long hwnd; + RefPtr<IDispatch> dispDesktop; + hr = shellWindows->FindWindowSW(&loc, &empty, SWC_DESKTOP, &hwnd, + SWFO_NEEDDISPATCH, + getter_AddRefs(dispDesktop)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + if (hr == S_FALSE) { + // The call succeeded but the window was not found. + return LAUNCHER_ERROR_FROM_WIN32(ERROR_NOT_FOUND); + } + + RefPtr<IServiceProvider> servProv; + hr = dispDesktop->QueryInterface(IID_IServiceProvider, + getter_AddRefs(servProv)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + RefPtr<IShellBrowser> browser; + hr = servProv->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, + getter_AddRefs(browser)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + RefPtr<IShellView> activeShellView; + hr = browser->QueryActiveShellView(getter_AddRefs(activeShellView)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // 2. Get the automation object for the desktop. + RefPtr<IDispatch> dispView; + hr = activeShellView->GetItemObject(SVGIO_BACKGROUND, IID_IDispatch, + getter_AddRefs(dispView)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + RefPtr<IShellFolderViewDual> folderView; + hr = dispView->QueryInterface(IID_IShellFolderViewDual, + getter_AddRefs(folderView)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // 3. Get the interface to IShellDispatch2 + RefPtr<IDispatch> dispShell; + hr = folderView->get_Application(getter_AddRefs(dispShell)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + RefPtr<IShellDispatch2> shellDisp; + hr = + dispShell->QueryInterface(IID_IShellDispatch2, getter_AddRefs(shellDisp)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // Passing the foreground privilege so that the shell can launch an + // application in the foreground. This fails with E_ACCESSDENIED if the + // current window is shown in the background. We keep a soft assert for + // the other failures to investigate how it happened. + hr = ::CoAllowSetForegroundWindow(shellDisp, nullptr); + MOZ_ASSERT(SUCCEEDED(hr) || hr == E_ACCESSDENIED); + + // shellapi.h macros interfere with the correct naming of the method being + // called on IShellDispatch2. Temporarily remove that definition. +#if defined(ShellExecute) +# define MOZ_REDEFINE_SHELLEXECUTE +# undef ShellExecute +#endif // defined(ShellExecute) + + // 4. Now call IShellDispatch2::ShellExecute to ask Explorer to execute. + hr = shellDisp->ShellExecute(aPath, aArgs, aWorkingDir, aVerb, aShowCmd); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // Restore the macro that was removed prior to IShellDispatch2::ShellExecute +#if defined(MOZ_REDEFINE_SHELLEXECUTE) +# if defined(UNICODE) +# define ShellExecute ShellExecuteW +# else +# define ShellExecute ShellExecuteA +# endif +# undef MOZ_REDEFINE_SHELLEXECUTE +#endif // defined(MOZ_REDEFINE_SHELLEXECUTE) + + return Ok(); +} + +using UniqueAbsolutePidl = + UniquePtr<std::remove_pointer_t<PIDLIST_ABSOLUTE>, CoTaskMemFreeDeleter>; + +inline LauncherResult<UniqueAbsolutePidl> ShellParseDisplayName( + const wchar_t* aPath) { + PIDLIST_ABSOLUTE rawAbsPidl = nullptr; + SFGAOF sfgao; + HRESULT hr = ::SHParseDisplayName(aPath, nullptr, &rawAbsPidl, 0, &sfgao); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + return UniqueAbsolutePidl(rawAbsPidl); +} + +} // namespace mozilla + +#endif // mozilla_ShellHeaderOnlyUtils_h diff --git a/widget/windows/SystemStatusBar.cpp b/widget/windows/SystemStatusBar.cpp new file mode 100644 index 0000000000..8dc4ffed4b --- /dev/null +++ b/widget/windows/SystemStatusBar.cpp @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <strsafe.h> +#include "SystemStatusBar.h" + +#include "mozilla/dom/Element.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/LinkedList.h" +#include "mozilla/StaticPtr.h" +#include "nsComputedDOMStyle.h" +#include "nsIContentPolicy.h" +#include "nsMenuFrame.h" +#include "nsMenuPopupFrame.h" +#include "nsXULPopupManager.h" +#include "IconLoaderHelperWin.h" +#include "nsIDocShell.h" +#include "nsDocShell.h" + +namespace mozilla::widget { + +using mozilla::LinkedListElement; +using mozilla::dom::Element; +using mozilla::widget::IconLoaderListenerWin; + +class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>, + public IconLoaderListenerWin { + public: + explicit StatusBarEntry(Element* aMenu); + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry) + nsresult Init(); + LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); + const Element* GetMenu() { return mMenu; }; + + nsresult OnComplete(); + + private: + ~StatusBarEntry(); + RefPtr<mozilla::widget::IconLoader> mIconLoader; + RefPtr<mozilla::widget::IconLoaderHelperWin> mIconLoaderHelper; + RefPtr<Element> mMenu; + NOTIFYICONDATAW mIconData; + boolean mInitted; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry) + tmp->OnComplete(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoaderHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry) +NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry) + +StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) { + mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA), + /* hWnd */ 0, + /* uID */ 2, + /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP, + /* uCallbackMessage */ WM_USER, + /* hIcon */ 0, + /* szTip */ L"", // This is updated in Init() + /* dwState */ 0, + /* dwStateMask */ 0, + /* szInfo */ L"", + /* uVersion */ {NOTIFYICON_VERSION_4}, + /* szInfoTitle */ L"", + /* dwInfoFlags */ 0}; + MOZ_ASSERT(mMenu); +} + +StatusBarEntry::~StatusBarEntry() { + if (!mInitted) { + return; + } + ::Shell_NotifyIconW(NIM_DELETE, &mIconData); + VERIFY(::DestroyWindow(mIconData.hWnd)); +} + +nsresult StatusBarEntry::Init() { + MOZ_ASSERT(NS_IsMainThread()); + + // First, look at the content node's "image" attribute. + nsAutoString imageURIString; + bool hasImageAttr = + mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString); + + nsresult rv; + RefPtr<ComputedStyle> sc; + nsCOMPtr<nsIURI> iconURI; + if (!hasImageAttr) { + // If the content node has no "image" attribute, get the + // "list-style-image" property from CSS. + RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc(); + if (!document) { + return NS_ERROR_FAILURE; + } + + sc = nsComputedDOMStyle::GetComputedStyle(mMenu, nullptr); + if (!sc) { + return NS_ERROR_FAILURE; + } + + iconURI = sc->StyleList()->GetListStyleImageURI(); + } else { + uint64_t dummy = 0; + nsContentPolicyType policyType; + nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal(); + nsContentUtils::GetContentPolicyTypeForUIImageLoading( + mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy); + if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // If this menu item shouldn't have an icon, the string will be empty, + // and NS_NewURI will fail. + rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString); + if (NS_FAILED(rv)) return rv; + } + + mIconLoaderHelper = new IconLoaderHelperWin(this); + nsIntRect rect; + mIconLoader = new IconLoader(mIconLoaderHelper, mMenu, rect); + + if (iconURI) { + rv = mIconLoader->LoadIcon(iconURI); + } + + HWND iconWindow; + NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW( + /* extended style */ 0, + /* className */ L"IconWindowClass", + /* title */ 0, + /* style */ WS_CAPTION, + /* x, y, cx, cy */ 0, 0, 0, 0, + /* parent */ 0, + /* menu */ 0, + /* instance */ 0, + /* create struct */ 0), + NS_ERROR_FAILURE); + ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this); + + mIconData.hWnd = iconWindow; + mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage(); + + nsAutoString labelAttr; + mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::label, labelAttr); + const nsString& label = PromiseFlatString(labelAttr); + + size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]); + wchar_t* tooltip = &(mIconData.szTip[0]); + ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length()); + + ::Shell_NotifyIconW(NIM_ADD, &mIconData); + ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData); + + mInitted = true; + return NS_OK; +} + +nsresult StatusBarEntry::OnComplete() { + RefPtr<StatusBarEntry> kungFuDeathGrip = this; + mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage(); + + ::Shell_NotifyIconW(NIM_MODIFY, &mIconData); + + // To simplify things, we won't react to CSS changes to update the icon + // with this implementation. We can get rid of the IconLoader and Helper + // at this point, which will also free the allocated HICON. + mIconLoaderHelper->Destroy(); + mIconLoader->ReleaseJSObjects(); + mIconLoader->Destroy(); + mIconLoader = nullptr; + mIconLoaderHelper = nullptr; + return NS_OK; +} + +LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { + if (msg == WM_USER && + (LOWORD(lp) == WM_LBUTTONUP || LOWORD(lp) == WM_RBUTTONUP)) { + nsMenuFrame* menu = do_QueryFrame(mMenu->GetPrimaryFrame()); + if (!menu) { + return TRUE; + } + + nsMenuPopupFrame* popupFrame = menu->GetPopup(); + if (!popupFrame) { + return TRUE; + } + + nsIWidget* widget = popupFrame->GetNearestWidget(); + if (!widget) { + return TRUE; + } + + HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); + if (!win) { + return TRUE; + } + + nsCOMPtr<nsIDocShell> docShell = popupFrame->PresContext()->GetDocShell(); + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(docShell); + if (!baseWin) { + return TRUE; + } + + double scale = 1.0; + baseWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + int32_t x = NSToIntRound(GET_X_LPARAM(wp) / scale); + int32_t y = NSToIntRound(GET_Y_LPARAM(wp) / scale); + + // The menu that is being opened is a Gecko <xul:menu>, and the popup code + // that manages it expects that the window that the <xul:menu> belongs to + // will be in the foreground when it opens. If we don't do this, then if the + // icon is clicked when the window is _not_ in the foreground, then the + // opened menu will not be keyboard focusable, nor will it close on its own + // if the user clicks away from the menu (at least, not until the user + // focuses any window in the parent process). + ::SetForegroundWindow(win); + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + pm->ShowPopupAtScreen(popupFrame->GetContent(), x, y, false, nullptr); + } + + return DefWindowProc(hWnd, msg, wp, lp); +} + +NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar) + +static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { + StatusBarEntry* entry = + (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (entry) { + return entry->OnMessage(hWnd, msg, wp, lp); + } + return TRUE; +} + +static StaticRefPtr<SystemStatusBar> sSingleton; + +SystemStatusBar& SystemStatusBar::GetSingleton() { + if (!sSingleton) { + sSingleton = new SystemStatusBar(); + ClearOnShutdown(&sSingleton); + } + return *sSingleton; +} + +already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() { + RefPtr<SystemStatusBar> sm = &GetSingleton(); + return sm.forget(); +} + +nsresult SystemStatusBar::Init() { + WNDCLASS classStruct = {/* style */ 0, + /* lpfnWndProc */ &WindowProc, + /* cbClsExtra */ 0, + /* cbWndExtra */ 0, + /* hInstance */ 0, + /* hIcon */ 0, + /* hCursor */ 0, + /* hbrBackground */ 0, + /* lpszMenuName */ 0, + /* lpszClassName */ L"IconWindowClass"}; + NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +SystemStatusBar::AddItem(Element* aElement) { + RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement); + nsresult rv = entry->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mStatusBarEntries.insertBack(entry); + return NS_OK; +} + +NS_IMETHODIMP +SystemStatusBar::RemoveItem(Element* aElement) { + for (StatusBarEntry* entry : mStatusBarEntries) { + if (entry->GetMenu() == aElement) { + entry->removeFrom(mStatusBarEntries); + return NS_OK; + } + } + return NS_ERROR_NOT_AVAILABLE; +} + +} // namespace mozilla::widget diff --git a/widget/windows/SystemStatusBar.h b/widget/windows/SystemStatusBar.h new file mode 100644 index 0000000000..6c90fc0097 --- /dev/null +++ b/widget/windows/SystemStatusBar.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_SystemStatusBar_h +#define widget_windows_SystemStatusBar_h + +#include "nsISystemStatusBar.h" + +namespace mozilla::widget { +class StatusBarEntry; + +class SystemStatusBar final : public nsISystemStatusBar { + public: + explicit SystemStatusBar() = default; + NS_DECL_ISUPPORTS + NS_DECL_NSISYSTEMSTATUSBAR + + static SystemStatusBar& GetSingleton(); + static already_AddRefed<SystemStatusBar> GetAddRefedSingleton(); + + nsresult Init(); + + private: + ~SystemStatusBar() = default; + mozilla::LinkedList<RefPtr<StatusBarEntry>> mStatusBarEntries; +}; + +} // namespace mozilla::widget + +#endif // widget_windows_SystemStatusBar_h diff --git a/widget/windows/TSFTextStore.cpp b/widget/windows/TSFTextStore.cpp new file mode 100644 index 0000000000..e67975779f --- /dev/null +++ b/widget/windows/TSFTextStore.cpp @@ -0,0 +1,7398 @@ +/* -*- 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 "IMMHandler.h" +#include "KeyboardLayout.h" +#include "WinIMEHandler.h" +#include "WinUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_intl.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/ToString.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindow.h" +#include "nsPrintfCString.h" + +// Workaround for mingw32 +#ifndef TS_SD_INPUTPANEMANUALDISPLAYENABLE +# define TS_SD_INPUTPANEMANUALDISPLAYENABLE 0x40 +#endif + +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"); + +enum class TextInputProcessorID { + // Internal use only. This won't be returned by TSFStaticSink::ActiveTIP(). + eNotComputed, + + // Not a TIP. E.g., simple keyboard layout or IMM-IME. + eNone, + + // Used for other TIPs, i.e., any TIPs which we don't support specifically. + eUnknown, + + // TIP for Japanese. + eMicrosoftIMEForJapanese, + eMicrosoftOfficeIME2010ForJapanese, + eGoogleJapaneseInput, + eATOK2011, + eATOK2012, + eATOK2013, + eATOK2014, + eATOK2015, + eATOK2016, + eATOKUnknown, + eJapanist10, + + // TIP for Traditional Chinese. + eMicrosoftBopomofo, + eMicrosoftChangJie, + eMicrosoftPhonetic, + eMicrosoftQuick, + eMicrosoftNewChangJie, + eMicrosoftNewPhonetic, + eMicrosoftNewQuick, + eFreeChangJie, + + // TIP for Simplified Chinese. + eMicrosoftPinyin, + eMicrosoftPinyinNewExperienceInputStyle, + eMicrosoftWubi, + + // TIP for Korean. + eMicrosoftIMEForKorean, + eMicrosoftOldHangul, + + // Keyman Desktop, which can install various language keyboards. + eKeymanDesktop, +}; + +static const char* GetBoolName(bool aBool) { return aBool ? "true" : "false"; } + +static void HandleSeparator(nsCString& aDesc) { + if (!aDesc.IsEmpty()) { + aDesc.AppendLiteral(" | "); + } +} + +static const nsCString GetFindFlagName(DWORD aFindFlag) { + nsCString description; + if (!aFindFlag) { + description.AppendLiteral("no flags (0)"); + return description; + } + if (aFindFlag & TS_ATTR_FIND_BACKWARDS) { + description.AppendLiteral("TS_ATTR_FIND_BACKWARDS"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_OFFSET) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_OFFSET"); + } + if (aFindFlag & TS_ATTR_FIND_UPDATESTART) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_UPDATESTART"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_VALUE) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_VALUE"); + } + if (aFindFlag & TS_ATTR_FIND_WANT_END) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_WANT_END"); + } + if (aFindFlag & TS_ATTR_FIND_HIDDEN) { + HandleSeparator(description); + description.AppendLiteral("TS_ATTR_FIND_HIDDEN"); + } + if (description.IsEmpty()) { + description.AppendLiteral("Unknown ("); + description.AppendInt(static_cast<uint32_t>(aFindFlag)); + description.Append(')'); + } + return description; +} + +class GetACPFromPointFlagName : public nsAutoCString { + public: + explicit GetACPFromPointFlagName(DWORD aFlags) { + if (!aFlags) { + AppendLiteral("no flags (0)"); + return; + } + if (aFlags & GXFPF_ROUND_NEAREST) { + AppendLiteral("GXFPF_ROUND_NEAREST"); + aFlags &= ~GXFPF_ROUND_NEAREST; + } + if (aFlags & GXFPF_NEAREST) { + HandleSeparator(*this); + AppendLiteral("GXFPF_NEAREST"); + aFlags &= ~GXFPF_NEAREST; + } + if (aFlags) { + HandleSeparator(*this); + AppendLiteral("Unknown("); + AppendInt(static_cast<uint32_t>(aFlags)); + Append(')'); + } + } + virtual ~GetACPFromPointFlagName() {} +}; + +static const char* GetFocusChangeName( + InputContextAction::FocusChange aFocusChange) { + switch (aFocusChange) { + case InputContextAction::FOCUS_NOT_CHANGED: + return "FOCUS_NOT_CHANGED"; + case InputContextAction::GOT_FOCUS: + return "GOT_FOCUS"; + case InputContextAction::LOST_FOCUS: + return "LOST_FOCUS"; + case InputContextAction::MENU_GOT_PSEUDO_FOCUS: + return "MENU_GOT_PSEUDO_FOCUS"; + case InputContextAction::MENU_LOST_PSEUDO_FOCUS: + return "MENU_LOST_PSEUDO_FOCUS"; + case InputContextAction::WIDGET_CREATED: + return "WIDGET_CREATED"; + default: + return "Unknown"; + } +} + +static nsCString GetCLSIDNameStr(REFCLSID aCLSID) { + LPOLESTR str = nullptr; + HRESULT hr = ::StringFromCLSID(aCLSID, &str); + if (FAILED(hr) || !str || !str[0]) { + return ""_ns; + } + + nsCString result; + result = NS_ConvertUTF16toUTF8(str); + ::CoTaskMemFree(str); + return result; +} + +static nsCString GetGUIDNameStr(REFGUID aGUID) { + OLECHAR str[40]; + int len = ::StringFromGUID2(aGUID, str, ArrayLength(str)); + if (!len || !str[0]) { + return ""_ns; + } + + return NS_ConvertUTF16toUTF8(str); +} + +static nsCString GetGUIDNameStrWithTable(REFGUID aGUID) { +#define RETURN_GUID_NAME(aNamedGUID) \ + if (IsEqualGUID(aGUID, aNamedGUID)) { \ + return nsLiteralCString(#aNamedGUID); \ + } + + RETURN_GUID_NAME(GUID_PROP_INPUTSCOPE) + RETURN_GUID_NAME(TSATTRID_OTHERS) + RETURN_GUID_NAME(TSATTRID_Font) + RETURN_GUID_NAME(TSATTRID_Font_FaceName) + RETURN_GUID_NAME(TSATTRID_Font_SizePts) + RETURN_GUID_NAME(TSATTRID_Font_Style) + RETURN_GUID_NAME(TSATTRID_Font_Style_Bold) + RETURN_GUID_NAME(TSATTRID_Font_Style_Italic) + RETURN_GUID_NAME(TSATTRID_Font_Style_SmallCaps) + RETURN_GUID_NAME(TSATTRID_Font_Style_Capitalize) + RETURN_GUID_NAME(TSATTRID_Font_Style_Uppercase) + RETURN_GUID_NAME(TSATTRID_Font_Style_Lowercase) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_LasVegasLights) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_BlinkingBackground) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_SparkleText) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingBlackAnts) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_MarchingRedAnts) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_Shimmer) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeDown) + RETURN_GUID_NAME(TSATTRID_Font_Style_Animation_WipeRight) + RETURN_GUID_NAME(TSATTRID_Font_Style_Emboss) + RETURN_GUID_NAME(TSATTRID_Font_Style_Engrave) + RETURN_GUID_NAME(TSATTRID_Font_Style_Hidden) + RETURN_GUID_NAME(TSATTRID_Font_Style_Kerning) + RETURN_GUID_NAME(TSATTRID_Font_Style_Outlined) + RETURN_GUID_NAME(TSATTRID_Font_Style_Position) + RETURN_GUID_NAME(TSATTRID_Font_Style_Protected) + RETURN_GUID_NAME(TSATTRID_Font_Style_Shadow) + RETURN_GUID_NAME(TSATTRID_Font_Style_Spacing) + RETURN_GUID_NAME(TSATTRID_Font_Style_Weight) + RETURN_GUID_NAME(TSATTRID_Font_Style_Height) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Underline_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Strikethrough_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Single) + RETURN_GUID_NAME(TSATTRID_Font_Style_Overline_Double) + RETURN_GUID_NAME(TSATTRID_Font_Style_Blink) + RETURN_GUID_NAME(TSATTRID_Font_Style_Subscript) + RETURN_GUID_NAME(TSATTRID_Font_Style_Superscript) + RETURN_GUID_NAME(TSATTRID_Font_Style_Color) + RETURN_GUID_NAME(TSATTRID_Font_Style_BackgroundColor) + RETURN_GUID_NAME(TSATTRID_Text) + RETURN_GUID_NAME(TSATTRID_Text_VerticalWriting) + RETURN_GUID_NAME(TSATTRID_Text_RightToLeft) + RETURN_GUID_NAME(TSATTRID_Text_Orientation) + RETURN_GUID_NAME(TSATTRID_Text_Language) + RETURN_GUID_NAME(TSATTRID_Text_ReadOnly) + RETURN_GUID_NAME(TSATTRID_Text_EmbeddedObject) + RETURN_GUID_NAME(TSATTRID_Text_Alignment) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Left) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Right) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Center) + RETURN_GUID_NAME(TSATTRID_Text_Alignment_Justify) + RETURN_GUID_NAME(TSATTRID_Text_Link) + RETURN_GUID_NAME(TSATTRID_Text_Hyphenation) + RETURN_GUID_NAME(TSATTRID_Text_Para) + RETURN_GUID_NAME(TSATTRID_Text_Para_FirstLineIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_LeftIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_RightIndent) + RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceAfter) + RETURN_GUID_NAME(TSATTRID_Text_Para_SpaceBefore) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Single) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_OnePtFive) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Double) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_AtLeast) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Exactly) + RETURN_GUID_NAME(TSATTRID_Text_Para_LineSpacing_Multiple) + RETURN_GUID_NAME(TSATTRID_List) + RETURN_GUID_NAME(TSATTRID_List_LevelIndel) + RETURN_GUID_NAME(TSATTRID_List_Type) + RETURN_GUID_NAME(TSATTRID_List_Type_Bullet) + RETURN_GUID_NAME(TSATTRID_List_Type_Arabic) + RETURN_GUID_NAME(TSATTRID_List_Type_LowerLetter) + RETURN_GUID_NAME(TSATTRID_List_Type_UpperLetter) + RETURN_GUID_NAME(TSATTRID_List_Type_LowerRoman) + RETURN_GUID_NAME(TSATTRID_List_Type_UpperRoman) + RETURN_GUID_NAME(TSATTRID_App) + RETURN_GUID_NAME(TSATTRID_App_IncorrectSpelling) + RETURN_GUID_NAME(TSATTRID_App_IncorrectGrammar) + +#undef RETURN_GUID_NAME + + return GetGUIDNameStr(aGUID); +} + +static nsCString GetRIIDNameStr(REFIID aRIID) { + LPOLESTR str = nullptr; + HRESULT hr = ::StringFromIID(aRIID, &str); + if (FAILED(hr) || !str || !str[0]) { + return ""_ns; + } + + nsAutoString key(L"Interface\\"); + key += str; + + nsCString result; + wchar_t buf[256]; + if (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) { + nsCString description; + if (aSinkMask & TS_AS_TEXT_CHANGE) { + description.AppendLiteral("TS_AS_TEXT_CHANGE"); + } + if (aSinkMask & TS_AS_SEL_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_SEL_CHANGE"); + } + if (aSinkMask & TS_AS_LAYOUT_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_LAYOUT_CHANGE"); + } + if (aSinkMask & TS_AS_ATTR_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_ATTR_CHANGE"); + } + if (aSinkMask & TS_AS_STATUS_CHANGE) { + HandleSeparator(description); + description.AppendLiteral("TS_AS_STATUS_CHANGE"); + } + if (description.IsEmpty()) { + description.AppendLiteral("not-specified"); + } + return description; +} + +static const nsCString GetLockFlagNameStr(DWORD aLockFlags) { + nsCString description; + if ((aLockFlags & TS_LF_READWRITE) == TS_LF_READWRITE) { + description.AppendLiteral("TS_LF_READWRITE"); + } else if (aLockFlags & TS_LF_READ) { + description.AppendLiteral("TS_LF_READ"); + } + if (aLockFlags & TS_LF_SYNC) { + if (!description.IsEmpty()) { + description.AppendLiteral(" | "); + } + description.AppendLiteral("TS_LF_SYNC"); + } + if (description.IsEmpty()) { + description.AppendLiteral("not-specified"); + } + return description; +} + +static const char* GetTextRunTypeName(TsRunType aRunType) { + switch (aRunType) { + case TS_RT_PLAIN: + return "TS_RT_PLAIN"; + case TS_RT_HIDDEN: + return "TS_RT_HIDDEN"; + case TS_RT_OPAQUE: + return "TS_RT_OPAQUE"; + default: + return "Unknown"; + } +} + +static nsCString GetColorName(const TF_DA_COLOR& aColor) { + switch (aColor.type) { + case TF_CT_NONE: + return "TF_CT_NONE"_ns; + case TF_CT_SYSCOLOR: + return nsPrintfCString("TF_CT_SYSCOLOR, nIndex:0x%08X", + static_cast<int32_t>(aColor.nIndex)); + case TF_CT_COLORREF: + return nsPrintfCString("TF_CT_COLORREF, cr:0x%08X", + static_cast<int32_t>(aColor.cr)); + break; + default: + return nsPrintfCString("Unknown(%08X)", + static_cast<int32_t>(aColor.type)); + } +} + +static nsCString GetLineStyleName(TF_DA_LINESTYLE aLineStyle) { + switch (aLineStyle) { + case TF_LS_NONE: + return "TF_LS_NONE"_ns; + case TF_LS_SOLID: + return "TF_LS_SOLID"_ns; + case TF_LS_DOT: + return "TF_LS_DOT"_ns; + case TF_LS_DASH: + return "TF_LS_DASH"_ns; + case TF_LS_SQUIGGLE: + return "TF_LS_SQUIGGLE"_ns; + default: { + return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aLineStyle)); + } + } +} + +static nsCString GetClauseAttrName(TF_DA_ATTR_INFO aAttr) { + switch (aAttr) { + case TF_ATTR_INPUT: + return "TF_ATTR_INPUT"_ns; + case TF_ATTR_TARGET_CONVERTED: + return "TF_ATTR_TARGET_CONVERTED"_ns; + case TF_ATTR_CONVERTED: + return "TF_ATTR_CONVERTED"_ns; + case TF_ATTR_TARGET_NOTCONVERTED: + return "TF_ATTR_TARGET_NOTCONVERTED"_ns; + case TF_ATTR_INPUT_ERROR: + return "TF_ATTR_INPUT_ERROR"_ns; + case TF_ATTR_FIXEDCONVERTED: + return "TF_ATTR_FIXEDCONVERTED"_ns; + case TF_ATTR_OTHER: + return "TF_ATTR_OTHER"_ns; + default: { + return nsPrintfCString("Unknown(%08X)", static_cast<int32_t>(aAttr)); + } + } +} + +static nsCString GetDisplayAttrStr(const TF_DISPLAYATTRIBUTE& aDispAttr) { + nsCString str; + str = "crText:{ "; + str += GetColorName(aDispAttr.crText); + str += " }, crBk:{ "; + str += GetColorName(aDispAttr.crBk); + str += " }, lsStyle: "; + str += GetLineStyleName(aDispAttr.lsStyle); + str += ", fBoldLine: "; + str += GetBoolName(aDispAttr.fBoldLine); + str += ", crLine:{ "; + str += GetColorName(aDispAttr.crLine); + str += " }, bAttr: "; + str += GetClauseAttrName(aDispAttr.bAttr); + return str; +} + +static const char* GetMouseButtonName(int16_t aButton) { + switch (aButton) { + case MouseButton::ePrimary: + return "LeftButton"; + case MouseButton::eMiddle: + return "MiddleButton"; + case MouseButton::eSecondary: + return "RightButton"; + default: + return "UnknownButton"; + } +} + +#define ADD_SEPARATOR_IF_NECESSARY(aStr) \ + if (!aStr.IsEmpty()) { \ + aStr.AppendLiteral(", "); \ + } + +static nsCString GetMouseButtonsName(int16_t aButtons) { + if (!aButtons) { + return "no buttons"_ns; + } + nsCString names; + if (aButtons & MouseButtonsFlag::ePrimaryFlag) { + names = "LeftButton"; + } + if (aButtons & MouseButtonsFlag::eSecondaryFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "RightButton"; + } + if (aButtons & MouseButtonsFlag::eMiddleFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "MiddleButton"; + } + if (aButtons & MouseButtonsFlag::e4thFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "4thButton"; + } + if (aButtons & MouseButtonsFlag::e5thFlag) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += "5thButton"; + } + return names; +} + +static nsCString GetModifiersName(Modifiers aModifiers) { + if (aModifiers == MODIFIER_NONE) { + return "no modifiers"_ns; + } + nsCString names; + if (aModifiers & MODIFIER_ALT) { + names = NS_DOM_KEYNAME_ALT; + } + if (aModifiers & MODIFIER_ALTGRAPH) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_ALTGRAPH; + } + if (aModifiers & MODIFIER_CAPSLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_CAPSLOCK; + } + if (aModifiers & MODIFIER_CONTROL) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_CONTROL; + } + if (aModifiers & MODIFIER_FN) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_FN; + } + if (aModifiers & MODIFIER_FNLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_FNLOCK; + } + if (aModifiers & MODIFIER_META) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_META; + } + if (aModifiers & MODIFIER_NUMLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_NUMLOCK; + } + if (aModifiers & MODIFIER_SCROLLLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SCROLLLOCK; + } + if (aModifiers & MODIFIER_SHIFT) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SHIFT; + } + if (aModifiers & MODIFIER_SYMBOL) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SYMBOL; + } + if (aModifiers & MODIFIER_SYMBOLLOCK) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_SYMBOLLOCK; + } + if (aModifiers & MODIFIER_OS) { + ADD_SEPARATOR_IF_NECESSARY(names); + names += NS_DOM_KEYNAME_OS; + } + return names; +} + +class GetWritingModeName : public nsAutoCString { + public: + explicit GetWritingModeName(const WritingMode& aWritingMode) { + if (!aWritingMode.IsVertical()) { + AssignLiteral("Horizontal"); + return; + } + if (aWritingMode.IsVerticalLR()) { + AssignLiteral("Vertical (LR)"); + return; + } + AssignLiteral("Vertical (RL)"); + } + virtual ~GetWritingModeName() {} +}; + +class GetEscapedUTF8String final : public NS_ConvertUTF16toUTF8 { + public: + explicit GetEscapedUTF8String(const nsAString& aString) + : NS_ConvertUTF16toUTF8(aString) { + Escape(); + } + explicit GetEscapedUTF8String(const char16ptr_t aString) + : NS_ConvertUTF16toUTF8(aString) { + Escape(); + } + GetEscapedUTF8String(const char16ptr_t aString, uint32_t aLength) + : NS_ConvertUTF16toUTF8(aString, aLength) { + Escape(); + } + + private: + void Escape() { + ReplaceSubstring("\r", "\\r"); + ReplaceSubstring("\n", "\\n"); + ReplaceSubstring("\t", "\\t"); + } +}; + +class GetInputScopeString : public nsAutoCString { + public: + explicit GetInputScopeString(const nsTArray<InputScope>& aList) { + for (InputScope inputScope : aList) { + if (!IsEmpty()) { + AppendLiteral(", "); + } + switch (inputScope) { + case IS_DEFAULT: + AppendLiteral("IS_DEFAULT"); + break; + case IS_URL: + AppendLiteral("IS_URL"); + break; + case IS_FILE_FULLFILEPATH: + AppendLiteral("IS_FILE_FULLFILEPATH"); + break; + case IS_FILE_FILENAME: + AppendLiteral("IS_FILE_FILENAME"); + break; + case IS_EMAIL_USERNAME: + AppendLiteral("IS_EMAIL_USERNAME"); + break; + case IS_EMAIL_SMTPEMAILADDRESS: + AppendLiteral("IS_EMAIL_SMTPEMAILADDRESS"); + break; + case IS_LOGINNAME: + AppendLiteral("IS_LOGINNAME"); + break; + case IS_PERSONALNAME_FULLNAME: + AppendLiteral("IS_PERSONALNAME_FULLNAME"); + break; + case IS_PERSONALNAME_PREFIX: + AppendLiteral("IS_PERSONALNAME_PREFIX"); + break; + case IS_PERSONALNAME_GIVENNAME: + AppendLiteral("IS_PERSONALNAME_GIVENNAME"); + break; + case IS_PERSONALNAME_MIDDLENAME: + AppendLiteral("IS_PERSONALNAME_MIDDLENAME"); + break; + case IS_PERSONALNAME_SURNAME: + AppendLiteral("IS_PERSONALNAME_SURNAME"); + break; + case IS_PERSONALNAME_SUFFIX: + AppendLiteral("IS_PERSONALNAME_SUFFIX"); + break; + case IS_ADDRESS_FULLPOSTALADDRESS: + AppendLiteral("IS_ADDRESS_FULLPOSTALADDRESS"); + break; + case IS_ADDRESS_POSTALCODE: + AppendLiteral("IS_ADDRESS_POSTALCODE"); + break; + case IS_ADDRESS_STREET: + AppendLiteral("IS_ADDRESS_STREET"); + break; + case IS_ADDRESS_STATEORPROVINCE: + AppendLiteral("IS_ADDRESS_STATEORPROVINCE"); + break; + case IS_ADDRESS_CITY: + AppendLiteral("IS_ADDRESS_CITY"); + break; + case IS_ADDRESS_COUNTRYNAME: + AppendLiteral("IS_ADDRESS_COUNTRYNAME"); + break; + case IS_ADDRESS_COUNTRYSHORTNAME: + AppendLiteral("IS_ADDRESS_COUNTRYSHORTNAME"); + break; + case IS_CURRENCY_AMOUNTANDSYMBOL: + AppendLiteral("IS_CURRENCY_AMOUNTANDSYMBOL"); + break; + case IS_CURRENCY_AMOUNT: + AppendLiteral("IS_CURRENCY_AMOUNT"); + break; + case IS_DATE_FULLDATE: + AppendLiteral("IS_DATE_FULLDATE"); + break; + case IS_DATE_MONTH: + AppendLiteral("IS_DATE_MONTH"); + break; + case IS_DATE_DAY: + AppendLiteral("IS_DATE_DAY"); + break; + case IS_DATE_YEAR: + AppendLiteral("IS_DATE_YEAR"); + break; + case IS_DATE_MONTHNAME: + AppendLiteral("IS_DATE_MONTHNAME"); + break; + case IS_DATE_DAYNAME: + AppendLiteral("IS_DATE_DAYNAME"); + break; + case IS_DIGITS: + AppendLiteral("IS_DIGITS"); + break; + case IS_NUMBER: + AppendLiteral("IS_NUMBER"); + break; + case IS_ONECHAR: + AppendLiteral("IS_ONECHAR"); + break; + case IS_PASSWORD: + AppendLiteral("IS_PASSWORD"); + break; + case IS_TELEPHONE_FULLTELEPHONENUMBER: + AppendLiteral("IS_TELEPHONE_FULLTELEPHONENUMBER"); + break; + case IS_TELEPHONE_COUNTRYCODE: + AppendLiteral("IS_TELEPHONE_COUNTRYCODE"); + break; + case IS_TELEPHONE_AREACODE: + AppendLiteral("IS_TELEPHONE_AREACODE"); + break; + case IS_TELEPHONE_LOCALNUMBER: + AppendLiteral("IS_TELEPHONE_LOCALNUMBER"); + break; + case IS_TIME_FULLTIME: + AppendLiteral("IS_TIME_FULLTIME"); + break; + case IS_TIME_HOUR: + AppendLiteral("IS_TIME_HOUR"); + break; + case IS_TIME_MINORSEC: + AppendLiteral("IS_TIME_MINORSEC"); + break; + case IS_NUMBER_FULLWIDTH: + AppendLiteral("IS_NUMBER_FULLWIDTH"); + break; + case IS_ALPHANUMERIC_HALFWIDTH: + AppendLiteral("IS_ALPHANUMERIC_HALFWIDTH"); + break; + case IS_ALPHANUMERIC_FULLWIDTH: + AppendLiteral("IS_ALPHANUMERIC_FULLWIDTH"); + break; + case IS_CURRENCY_CHINESE: + AppendLiteral("IS_CURRENCY_CHINESE"); + break; + case IS_BOPOMOFO: + AppendLiteral("IS_BOPOMOFO"); + break; + case IS_HIRAGANA: + AppendLiteral("IS_HIRAGANA"); + break; + case IS_KATAKANA_HALFWIDTH: + AppendLiteral("IS_KATAKANA_HALFWIDTH"); + break; + case IS_KATAKANA_FULLWIDTH: + AppendLiteral("IS_KATAKANA_FULLWIDTH"); + break; + case IS_HANJA: + AppendLiteral("IS_HANJA"); + break; + case IS_PHRASELIST: + AppendLiteral("IS_PHRASELIST"); + break; + case IS_REGULAREXPRESSION: + AppendLiteral("IS_REGULAREXPRESSION"); + break; + case IS_SRGS: + AppendLiteral("IS_SRGS"); + break; + case IS_XML: + AppendLiteral("IS_XML"); + break; + case IS_PRIVATE: + AppendLiteral("IS_PRIVATE"); + break; + default: + AppendPrintf("Unknown Value(%d)", inputScope); + break; + } + } + } +}; + +/******************************************************************/ +/* InputScopeImpl */ +/******************************************************************/ + +class InputScopeImpl final : public ITfInputScope { + ~InputScopeImpl() {} + + public: + explicit InputScopeImpl(const nsTArray<InputScope>& aList) + : mInputScopes(aList.Clone()) { + MOZ_LOG( + sTextStoreLog, LogLevel::Info, + ("0x%p InputScopeImpl(%s)", this, GetInputScopeString(aList).get())); + } + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(InputScopeImpl) + + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { + *ppv = nullptr; + if ((IID_IUnknown == riid) || (IID_ITfInputScope == riid)) { + *ppv = static_cast<ITfInputScope*>(this); + } + if (*ppv) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP GetInputScopes(InputScope** pprgInputScopes, UINT* pcCount) { + uint32_t count = (mInputScopes.IsEmpty() ? 1 : mInputScopes.Length()); + + InputScope* pScope = + (InputScope*)CoTaskMemAlloc(sizeof(InputScope) * count); + NS_ENSURE_TRUE(pScope, E_OUTOFMEMORY); + + if (mInputScopes.IsEmpty()) { + *pScope = IS_DEFAULT; + *pcCount = 1; + *pprgInputScopes = pScope; + return S_OK; + } + + *pcCount = 0; + + for (uint32_t idx = 0; idx < count; idx++) { + *(pScope + idx) = mInputScopes[idx]; + (*pcCount)++; + } + + *pprgInputScopes = pScope; + return S_OK; + } + + STDMETHODIMP GetPhrase(BSTR** ppbstrPhrases, UINT* pcCount) { + return E_NOTIMPL; + } + STDMETHODIMP GetRegularExpression(BSTR* pbstrRegExp) { return E_NOTIMPL; } + STDMETHODIMP GetSRGS(BSTR* pbstrSRGS) { return E_NOTIMPL; } + STDMETHODIMP GetXML(BSTR* pbstrXML) { return E_NOTIMPL; } + + private: + nsTArray<InputScope> mInputScopes; +}; + +/******************************************************************/ +/* TSFStaticSink */ +/******************************************************************/ + +class TSFStaticSink final : public ITfInputProcessorProfileActivationSink { + public: + static TSFStaticSink* GetInstance() { + if (!sInstance) { + RefPtr<ITfThreadMgr> threadMgr = TSFTextStore::GetThreadMgr(); + if (NS_WARN_IF(!threadMgr)) { + MOZ_LOG( + sTextStoreLog, LogLevel::Error, + ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink " + "instance due to no ThreadMgr instance")); + return nullptr; + } + RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles = + TSFTextStore::GetInputProcessorProfiles(); + if (NS_WARN_IF(!inputProcessorProfiles)) { + MOZ_LOG( + sTextStoreLog, LogLevel::Error, + ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink " + "instance due to no InputProcessorProfiles instance")); + return nullptr; + } + RefPtr<TSFStaticSink> staticSink = new TSFStaticSink(); + if (NS_WARN_IF(!staticSink->Init(threadMgr, inputProcessorProfiles))) { + staticSink->Destroy(); + MOZ_LOG( + sTextStoreLog, LogLevel::Error, + ("TSFStaticSink::GetInstance() FAILED to initialize TSFStaticSink " + "instance")); + return nullptr; + } + sInstance = staticSink.forget(); + } + return sInstance; + } + + static void Shutdown() { + if (sInstance) { + sInstance->Destroy(); + sInstance = nullptr; + } + } + + bool Init(ITfThreadMgr* aThreadMgr, + ITfInputProcessorProfiles* aInputProcessorProfiles); + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { + *ppv = nullptr; + if (IID_IUnknown == riid || + IID_ITfInputProcessorProfileActivationSink == riid) { + *ppv = static_cast<ITfInputProcessorProfileActivationSink*>(this); + } + if (*ppv) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFStaticSink) + + const nsString& GetActiveTIPKeyboardDescription() const { + return mActiveTIPKeyboardDescription; + } + + static bool IsIMM_IMEActive() { + // Use IMM API until TSFStaticSink starts to work. + if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) { + return IsIMM_IME(::GetKeyboardLayout(0)); + } + return sInstance->mIsIMM_IME; + } + + static bool IsIMM_IME(HKL aHKL) { + return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0); + } + + static bool IsTraditionalChinese() { + EnsureInstance(); + return sInstance && sInstance->IsTraditionalChineseInternal(); + } + static bool IsSimplifiedChinese() { + EnsureInstance(); + return sInstance && sInstance->IsSimplifiedChineseInternal(); + } + static bool IsJapanese() { + EnsureInstance(); + return sInstance && sInstance->IsJapaneseInternal(); + } + static bool IsKorean() { + EnsureInstance(); + return sInstance && sInstance->IsKoreanInternal(); + } + + /** + * ActiveTIP() returns an ID for currently active TIP. + * Please note that this method is expensive due to needs a lot of GUID + * comparations if active language ID is one of CJKT. If you need to + * check TIPs for a specific language, you should check current language + * first. + */ + static TextInputProcessorID ActiveTIP() { + EnsureInstance(); + if (!sInstance || !sInstance->EnsureInitActiveTIPKeyboard()) { + return TextInputProcessorID::eUnknown; + } + sInstance->ComputeActiveTextInputProcessor(); + if (NS_WARN_IF(sInstance->mActiveTIP == + TextInputProcessorID::eNotComputed)) { + return TextInputProcessorID::eUnknown; + } + return sInstance->mActiveTIP; + } + + static bool IsMSChangJieOrMSQuickActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if the language + // for current TIP is Traditional Chinese. + if (!IsTraditionalChinese()) { + return false; + } + switch (ActiveTIP()) { + case TextInputProcessorID::eMicrosoftChangJie: + case TextInputProcessorID::eMicrosoftQuick: + return true; + default: + return false; + } + } + + static bool IsMSPinyinOrMSWubiActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if the language + // for current TIP is Simplified Chinese. + if (!IsSimplifiedChinese()) { + return false; + } + switch (ActiveTIP()) { + case TextInputProcessorID::eMicrosoftPinyin: + case TextInputProcessorID::eMicrosoftWubi: + return true; + default: + return false; + } + } + + static bool IsMSJapaneseIMEActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if the language + // for current TIP is Japanese. + if (!IsJapanese()) { + return false; + } + return ActiveTIP() == TextInputProcessorID::eMicrosoftIMEForJapanese; + } + + static bool IsGoogleJapaneseInputActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if the language + // for current TIP is Japanese. + if (!IsJapanese()) { + return false; + } + return ActiveTIP() == TextInputProcessorID::eGoogleJapaneseInput; + } + + static bool IsATOKActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if active TIP is + // ATOK first since it's cheaper. + return IsJapanese() && sInstance->IsATOKActiveInternal(); + } + + // Note that ATOK 2011 - 2016 refers native caret position for deciding its + // popup window position. + static bool IsATOKReferringNativeCaretActive() { + // ActiveTIP() is expensive if it hasn't computed active TIP yet. + // For avoiding unnecessary computation, we should check if active TIP is + // ATOK first since it's cheaper. + if (!IsJapanese() || !sInstance->IsATOKActiveInternal()) { + return false; + } + switch (ActiveTIP()) { + case TextInputProcessorID::eATOK2011: + case TextInputProcessorID::eATOK2012: + case TextInputProcessorID::eATOK2013: + case TextInputProcessorID::eATOK2014: + case TextInputProcessorID::eATOK2015: + return true; + default: + return false; + } + } + + private: + static void EnsureInstance() { + if (!sInstance) { + RefPtr<TSFStaticSink> staticSink = GetInstance(); + Unused << staticSink; + } + } + + bool IsTraditionalChineseInternal() const { return mLangID == 0x0404; } + bool IsSimplifiedChineseInternal() const { return mLangID == 0x0804; } + bool IsJapaneseInternal() const { return mLangID == 0x0411; } + bool IsKoreanInternal() const { return mLangID == 0x0412; } + + bool IsATOKActiveInternal() { + EnsureInitActiveTIPKeyboard(); + // FYI: Name of packaged ATOK includes the release year like "ATOK 2015". + // Name of ATOK Passport (subscription) equals "ATOK". + return StringBeginsWith(mActiveTIPKeyboardDescription, u"ATOK "_ns) || + mActiveTIPKeyboardDescription.EqualsLiteral("ATOK"); + } + + void ComputeActiveTextInputProcessor() { + if (mActiveTIP != TextInputProcessorID::eNotComputed) { + return; + } + + if (mActiveTIPGUID == GUID_NULL) { + mActiveTIP = TextInputProcessorID::eNone; + return; + } + + // Comparing GUID is slow. So, we should use language information to + // reduce the comparing cost for TIP which is not we do not support + // specifically since they are always compared with all supported TIPs. + switch (mLangID) { + case 0x0404: + mActiveTIP = ComputeActiveTIPAsTraditionalChinese(); + break; + case 0x0411: + mActiveTIP = ComputeActiveTIPAsJapanese(); + break; + case 0x0412: + mActiveTIP = ComputeActiveTIPAsKorean(); + break; + case 0x0804: + mActiveTIP = ComputeActiveTIPAsSimplifiedChinese(); + break; + default: + mActiveTIP = TextInputProcessorID::eUnknown; + break; + } + // Special case for Keyman Desktop, it is available for any languages. + // Therefore, we need to check it only if we don't know the active TIP. + if (mActiveTIP != TextInputProcessorID::eUnknown) { + return; + } + + // Note that keyboard layouts for Keyman assign its GUID on install + // randomly, but CLSID is constant in any environments. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1670834#c7 + // https://github.com/keymanapp/keyman/blob/318c73a9e1d571d942837ff9964590626e5bd5aa/windows/src/engine/kmtip/globals.cpp#L37 + // {FE0420F1-38D1-4B4C-96BF-E7E20A74CFB7} + static constexpr CLSID kKeymanDesktop_CLSID = { + 0xFE0420F1, + 0x38D1, + 0x4B4C, + {0x96, 0xBF, 0xE7, 0xE2, 0x0A, 0x74, 0xCF, 0xB7}}; + if (mActiveTIPCLSID == kKeymanDesktop_CLSID) { + mActiveTIP = TextInputProcessorID::eKeymanDesktop; + } + } + + TextInputProcessorID ComputeActiveTIPAsJapanese() { + // {A76C93D9-5523-4E90-AAFA-4DB112F9AC76} (Win7, Win8.1, Win10) + static constexpr GUID kMicrosoftIMEForJapaneseGUID = { + 0xA76C93D9, + 0x5523, + 0x4E90, + {0xAA, 0xFA, 0x4D, 0xB1, 0x12, 0xF9, 0xAC, 0x76}}; + if (mActiveTIPGUID == kMicrosoftIMEForJapaneseGUID) { + return TextInputProcessorID::eMicrosoftIMEForJapanese; + } + // {54EDCC94-1524-4BB1-9FB7-7BABE4F4CA64} + static constexpr GUID kMicrosoftOfficeIME2010ForJapaneseGUID = { + 0x54EDCC94, + 0x1524, + 0x4BB1, + {0x9F, 0xB7, 0x7B, 0xAB, 0xE4, 0xF4, 0xCA, 0x64}}; + if (mActiveTIPGUID == kMicrosoftOfficeIME2010ForJapaneseGUID) { + return TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese; + } + // {773EB24E-CA1D-4B1B-B420-FA985BB0B80D} + static constexpr GUID kGoogleJapaneseInputGUID = { + 0x773EB24E, + 0xCA1D, + 0x4B1B, + {0xB4, 0x20, 0xFA, 0x98, 0x5B, 0xB0, 0xB8, 0x0D}}; + if (mActiveTIPGUID == kGoogleJapaneseInputGUID) { + return TextInputProcessorID::eGoogleJapaneseInput; + } + // {F9C24A5C-8A53-499D-9572-93B2FF582115} + static const GUID kATOK2011GUID = { + 0xF9C24A5C, + 0x8A53, + 0x499D, + {0x95, 0x72, 0x93, 0xB2, 0xFF, 0x58, 0x21, 0x15}}; + if (mActiveTIPGUID == kATOK2011GUID) { + return TextInputProcessorID::eATOK2011; + } + // {1DE01562-F445-401B-B6C3-E5B18DB79461} + static constexpr GUID kATOK2012GUID = { + 0x1DE01562, + 0xF445, + 0x401B, + {0xB6, 0xC3, 0xE5, 0xB1, 0x8D, 0xB7, 0x94, 0x61}}; + if (mActiveTIPGUID == kATOK2012GUID) { + return TextInputProcessorID::eATOK2012; + } + // {3C4DB511-189A-4168-B6EA-BFD0B4C85615} + static constexpr GUID kATOK2013GUID = { + 0x3C4DB511, + 0x189A, + 0x4168, + {0xB6, 0xEA, 0xBF, 0xD0, 0xB4, 0xC8, 0x56, 0x15}}; + if (mActiveTIPGUID == kATOK2013GUID) { + return TextInputProcessorID::eATOK2013; + } + // {4EF33B79-6AA9-4271-B4BF-9321C279381B} + static constexpr GUID kATOK2014GUID = { + 0x4EF33B79, + 0x6AA9, + 0x4271, + {0xB4, 0xBF, 0x93, 0x21, 0xC2, 0x79, 0x38, 0x1B}}; + if (mActiveTIPGUID == kATOK2014GUID) { + return TextInputProcessorID::eATOK2014; + } + // {EAB4DC00-CE2E-483D-A86A-E6B99DA9599A} + static constexpr GUID kATOK2015GUID = { + 0xEAB4DC00, + 0xCE2E, + 0x483D, + {0xA8, 0x6A, 0xE6, 0xB9, 0x9D, 0xA9, 0x59, 0x9A}}; + if (mActiveTIPGUID == kATOK2015GUID) { + return TextInputProcessorID::eATOK2015; + } + // {0B557B4C-5740-4110-A60A-1493FA10BF2B} + static constexpr GUID kATOK2016GUID = { + 0x0B557B4C, + 0x5740, + 0x4110, + {0xA6, 0x0A, 0x14, 0x93, 0xFA, 0x10, 0xBF, 0x2B}}; + if (mActiveTIPGUID == kATOK2016GUID) { + return TextInputProcessorID::eATOK2016; + } + + // * ATOK 2017 + // - {6DBFD8F5-701D-11E6-920F-782BCBA6348F} + // * ATOK Passport (confirmed with version 31.1.2) + // - {A38F2FD9-7199-45E1-841C-BE0313D8052F} + + if (IsATOKActiveInternal()) { + return TextInputProcessorID::eATOKUnknown; + } + + // {E6D66705-1EDA-4373-8D01-1D0CB2D054C7} + static constexpr GUID kJapanist10GUID = { + 0xE6D66705, + 0x1EDA, + 0x4373, + {0x8D, 0x01, 0x1D, 0x0C, 0xB2, 0xD0, 0x54, 0xC7}}; + if (mActiveTIPGUID == kJapanist10GUID) { + return TextInputProcessorID::eJapanist10; + } + + return TextInputProcessorID::eUnknown; + } + + TextInputProcessorID ComputeActiveTIPAsTraditionalChinese() { + // {B2F9C502-1742-11D4-9790-0080C882687E} (Win8.1, Win10) + static constexpr GUID kMicrosoftBopomofoGUID = { + 0xB2F9C502, + 0x1742, + 0x11D4, + {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftBopomofoGUID) { + return TextInputProcessorID::eMicrosoftBopomofo; + } + // {4BDF9F03-C7D3-11D4-B2AB-0080C882687E} (Win7, Win8.1, Win10) + static const GUID kMicrosoftChangJieGUID = { + 0x4BDF9F03, + 0xC7D3, + 0x11D4, + {0xB2, 0xAB, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftChangJieGUID) { + return TextInputProcessorID::eMicrosoftChangJie; + } + // {761309DE-317A-11D4-9B5D-0080C882687E} (Win7) + static constexpr GUID kMicrosoftPhoneticGUID = { + 0x761309DE, + 0x317A, + 0x11D4, + {0x9B, 0x5D, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftPhoneticGUID) { + return TextInputProcessorID::eMicrosoftPhonetic; + } + // {6024B45F-5C54-11D4-B921-0080C882687E} (Win7, Win8.1, Win10) + static constexpr GUID kMicrosoftQuickGUID = { + 0x6024B45F, + 0x5C54, + 0x11D4, + {0xB9, 0x21, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftQuickGUID) { + return TextInputProcessorID::eMicrosoftQuick; + } + // {F3BA907A-6C7E-11D4-97FA-0080C882687E} (Win7) + static constexpr GUID kMicrosoftNewChangJieGUID = { + 0xF3BA907A, + 0x6C7E, + 0x11D4, + {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftNewChangJieGUID) { + return TextInputProcessorID::eMicrosoftNewChangJie; + } + // {B2F9C502-1742-11D4-9790-0080C882687E} (Win7) + static constexpr GUID kMicrosoftNewPhoneticGUID = { + 0xB2F9C502, + 0x1742, + 0x11D4, + {0x97, 0x90, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftNewPhoneticGUID) { + return TextInputProcessorID::eMicrosoftNewPhonetic; + } + // {0B883BA0-C1C7-11D4-87F9-0080C882687E} (Win7) + static constexpr GUID kMicrosoftNewQuickGUID = { + 0x0B883BA0, + 0xC1C7, + 0x11D4, + {0x87, 0xF9, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftNewQuickGUID) { + return TextInputProcessorID::eMicrosoftNewQuick; + } + + // NOTE: There are some other Traditional Chinese TIPs installed in Windows: + // * Chinese Traditional Array (version 6.0) + // - {D38EFF65-AA46-4FD5-91A7-67845FB02F5B} (Win7, Win8.1) + // * Chinese Traditional DaYi (version 6.0) + // - {037B2C25-480C-4D7F-B027-D6CA6B69788A} (Win7, Win8.1) + + // {B58630B5-0ED3-4335-BBC9-E77BBCB43CAD} + static const GUID kFreeChangJieGUID = { + 0xB58630B5, + 0x0ED3, + 0x4335, + {0xBB, 0xC9, 0xE7, 0x7B, 0xBC, 0xB4, 0x3C, 0xAD}}; + if (mActiveTIPGUID == kFreeChangJieGUID) { + return TextInputProcessorID::eFreeChangJie; + } + + return TextInputProcessorID::eUnknown; + } + + TextInputProcessorID ComputeActiveTIPAsSimplifiedChinese() { + // FYI: This matches with neither "Microsoft Pinyin ABC Input Style" nor + // "Microsoft Pinyin New Experience Input Style" on Win7. + // {FA550B04-5AD7-411F-A5AC-CA038EC515D7} (Win8.1, Win10) + static constexpr GUID kMicrosoftPinyinGUID = { + 0xFA550B04, + 0x5AD7, + 0x411F, + {0xA5, 0xAC, 0xCA, 0x03, 0x8E, 0xC5, 0x15, 0xD7}}; + if (mActiveTIPGUID == kMicrosoftPinyinGUID) { + return TextInputProcessorID::eMicrosoftPinyin; + } + + // {F3BA9077-6C7E-11D4-97FA-0080C882687E} (Win7) + static constexpr GUID kMicrosoftPinyinNewExperienceInputStyleGUID = { + 0xF3BA9077, + 0x6C7E, + 0x11D4, + {0x97, 0xFA, 0x00, 0x80, 0xC8, 0x82, 0x68, 0x7E}}; + if (mActiveTIPGUID == kMicrosoftPinyinNewExperienceInputStyleGUID) { + return TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle; + } + // {82590C13-F4DD-44F4-BA1D-8667246FDF8E} (Win8.1, Win10) + static constexpr GUID kMicrosoftWubiGUID = { + 0x82590C13, + 0xF4DD, + 0x44F4, + {0xBA, 0x1D, 0x86, 0x67, 0x24, 0x6F, 0xDF, 0x8E}}; + if (mActiveTIPGUID == kMicrosoftWubiGUID) { + return TextInputProcessorID::eMicrosoftWubi; + } + // NOTE: There are some other Simplified Chinese TIPs installed in Windows: + // * Chinese Simplified QuanPin (version 6.0) + // - {54FC610E-6ABD-4685-9DDD-A130BDF1B170} (Win8.1) + // * Chinese Simplified ZhengMa (version 6.0) + // - {733B4D81-3BC3-4132-B91A-E9CDD5E2BFC9} (Win8.1) + // * Chinese Simplified ShuangPin (version 6.0) + // - {EF63706D-31C4-490E-9DBB-BD150ADC454B} (Win8.1) + // * Microsoft Pinyin ABC Input Style + // - {FCA121D2-8C6D-41FB-B2DE-A2AD110D4820} (Win7) + return TextInputProcessorID::eUnknown; + } + + TextInputProcessorID ComputeActiveTIPAsKorean() { + // {B5FE1F02-D5F2-4445-9C03-C568F23C99A1} (Win7, Win8.1, Win10) + static constexpr GUID kMicrosoftIMEForKoreanGUID = { + 0xB5FE1F02, + 0xD5F2, + 0x4445, + {0x9C, 0x03, 0xC5, 0x68, 0xF2, 0x3C, 0x99, 0xA1}}; + if (mActiveTIPGUID == kMicrosoftIMEForKoreanGUID) { + return TextInputProcessorID::eMicrosoftIMEForKorean; + } + // {B60AF051-257A-46BC-B9D3-84DAD819BAFB} (Win8.1, Win10) + static constexpr GUID kMicrosoftOldHangulGUID = { + 0xB60AF051, + 0x257A, + 0x46BC, + {0xB9, 0xD3, 0x84, 0xDA, 0xD8, 0x19, 0xBA, 0xFB}}; + if (mActiveTIPGUID == kMicrosoftOldHangulGUID) { + return TextInputProcessorID::eMicrosoftOldHangul; + } + + // NOTE: There is the other Korean TIP installed in Windows: + // * Microsoft IME 2010 + // - {48878C45-93F9-4aaf-A6A1-272CD863C4F5} (Win7) + + return TextInputProcessorID::eUnknown; + } + + public: // ITfInputProcessorProfileActivationSink + STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL, + DWORD); + + private: + TSFStaticSink(); + virtual ~TSFStaticSink() {} + + bool EnsureInitActiveTIPKeyboard(); + + void Destroy(); + + void GetTIPDescription(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile, nsAString& aDescription); + bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID, + REFGUID aProfile); + + TextInputProcessorID mActiveTIP; + + // Cookie of installing ITfInputProcessorProfileActivationSink + DWORD mIPProfileCookie; + + LANGID mLangID; + + // True if current IME is implemented with IMM. + bool mIsIMM_IME; + // True if OnActivated() is already called + bool mOnActivatedCalled; + + RefPtr<ITfThreadMgr> mThreadMgr; + RefPtr<ITfInputProcessorProfiles> mInputProcessorProfiles; + + // Active TIP keyboard's description. If active language profile isn't TIP, + // i.e., IMM-IME or just a keyboard layout, this is empty. + nsString mActiveTIPKeyboardDescription; + + // Active TIP's GUID and CLSID + GUID mActiveTIPGUID; + CLSID mActiveTIPCLSID; + + static StaticRefPtr<TSFStaticSink> sInstance; +}; + +StaticRefPtr<TSFStaticSink> TSFStaticSink::sInstance; + +TSFStaticSink::TSFStaticSink() + : mActiveTIP(TextInputProcessorID::eNotComputed), + mIPProfileCookie(TF_INVALID_COOKIE), + mLangID(0), + mIsIMM_IME(false), + mOnActivatedCalled(false), + mActiveTIPGUID(GUID_NULL) {} + +bool TSFStaticSink::Init(ITfThreadMgr* aThreadMgr, + ITfInputProcessorProfiles* aInputProcessorProfiles) { + MOZ_ASSERT(!mThreadMgr && !mInputProcessorProfiles, + "TSFStaticSink::Init() must be called only once"); + + mThreadMgr = aThreadMgr; + mInputProcessorProfiles = aInputProcessorProfiles; + + RefPtr<ITfSource> source; + HRESULT hr = + mThreadMgr->QueryInterface(IID_ITfSource, getter_AddRefs(source)); + if (FAILED(hr)) { + MOZ_LOG(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; + mActiveTIP = TextInputProcessorID::eNotComputed; + mActiveTIPGUID = guidProfile; + mActiveTIPCLSID = rclsid; + mLangID = langid & 0xFFFF; + mIsIMM_IME = IsIMM_IME(hkl); + GetTIPDescription(rclsid, langid, guidProfile, + mActiveTIPKeyboardDescription); + if (mActiveTIPGUID != GUID_NULL) { + // key should be "LocaleID|Description". Although GUID of the + // profile is unique key since description may be localized for system + // language, unfortunately, it's too long to record as key with its + // description. Therefore, we should record only the description with + // LocaleID because Microsoft IME may not include language information. + // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp + nsAutoString key; + key.AppendPrintf("0x%04X|", mLangID); + nsAutoString description(mActiveTIPKeyboardDescription); + static const uint32_t kMaxDescriptionLength = 72 - key.Length(); + if (description.Length() > kMaxDescriptionLength) { + if (NS_IS_LOW_SURROGATE(description[kMaxDescriptionLength - 1]) && + NS_IS_HIGH_SURROGATE(description[kMaxDescriptionLength - 2])) { + description.Truncate(kMaxDescriptionLength - 2); + } else { + description.Truncate(kMaxDescriptionLength - 1); + } + // U+2026 is "..." + description.Append(char16_t(0x2026)); + } + key.Append(description); + Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_WINDOWS, key, + true); + } + // 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; +} + +/******************************************************************/ +/* TSFPreference */ +/******************************************************************/ + +class TSFPrefs final { + public: +#define DECL_AND_IMPL_BOOL_PREF(aPref, aName, aDefaultValue) \ + static bool aName() { \ + static bool s##aName##Value = Preferences::GetBool(aPref, aDefaultValue); \ + return s##aName##Value; \ + } + + DECL_AND_IMPL_BOOL_PREF("intl.ime.hack.set_input_scope_of_url_bar_to_default", + ShouldSetInputScopeOfURLBarToDefault, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.allow_to_stop_hacking_on_build_17643_or_later", + AllowToStopHackingOnBuild17643OrLater, false) + DECL_AND_IMPL_BOOL_PREF("intl.tsf.hack.atok.create_native_caret", + NeedToCreateNativeCaretForLegacyATOK, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.atok.do_not_return_no_layout_error_of_composition_string", + DoNotReturnNoLayoutErrorToATOKOfCompositionString, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.japanist10." + "do_not_return_no_layout_error_of_composition_string", + DoNotReturnNoLayoutErrorToJapanist10OfCompositionString, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_simplified_chinese.do_not_return_no_layout_error", + DoNotReturnNoLayoutErrorToMSSimplifiedTIP, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_traditional_chinese.do_not_return_no_layout_error", + DoNotReturnNoLayoutErrorToMSTraditionalTIP, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.free_chang_jie.do_not_return_no_layout_error", + DoNotReturnNoLayoutErrorToFreeChangJie, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_first_" + "char", + DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_japanese_ime.do_not_return_no_layout_error_at_caret", + DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_simplified_chinese.query_insert_result", + NeedToHackQueryInsertForMSSimplifiedTIP, true) + DECL_AND_IMPL_BOOL_PREF( + "intl.tsf.hack.ms_traditional_chinese.query_insert_result", + NeedToHackQueryInsertForMSTraditionalTIP, true) + +#undef DECL_AND_IMPL_BOOL_PREF +}; + +/******************************************************************/ +/* TSFTextStore */ +/******************************************************************/ + +StaticRefPtr<ITfThreadMgr> TSFTextStore::sThreadMgr; +StaticRefPtr<ITfMessagePump> TSFTextStore::sMessagePump; +StaticRefPtr<ITfKeystrokeMgr> TSFTextStore::sKeystrokeMgr; +StaticRefPtr<ITfDisplayAttributeMgr> TSFTextStore::sDisplayAttrMgr; +StaticRefPtr<ITfCategoryMgr> TSFTextStore::sCategoryMgr; +StaticRefPtr<ITfCompartment> TSFTextStore::sCompartmentForOpenClose; +StaticRefPtr<ITfDocumentMgr> TSFTextStore::sDisabledDocumentMgr; +StaticRefPtr<ITfContext> TSFTextStore::sDisabledContext; +StaticRefPtr<ITfInputProcessorProfiles> TSFTextStore::sInputProcessorProfiles; +StaticRefPtr<TSFTextStore> TSFTextStore::sEnabledTextStore; +const MSG* TSFTextStore::sHandlingKeyMsg = nullptr; +DWORD TSFTextStore::sClientId = 0; +bool TSFTextStore::sIsKeyboardEventDispatched = false; + +#define TEXTSTORE_DEFAULT_VIEW (1) + +TSFTextStore::TSFTextStore() + : mEditCookie(0), + mSinkMask(0), + mLock(0), + mLockQueued(0), + mHandlingKeyMessage(0), + mRequestedAttrValues(false), + mIsRecordingActionsWithoutLock(false), + mHasReturnedNoLayoutError(false), + mWaitingQueryLayout(false), + mPendingDestroy(false), + mDeferClearingContentForTSF(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; + } + + 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, + aContext.mInPrivateBrowsing); + + // 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=%s, mHandlingKeyMessage=%u", + this, GetLockFlagNameStr(mLock).get(), + ToString(mComposition).c_str(), mHandlingKeyMessage)); + + mDestroyed = true; + + // Destroy native caret first because it's not directly related to TSF and + // there may be another textstore which gets focus. So, we should avoid + // to destroy caret after the new one recreates caret. + IMEHandler::MaybeDestroyNativeCaret(); + + if (mLock) { + mPendingDestroy = true; + return; + } + + AutoRestore<bool> savedBeingDestroyed(mBeingDestroyed); + mBeingDestroyed = true; + + // If there is composition, TSF keeps the composition even after the text + // store destroyed. So, we should clear the composition here. + if (mComposition.isSome()) { + CommitCompositionInternal(false); + } + + if (mSink) { + MOZ_LOG(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.isNothing() || mSelectionForTSF.isNothing())) { + 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; + } + + // Some TIP may request lock but does nothing during the lock. In such case, + // this should do nothing. For example, when MS-IME for Japanese is active + // and we're inactivating, this case occurs and causes different behavior + // from the other TIPs. + if (mPendingActions.IsEmpty()) { + return; + } + + RefPtr<nsWindowBase> widget(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::Type::eKeyboardEvent: + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::FlushPendingActions() " + "IGNORED pending KeyboardEvent(%s) due to already destroyed", + action.mKeyMsg.message == WM_KEYDOWN ? "eKeyDown" : "eKeyUp", + this)); + } + MOZ_DIAGNOSTIC_ASSERT(action.mKeyMsg.message == WM_KEYDOWN || + action.mKeyMsg.message == WM_KEYUP); + DispatchKeyboardEventAsProcessedByIME(action.mKeyMsg); + if (!widget || widget->Destroyed()) { + break; + } + break; + case PendingAction::Type::eCompositionStart: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing Type::eCompositionStart={ 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, widget); + widget->InitEvent(selectionSet); + selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart); + selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength); + selectionSet.mReversed = false; + selectionSet.mExpandToClusterBoundary = + TSFStaticSink::ActiveTIP() != + TextInputProcessorID::eKeymanDesktop && + StaticPrefs:: + intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries(); + DispatchEvent(selectionSet); + if (!selectionSet.mSucceeded) { + MOZ_LOG(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 = widget->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, " + "IsHandlingCompositionInContent()=%s", + this, GetBoolName(IsHandlingCompositionInContent()))); + // XXX Is this right? If there is a composition in content, + // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED? + mDeferClearingContentForTSF = !IsHandlingCompositionInContent(); + } + if (!widget || widget->Destroyed()) { + break; + } + break; + } + case PendingAction::Type::eCompositionUpdate: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing Type::eCompositionUpdate={ 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... " + "IsHandlingCompositionInContent()=%s", + this, GetBoolName(IsHandlingCompositionInContent()))); + // XXX Is this right? If there is a composition in content, + // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED? + mDeferClearingContentForTSF = !IsHandlingCompositionInContent(); + } else { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "dispatching compositionchange event...", + this)); + WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = mDispatcher->FlushPendingComposition(status, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to dispatch compositionchange event, " + "IsHandlingCompositionInContent()=%s", + this, GetBoolName(IsHandlingCompositionInContent()))); + // XXX Is this right? If there is a composition in content, + // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED? + mDeferClearingContentForTSF = !IsHandlingCompositionInContent(); + } + // Be aware, the mWidget might already have been destroyed. + } + break; + } + case PendingAction::Type::eCompositionEnd: { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing Type::eCompositionEnd={ mData=\"%s\" }", + this, GetEscapedUTF8String(action.mData).get())); + + // Dispatching eCompositionCommit causes a DOM text event, then, + // the IME will be notified of NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED + // when focused content actually handles the event. For example, + // when focused content is in a remote process, it's sent when + // all dispatched composition events have been handled in the remote + // process. So, until then, we don't have newer content information. + // Therefore, we need to put off to clear mContentForTSF. + mDeferClearingContentForTSF = true; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions(), " + "dispatching compositioncommit event...", + this)); + WidgetEventTime eventTime = widget->CurrentMessageWidgetEventTime(); + nsEventStatus status; + rv = mDispatcher->CommitComposition(status, &action.mData, &eventTime); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED to dispatch compositioncommit event, " + "IsHandlingCompositionInContent()=%s", + this, GetBoolName(IsHandlingCompositionInContent()))); + // XXX Is this right? If there is a composition in content, + // shouldn't we wait NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED? + mDeferClearingContentForTSF = !IsHandlingCompositionInContent(); + } + break; + } + case PendingAction::Type::eSetSelection: { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::FlushPendingActions() " + "flushing Type::eSetSelection={ 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, widget); + selectionSet.mOffset = static_cast<uint32_t>(action.mSelectionStart); + selectionSet.mLength = static_cast<uint32_t>(action.mSelectionLength); + selectionSet.mReversed = action.mSelectionReversed; + selectionSet.mExpandToClusterBoundary = + TSFStaticSink::ActiveTIP() != + TextInputProcessorID::eKeymanDesktop && + StaticPrefs:: + intl_tsf_hack_extend_setting_selection_range_to_cluster_boundaries(); + DispatchEvent(selectionSet); + if (!selectionSet.mSucceeded) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::FlushPendingActions() " + "FAILED due to eSetSelection failure", + this)); + break; + } + break; + } + default: + MOZ_CRASH("unexpected action type"); + } + + if (widget && !widget->Destroyed()) { + continue; + } + + MOZ_LOG(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.isSome()) { + mContentForTSF.reset(); + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeFlushPendingNotifications(), " + "mContentForTSF is set to `Nothing`", + this)); + } + + // When there is no cached content, we can sync actual contents and TSF/TIP + // expecting contents. + RefPtr<TSFTextStore> kungFuDeathGrip = this; + Unused << kungFuDeathGrip; + if (mContentForTSF.isNothing()) { + if (mPendingTextChangeData.IsValid()) { + MOZ_LOG(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(); + } +} + +void TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME() { + // If we've already been destroyed, we cannot do anything. + if (mDestroyed) { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), " + "does nothing because it's already been destroyed", + this)); + return; + } + + // If we're not handling key message or we've already dispatched a keyboard + // event for the handling key message, we should do nothing anymore. + if (!sHandlingKeyMsg || sIsKeyboardEventDispatched) { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), " + "does nothing because not necessary to dispatch keyboard event", + this)); + return; + } + + sIsKeyboardEventDispatched = true; + // If the document is locked, just adding the task to dispatching an event + // to the queue. + if (IsReadLocked()) { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), " + "adding to dispatch a keyboard event into the queue...", + this)); + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::Type::eKeyboardEvent; + memcpy(&action->mKeyMsg, sHandlingKeyMsg, sizeof(MSG)); + return; + } + + // Otherwise, dispatch a keyboard event. + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MaybeDispatchKeyboardEventAsProcessedByIME(), " + "trying to dispatch a keyboard event...", + this)); + DispatchKeyboardEventAsProcessedByIME(*sHandlingKeyMsg); +} + +void TSFTextStore::DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg) { + MOZ_ASSERT(mWidget); + MOZ_ASSERT(!mWidget->Destroyed()); + MOZ_ASSERT(!mDestroyed); + + ModifierKeyState modKeyState; + MSG msg(aMsg); + msg.wParam = VK_PROCESSKEY; + NativeKey nativeKey(mWidget, msg, modKeyState); + switch (aMsg.message) { + case WM_KEYDOWN: + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), " + "dispatching an eKeyDown event...", + this)); + nativeKey.HandleKeyDownMessage(); + break; + case WM_KEYUP: + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), " + "dispatching an eKeyUp event...", + this)); + nativeKey.HandleKeyUpMessage(); + break; + default: + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::DispatchKeyboardEventAsProcessedByIME(), " + "ERROR, it doesn't handle the message", + this)); + break; + } +} + +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; + } + // We manage on-screen keyboard by own. + pdcs->dwDynamicFlags = TS_SD_INPUTPANEMANUALDISPLAYENABLE; + // we use a "flat" text model for TSF support so no hidden text + pdcs->dwStaticFlags = TS_SS_NOHIDDENTEXT; + return S_OK; +} + +STDMETHODIMP +TSFTextStore::QueryInsert(LONG acpTestStart, LONG acpTestEnd, ULONG cch, + LONG* pacpResultStart, LONG* pacpResultEnd) { + MOZ_LOG(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 + if (IsWin8OrLater() && mComposition.isNothing() && + ((TSFPrefs::NeedToHackQueryInsertForMSTraditionalTIP() && + TSFStaticSink::IsMSChangJieOrMSQuickActive()) || + (TSFPrefs::NeedToHackQueryInsertForMSSimplifiedTIP() && + TSFStaticSink::IsMSPinyinOrMSWubiActive()))) { + 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; + } + + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + if (DoNotReturnErrorFromGetSelection()) { + TS_SELECTION_ACP acp; + acp.acpStart = acp.acpEnd = 0; + acp.style.ase = TS_AE_START; + acp.style.fInterimChar = FALSE; + *pSelection = acp; + *pcFetched = 1; + MOZ_LOG( + sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetSelection() returns fake selection range " + "for avoiding a crash in TSF, *pSelection=%s", + this, mozilla::ToString(*pSelection).c_str())); + return S_OK; + } + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetSelection() FAILED due to " + "SelectionForTSF() failure", + this)); + return E_FAIL; + } + *pSelection = selectionForTSF->ACPRef(); + *pcFetched = 1; + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::GetSelection() succeeded, *pSelection=%s", + this, mozilla::ToString(*pSelection).c_str())); + return S_OK; +} + +// static +bool TSFTextStore::DoNotReturnErrorFromGetSelection() { + // There is a crash bug of TSF if we return error from GetSelection(). + // That was introduced in Anniversary Update (build 14393, see bug 1312302) + // TODO: We should avoid to run this hack on fixed builds. When we get + // exact build number, we should get back here. + static bool sTSFMayCrashIfGetSelectionReturnsError = + IsWindows10BuildOrLater(14393); + return sTSFMayCrashIfGetSelectionReturnsError; +} + +Maybe<TSFTextStore::Content>& TSFTextStore::ContentForTSF() { + // This should be called when the document is locked or the content hasn't + // been abandoned yet. + if (NS_WARN_IF(!IsReadLocked() && mContentForTSF.isNothing())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to " + "called wrong timing, IsReadLocked()=%s, mContentForTSF=Nothing", + this, GetBoolName(IsReadLocked()))); + return mContentForTSF; + } + + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to " + "SelectionForTSF() failure", + this)); + mContentForTSF.reset(); + return mContentForTSF; + } + + if (mContentForTSF.isNothing()) { + nsString text; // Don't use auto string for avoiding to copy long string. + if (NS_WARN_IF(!GetCurrentText(text))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::ContentForTSF(), FAILED, due to " + "GetCurrentText() failure", + this)); + return mContentForTSF; + } + + mContentForTSF.emplace(*this, text); + // Basically, the cached content which is expected by TSF/TIP should be + // cleared after active composition is committed or the document lock is + // unlocked. However, in e10s mode, content will be modified + // asynchronously. In such case, mDeferClearingContentForTSF may be + // true until whole dispatched events are handled by the focused editor. + mDeferClearingContentForTSF = false; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::ContentForTSF(): mContentForTSF=%s", this, + mozilla::ToString(mContentForTSF).c_str())); + + return mContentForTSF; +} + +bool TSFTextStore::CanAccessActualContentDirectly() const { + if (mContentForTSF.isNothing() || mSelectionForTSF.isNothing()) { + return true; + } + + // If the cached content has been changed by something except composition, + // the content cache may be different from actual content. + if (mPendingTextChangeData.IsValid() && + !mPendingTextChangeData.mCausedOnlyByComposition) { + return false; + } + + // If the cached selection isn't changed, cached content and actual content + // should be same. + if (!mPendingSelectionChangeData.IsValid()) { + return true; + } + + return mSelectionForTSF->EqualsExceptDirection(mPendingSelectionChangeData); +} + +bool TSFTextStore::GetCurrentText(nsAString& aTextContent) { + if (mContentForTSF.isSome()) { + aTextContent = mContentForTSF->TextRef(); + return true; + } + + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(mWidget && !mWidget->Destroyed()); + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::GetCurrentText(): " + "retrieving text from the content...", + this)); + + WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, + mWidget); + queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX); + mWidget->InitEvent(queryTextContentEvent); + DispatchEvent(queryTextContentEvent); + if (NS_WARN_IF(queryTextContentEvent.Failed())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetCurrentText(), FAILED, due to " + "eQueryTextContent failure", + this)); + aTextContent.Truncate(); + return false; + } + + aTextContent = queryTextContentEvent.mReply->DataRef(); + return true; +} + +Maybe<TSFTextStore::Selection>& TSFTextStore::SelectionForTSF() { + if (mSelectionForTSF.isNothing()) { + MOZ_ASSERT(!mDestroyed); + // If the window has never been available, we should crash since working + // with broken values may make TIP confused. + if (!mWidget || mWidget->Destroyed()) { + MOZ_ASSERT_UNREACHABLE("There should be non-destroyed widget"); + } + + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + mWidget); + mWidget->InitEvent(querySelectedTextEvent); + DispatchEvent(querySelectedTextEvent); + if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) { + return mSelectionForTSF; + } + MOZ_ASSERT(querySelectedTextEvent.mReply->mOffsetAndData.isSome()); + mSelectionForTSF = + Some(Selection(querySelectedTextEvent.mReply->StartOffset(), + querySelectedTextEvent.mReply->DataLength(), + querySelectedTextEvent.mReply->mReversed, + querySelectedTextEvent.mReply->WritingModeRef())); + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::SelectionForTSF() succeeded, " + "mSelectionForTSF=%s", + this, ToString(mSelectionForTSF).c_str())); + + return mSelectionForTSF; +} + +static HRESULT GetRangeExtent(ITfRange* aRange, LONG* aStart, LONG* aLength) { + RefPtr<ITfRangeACP> rangeACP; + aRange->QueryInterface(IID_ITfRangeACP, getter_AddRefs(rangeACP)); + NS_ENSURE_TRUE(rangeACP, E_FAIL); + return rangeACP->GetExtent(aStart, aLength); +} + +static TextRangeType GetGeckoSelectionValue(TF_DISPLAYATTRIBUTE& aDisplayAttr) { + switch (aDisplayAttr.bAttr) { + case TF_ATTR_TARGET_CONVERTED: + return TextRangeType::eSelectedClause; + case TF_ATTR_CONVERTED: + return TextRangeType::eConvertedClause; + case TF_ATTR_TARGET_NOTCONVERTED: + return TextRangeType::eSelectedRawClause; + default: + return TextRangeType::eRawClause; + } +} + +HRESULT +TSFTextStore::GetDisplayAttribute(ITfProperty* aAttrProperty, ITfRange* aRange, + TF_DISPLAYATTRIBUTE* aResult) { + NS_ENSURE_TRUE(aAttrProperty, E_FAIL); + NS_ENSURE_TRUE(aRange, E_FAIL); + NS_ENSURE_TRUE(aResult, E_FAIL); + + HRESULT hr; + + if (MOZ_LOG_TEST(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->StartOffset(), + start - mComposition->StartOffset() + 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; + } + + RefPtr<ITfCategoryMgr> categoryMgr = GetCategoryMgr(); + if (NS_WARN_IF(!categoryMgr)) { + return E_FAIL; + } + GUID guid; + hr = categoryMgr->GetGUID(DWORD(propValue.lVal), &guid); + ::VariantClear(&propValue); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetDisplayAttribute() FAILED due to " + "ITfCategoryMgr::GetGUID() failed", + this)); + return hr; + } + + RefPtr<ITfDisplayAttributeMgr> displayAttrMgr = GetDisplayAttributeMgr(); + if (NS_WARN_IF(!displayAttrMgr)) { + return E_FAIL; + } + RefPtr<ITfDisplayAttributeInfo> info; + hr = displayAttrMgr->GetDisplayAttributeInfo(guid, getter_AddRefs(info), + nullptr); + if (FAILED(hr) || !info) { + MOZ_LOG(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=%s", + this, aRangeNew, ToString(mComposition).c_str())); + + if (mComposition.isNothing()) { + 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->GetView()); + 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; + } + + if (mComposition->StartOffset() == compStart && + mComposition->Length() == compLength) { + return S_OK; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartCompositionIfNecessary(), " + "restaring composition because of compostion range is changed " + "(range=%ld-%ld, mComposition=%s)", + this, compStart, compStart + compLength, + ToString(mComposition).c_str())); + + // If the queried composition length is different from the length + // of our composition string, OnUpdateComposition is being called + // because a part of the original composition was committed. + hr = RestartComposition(*mComposition, pComposition, composingRange); + if (FAILED(hr)) { + MOZ_LOG(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(Composition& aCurrentComposition, + ITfCompositionView* aCompositionView, + ITfRange* aNewRange) { + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + + LONG newStart, newLength; + HRESULT hr = GetRangeExtent(aNewRange, &newStart, &newLength); + LONG newEnd = newStart + newLength; + + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartComposition() FAILED " + "due to SelectionForTSF() 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 >= aCurrentComposition.EndOffset() || + newEnd <= aCurrentComposition.StartOffset()) { + RecordCompositionEndAction(); + RecordCompositionStartAction(aCompositionView, newStart, newLength, true); + return S_OK; + } + + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartComposition(aCompositionView=0x%p, " + "aNewRange=0x%p { newStart=%d, newLength=%d }), aCurrentComposition=%s, " + "selectionForTSF=%s", + this, aCompositionView, aNewRange, newStart, newLength, + ToString(aCurrentComposition).c_str(), + ToString(selectionForTSF).c_str())); + + // If the new range has an overlap with the current one, we should not commit + // the whole current range to avoid creating an odd undo transaction. + // I.e., the overlapped range which is being composed should not appear in + // undo transaction. + + // Backup current composition data and selection data. + Composition oldComposition = aCurrentComposition; + Selection oldSelection = *selectionForTSF; + + // Commit only the part of composition. + LONG keepComposingStartOffset = + std::max(oldComposition.StartOffset(), newStart); + LONG keepComposingEndOffset = std::min(oldComposition.EndOffset(), newEnd); + MOZ_ASSERT( + keepComposingStartOffset <= keepComposingEndOffset, + "Why keepComposingEndOffset is smaller than keepComposingStartOffset?"); + LONG keepComposingLength = keepComposingEndOffset - keepComposingStartOffset; + // Remove the overlapped part from the commit string. + nsAutoString commitString(oldComposition.DataRef()); + commitString.Cut(keepComposingStartOffset - oldComposition.StartOffset(), + keepComposingLength); + // Update the composition string. + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RestartComposition() FAILED " + "due to ContentForTSF() failure", + this)); + return E_FAIL; + } + contentForTSF->ReplaceTextWith(oldComposition.StartOffset(), + oldComposition.Length(), commitString); + MOZ_ASSERT(mComposition.isSome()); + // Record a compositionupdate action for commit the part of composing string. + PendingAction* action = LastOrNewPendingCompositionUpdate(); + if (mComposition.isSome()) { + action->mData = mComposition->DataRef(); + } + action->mRanges->Clear(); + // Note that we shouldn't append ranges when composition string + // is empty because it may cause TextComposition confused. + if (!action->mData.IsEmpty()) { + TextRange caretRange; + caretRange.mStartOffset = caretRange.mEndOffset = static_cast<uint32_t>( + oldComposition.StartOffset() + commitString.Length()); + caretRange.mRangeType = TextRangeType::eCaret; + action->mRanges->AppendElement(caretRange); + } + action->mIncomplete = false; + + // Record compositionend action. + RecordCompositionEndAction(); + + // Record compositionstart action only with the new start since this method + // hasn't restored composing string yet. + RecordCompositionStartAction(aCompositionView, newStart, 0, false); + + // Restore the latest text content and selection. + contentForTSF->ReplaceSelectedTextWith(nsDependentSubstring( + oldComposition.DataRef(), + keepComposingStartOffset - oldComposition.StartOffset(), + keepComposingLength)); + selectionForTSF = Some(oldSelection); + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RestartComposition() succeeded, " + "mComposition=%s, selectionForTSF=%s", + this, ToString(mComposition).c_str(), + ToString(selectionForTSF).c_str())); + + return S_OK; +} + +static bool GetColor(const TF_DA_COLOR& aTSFColor, nscolor& aResult) { + switch (aTSFColor.type) { + case TF_CT_SYSCOLOR: { + DWORD sysColor = ::GetSysColor(aTSFColor.nIndex); + aResult = + NS_RGB(GetRValue(sysColor), GetGValue(sysColor), GetBValue(sysColor)); + return true; + } + case TF_CT_COLORREF: + aResult = NS_RGB(GetRValue(aTSFColor.cr), GetGValue(aTSFColor.cr), + GetBValue(aTSFColor.cr)); + return true; + case TF_CT_NONE: + default: + return false; + } +} + +static bool GetLineStyle(TF_DA_LINESTYLE aTSFLineStyle, + TextRangeStyle::LineStyle& aTextRangeLineStyle) { + switch (aTSFLineStyle) { + case TF_LS_NONE: + aTextRangeLineStyle = TextRangeStyle::LineStyle::None; + return true; + case TF_LS_SOLID: + aTextRangeLineStyle = TextRangeStyle::LineStyle::Solid; + return true; + case TF_LS_DOT: + aTextRangeLineStyle = TextRangeStyle::LineStyle::Dotted; + return true; + case TF_LS_DASH: + aTextRangeLineStyle = TextRangeStyle::LineStyle::Dashed; + return true; + case TF_LS_SQUIGGLE: + aTextRangeLineStyle = TextRangeStyle::LineStyle::Wavy; + return true; + default: + return false; + } +} + +HRESULT +TSFTextStore::RecordCompositionUpdateAction() { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionUpdateAction(), mComposition=%s", + this, ToString(mComposition).c_str())); + + if (mComposition.isNothing()) { + 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->GetView()->GetRange(getter_AddRefs(composingRange)); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "FAILED due to mComposition->GetView()->GetRange() failure", + this)); + return hr; + } + + RefPtr<IEnumTfRanges> enumRanges; + hr = attrPropetry->EnumRanges(TfEditCookie(mEditCookie), + getter_AddRefs(enumRanges), composingRange); + if (FAILED(hr) || !enumRanges) { + MOZ_LOG(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. + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() FAILED " + "due to SelectionForTSF() failure", + this)); + return E_FAIL; + } + + PendingAction* action = LastOrNewPendingCompositionUpdate(); + action->mData = mComposition->DataRef(); + // The ranges might already have been initialized, however, if this is + // called again, that means we need to overwrite the ranges with current + // information. + action->mRanges->Clear(); + + // Note that we shouldn't append ranges when composition string + // is empty because it may cause TextComposition confused. + if (!action->mData.IsEmpty()) { + TextRange newRange; + // No matter if we have display attribute info or not, + // we always pass in at least one range to eCompositionChange + newRange.mStartOffset = 0; + newRange.mEndOffset = action->mData.Length(); + newRange.mRangeType = TextRangeType::eRawClause; + action->mRanges->AppendElement(newRange); + + RefPtr<ITfRange> range; + while (enumRanges->Next(1, getter_AddRefs(range), nullptr) == S_OK) { + if (NS_WARN_IF(!range)) { + break; + } + + LONG rangeStart = 0, rangeLength = 0; + if (FAILED(GetRangeExtent(range, &rangeStart, &rangeLength))) { + continue; + } + // The range may include out of composition string. We should ignore + // outside of the composition string. + LONG start = std::min(std::max(rangeStart, mComposition->StartOffset()), + mComposition->EndOffset()); + LONG end = std::max( + std::min(rangeStart + rangeLength, mComposition->EndOffset()), + mComposition->StartOffset()); + LONG length = end - start; + if (length < 0) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionUpdateAction() " + "ignores invalid range (%d-%d)", + this, rangeStart - mComposition->StartOffset(), + rangeStart - mComposition->StartOffset() + 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->StartOffset(), + rangeStart - mComposition->StartOffset() + rangeLength)); + continue; + } + + TextRange newRange; + newRange.mStartOffset = + static_cast<uint32_t>(start - mComposition->StartOffset()); + // The end of the last range in the array is + // always kept at the end of composition + newRange.mEndOffset = mComposition->Length(); + + TF_DISPLAYATTRIBUTE attr; + hr = GetDisplayAttribute(attrPropetry, range, &attr); + if (FAILED(hr)) { + newRange.mRangeType = TextRangeType::eRawClause; + } else { + newRange.mRangeType = GetGeckoSelectionValue(attr); + if (GetColor(attr.crText, newRange.mRangeStyle.mForegroundColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_FOREGROUND_COLOR; + } + if (GetColor(attr.crBk, newRange.mRangeStyle.mBackgroundColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_BACKGROUND_COLOR; + } + if (GetColor(attr.crLine, newRange.mRangeStyle.mUnderlineColor)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_UNDERLINE_COLOR; + } + if (GetLineStyle(attr.lsStyle, newRange.mRangeStyle.mLineStyle)) { + newRange.mRangeStyle.mDefinedStyles |= + TextRangeStyle::DEFINED_LINESTYLE; + newRange.mRangeStyle.mIsBoldLine = attr.fBoldLine != 0; + } + } + + TextRange& lastRange = action->mRanges->LastElement(); + if (lastRange.mStartOffset == newRange.mStartOffset) { + // Replace range if last range is the same as this one + // So that ranges don't overlap and confuse the editor + lastRange = newRange; + } else { + lastRange.mEndOffset = newRange.mStartOffset; + action->mRanges->AppendElement(newRange); + } + } + + // We need to hack for Korean Input System which is Korean standard TIP. + // It sets no change style to IME selection (the selection is always only + // one). So, the composition string looks like normal (or committed) + // string. At this time, current selection range is same as the + // composition string range. Other applications set a wide caret which + // covers the composition string, however, Gecko doesn't support the wide + // caret drawing now (Gecko doesn't support XOR drawing), unfortunately. + // For now, we should change the range style to undefined. + if (!selectionForTSF->Collapsed() && action->mRanges->Length() == 1) { + TextRange& range = action->mRanges->ElementAt(0); + LONG start = selectionForTSF->MinOffset(); + LONG end = selectionForTSF->MaxOffset(); + if (range.mStartOffset == start - mComposition->StartOffset() && + range.mEndOffset == end - mComposition->StartOffset() && + range.mRangeStyle.IsNoChangeStyle()) { + range.mRangeStyle.Clear(); + // The looks of selected type is better than others. + range.mRangeType = TextRangeType::eSelectedRawClause; + } + } + + // The caret position has to be collapsed. + uint32_t caretPosition = static_cast<uint32_t>( + selectionForTSF->MaxOffset() - mComposition->StartOffset()); + + // If caret is in the target clause and it doesn't have specific style, + // the target clause will be painted as normal selection range. Since + // caret shouldn't be in selection range on Windows, we shouldn't append + // caret range in such case. + const TextRange* targetClause = action->mRanges->GetTargetClause(); + if (!targetClause || targetClause->mRangeStyle.IsDefined() || + caretPosition < targetClause->mStartOffset || + caretPosition > targetClause->mEndOffset) { + TextRange caretRange; + caretRange.mStartOffset = caretRange.mEndOffset = caretPosition; + caretRange.mRangeType = TextRangeType::eCaret; + action->mRanges->AppendElement(caretRange); + } + } + + action->mIncomplete = false; + + MOZ_LOG(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=%s, " + "aDispatchCompositionChangeEvent=%s), mComposition=%s", + this, pSelection ? mozilla::ToString(*pSelection).c_str() : "nullptr", + GetBoolName(aDispatchCompositionChangeEvent), + ToString(mComposition).c_str())); + + MOZ_ASSERT(IsReadWriteLocked()); + + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "SelectionForTSF() failure", + this)); + return E_FAIL; + } + + MaybeDispatchKeyboardEventAsProcessedByIME(); + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "destroyed during dispatching a keyboard event", + this)); + return E_FAIL; + } + + // If actually the range is not changing, we should do nothing. + // Perhaps, we can ignore the difference change because it must not be + // important for following edit. + if (selectionForTSF->EqualsExceptDirection(*pSelection)) { + MOZ_LOG(sTextStoreLog, LogLevel::Warning, + ("0x%p TSFTextStore::SetSelectionInternal() Succeeded but " + "did nothing because the selection range isn't changing", + this)); + selectionForTSF->SetSelection(*pSelection); + return S_OK; + } + + if (mComposition.isSome()) { + if (aDispatchCompositionChangeEvent) { + HRESULT hr = RestartCompositionIfNecessary(); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::SetSelectionInternal() FAILED due to " + "RestartCompositionIfNecessary() failure", + this)); + return hr; + } + } + if (pSelection->acpStart < mComposition->StartOffset() || + pSelection->acpEnd > mComposition->EndOffset()) { + MOZ_LOG(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.isSome() && mPendingTextChangeData.IsValid() && + !mPendingTextChangeData.mCausedOnlyByComposition) { + uint32_t startOffset = static_cast<uint32_t>(selectionInContent.acpStart); + uint32_t endOffset = static_cast<uint32_t>(selectionInContent.acpEnd); + if (mPendingTextChangeData.mStartOffset >= endOffset) { + // Setting selection before any changed ranges is fine. + } else if (mPendingTextChangeData.mRemovedEndOffset <= startOffset) { + // Setting selection after removed range is fine with following + // adjustment. + selectionInContent.acpStart += mPendingTextChangeData.Difference(); + selectionInContent.acpEnd += mPendingTextChangeData.Difference(); + } else if (startOffset == endOffset) { + // Moving caret position may be fine in most cases even if the insertion + // point has already gone but in this case, composition will be inserted + // to unexpected position, though. + // It seems that moving caret into middle of the new text is odd. + // Perhaps, end of it is expected by users in most cases. + selectionInContent.acpStart = mPendingTextChangeData.mAddedEndOffset; + selectionInContent.acpEnd = selectionInContent.acpStart; + } else { + // Otherwise, i.e., setting range has already gone, we cannot set + // selection properly. + MOZ_LOG(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::Type::eSetSelection; + action->mSelectionStart = selectionInContent.acpStart; + action->mSelectionLength = + selectionInContent.acpEnd - selectionInContent.acpStart; + action->mSelectionReversed = (selectionInContent.style.ase == TS_AE_START); + + // Use TSF specified selection for updating mSelectionForTSF. + selectionForTSF->SetSelection(*pSelection); + + return S_OK; +} + +STDMETHODIMP +TSFTextStore::SetSelection(ULONG ulCount, const TS_SELECTION_ACP* pSelection) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::SetSelection(ulCount=%lu, pSelection=%s }), " + "mComposition=%s", + this, ulCount, + pSelection ? mozilla::ToString(pSelection).c_str() : "nullptr", + ToString(mComposition).c_str())); + + if (!IsReadWriteLocked()) { + MOZ_LOG(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=%s", + this, acpStart, acpEnd, pchPlain, cchPlainReq, pcchPlainOut, prgRunInfo, + ulRunInfoReq, pulRunInfoOut, pacpNext, ToString(mComposition).c_str())); + + 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; + } + + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetText() FAILED due to " + "ContentForTSF() failure", + this)); + return E_FAIL; + } + if (contentForTSF->TextRef().Length() < static_cast<uint32_t>(acpStart)) { + MOZ_LOG(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->TextRef().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->TextRef().Length() - + static_cast<uint32_t>(acpStart) + : static_cast<uint32_t>(acpEnd - acpStart); + if (cchPlainReq && cchPlainReq - 1 < length) { + length = cchPlainReq - 1; + } + if (length) { + if (pchPlain && cchPlainReq) { + const char16_t* startChar = + contentForTSF->TextRef().BeginReading() + acpStart; + memcpy(pchPlain, startChar, length * sizeof(*pchPlain)); + pchPlain[length] = 0; + *pcchPlainOut = length; + } + if (prgRunInfo && ulRunInfoReq) { + prgRunInfo->uCount = length; + prgRunInfo->type = TS_RT_PLAIN; + if (pulRunInfoOut) *pulRunInfoOut = 1; + } + if (pacpNext) *pacpNext = acpStart + length; + } + + MOZ_LOG(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=%s", + this, dwFlags == TS_ST_CORRECTION ? "TS_ST_CORRECTION" : "not-specified", + acpStart, acpEnd, pchText, + pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", cch, + pChange, ToString(mComposition).c_str())); + + // Per SDK documentation, and since we don't have better + // ways to do this, this method acts as a helper to + // call SetSelection followed by InsertTextAtSelection + if (!IsReadWriteLocked()) { + MOZ_LOG(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; +} + +// static +bool TSFTextStore::ShouldSetInputScopeOfURLBarToDefault() { + // FYI: Google Japanese Input may be an IMM-IME. If it's installed on + // Win7, it's always IMM-IME. Otherwise, basically, it's a TIP. + // However, if it's installed on Win7 and has not been updated yet + // after the OS is upgraded to Win8 or later, it's still an IMM-IME. + // Therefore, we also need to check with IMMHandler here. + if (!TSFPrefs::ShouldSetInputScopeOfURLBarToDefault()) { + return false; + } + + if (IMMHandler::IsGoogleJapaneseInputActive()) { + return true; + } + + switch (TSFStaticSink::ActiveTIP()) { + case TextInputProcessorID::eMicrosoftIMEForJapanese: + case TextInputProcessorID::eGoogleJapaneseInput: + case TextInputProcessorID::eMicrosoftBopomofo: + case TextInputProcessorID::eMicrosoftChangJie: + case TextInputProcessorID::eMicrosoftPhonetic: + case TextInputProcessorID::eMicrosoftQuick: + case TextInputProcessorID::eMicrosoftNewChangJie: + case TextInputProcessorID::eMicrosoftNewPhonetic: + case TextInputProcessorID::eMicrosoftNewQuick: + case TextInputProcessorID::eMicrosoftPinyin: + case TextInputProcessorID::eMicrosoftPinyinNewExperienceInputStyle: + case TextInputProcessorID::eMicrosoftOldHangul: + case TextInputProcessorID::eMicrosoftWubi: + return true; + case TextInputProcessorID::eMicrosoftIMEForKorean: + return IsWin8OrLater(); + default: + return false; + } +} + +void TSFTextStore::SetInputScope(const nsString& aHTMLInputType, + const nsString& aHTMLInputInputMode, + bool aInPrivateBrowsing) { + mInputScopes.Clear(); + + // IME may refer only first input scope, but we will append inputmode's + // input scopes too like Chrome since IME may refer it. + IMEHandler::AppendInputScopeFromType(aHTMLInputType, mInputScopes); + IMEHandler::AppendInputScopeFromInputmode(aHTMLInputInputMode, mInputScopes); + + if (aInPrivateBrowsing) { + mInputScopes.AppendElement(IS_PRIVATE); + } +} + +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: { + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + paAttrVals[count].varValue.vt = VT_BOOL; + paAttrVals[count].varValue.boolVal = + selectionForTSF.isSome() && + selectionForTSF->GetWritingMode().IsVertical() + ? VARIANT_TRUE + : VARIANT_FALSE; + break; + } + case eTextOrientation: { + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + paAttrVals[count].varValue.vt = VT_I4; + paAttrVals[count].varValue.lVal = + selectionForTSF.isSome() && + selectionForTSF->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; + } + + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetEndACP() FAILED due to " + "ContentForTSF() failure", + this)); + return E_FAIL; + } + *pacp = static_cast<LONG>(contentForTSF->TextRef().Length()); + return S_OK; +} + +STDMETHODIMP +TSFTextStore::GetActiveView(TsViewCookie* pvcView) { + MOZ_LOG(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.isSome() && 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 queryCharAtPointEvent(true, eQueryCharacterAtPoint, + mWidget); + mWidget->InitEvent(queryCharAtPointEvent, &ourPt); + + // FYI: WidgetQueryContentEvent may cause flushing pending layout and it + // may cause focus change or something. + RefPtr<TSFTextStore> kungFuDeathGrip(this); + DispatchEvent(queryCharAtPointEvent); + if (!mWidget || mWidget->Destroyed()) { + MOZ_LOG(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(), queryCharAtPointEvent={ " + "mReply=%s }", + this, ToString(queryCharAtPointEvent.mReply).c_str())); + + if (NS_WARN_IF(queryCharAtPointEvent.Failed())) { + 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) && queryCharAtPointEvent.DidNotFindChar()) { + 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. + NS_WARNING_ASSERTION(queryCharAtPointEvent.DidNotFindTentativeCaretOffset(), + "Tentative caret offset was not found"); + + uint32_t offset; + + // If dwFlags includes GXFPF_ROUND_NEAREST, we should return tentative + // caret offset (MSDN calls it "range position"). + if (dwFlags & GXFPF_ROUND_NEAREST) { + offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0); + } else if (queryCharAtPointEvent.FoundChar()) { + // Otherwise, we should return character offset whose bounding box contains + // the point. + offset = queryCharAtPointEvent.mReply->StartOffset(); + } else { + // If the point isn't in any character's bounding box but we need to return + // the nearest character from the point, we should *guess* the character + // offset since there is no inexpensive API to check it strictly. + // XXX If we retrieve 2 bounding boxes, one is before the offset and + // the other is after the offset, we could resolve the offset. + // However, dispatching 2 eQueryTextRect may be expensive. + + // So, use tentative offset for now. + offset = queryCharAtPointEvent.mReply->mTentativeCaretOffset.valueOr(0); + + // However, if it's after the last character, we need to decrement the + // offset. + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetACPFromPoint() FAILED due to " + "ContentForTSF() failure", + this)); + return E_FAIL; + } + if (contentForTSF->TextRef().Length() <= offset) { + // If the tentative caret is after the last character, let's return + // the last character's offset. + offset = contentForTSF->TextRef().Length() - 1; + } + } + + if (NS_WARN_IF(offset > LONG_MAX)) { + MOZ_LOG(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), " + "IsHandlingCompositionInParent()=%s, " + "IsHandlingCompositionInContent()=%s, mContentForTSF=%s, " + "mSelectionForTSF=%s, mComposition=%s, mDeferNotifyingTSF=%s, " + "mWaitingQueryLayout=%s, IMEHandler::IsA11yHandlingNativeCaret()=%s", + this, vcView, acpStart, acpEnd, prc, pfClipped, + GetBoolName(IsHandlingCompositionInParent()), + GetBoolName(IsHandlingCompositionInContent()), + mozilla::ToString(mContentForTSF).c_str(), + ToString(mSelectionForTSF).c_str(), ToString(mComposition).c_str(), + GetBoolName(mDeferNotifyingTSF), GetBoolName(mWaitingQueryLayout), + GetBoolName(IMEHandler::IsA11yHandlingNativeCaret()))); + + 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; + } + + // According to MSDN, ITextStoreACP::GetTextExt() should return + // TS_E_INVALIDARG when acpStart and acpEnd are same (i.e., collapsed range). + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms538435(v=vs.85).aspx + // > TS_E_INVALIDARG: The specified start and end character positions are + // > equal. + // However, some TIPs (including Microsoft's Chinese TIPs!) call this with + // collapsed range and if we return TS_E_INVALIDARG, they stops showing their + // owning window or shows it but odd position. So, we should just return + // error only when acpStart and/or acpEnd are really odd. + + if (acpStart < 0 || acpEnd < acpStart) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "invalid position", + this)); + return TS_E_INVALIDPOS; + } + + mWaitingQueryLayout = false; + + if (IsHandlingCompositionInContent() && mContentForTSF.isSome() && + mContentForTSF->HasOrHadComposition() && + mContentForTSF->IsLayoutChanged() && + mContentForTSF->MinModifiedOffset().value() > + static_cast<uint32_t>(LONG_MAX)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt(), FAILED due to the text " + "is too big for TSF (cannot treat modified offset as LONG), " + "mContentForTSF=%s", + this, ToString(mContentForTSF).c_str())); + return E_FAIL; + } + + // At Windows 10 build 17643 (an insider preview for RS5), Microsoft fixed + // the bug of TS_E_NOLAYOUT (even when we returned TS_E_NOLAYOUT, TSF + // returned E_FAIL to TIP). However, until we drop to support older Windows + // and all TIPs are aware of TS_E_NOLAYOUT result, we need to keep returning + // S_OK and available rectangle only for them. + if (!MaybeHackNoErrorLayoutBugs(acpStart, acpEnd) && + mContentForTSF.isSome() && mContentForTSF->IsLayoutChangedAt(acpEnd)) { + MOZ_LOG(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 queryTextRectEvent(true, eQueryTextRect, mWidget); + mWidget->InitEvent(queryTextRectEvent); + + WidgetQueryContentEvent::Options options; + int64_t startOffset = acpStart; + if (mComposition.isSome()) { + // If there is a composition, TSF must want character rects related to + // the composition. Therefore, we should use insertion point relative + // query because the composition might be at different position from + // the position where TSFTextStore believes it at. + options.mRelativeToInsertionPoint = true; + startOffset -= mComposition->StartOffset(); + } else if (IsHandlingCompositionInParent() && mContentForTSF.isSome() && + mContentForTSF->HasOrHadComposition()) { + // If there was a composition and its commit event hasn't been dispatched + // yet, ContentCacheInParent is still open for relative offset query from + // the latest composition. + options.mRelativeToInsertionPoint = true; + startOffset -= mContentForTSF->LatestCompositionRange()->StartOffset(); + } else if (!CanAccessActualContentDirectly()) { + // If TSF/TIP cannot access actual content directly, there may be pending + // text and/or selection changes which have not been notified TSF yet. + // Therefore, we should use relative to insertion point query since + // TSF/TIP computes the offset from the cached selection. + options.mRelativeToInsertionPoint = true; + startOffset -= mSelectionForTSF->StartOffset(); + } + // ContentEventHandler and ContentCache return actual caret rect when + // the queried range is collapsed and selection is collapsed at the + // queried range. Then, its height (in horizontal layout, width in vertical + // layout) may be different from actual font height of the line. In such + // case, users see "dancing" of candidate or suggest window of TIP. + // For preventing it, we should query text rect with at least 1 length. + uint32_t length = std::max(static_cast<int32_t>(acpEnd - acpStart), 1); + queryTextRectEvent.InitForQueryTextRect(startOffset, length, options); + + DispatchEvent(queryTextRectEvent); + if (NS_WARN_IF(queryTextRectEvent.Failed())) { + MOZ_LOG(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 (queryTextRectEvent.mReply->mRect.Width() <= 0) { + queryTextRectEvent.mReply->mRect.SetWidth(1); + } + if (queryTextRectEvent.mReply->mRect.Height() <= 0) { + queryTextRectEvent.mReply->mRect.SetHeight(1); + } + + // convert to unclipped screen rect + nsWindow* refWindow = + static_cast<nsWindow*>(!!queryTextRectEvent.mReply->mFocusedWidget + ? queryTextRectEvent.mReply->mFocusedWidget + : static_cast<nsIWidget*>(mWidget.get())); + // Result rect is in top level widget coordinates + refWindow = refWindow->GetTopLevelWindow(false); + if (!refWindow) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetTextExt() FAILED due to " + "no top level window", + this)); + return E_FAIL; + } + + queryTextRectEvent.mReply->mRect.MoveBy(refWindow->WidgetToScreenOffset()); + + // get bounding screen rect to test for clipping + if (!GetScreenExtInternal(*prc)) { + MOZ_LOG(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, queryTextRectEvent.mReply->mRect.X(), + queryTextRectEvent.mReply->mRect.Y(), + queryTextRectEvent.mReply->mRect.XMost(), + queryTextRectEvent.mReply->mRect.YMost()); + if (!::IntersectRect(prc, prc, &textRect)) + // Text is not visible + ::SetRectEmpty(prc); + + // not equal if text rect was clipped + *pfClipped = !::EqualRect(prc, &textRect); + + // ATOK 2011 - 2016 refers native caret position and size on windows whose + // class name is one of Mozilla's windows for deciding candidate window + // position. Additionally, ATOK 2015 and earlier behaves really odd when + // we don't create native caret. Therefore, we need to create native caret + // only when ATOK 2011 - 2015 is active (i.e., not necessary for ATOK 2016). + // However, if a11y module is handling native caret, we shouldn't touch it. + // Note that ATOK must require the latest information of the caret. So, + // even if we'll create native caret later, we need to creat it here with + // current information. + if (!IMEHandler::IsA11yHandlingNativeCaret() && + TSFPrefs::NeedToCreateNativeCaretForLegacyATOK() && + TSFStaticSink::IsATOKReferringNativeCaretActive() && + mComposition.isSome() && + mComposition->IsOffsetInRangeOrEndOffset(acpStart) && + mComposition->IsOffsetInRangeOrEndOffset(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; +} + +bool TSFTextStore::MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd) { + // When ITextStoreACP::GetTextExt() returns TS_E_NOLAYOUT, TSF returns E_FAIL + // to its caller (typically, active TIP). Then, most TIPs abort current job + // or treat such application as non-GUI apps. E.g., some of them give up + // showing candidate window, some others show candidate window at top-left of + // the screen. For avoiding this issue, when there is composition (until + // composition is actually committed in remote content), we should not + // return TS_E_NOLAYOUT error for TIPs whose some features are broken by + // this issue. + // Note that ideally, this issue should be avoided by each TIP since this + // won't be fixed at least on non-latest Windows. Actually, Google Japanese + // Input (based on Mozc) does it. When GetTextExt() returns E_FAIL, TIPs + // should try to check result of GetRangeFromPoint() because TSF returns + // TS_E_NOLAYOUT correctly in this case. See: + // https://github.com/google/mozc/blob/6b878e31fb6ac4347dc9dfd8ccc1080fe718479f/src/win32/tip/tip_range_util.cc#L237-L257 + + if (!IsHandlingCompositionInContent() || mContentForTSF.isNothing() || + !mContentForTSF->HasOrHadComposition() || + !mContentForTSF->IsLayoutChangedAt(aACPEnd)) { + return false; + } + + MOZ_ASSERT(mComposition.isNothing() || + mComposition->StartOffset() == + mContentForTSF->LatestCompositionRange()->StartOffset()); + MOZ_ASSERT(mComposition.isNothing() || + mComposition->EndOffset() == + mContentForTSF->LatestCompositionRange()->EndOffset()); + + // If TSF does not have the bug, we need to hack only with a few TIPs. + static const bool sAlllowToStopHackingIfFine = + IsWindows10BuildOrLater(17643) && + TSFPrefs::AllowToStopHackingOnBuild17643OrLater(); + + // We need to compute active TIP now. This may take a couple of milliseconds, + // however, it'll be cached, so, must be faster than check active TIP every + // GetTextExt() calls. + const Maybe<Selection>& selectionForTSF = SelectionForTSF(); + switch (TSFStaticSink::ActiveTIP()) { + // MS IME for Japanese doesn't support asynchronous handling at deciding + // its suggest list window position. The feature was implemented + // starting from Windows 8. And also we may meet same trouble in e10s + // mode on Win7. So, we should never return TS_E_NOLAYOUT to MS IME for + // Japanese. + case TextInputProcessorID::eMicrosoftIMEForJapanese: + // Basically, MS-IME tries to retrieve whole composition string rect + // at deciding suggest window immediately after unlocking the document. + // However, in e10s mode, the content hasn't updated yet in most cases. + // Therefore, if the first character at the retrieving range rect is + // available, we should use it as the result. + // Note that according to bug 1609675, MS-IME for Japanese itself does + // not handle TS_E_NOLAYOUT correctly at least on Build 18363.657 (1909). + if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtFirstChar() && + aACPStart < aACPEnd) { + aACPEnd = aACPStart; + break; + } + if (sAlllowToStopHackingIfFine) { + return false; + } + // Although, the condition is not clear, MS-IME sometimes retrieves the + // caret rect immediately after modifying the composition string but + // before unlocking the document. In such case, we should return the + // nearest character rect. + if (TSFPrefs::DoNotReturnNoLayoutErrorToMSJapaneseIMEAtCaret() && + selectionForTSF.isSome() && aACPStart == aACPEnd && + selectionForTSF->Collapsed() && + selectionForTSF->EndOffset() == aACPEnd) { + int32_t minOffsetOfLayoutChanged = + static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value()); + aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0); + } else { + return false; + } + break; + // The bug of Microsoft Office IME 2010 for Japanese is similar to + // MS-IME for Win 8.1 and Win 10. Newer version of MS Office IME is not + // released yet. So, we can hack it without prefs because there must be + // no developers who want to disable this hack for tests. + // XXX We have not tested with Microsoft Office IME 2010 since it's + // installable only with Win7 and Win8 (i.e., cannot install Win8.1 + // and Win10), and requires upgrade to Win10. + case TextInputProcessorID::eMicrosoftOfficeIME2010ForJapanese: + // Basically, MS-IME tries to retrieve whole composition string rect + // at deciding suggest window immediately after unlocking the document. + // However, in e10s mode, the content hasn't updated yet in most cases. + // Therefore, if the first character at the retrieving range rect is + // available, we should use it as the result. + if (aACPStart < aACPEnd) { + aACPEnd = aACPStart; + } + // Although, the condition is not clear, MS-IME sometimes retrieves the + // caret rect immediately after modifying the composition string but + // before unlocking the document. In such case, we should return the + // nearest character rect. + else if (aACPStart == aACPEnd && selectionForTSF.isSome() && + selectionForTSF->Collapsed() && + selectionForTSF->EndOffset() == aACPEnd) { + int32_t minOffsetOfLayoutChanged = + static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value()); + aACPEnd = aACPStart = std::max(minOffsetOfLayoutChanged - 1, 0); + } else { + return false; + } + break; + // ATOK fails to handle TS_E_NOLAYOUT only when it decides the position of + // suggest window. In such case, ATOK tries to query rect of whole or a + // part of composition string. + // FYI: ATOK changes their implementation around candidate window and + // suggest widget at ATOK 2016. Therefore, there are some differences + // ATOK 2015 (or older) and ATOK 2016 (or newer). + // FYI: ATOK 2017 stops referring our window class name. I.e., ATOK 2016 + // and older may behave differently only on Gecko but this must be + // finished from ATOK 2017. + // FYI: For testing with legacy ATOK, we should hack it even if current ATOK + // refers native caret rect on windows whose window class is one of + // Mozilla window classes and we stop creating native caret for ATOK + // because creating native caret causes ATOK refers caret position + // when GetTextExt() returns TS_E_NOLAYOUT. + case TextInputProcessorID::eATOK2011: + case TextInputProcessorID::eATOK2012: + case TextInputProcessorID::eATOK2013: + case TextInputProcessorID::eATOK2014: + case TextInputProcessorID::eATOK2015: + // ATOK 2016 and later may temporarily show candidate window at odd + // position when you convert a word quickly (e.g., keep pressing + // space bar). So, on ATOK 2016 or later, we need to keep hacking the + // result of GetTextExt(). + if (sAlllowToStopHackingIfFine) { + return false; + } + // If we'll create native caret where we paint our caret. Then, ATOK + // will refer native caret. So, we don't need to hack anything in + // this case. + if (TSFPrefs::NeedToCreateNativeCaretForLegacyATOK()) { + MOZ_ASSERT(TSFStaticSink::IsATOKReferringNativeCaretActive()); + return false; + } + [[fallthrough]]; + case TextInputProcessorID::eATOK2016: + case TextInputProcessorID::eATOKUnknown: + if (!TSFPrefs::DoNotReturnNoLayoutErrorToATOKOfCompositionString()) { + return false; + } + // If the range is in the composition string, we should return rectangle + // in it as far as possible. + if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset( + aACPStart) || + !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset( + aACPEnd)) { + return false; + } + break; + // Japanist 10 fails to handle TS_E_NOLAYOUT when it decides the position + // of candidate window. In such case, Japanist shows candidate window at + // top-left of the screen. So, we should return the nearest caret rect + // where we know. This is Japanist's bug. So, even after build 17643, + // we need this hack. + case TextInputProcessorID::eJapanist10: + if (!TSFPrefs:: + DoNotReturnNoLayoutErrorToJapanist10OfCompositionString()) { + return false; + } + if (!mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset( + aACPStart) || + !mContentForTSF->LatestCompositionRange()->IsOffsetInRangeOrEndOffset( + aACPEnd)) { + return false; + } + break; + // Free ChangJie 2010 doesn't handle ITfContextView::GetTextExt() properly. + // This must be caused by the bug of TSF since Free ChangJie works fine on + // build 17643 and later. + case TextInputProcessorID::eFreeChangJie: + if (sAlllowToStopHackingIfFine) { + return false; + } + if (!TSFPrefs::DoNotReturnNoLayoutErrorToFreeChangJie()) { + return false; + } + aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset(); + aACPStart = std::min(aACPStart, aACPEnd); + break; + // Some Traditional Chinese TIPs of Microsoft don't show candidate window + // in e10s mode on Win8 or later. + case TextInputProcessorID::eMicrosoftQuick: + if (sAlllowToStopHackingIfFine) { + return false; // MS Quick works fine with Win10 build 17643. + } + [[fallthrough]]; + case TextInputProcessorID::eMicrosoftChangJie: + if (!IsWin8OrLater() || + !TSFPrefs::DoNotReturnNoLayoutErrorToMSTraditionalTIP()) { + return false; + } + aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset(); + aACPStart = std::min(aACPStart, aACPEnd); + break; + // Some Simplified Chinese TIPs of Microsoft don't show candidate window + // in e10s mode on Win8 or later. + // FYI: Only Simplified Chinese TIPs of Microsoft still require this hack + // because they sometimes do not show candidate window when we return + // TS_E_NOLAYOUT for first query. Note that even when they show + // candidate window properly, we return TS_E_NOLAYOUT and following + // log looks same as when they don't show candidate window. Perhaps, + // there is stateful cause or race in them. + case TextInputProcessorID::eMicrosoftPinyin: + case TextInputProcessorID::eMicrosoftWubi: + if (!IsWin8OrLater() || + !TSFPrefs::DoNotReturnNoLayoutErrorToMSSimplifiedTIP()) { + return false; + } + aACPEnd = mContentForTSF->LatestCompositionRange()->StartOffset(); + aACPStart = std::min(aACPStart, aACPEnd); + break; + default: + return false; + } + + // If we hack the queried range for active TIP, that means we should not + // return TS_E_NOLAYOUT even if hacked offset is still modified. So, as + // far as possible, we should adjust the offset. + MOZ_ASSERT(mContentForTSF->IsLayoutChanged()); + bool collapsed = aACPStart == aACPEnd; + // Note that even if all characters in the editor or the composition + // string was modified, 0 or start offset of the composition string is + // useful because it may return caret rect or old character's rect which + // the user still see. That must be useful information for TIP. + int32_t firstModifiedOffset = + static_cast<int32_t>(mContentForTSF->MinModifiedOffset().value()); + LONG lastUnmodifiedOffset = std::max(firstModifiedOffset - 1, 0); + if (mContentForTSF->IsLayoutChangedAt(aACPStart)) { + if (aACPStart >= mContentForTSF->LatestCompositionRange()->StartOffset()) { + // If mContentForTSF has last composition string and current + // composition string, we can assume that ContentCacheInParent has + // cached rects of composition string at least length of current + // composition string. Otherwise, we can assume that rect for + // first character of composition string is stored since it was + // selection start or caret position. + LONG maxCachedOffset = + mContentForTSF->LatestCompositionRange()->EndOffset(); + if (mContentForTSF->LastComposition().isSome()) { + maxCachedOffset = std::min( + maxCachedOffset, mContentForTSF->LastComposition()->EndOffset()); + } + aACPStart = std::min(aACPStart, maxCachedOffset); + } + // Otherwise, we don't know which character rects are cached. So, we + // need to use first unmodified character's rect in this case. Even + // if there is no character, the query event will return caret rect + // instead. + else { + aACPStart = lastUnmodifiedOffset; + } + MOZ_ASSERT(aACPStart <= aACPEnd); + } + + // If TIP requests caret rect with collapsed range, we should keep + // collapsing the range. + if (collapsed) { + aACPEnd = aACPStart; + } + // Let's set aACPEnd to larger offset of last unmodified offset or + // aACPStart which may be the first character offset of the composition + // string. However, some TIPs may want to know the right edge of the + // range. Therefore, if aACPEnd is in composition string and active TIP + // doesn't retrieve caret rect (i.e., the range isn't collapsed), we + // should keep using the original aACPEnd. Otherwise, we should set + // aACPEnd to larger value of aACPStart and lastUnmodifiedOffset. + else if (mContentForTSF->IsLayoutChangedAt(aACPEnd) && + !mContentForTSF->LatestCompositionRange() + ->IsOffsetInRangeOrEndOffset(aACPEnd)) { + aACPEnd = std::max(aACPStart, lastUnmodifiedOffset); + } + + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::HackNoErrorLayoutBugs() hacked the queried range " + "for not returning TS_E_NOLAYOUT, new values are: " + "aACPStart=%d, aACPEnd=%d", + this, aACPStart, aACPEnd)); + + return true; +} + +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->bottom = 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 queryEditorRectEvent(true, eQueryEditorRect, mWidget); + mWidget->InitEvent(queryEditorRectEvent); + DispatchEvent(queryEditorRectEvent); + if (queryEditorRectEvent.Failed()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::GetScreenExtInternal() FAILED due to " + "eQueryEditorRect failure", + this)); + return false; + } + + nsWindow* refWindow = + static_cast<nsWindow*>(!!queryEditorRectEvent.mReply->mFocusedWidget + ? queryEditorRectEvent.mReply->mFocusedWidget + : static_cast<nsIWidget*>(mWidget.get())); + // Result rect is in top level widget coordinates + refWindow = refWindow->GetTopLevelWindow(false); + if (!refWindow) { + MOZ_LOG(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(queryEditorRectEvent.mReply->mRect, boundRect); + if (!boundRect.IsEmpty()) { + boundRect.MoveBy(refWindow->WidgetToScreenOffset()); + ::SetRect(&aScreenExt, boundRect.X(), boundRect.Y(), boundRect.XMost(), + boundRect.YMost()); + } else { + ::SetRectEmpty(&aScreenExt); + } + + MOZ_LOG(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), mComposition=%s", + this, + dwFlags == 0 ? "0" + : dwFlags == TF_IAS_NOQUERY ? "TF_IAS_NOQUERY" + : dwFlags == TF_IAS_QUERYONLY ? "TF_IAS_QUERYONLY" + : "Unknown", + pchText, pchText && cch ? GetEscapedUTF8String(pchText, cch).get() : "", + cch, pacpStart, pacpEnd, pChange, ToString(mComposition).c_str())); + + if (cch && !pchText) { + MOZ_LOG(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 + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelection() FAILED due to " + "SelectionForTSF() 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), mComposition=%s", + this, GetEscapedUTF8String(aInsertStr).get(), aTextChange, + ToString(mComposition).c_str())); + + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() failed " + "due to ContentForTSF() failure()", + this)); + return false; + } + + MaybeDispatchKeyboardEventAsProcessedByIME(); + if (mDestroyed) { + MOZ_LOG( + sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() FAILED due to " + "destroyed during dispatching a keyboard event", + this)); + return false; + } + + TS_SELECTION_ACP oldSelection = contentForTSF->Selection()->ACPRef(); + if (mComposition.isNothing()) { + // Use a temporary composition to contain the text + PendingAction* compositionStart = mPendingActions.AppendElements(2); + PendingAction* compositionEnd = compositionStart + 1; + + compositionStart->mType = PendingAction::Type::eCompositionStart; + compositionStart->mSelectionStart = oldSelection.acpStart; + compositionStart->mSelectionLength = + oldSelection.acpEnd - oldSelection.acpStart; + compositionStart->mAdjustSelection = false; + + compositionEnd->mType = PendingAction::Type::eCompositionEnd; + compositionEnd->mData = aInsertStr; + compositionEnd->mSelectionStart = compositionStart->mSelectionStart; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::InsertTextAtSelectionInternal() " + "appending pending compositionstart and compositionend... " + "PendingCompositionStart={ mSelectionStart=%d, " + "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" " + "(Length()=%u), mSelectionStart=%d }", + this, compositionStart->mSelectionStart, + compositionStart->mSelectionLength, + GetEscapedUTF8String(compositionEnd->mData).get(), + compositionEnd->mData.Length(), compositionEnd->mSelectionStart)); + } + + contentForTSF->ReplaceSelectedTextWith(aInsertStr); + + if (aTextChange) { + aTextChange->acpStart = oldSelection.acpStart; + aTextChange->acpOldEnd = oldSelection.acpEnd; + aTextChange->acpNewEnd = contentForTSF->Selection()->EndOffset(); + } + + MOZ_LOG( + 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* aCompositionView, ITfRange* aRange, + bool aPreserveSelection) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionStartAction(" + "aCompositionView=0x%p, aRange=0x%p, aPreserveSelection=%s), " + "mComposition=%s", + this, aCompositionView, aRange, GetBoolName(aPreserveSelection), + ToString(mComposition).c_str())); + + LONG start = 0, length = 0; + HRESULT hr = GetRangeExtent(aRange, &start, &length); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to GetRangeExtent() failure", + this)); + return hr; + } + + return RecordCompositionStartAction(aCompositionView, start, length, + aPreserveSelection); +} + +HRESULT TSFTextStore::RecordCompositionStartAction( + ITfCompositionView* aCompositionView, LONG aStart, LONG aLength, + bool aPreserveSelection) { + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionStartAction(" + "aCompositionView=0x%p, aStart=%d, aLength=%d, aPreserveSelection=%s), " + "mComposition=%s", + this, aCompositionView, aStart, aLength, GetBoolName(aPreserveSelection), + ToString(mComposition).c_str())); + + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to ContentForTSF() failure", + this)); + return E_FAIL; + } + + MaybeDispatchKeyboardEventAsProcessedByIME(); + if (mDestroyed) { + MOZ_LOG( + sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED due to " + "destroyed during dispatching a keyboard event", + this)); + return false; + } + + CompleteLastActionIfStillIncomplete(); + + // TIP may have inserted text at selection before calling + // OnStartComposition(). In this case, we've already created a pending + // compositionend. If new composition replaces all commit string of the + // pending compositionend, we should cancel the pending compositionend and + // keep the previous composition normally. + // On Windows 7, MS-IME for Korean, MS-IME 2010 for Korean and MS Old Hangul + // may start composition with calling InsertTextAtSelection() and + // OnStartComposition() with this order (bug 1208043). + // On Windows 10, MS Pinyin, MS Wubi, MS ChangJie and MS Quick commits + // last character and replace it with empty string with new composition + // when user removes last character of composition string with Backspace + // key (bug 1462257). + if (!aPreserveSelection && + IsLastPendingActionCompositionEndAt(aStart, aLength)) { + const PendingAction& pendingCompositionEnd = mPendingActions.LastElement(); + contentForTSF->RestoreCommittedComposition(aCompositionView, + pendingCompositionEnd); + mPendingActions.RemoveLastElement(); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionStartAction() " + "succeeded: restoring the committed string as composing string, " + "mComposition=%s, mSelectionForTSF=%s", + this, ToString(mComposition).c_str(), + ToString(mSelectionForTSF).c_str())); + return S_OK; + } + + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::Type::eCompositionStart; + action->mSelectionStart = aStart; + action->mSelectionLength = aLength; + + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionStartAction() FAILED " + "due to SelectionForTSF() 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(aCompositionView, *action, + aPreserveSelection); + MOZ_ASSERT(mComposition.isSome()); + action->mData = mComposition->DataRef(); + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::RecordCompositionStartAction() succeeded: " + "mComposition=%s, mSelectionForTSF=%s }", + this, ToString(mComposition).c_str(), + ToString(mSelectionForTSF).c_str())); + return S_OK; +} + +HRESULT +TSFTextStore::RecordCompositionEndAction() { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::RecordCompositionEndAction(), " + "mComposition=%s", + this, ToString(mComposition).c_str())); + + MOZ_ASSERT(mComposition.isSome()); + + if (mComposition.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to " + "no composition", + this)); + return false; + } + + MaybeDispatchKeyboardEventAsProcessedByIME(); + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due to " + "destroyed during dispatching a keyboard event", + this)); + return false; + } + + // If we're handling incomplete composition update or already handled + // composition update, we can forget them since composition end will send + // the latest composition string and it overwrites the composition string + // even if we dispatch eCompositionChange event before that. So, let's + // forget all composition updates now. + RemoveLastCompositionUpdateActions(); + PendingAction* action = mPendingActions.AppendElement(); + action->mType = PendingAction::Type::eCompositionEnd; + action->mData = mComposition->DataRef(); + action->mSelectionStart = mComposition->StartOffset(); + + Maybe<Content>& contentForTSF = ContentForTSF(); + if (contentForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::RecordCompositionEndAction() FAILED due " + "to ContentForTSF() failure", + this)); + return E_FAIL; + } + contentForTSF->EndComposition(*action); + + // If this composition was restart but the composition doesn't modify + // anything, we should remove the pending composition for preventing to + // dispatch redundant composition events. + for (size_t i = mPendingActions.Length(), j = 1; i > 0; --i, ++j) { + PendingAction& pendingAction = mPendingActions[i - 1]; + if (pendingAction.mType == PendingAction::Type::eCompositionStart) { + if (pendingAction.mData != action->mData) { + break; + } + // When only setting selection is necessary, we should append it. + if (pendingAction.mAdjustSelection) { + LONG selectionStart = pendingAction.mSelectionStart; + LONG selectionLength = pendingAction.mSelectionLength; + + PendingAction* setSelection = mPendingActions.AppendElement(); + setSelection->mType = PendingAction::Type::eSetSelection; + setSelection->mSelectionStart = selectionStart; + setSelection->mSelectionLength = selectionLength; + setSelection->mSelectionReversed = false; + } + // Remove the redundant pending composition. + mPendingActions.RemoveElementsAt(i - 1, j); + MOZ_LOG(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=%s", + this, pComposition, pfOk, ToString(mComposition).c_str())); + + AutoPendingActionAndContentFlusher flusher(this); + + *pfOk = FALSE; + + // Only one composition at a time + if (mComposition.isSome()) { + 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=%s", + this, pComposition, pRangeNew, ToString(mComposition).c_str())); + + 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.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "no active composition", + this)); + return E_UNEXPECTED; + } + if (mComposition->GetView() != 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) { + MaybeDispatchKeyboardEventAsProcessedByIME(); + if (mDestroyed) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "destroyed during dispatching a keyboard event", + this)); + return E_FAIL; + } + PendingAction* action = LastOrNewPendingCompositionUpdate(); + action->mIncomplete = true; + MOZ_LOG(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)) { + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnUpdateComposition() FAILED due to " + "SelectionForTSF() failure", + this)); + return E_FAIL; + } + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnUpdateComposition() succeeded: " + "mComposition=%s, SelectionForTSF()=%s", + this, ToString(mComposition).c_str(), + ToString(selectionForTSF).c_str())); + } + return S_OK; +} + +STDMETHODIMP +TSFTextStore::OnEndComposition(ITfCompositionView* pComposition) { + MOZ_LOG(sTextStoreLog, LogLevel::Info, + ("0x%p TSFTextStore::OnEndComposition(pComposition=0x%p), " + "mComposition=%s", + this, pComposition, ToString(mComposition).c_str())); + + AutoPendingActionAndContentFlusher flusher(this); + + if (mComposition.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::OnEndComposition() FAILED due to " + "no active composition", + this)); + return E_UNEXPECTED; + } + + if (mComposition->GetView() != 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=%s), " + "sThreadMgr=0x%p, sEnabledTextStore=0x%p", + GetBoolName(aGotFocus), aFocusedWidget, + mozilla::ToString(aContext).c_str(), sThreadMgr.get(), + sEnabledTextStore.get())); + + if (NS_WARN_IF(!IsInTSFMode())) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<ITfDocumentMgr> prevFocusedDocumentMgr; + bool hasFocus = ThinksHavingFocus(); + RefPtr<TSFTextStore> oldTextStore = sEnabledTextStore.forget(); + + // If currently oldTextStore still has focus, notifies TSF of losing focus. + if (hasFocus) { + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + DebugOnly<HRESULT> hr = threadMgr->AssociateFocus( + oldTextStore->mWidget->GetWindowHandle(), nullptr, + getter_AddRefs(prevFocusedDocumentMgr)); + NS_ASSERTION(SUCCEEDED(hr), "Disassociating focus failed"); + NS_ASSERTION(prevFocusedDocumentMgr == oldTextStore->mDocumentMgr, + "different documentMgr has been associated with the window"); + } + + // Even if there was a focused TextStore, we won't use it with new focused + // editor. So, release it now. + if (oldTextStore) { + oldTextStore->Destroy(); + } + + if (NS_WARN_IF(!sThreadMgr)) { + MOZ_LOG(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 == IMEEnabled::Password) { + MarkContextAsKeyboardDisabled(textStore->mContext); + RefPtr<ITfContext> topContext; + newDocMgr->GetTop(getter_AddRefs(topContext)); + if (topContext && topContext != textStore->mContext) { + MarkContextAsKeyboardDisabled(topContext); + } + } + + HRESULT hr; + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + hr = threadMgr->SetFocus(newDocMgr); + + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(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 +IMENotificationRequests TSFTextStore::GetIMENotificationRequests() { + if (!sEnabledTextStore || NS_WARN_IF(!sEnabledTextStore->mDocumentMgr)) { + // If there is no active text store, we don't need any notifications + // since there is no sink which needs notifications. + return IMENotificationRequests(); + } + + // Otherwise, requests all notifications since even if some of them may not + // be required by the sink of active TIP, active TIP may be changed and + // other TIPs may need all notifications. + // Note that Windows temporarily steal focus from active window if the main + // process which created the window becomes busy. In this case, we shouldn't + // commit composition since user may want to continue to compose the + // composition after becoming not busy. Therefore, we need notifications + // even during deactive. + // Be aware, we don't need to check actual focused text store. For example, + // MS-IME for Japanese handles focus messages by themselves and sets focused + // text store to nullptr when the process is being inactivated. However, + // we still need to reuse sEnabledTextStore if the process is activated and + // focused element isn't changed. Therefore, if sEnabledTextStore isn't + // nullptr, we need to keep notifying the sink even when it is not focused + // text store for the thread manager. + return IMENotificationRequests( + IMENotificationRequests::NOTIFY_TEXT_CHANGE | + IMENotificationRequests::NOTIFY_POSITION_CHANGE | + IMENotificationRequests::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR | + IMENotificationRequests::NOTIFY_DURING_DEACTIVE); +} + +nsresult TSFTextStore::OnTextChangeInternal( + const IMENotification& aIMENotification) { + const TextChangeDataBase& textChangeData = aIMENotification.mTextChangeData; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::OnTextChangeInternal(aIMENotification={ " + "mMessage=0x%08X, mTextChangeData=%s }), " + "mDestroyed=%s, mSink=0x%p, mSinkMask=%s, " + "mComposition=%s", + this, aIMENotification.mMessage, + mozilla::ToString(textChangeData).c_str(), GetBoolName(mDestroyed), + mSink.get(), GetSinkMaskNameStr(mSinkMask).get(), + ToString(mComposition).c_str())); + + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // changes. + return NS_OK; + } + + 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.isNothing()); + MOZ_ASSERT(mPendingTextChangeData.IsValid()); + + // If the text changes are caused only by composition, we don't need to + // notify TSF of the text changes. + if (mPendingTextChangeData.mCausedOnlyByComposition) { + mPendingTextChangeData.Clear(); + return; + } + + // First, forget cached selection. + mSelectionForTSF.reset(); + + // For making it safer, we should check if there is a valid sink to receive + // text change notification. + if (NS_WARN_IF(!mSink) || NS_WARN_IF(!(mSinkMask & TS_AS_TEXT_CHANGE))) { + MOZ_LOG(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=%s }), mDestroyed=%s, " + "mSink=0x%p, mSinkMask=%s, mIsRecordingActionsWithoutLock=%s, " + "mComposition=%s", + this, mozilla::ToString(selectionChangeData).c_str(), + GetBoolName(mDestroyed), mSink.get(), + GetSinkMaskNameStr(mSinkMask).get(), + GetBoolName(mIsRecordingActionsWithoutLock), + ToString(mComposition).c_str())); + + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // changes. + return NS_OK; + } + + 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(); + + // If we're available, we should create native caret instead of IMEHandler + // because we may have some cache to do it. + // Note that if we have composition, we'll notified composition-updated + // later so that we don't need to create native caret in such case. + if (!IsHandlingCompositionInContent() && + IMEHandler::NeedsToCreateNativeCaret()) { + CreateNativeCaret(); + } + + return NS_OK; +} + +void TSFTextStore::NotifyTSFOfSelectionChange() { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(!IsReadLocked()); + MOZ_ASSERT(mComposition.isNothing()); + MOZ_ASSERT(mPendingSelectionChangeData.IsValid()); + + // If selection range isn't actually changed, we don't need to notify TSF + // of this selection change. + if (mSelectionForTSF.isNothing()) { + mSelectionForTSF.emplace(mPendingSelectionChangeData.mOffset, + mPendingSelectionChangeData.Length(), + mPendingSelectionChangeData.mReversed, + mPendingSelectionChangeData.GetWritingMode()); + } else 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.isSome()) { + mContentForTSF->OnLayoutChanged(); + } + + if (IMEHandler::NeedsToCreateNativeCaret()) { + // If we're available, we should create native caret instead of IMEHandler + // because we may have some cache to do it. + CreateNativeCaret(); + } else { + // Now, the caret position is different from ours. Destroy the native caret + // if we've create it only for GetTextExt(). + IMEHandler::MaybeDestroyNativeCaret(); + } + + // This method should return true if either way succeeds. + bool ret = true; + + if (mSink) { + MOZ_LOG(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; + } + + // Update cached data now because all pending events have been handled now. + if (mContentForTSF.isSome()) { + mContentForTSF->OnCompositionEventsHandled(); + } + + // If composition is completely finished both in TSF/TIP and the focused + // editor which may be in a remote process, we can clear the cache and don't + // have it until starting next composition. + if (mComposition.isNothing() && !IsHandlingCompositionInContent()) { + mDeferClearingContentForTSF = false; + } + mDeferNotifyingTSF = false; + MaybeFlushPendingNotifications(); + + // If we're available, we should create native caret instead of IMEHandler + // because we may have some cache to do it. + if (IMEHandler::NeedsToCreateNativeCaret()) { + CreateNativeCaret(); + } + + return NS_OK; +} + +nsresult TSFTextStore::OnMouseButtonEventInternal( + const IMENotification& aIMENotification) { + if (mDestroyed) { + // If this instance is already destroyed, we shouldn't notify TSF of any + // events. + return NS_OK; + } + + if (mMouseTrackers.IsEmpty()) { + return NS_OK; + } + + MOZ_LOG(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; + if (offset > static_cast<uint32_t>(LONG_MAX)) { + return NS_OK; + } + 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 MouseButton::ePrimary: + buttonStatus = MK_LBUTTON; + break; + case MouseButton::eMiddle: + buttonStatus = MK_MBUTTON; + break; + case MouseButton::eSecondary: + buttonStatus = MK_RBUTTON; + break; + } + } + if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_CONTROL) { + buttonStatus |= MK_CONTROL; + } + if (aIMENotification.mMouseButtonEventData.mModifiers & MODIFIER_SHIFT) { + buttonStatus |= MK_SHIFT; + } + for (size_t i = 0; i < mMouseTrackers.Length(); i++) { + MouseTracker& tracker = mMouseTrackers[i]; + if (!tracker.IsUsing() || tracker.Range().isNothing() || + !tracker.Range()->IsOffsetInRange(offset)) { + continue; + } + if (tracker.OnMouseButtonEvent(edge - tracker.Range()->StartOffset(), + quadrant, buttonStatus)) { + return NS_SUCCESS_EVENT_CONSUMED; + } + } + return NS_OK; +} + +void TSFTextStore::CreateNativeCaret() { + MOZ_ASSERT(!IMEHandler::IsA11yHandlingNativeCaret()); + + IMEHandler::MaybeDestroyNativeCaret(); + + // Don't create native caret after destroyed. + if (mDestroyed) { + return; + } + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CreateNativeCaret(), mComposition=%s", this, + ToString(mComposition).c_str())); + + Maybe<Selection>& selectionForTSF = SelectionForTSF(); + if (selectionForTSF.isNothing()) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "SelectionForTSF() failure", + this)); + return; + } + + WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, mWidget); + mWidget->InitEvent(queryCaretRectEvent); + + WidgetQueryContentEvent::Options options; + // XXX If this is called without composition and the selection isn't + // collapsed, is it OK? + int64_t caretOffset = selectionForTSF->MaxOffset(); + if (mComposition.isSome()) { + // 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->StartOffset(); + } else if (!CanAccessActualContentDirectly()) { + // If TSF/TIP cannot access actual content directly, there may be pending + // text and/or selection changes which have not been notified TSF yet. + // Therefore, we should use relative to insertion point query since + // TSF/TIP computes the offset from the cached selection. + options.mRelativeToInsertionPoint = true; + caretOffset -= selectionForTSF->StartOffset(); + } + queryCaretRectEvent.InitForQueryCaretRect(caretOffset, options); + + DispatchEvent(queryCaretRectEvent); + if (NS_WARN_IF(queryCaretRectEvent.Failed())) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "eQueryCaretRect failure (offset=%d)", + this, caretOffset)); + return; + } + + if (!IMEHandler::CreateNativeCaret(static_cast<nsWindow*>(mWidget.get()), + queryCaretRectEvent.mReply->mRect)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::CreateNativeCaret() FAILED due to " + "IMEHandler::CreateNativeCaret() failure", + this)); + return; + } +} + +void TSFTextStore::CommitCompositionInternal(bool aDiscard) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::CommitCompositionInternal(aDiscard=%s), " + "mSink=0x%p, mContext=0x%p, mComposition=%s", + this, GetBoolName(aDiscard), mSink.get(), mContext.get(), + ToString(mComposition).c_str())); + + // If the document is locked, TSF will fail to commit composition since + // TSF needs another document lock. So, let's put off the request. + // Note that TextComposition will commit composition in the focused editor + // with the latest composition string for web apps and waits asynchronous + // committing messages. Therefore, we can and need to perform this + // asynchronously. + if (IsReadLocked()) { + if (mDeferCommittingComposition || mDeferCancellingComposition) { + MOZ_LOG(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.isSome() && aDiscard) { + LONG endOffset = mComposition->EndOffset(); + mComposition->SetData(EmptyString()); + // Note that don't notify TSF of text change after this is destroyed. + if (mSink && !mDestroyed) { + TS_TEXTCHANGE textChange; + textChange.acpStart = mComposition->StartOffset(); + textChange.acpOldEnd = endOffset; + textChange.acpNewEnd = mComposition->StartOffset(); + MOZ_LOG(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))); + + if (!sThreadMgr) { + return; + } + + RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose(); + if (NS_WARN_IF(!comp)) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetIMEOpenState() FAILED due to" + "no compartment available")); + return; + } + + VARIANT variant; + variant.vt = VT_I4; + variant.lVal = aState; + HRESULT hr = comp->SetValue(sClientId, &variant); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::SetIMEOpenState() FAILED due to " + "ITfCompartment::SetValue() failure, hr=0x%08X", + hr)); + return; + } + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetIMEOpenState(), setting " + "0x%04X to GUID_COMPARTMENT_KEYBOARD_OPENCLOSE...", + variant.lVal)); +} + +// static +bool TSFTextStore::GetIMEOpenState() { + if (!sThreadMgr) { + return false; + } + + RefPtr<ITfCompartment> comp = GetCompartmentForOpenClose(); + if (NS_WARN_IF(!comp)) { + return false; + } + + VARIANT variant; + ::VariantInit(&variant); + HRESULT hr = comp->GetValue(&variant); + if (NS_WARN_IF(FAILED(hr))) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetIMEOpenState() FAILED due to " + "ITfCompartment::GetValue() failure, hr=0x%08X", + hr)); + return false; + } + // Until IME is open in this process, the result may be empty. + if (variant.vt == VT_EMPTY) { + return false; + } + if (NS_WARN_IF(variant.vt != VT_I4)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetIMEOpenState() FAILED due to " + "invalid result of ITfCompartment::GetValue()")); + ::VariantClear(&variant); + return false; + } + + return variant.lVal != 0; +} + +// static +void TSFTextStore::SetInputContext(nsWindowBase* aWidget, + const InputContext& aContext, + const InputContextAction& aAction) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("TSFTextStore::SetInputContext(aWidget=%p, " + "aContext=%s, aAction.mFocusChange=%s), " + "sEnabledTextStore(0x%p)={ mWidget=0x%p }, ThinksHavingFocus()=%s", + aWidget, mozilla::ToString(aContext).c_str(), + GetFocusChangeName(aAction.mFocusChange), sEnabledTextStore.get(), + sEnabledTextStore ? sEnabledTextStore->mWidget.get() : nullptr, + GetBoolName(ThinksHavingFocus()))); + + switch (aAction.mFocusChange) { + case InputContextAction::WIDGET_CREATED: + // If this is called when the widget is created, there is nothing to do. + return; + case InputContextAction::FOCUS_NOT_CHANGED: + case InputContextAction::MENU_LOST_PSEUDO_FOCUS: + if (NS_WARN_IF(!IsInTSFMode())) { + return; + } + // In these cases, `NOTIFY_IME_OF_FOCUS` won't be sent. Therefore, + // we need to reset text store for new state right now. + break; + default: + NS_WARNING_ASSERTION(IsInTSFMode(), + "Why is this called when TSF is disabled?"); + if (sEnabledTextStore) { + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + textStore->SetInputScope(aContext.mHTMLInputType, + aContext.mHTMLInputInputmode, + aContext.mInPrivateBrowsing); + } + return; + } + + // If focus isn't actually changed but the enabled state is changed, + // emulate the focus move. + if (!ThinksHavingFocus() && aContext.mIMEState.IsEditable()) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetInputContent() emulates focus for IME " + "state change")); + OnFocusChange(true, aWidget, aContext); + } else if (ThinksHavingFocus() && !aContext.mIMEState.IsEditable()) { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + (" TSFTextStore::SetInputContent() emulates blur for IME " + "state change")); + OnFocusChange(false, aWidget, aContext); + } +} + +// static +void TSFTextStore::MarkContextAsKeyboardDisabled(ITfContext* aContext) { + VARIANT variant_int4_value1; + variant_int4_value1.vt = VT_I4; + variant_int4_value1.lVal = 1; + + RefPtr<ITfCompartment> comp; + if (!GetCompartment(aContext, GUID_COMPARTMENT_KEYBOARD_DISABLED, + getter_AddRefs(comp))) { + MOZ_LOG(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 = Preferences::GetBool(kPrefNameEnableTSF, false); + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::Initialize(), TSF is %s", + enableTsf ? "enabled" : "disabled")); + if (!enableTsf) { + return; + } + + RefPtr<ITfThreadMgr> threadMgr; + HRESULT hr = + ::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_INPROC_SERVER, + IID_ITfThreadMgr, getter_AddRefs(threadMgr)); + if (FAILED(hr) || !threadMgr) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + (" TSFTextStore::Initialize() FAILED to " + "create 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<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); + + sThreadMgr = threadMgr; + sDisabledDocumentMgr = disabledDocumentMgr; + sDisabledContext = disabledContext; + + MOZ_LOG(sTextStoreLog, LogLevel::Info, + (" TSFTextStore::Initialize(), sThreadMgr=0x%p, " + "sClientId=0x%08X, sDisabledDocumentMgr=0x%p, sDisabledContext=%p", + sThreadMgr.get(), sClientId, sDisabledDocumentMgr.get(), + sDisabledContext.get())); +} + +// static +already_AddRefed<ITfThreadMgr> TSFTextStore::GetThreadMgr() { + RefPtr<ITfThreadMgr> threadMgr = sThreadMgr; + return threadMgr.forget(); +} + +// static +already_AddRefed<ITfMessagePump> TSFTextStore::GetMessagePump() { + static bool sInitialized = false; + if (!sThreadMgr) { + return nullptr; + } + if (sMessagePump) { + RefPtr<ITfMessagePump> messagePump = sMessagePump; + return messagePump.forget(); + } + // If it tried to retrieve ITfMessagePump from sThreadMgr but it failed, + // we shouldn't retry it at every message due to performance reason. + // Although this shouldn't occur actually. + if (sInitialized) { + return nullptr; + } + sInitialized = true; + + RefPtr<ITfMessagePump> messagePump; + HRESULT hr = sThreadMgr->QueryInterface(IID_ITfMessagePump, + getter_AddRefs(messagePump)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!messagePump)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetMessagePump() FAILED to " + "QI message pump from the thread manager, hr=0x%08X", + hr)); + return nullptr; + } + sMessagePump = messagePump; + return messagePump.forget(); +} + +// static +already_AddRefed<ITfDisplayAttributeMgr> +TSFTextStore::GetDisplayAttributeMgr() { + RefPtr<ITfDisplayAttributeMgr> displayAttributeMgr; + if (sDisplayAttrMgr) { + displayAttributeMgr = sDisplayAttrMgr; + return displayAttributeMgr.forget(); + } + + HRESULT hr = ::CoCreateInstance( + CLSID_TF_DisplayAttributeMgr, nullptr, CLSCTX_INPROC_SERVER, + IID_ITfDisplayAttributeMgr, getter_AddRefs(displayAttributeMgr)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!displayAttributeMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetDisplayAttributeMgr() FAILED to create " + "a display attribute manager instance, hr=0x%08X", + hr)); + return nullptr; + } + sDisplayAttrMgr = displayAttributeMgr; + return displayAttributeMgr.forget(); +} + +// static +already_AddRefed<ITfCategoryMgr> TSFTextStore::GetCategoryMgr() { + RefPtr<ITfCategoryMgr> categoryMgr; + if (sCategoryMgr) { + categoryMgr = sCategoryMgr; + return categoryMgr.forget(); + } + HRESULT hr = + ::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_INPROC_SERVER, + IID_ITfCategoryMgr, getter_AddRefs(categoryMgr)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!categoryMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetCategoryMgr() FAILED to create " + "a category manager instance, hr=0x%08X", + hr)); + return nullptr; + } + sCategoryMgr = categoryMgr; + return categoryMgr.forget(); +} + +// static +already_AddRefed<ITfCompartment> TSFTextStore::GetCompartmentForOpenClose() { + if (sCompartmentForOpenClose) { + RefPtr<ITfCompartment> compartment = sCompartmentForOpenClose; + return compartment.forget(); + } + + if (!sThreadMgr) { + return nullptr; + } + + RefPtr<ITfCompartmentMgr> compartmentMgr; + HRESULT hr = sThreadMgr->QueryInterface(IID_ITfCompartmentMgr, + getter_AddRefs(compartmentMgr)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartmentMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to" + "sThreadMgr not having ITfCompartmentMgr, hr=0x%08X", + hr)); + return nullptr; + } + + RefPtr<ITfCompartment> compartment; + hr = compartmentMgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, + getter_AddRefs(compartment)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!compartment)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetCompartmentForOpenClose() FAILED due to" + "ITfCompartmentMgr::GetCompartment() failuere, hr=0x%08X", + hr)); + return nullptr; + } + + sCompartmentForOpenClose = compartment; + return compartment.forget(); +} + +// static +already_AddRefed<ITfInputProcessorProfiles> +TSFTextStore::GetInputProcessorProfiles() { + RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles; + if (sInputProcessorProfiles) { + inputProcessorProfiles = sInputProcessorProfiles; + return inputProcessorProfiles.forget(); + } + // XXX MSDN documents that ITfInputProcessorProfiles is available only on + // desktop apps. However, there is no known way to obtain + // ITfInputProcessorProfileMgr instance without ITfInputProcessorProfiles + // instance. + HRESULT hr = ::CoCreateInstance( + CLSID_TF_InputProcessorProfiles, nullptr, CLSCTX_INPROC_SERVER, + IID_ITfInputProcessorProfiles, getter_AddRefs(inputProcessorProfiles)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!inputProcessorProfiles)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::GetInputProcessorProfiles() FAILED to create input " + "processor profiles, hr=0x%08X", + hr)); + return nullptr; + } + sInputProcessorProfiles = inputProcessorProfiles; + return inputProcessorProfiles.forget(); +} + +// static +void TSFTextStore::Terminate() { + MOZ_LOG(sTextStoreLog, LogLevel::Info, ("TSFTextStore::Terminate()")); + + TSFStaticSink::Shutdown(); + + sDisplayAttrMgr = nullptr; + sCategoryMgr = nullptr; + sEnabledTextStore = nullptr; + sDisabledDocumentMgr = nullptr; + sDisabledContext = nullptr; + sCompartmentForOpenClose = nullptr; + sInputProcessorProfiles = nullptr; + sClientId = 0; + if (sThreadMgr) { + sThreadMgr->Deactivate(); + sThreadMgr = nullptr; + sMessagePump = nullptr; + sKeystrokeMgr = nullptr; + } +} + +// static +bool TSFTextStore::ProcessRawKeyMessage(const MSG& aMsg) { + if (!sThreadMgr) { + return false; // not in TSF mode + } + static bool sInitialized = false; + if (!sKeystrokeMgr) { + // If it tried to retrieve ITfKeystrokeMgr from sThreadMgr but it failed, + // we shouldn't retry it at every keydown nor keyup due to performance + // reason. Although this shouldn't occur actually. + if (sInitialized) { + return false; + } + sInitialized = true; + RefPtr<ITfKeystrokeMgr> keystrokeMgr; + HRESULT hr = sThreadMgr->QueryInterface(IID_ITfKeystrokeMgr, + getter_AddRefs(keystrokeMgr)); + if (NS_WARN_IF(FAILED(hr)) || NS_WARN_IF(!keystrokeMgr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::ProcessRawKeyMessage() FAILED to " + "QI keystroke manager from the thread manager, hr=0x%08X", + hr)); + return false; + } + sKeystrokeMgr = keystrokeMgr.forget(); + } + + if (aMsg.message == WM_KEYDOWN) { + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + if (textStore) { + textStore->OnStartToHandleKeyMessage(); + if (NS_WARN_IF(textStore != sEnabledTextStore)) { + // Let's handle the key message with new focused TSFTextStore. + textStore = sEnabledTextStore; + } + } + AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg); + AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched); + sHandlingKeyMsg = &aMsg; + sIsKeyboardEventDispatched = false; + BOOL eaten; + RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr; + HRESULT hr = keystrokeMgr->TestKeyDown(aMsg.wParam, aMsg.lParam, &eaten); + if (FAILED(hr) || !sKeystrokeMgr || !eaten) { + return false; + } + hr = keystrokeMgr->KeyDown(aMsg.wParam, aMsg.lParam, &eaten); + if (textStore) { + textStore->OnEndHandlingKeyMessage(!!eaten); + } + return SUCCEEDED(hr) && + (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched); + } + if (aMsg.message == WM_KEYUP) { + RefPtr<TSFTextStore> textStore(sEnabledTextStore); + if (textStore) { + textStore->OnStartToHandleKeyMessage(); + if (NS_WARN_IF(textStore != sEnabledTextStore)) { + // Let's handle the key message with new focused TSFTextStore. + textStore = sEnabledTextStore; + } + } + AutoRestore<const MSG*> savePreviousKeyMsg(sHandlingKeyMsg); + AutoRestore<bool> saveKeyEventDispatched(sIsKeyboardEventDispatched); + sHandlingKeyMsg = &aMsg; + sIsKeyboardEventDispatched = false; + BOOL eaten; + RefPtr<ITfKeystrokeMgr> keystrokeMgr = sKeystrokeMgr; + HRESULT hr = keystrokeMgr->TestKeyUp(aMsg.wParam, aMsg.lParam, &eaten); + if (FAILED(hr) || !sKeystrokeMgr || !eaten) { + return false; + } + hr = keystrokeMgr->KeyUp(aMsg.wParam, aMsg.lParam, &eaten); + if (textStore) { + textStore->OnEndHandlingKeyMessage(!!eaten); + } + return SUCCEEDED(hr) && + (eaten || !sKeystrokeMgr || sIsKeyboardEventDispatched); + } + return false; +} + +// static +void TSFTextStore::ProcessMessage(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::IsMSJapaneseIMEActive(); +} + +// static +bool TSFTextStore::IsGoogleJapaneseInputActive() { + return TSFStaticSink::IsGoogleJapaneseInputActive(); +} + +/****************************************************************************** + * TSFTextStore::Content + *****************************************************************************/ + +const nsDependentSubstring TSFTextStore::Content::GetSelectedText() const { + if (NS_WARN_IF(mSelection.isNothing())) { + return nsDependentSubstring(); + } + return GetSubstring(static_cast<uint32_t>(mSelection->StartOffset()), + static_cast<uint32_t>(mSelection->Length())); +} + +const nsDependentSubstring TSFTextStore::Content::GetSubstring( + uint32_t aStart, uint32_t aLength) const { + return nsDependentSubstring(mText, aStart, aLength); +} + +void TSFTextStore::Content::ReplaceSelectedTextWith(const nsAString& aString) { + if (NS_WARN_IF(mSelection.isNothing())) { + return; + } + ReplaceTextWith(mSelection->StartOffset(), mSelection->Length(), aString); +} + +inline uint32_t FirstDifferentCharOffset(const nsAString& aStr1, + const nsAString& aStr2) { + MOZ_ASSERT(aStr1 != aStr2); + uint32_t i = 0; + uint32_t minLength = std::min(aStr1.Length(), aStr2.Length()); + for (; i < minLength && aStr1[i] == aStr2[i]; i++) { + /* nothing to do */ + } + return i; +} + +void TSFTextStore::Content::ReplaceTextWith(LONG aStart, LONG aLength, + const nsAString& aReplaceString) { + MOZ_ASSERT(aStart >= 0); + MOZ_ASSERT(aLength >= 0); + const nsDependentSubstring replacedString = GetSubstring( + static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength)); + if (aReplaceString != replacedString) { + uint32_t firstDifferentOffset = mMinModifiedOffset.valueOr(UINT32_MAX); + if (mComposition.isSome()) { + // Emulate text insertion during compositions, because during a + // composition, editor expects the whole composition string to + // be sent in eCompositionChange, not just the inserted part. + // The actual eCompositionChange will be sent in SetSelection + // or OnUpdateComposition. + MOZ_ASSERT(aStart >= mComposition->StartOffset()); + MOZ_ASSERT(aStart + aLength <= mComposition->EndOffset()); + mComposition->ReplaceData( + static_cast<uint32_t>(aStart - mComposition->StartOffset()), + static_cast<uint32_t>(aLength), aReplaceString); + // TIP may set composition string twice or more times during a document + // lock. Therefore, we should compute the first difference offset with + // mLastComposition. + if (mLastComposition.isNothing()) { + firstDifferentOffset = mComposition->StartOffset(); + } else if (mComposition->DataRef() != mLastComposition->DataRef()) { + firstDifferentOffset = + mComposition->StartOffset() + + FirstDifferentCharOffset(mComposition->DataRef(), + mLastComposition->DataRef()); + // The previous change to the composition string is canceled. + if (mMinModifiedOffset.isSome() && + mMinModifiedOffset.value() >= + static_cast<uint32_t>(mComposition->StartOffset()) && + mMinModifiedOffset.value() < firstDifferentOffset) { + mMinModifiedOffset = Some(firstDifferentOffset); + } + } else if (mMinModifiedOffset.isSome() && + mMinModifiedOffset.value() < static_cast<uint32_t>(LONG_MAX) && + mComposition->IsOffsetInRange( + static_cast<long>(mMinModifiedOffset.value()))) { + // The previous change to the composition string is canceled. + firstDifferentOffset = mComposition->EndOffset(); + mMinModifiedOffset = Some(firstDifferentOffset); + } + mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets()); + MOZ_LOG( + sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::Content::ReplaceTextWith(aStart=%d, " + "aLength=%d, aReplaceString=\"%s\"), mComposition=%s, " + "mLastComposition=%s, mMinModifiedOffset=%s, " + "firstDifferentOffset=%u", + this, aStart, aLength, GetEscapedUTF8String(aReplaceString).get(), + ToString(mComposition).c_str(), ToString(mLastComposition).c_str(), + ToString(mMinModifiedOffset).c_str(), firstDifferentOffset)); + } else { + firstDifferentOffset = + static_cast<uint32_t>(aStart) + + FirstDifferentCharOffset(aReplaceString, replacedString); + } + mMinModifiedOffset = + mMinModifiedOffset.isNothing() + ? Some(firstDifferentOffset) + : Some(std::min(mMinModifiedOffset.value(), firstDifferentOffset)); + mText.Replace(static_cast<uint32_t>(aStart), static_cast<uint32_t>(aLength), + aReplaceString); + } + // Selection should be collapsed at the end of the inserted string. + mSelection = Some(TSFTextStore::Selection(static_cast<uint32_t>(aStart) + + aReplaceString.Length())); +} + +void TSFTextStore::Content::StartComposition( + ITfCompositionView* aCompositionView, const PendingAction& aCompStart, + bool aPreserveSelection) { + MOZ_ASSERT(aCompositionView); + MOZ_ASSERT(mComposition.isNothing()); + MOZ_ASSERT(aCompStart.mType == PendingAction::Type::eCompositionStart); + + mComposition.reset(); // Avoid new crash in the beta and nightly channels. + mComposition.emplace( + aCompositionView, aCompStart.mSelectionStart, + GetSubstring(static_cast<uint32_t>(aCompStart.mSelectionStart), + static_cast<uint32_t>(aCompStart.mSelectionLength))); + mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets()); + if (!aPreserveSelection) { + // XXX Do we need to set a new writing-mode here when setting a new + // selection? Currently, we just preserve the existing value. + WritingMode writingMode = + mSelection.isNothing() ? WritingMode() : mSelection->GetWritingMode(); + mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset(), + mComposition->Length(), false, + writingMode)); + } +} + +void TSFTextStore::Content::RestoreCommittedComposition( + ITfCompositionView* aCompositionView, + const PendingAction& aCanceledCompositionEnd) { + MOZ_ASSERT(aCompositionView); + MOZ_ASSERT(mComposition.isNothing()); + MOZ_ASSERT(aCanceledCompositionEnd.mType == + PendingAction::Type::eCompositionEnd); + MOZ_ASSERT( + GetSubstring( + static_cast<uint32_t>(aCanceledCompositionEnd.mSelectionStart), + static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) == + aCanceledCompositionEnd.mData); + + // Restore the committed string as composing string. + mComposition.reset(); // Avoid new crash in the beta and nightly channels. + mComposition.emplace(aCompositionView, + aCanceledCompositionEnd.mSelectionStart, + aCanceledCompositionEnd.mData); + mLatestCompositionRange = Some(mComposition->CreateStartAndEndOffsets()); +} + +void TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd) { + MOZ_ASSERT(mComposition.isSome()); + MOZ_ASSERT(aCompEnd.mType == PendingAction::Type::eCompositionEnd); + + if (mComposition.isNothing()) { + return; // Avoid new crash in the beta and nightly channels. + } + + mSelection = Some(TSFTextStore::Selection(mComposition->StartOffset() + + aCompEnd.mData.Length())); + mComposition.reset(); +} + +/****************************************************************************** + * TSFTextStore::MouseTracker + *****************************************************************************/ + +TSFTextStore::MouseTracker::MouseTracker() : mCookie(kInvalidCookie) {} + +HRESULT +TSFTextStore::MouseTracker::Init(TSFTextStore* aTextStore) { + MOZ_LOG(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; + } + + MOZ_ASSERT(mRange.isNothing()); + + LONG start = 0, length = 0; + HRESULT hr = aTextRange->GetExtent(&start, &length); + if (FAILED(hr)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to failure of ITfRangeACP::GetExtent()", + this)); + return hr; + } + + if (start < 0 || length <= 0 || start + length > LONG_MAX) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to odd result of ITfRangeACP::GetExtent(), " + "start=%d, length=%d", + this, start, length)); + 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>(start) || + textContent.Length() < static_cast<uint32_t>(start + length)) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink() FAILED " + "due to out of range, start=%d, length=%d, " + "textContent.Length()=%d", + this, start, length, textContent.Length())); + return E_INVALIDARG; + } + + mRange.emplace(start, start + length); + + mSink = aMouseSink; + + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::AdviseMouseSink(), " + "succeeded, mRange=%s, textContent.Length()=%d", + this, ToString(mRange).c_str(), textContent.Length())); + return S_OK; +} + +void TSFTextStore::MouseTracker::UnadviseSink() { + MOZ_LOG(sTextStoreLog, LogLevel::Debug, + ("0x%p TSFTextStore::MouseTracker::UnadviseSink(), " + "mCookie=%d, mSink=0x%p, mRange=%s", + this, mCookie, mSink.get(), ToString(mRange).c_str())); + mSink = nullptr; + mRange.reset(); +} + +bool TSFTextStore::MouseTracker::OnMouseButtonEvent(ULONG aEdge, + ULONG aQuadrant, + DWORD aButtonStatus) { + MOZ_ASSERT(IsUsing(), "The caller must check before calling OnMouseEvent()"); + + BOOL eaten = FALSE; + RefPtr<ITfMouseSink> sink = mSink; + HRESULT hr = sink->OnMouseEvent(aEdge, aQuadrant, aButtonStatus, &eaten); + + MOZ_LOG(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() { + RefPtr<ITfInputProcessorProfiles> inputProcessorProfiles = + TSFTextStore::GetInputProcessorProfiles(); + if (!inputProcessorProfiles) { + MOZ_LOG(sTextStoreLog, LogLevel::Error, + ("TSFTextStore::CurrentKeyboardLayoutHasIME() FAILED due to " + "there is no input processor profiles instance")); + return false; + } + RefPtr<ITfInputProcessorProfileMgr> profileMgr; + HRESULT hr = inputProcessorProfiles->QueryInterface( + IID_ITfInputProcessorProfileMgr, getter_AddRefs(profileMgr)); + if (FAILED(hr) || !profileMgr) { + // On Windows Vista or later, ImmIsIME() API always returns true. + // If we failed to obtain the profile manager, we cannot know if current + // keyboard layout has IME. + MOZ_LOG(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 + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/TSFTextStore.h b/widget/windows/TSFTextStore.h new file mode 100644 index 0000000000..7be04df276 --- /dev/null +++ b/widget/windows/TSFTextStore.h @@ -0,0 +1,1053 @@ +/* -*- 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/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextRange.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/widget/IMEData.h" + +#include <msctf.h> +#include <textstor.h> + +// GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID. +// With initguid.h, we get its instance instead of extern declaration. +#ifdef INPUTSCOPE_INIT_GUID +# include <initguid.h> +#endif +#ifdef TEXTATTRS_INIT_GUID +# include <tsattrs.h> +#endif +#include <inputscope.h> + +// TSF InputScope, for earlier SDK 8 +#define IS_SEARCH static_cast<InputScope>(50) + +struct ITfThreadMgr; +struct ITfDocumentMgr; +struct ITfDisplayAttributeMgr; +struct ITfCategoryMgr; +class nsWindow; + +inline std::ostream& operator<<(std::ostream& aStream, + const TS_SELECTIONSTYLE& aSelectionStyle) { + const char* ase = "Unknown"; + switch (aSelectionStyle.ase) { + case TS_AE_START: + ase = "TS_AE_START"; + break; + case TS_AE_END: + ase = "TS_AE_END"; + break; + case TS_AE_NONE: + ase = "TS_AE_NONE"; + break; + } + aStream << "{ ase=" << ase << ", fInterimChar=" + << (aSelectionStyle.fInterimChar ? "TRUE" : "FALSE") << " }"; + return aStream; +} + +inline std::ostream& operator<<(std::ostream& aStream, + const TS_SELECTION_ACP& aACP) { + aStream << "{ acpStart=" << aACP.acpStart << ", acpEnd=" << aACP.acpEnd + << ", style=" << mozilla::ToString(aACP.style).c_str() << " }"; + return aStream; +} + +namespace mozilla { +namespace widget { + +class TSFStaticSink; +struct MSGResult; + +/* + * Text Services Framework text store + */ + +class TSFTextStore final : public ITextStoreACP, + public ITfContextOwnerCompositionSink, + public ITfMouseTrackerACP { + friend class TSFStaticSink; + + private: + typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase; + typedef IMENotification::SelectionChangeData SelectionChangeData; + typedef IMENotification::TextChangeDataBase TextChangeDataBase; + typedef IMENotification::TextChangeData TextChangeData; + + public: /*IUnknown*/ + STDMETHODIMP QueryInterface(REFIID, void**); + + NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(TSFTextStore) + + public: /*ITextStoreACP*/ + STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD); + STDMETHODIMP UnadviseSink(IUnknown*); + STDMETHODIMP RequestLock(DWORD, HRESULT*); + STDMETHODIMP GetStatus(TS_STATUS*); + STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*); + STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*); + STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*); + STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG, + ULONG*, LONG*); + STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*); + STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**); + STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**); + STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*); + STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*); + STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*); + STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD); + STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG, + const TS_ATTRID*, DWORD); + STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*, + DWORD, LONG*, BOOL*, LONG*); + STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*); + STDMETHODIMP GetEndACP(LONG*); + STDMETHODIMP GetActiveView(TsViewCookie*); + STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*); + STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*); + STDMETHODIMP GetScreenExt(TsViewCookie, RECT*); + STDMETHODIMP GetWnd(TsViewCookie, HWND*); + STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*, + TS_TEXTCHANGE*); + STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*, + TS_TEXTCHANGE*); + + public: /*ITfContextOwnerCompositionSink*/ + STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*); + STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*); + STDMETHODIMP OnEndComposition(ITfCompositionView*); + + public: /*ITfMouseTrackerACP*/ + STDMETHODIMP AdviseMouseSink(ITfRangeACP*, ITfMouseSink*, DWORD*); + STDMETHODIMP UnadviseMouseSink(DWORD); + + public: + static void Initialize(void); + static void Terminate(void); + + static bool ProcessRawKeyMessage(const MSG& aMsg); + static void ProcessMessage(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 IMENotificationRequests GetIMENotificationRequests(); + + // Returns the address of the pointer so that the TSF automatic test can + // replace the system object with a custom implementation for testing. + // XXX TSF doesn't work now. Should we remove it? + static void* GetNativeData(uint32_t aDataType) { + switch (aDataType) { + case NS_NATIVE_TSF_THREAD_MGR: + Initialize(); // Apply any previous changes + return static_cast<void*>(&sThreadMgr); + case NS_NATIVE_TSF_CATEGORY_MGR: + return static_cast<void*>(&sCategoryMgr); + case NS_NATIVE_TSF_DISPLAY_ATTR_MGR: + return static_cast<void*>(&sDisplayAttrMgr); + default: + return nullptr; + } + } + + static void* GetThreadManager() { return static_cast<void*>(sThreadMgr); } + + static bool ThinksHavingFocus() { + return (sEnabledTextStore && sEnabledTextStore->mContext); + } + + static bool IsInTSFMode() { return sThreadMgr != nullptr; } + + static bool IsComposing() { + return (sEnabledTextStore && sEnabledTextStore->mComposition.isSome()); + } + + static bool IsComposingOn(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(); + + /** + * Returns true if active TIP is Google Japanese Input. + * Note that if Google Japanese Input is installed as an IMM-IME, + * this return false even if Google Japanese Input is active. + * So, you may need to check IMMHandler::IsGoogleJapaneseInputActive() too. + */ + static bool IsGoogleJapaneseInputActive(); + + /** + * Returns true if active TIP or IME is a black listed one and we should + * set input scope of URL bar to IS_DEFAULT rather than IS_URL. + */ + static bool ShouldSetInputScopeOfURLBarToDefault(); + + /** + * Returns true if TSF may crash if GetSelection() returns E_FAIL. + */ + static bool DoNotReturnErrorFromGetSelection(); + +#ifdef DEBUG + // Returns true when keyboard layout has IME (TIP). + static bool CurrentKeyboardLayoutHasIME(); +#endif // #ifdef DEBUG + + protected: + TSFTextStore(); + ~TSFTextStore(); + + static bool CreateAndSetFocus(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); + class Composition; + HRESULT RestartComposition(Composition& aCurrentComposition, + ITfCompositionView* aCompositionView, + ITfRange* aNewRange); + + // Following methods record composing action(s) to mPendingActions. + // They will be flushed FlushPendingActions(). + HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView, + ITfRange* aRange, + bool aPreserveSelection); + HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView, + LONG aStart, LONG aLength, + bool aPreserveSelection); + HRESULT RecordCompositionUpdateAction(); + HRESULT RecordCompositionEndAction(); + + // DispatchEvent() dispatches the event and if it may not be handled + // synchronously, this makes the instance not notify TSF of pending + // notifications until next notification from content. + void DispatchEvent(WidgetGUIEvent& aEvent); + void OnLayoutInformationAvaliable(); + + // FlushPendingActions() performs pending actions recorded in mPendingActions + // and clear it. + void FlushPendingActions(); + // MaybeFlushPendingNotifications() performs pending notifications to TSF. + void MaybeFlushPendingNotifications(); + + nsresult OnTextChangeInternal(const IMENotification& aIMENotification); + nsresult OnSelectionChangeInternal(const IMENotification& aIMENotification); + nsresult OnMouseButtonEventInternal(const IMENotification& aIMENotification); + nsresult OnLayoutChangeInternal(); + nsresult OnUpdateCompositionInternal(); + + // mPendingSelectionChangeData stores selection change data until notifying + // TSF of selection change. If two or more selection changes occur, this + // stores the latest selection change data because only it is necessary. + 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, + bool aInPrivateBrowsing); + + // Creates native caret over our caret. This method only works on desktop + // application. Otherwise, this does nothing. + void CreateNativeCaret(); + // Destroys native caret if there is. + void MaybeDestroyNativeCaret(); + + /** + * MaybeHackNoErrorLayoutBugs() is a helper method of GetTextExt(). In + * strictly speaking, TSF is aware of asynchronous layout computation like us. + * However, Windows 10 version 1803 and older (including Windows 8.1 and + * older) Windows has a bug which is that the caller of GetTextExt() of TSF + * does not return TS_E_NOLAYOUT to TIP as is. Additionally, even after + * fixing this bug, some TIPs are not work well when we return TS_E_NOLAYOUT. + * For avoiding this issue, this method checks current Windows version and + * active TIP, and if in case we cannot return TS_E_NOLAYOUT, this modifies + * aACPStart and aACPEnd to making sure that they are in range of unmodified + * characters. + * + * @param aACPStart Initial value should be acpStart of GetTextExt(). + * If this method returns true, this may be modified + * to be in range of unmodified characters. + * @param aACPEnd Initial value should be acpEnd of GetTextExt(). + * If this method returns true, this may be modified + * to be in range of unmodified characters. + * And also this may become same as aACPStart. + * @return true if the caller shouldn't return TS_E_NOLAYOUT. + * In this case, this method modifies aACPStart and/or + * aASCPEnd to compute rectangle of unmodified characters. + * false if the caller can return TS_E_NOLAYOUT or + * we cannot have proper unmodified characters. + */ + bool MaybeHackNoErrorLayoutBugs(LONG& aACPStart, LONG& aACPEnd); + + // Holds the pointer to our current win32 widget + RefPtr<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() { + // If we're starting to handle another key message during handling a + // key message, let's assume that the handling key message is handled by + // TIP and it sends another key message for hacking something. + // Let's try to dispatch a keyboard event now. + // FYI: All callers of this method grab this instance with local variable. + // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(), + // we're safe to access any members. + if (!mDestroyed && sHandlingKeyMsg && !sIsKeyboardEventDispatched) { + MaybeDispatchKeyboardEventAsProcessedByIME(); + } + ++mHandlingKeyMessage; + } + void OnEndHandlingKeyMessage(bool aIsProcessedByTSF) { + // If sHandlingKeyMsg has been handled by TSF or TIP and we're still + // alive, but we haven't dispatch keyboard event for it, let's fire it now. + // FYI: All callers of this method grab this instance with local variable. + // So, even after calling MaybeDispatchKeyboardEventAsProcessedByIME(), + // we're safe to access any members. + if (!mDestroyed && sHandlingKeyMsg && aIsProcessedByTSF && + !sIsKeyboardEventDispatched) { + MaybeDispatchKeyboardEventAsProcessedByIME(); + } + MOZ_ASSERT(mHandlingKeyMessage); + if (--mHandlingKeyMessage) { + return; + } + // If TSFTextStore instance is destroyed during handling key message(s), + // release all TSF objects when all nested key messages have been handled. + if (mDestroyed) { + ReleaseTSFObjects(); + } + } + + /** + * MaybeDispatchKeyboardEventAsProcessedByIME() tries to dispatch eKeyDown + * event or eKeyUp event for sHandlingKeyMsg and marking the dispatching + * event as "processed by IME". Note that if the document is locked, this + * just adds a pending action into the queue and sets + * sIsKeyboardEventDispatched to true. + */ + void MaybeDispatchKeyboardEventAsProcessedByIME(); + + /** + * DispatchKeyboardEventAsProcessedByIME() dispatches an eKeyDown or + * eKeyUp event with NativeKey class and aMsg. + */ + void DispatchKeyboardEventAsProcessedByIME(const MSG& aMsg); + + // Composition class stores a copy of the active composition string. Only + // the data is updated during an InsertTextAtSelection call if we have a + // composition. The data acts as a buffer until OnUpdateComposition is + // called and the data is flushed to editor through eCompositionChange. + // This allows all changes to be updated in batches to avoid inconsistencies + // and artifacts. + class Composition final : public OffsetAndData<LONG> { + public: + explicit Composition(ITfCompositionView* aCompositionView, + LONG aCompositionStartOffset, + const nsAString& aCompositionString) + : OffsetAndData<LONG>(aCompositionStartOffset, aCompositionString), + mView(aCompositionView) {} + + ITfCompositionView* GetView() const { return mView; } + + friend std::ostream& operator<<(std::ostream& aStream, + const Composition& aComposition) { + aStream << "{ mView=0x" << aComposition.mView.get() + << ", OffsetAndData<LONG>=" + << static_cast<const OffsetAndData<LONG>&>(aComposition) << " }"; + return aStream; + } + + private: + RefPtr<ITfCompositionView> const mView; + }; + // While the document is locked, we cannot dispatch any events which cause + // DOM events since the DOM events' handlers may modify the locked document. + // However, even while the document is locked, TSF may queries us. + // For that, TSFTextStore modifies mComposition even while the document is + // locked. With mComposition, query methods can returns the text content + // information. + Maybe<Composition> mComposition; + + /** + * IsHandlingCompositionInParent() returns true if eCompositionStart is + * dispatched, but eCompositionCommit(AsIs) is not dispatched. This means + * that if composition is handled in a content process, this status indicates + * whether ContentCacheInParent has composition or not. On the other hand, + * if it's handled in the chrome process, this is exactly same as + * IsHandlingCompositionInContent(). + */ + bool IsHandlingCompositionInParent() const { + return mDispatcher && mDispatcher->IsComposing(); + } + + /** + * IsHandlingCompositionInContent() returns true if there is a composition in + * the focused editor which may be in a content process. + */ + bool IsHandlingCompositionInContent() const { + return mDispatcher && mDispatcher->IsHandlingComposition(); + } + + class Selection { + public: + const TS_SELECTION_ACP& ACPRef() const { return mACP; } + + explicit Selection(const TS_SELECTION_ACP& aSelection) { + SetSelection(aSelection); + } + + explicit Selection(uint32_t aOffsetToCollapse) { + Collapse(aOffsetToCollapse); + } + + explicit Selection(uint32_t aStart, uint32_t aLength, bool aReversed, + const WritingMode& aWritingMode) { + SetSelection(aStart, aLength, aReversed, aWritingMode); + } + + void SetSelection(const TS_SELECTION_ACP& aSelection) { + mACP = 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, + const WritingMode& aWritingMode) { + bool changed = mACP.acpStart != static_cast<LONG>(aStart) || + mACP.acpEnd != static_cast<LONG>(aStart + aLength); + 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 Collapsed() const { return mACP.acpStart == mACP.acpEnd; } + + void Collapse(uint32_t aOffset) { + // XXX This does not update the selection's mWritingMode. + // If it is ever used to "collapse" to an entirely new location, + // we may need to fix that. + mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset); + mACP.style.ase = TS_AE_END; + mACP.style.fInterimChar = FALSE; + } + + LONG MinOffset() const { + LONG min = std::min(mACP.acpStart, mACP.acpEnd); + MOZ_ASSERT(min >= 0); + return min; + } + + LONG MaxOffset() const { + LONG max = std::max(mACP.acpStart, mACP.acpEnd); + MOZ_ASSERT(max >= 0); + return max; + } + + LONG StartOffset() const { + MOZ_ASSERT(mACP.acpStart >= 0); + return mACP.acpStart; + } + + LONG EndOffset() const { + MOZ_ASSERT(mACP.acpEnd >= 0); + return mACP.acpEnd; + } + + LONG Length() const { + MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart); + return std::abs(mACP.acpEnd - mACP.acpStart); + } + + bool IsReversed() const { return mACP.style.ase == TS_AE_START; } + + TsActiveSelEnd ActiveSelEnd() const { return mACP.style.ase; } + + bool IsInterimChar() const { return mACP.style.fInterimChar != FALSE; } + + WritingMode GetWritingMode() const { 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(aChangedSelection.IsValid()); + return aChangedSelection.Length() == static_cast<uint32_t>(Length()) && + aChangedSelection.mOffset == static_cast<uint32_t>(StartOffset()); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const Selection& aSelection) { + aStream << "{ mACP=" << ToString(aSelection.mACP).c_str() + << ", mWritingMode=" << ToString(aSelection.mWritingMode).c_str() + << ", Collapsed()=" + << (aSelection.Collapsed() ? "true" : "false") + << ", Length=" << aSelection.Length() << " }"; + return aStream; + } + + private: + TS_SELECTION_ACP mACP; + WritingMode mWritingMode; + }; + // Don't access mSelection directly. Instead, Use SelectionForTSFRef(). + // This is modified immediately when TSF requests to set selection and not + // updated by selection change in content until mContentForTSF is cleared. + Maybe<Selection> mSelectionForTSF; + + /** + * Get the selection expected by TSF. If mSelectionForTSF is already valid, + * this just return the reference to it. Otherwise, this initializes it + * with eQuerySelectedText. Please check if the result is valid before + * actually using it. + * Note that this is also called by ContentForTSF(). + */ + Maybe<Selection>& SelectionForTSF(); + + struct PendingAction final { + enum class Type : uint8_t { + eCompositionStart, + eCompositionUpdate, + eCompositionEnd, + eSetSelection, + eKeyboardEvent, + }; + Type mType; + // For eCompositionStart, eCompositionEnd and eSetSelection + LONG mSelectionStart; + // For eCompositionStart and eSetSelection + LONG mSelectionLength; + // For eCompositionStart, eCompositionUpdate and eCompositionEnd + nsString mData; + // For eCompositionUpdate + RefPtr<TextRangeArray> mRanges; + // For eKeyboardEvent + MSG mKeyMsg; + // For eSetSelection + bool mSelectionReversed; + // For eCompositionUpdate + bool mIncomplete; + // For eCompositionStart + bool mAdjustSelection; + }; + // Items of mPendingActions are appended when TSF tells us to need to dispatch + // DOM composition events. However, we cannot dispatch while the document is + // locked because it can cause modifying the locked document. So, the pending + // actions should be performed when document lock is unlocked. + nsTArray<PendingAction> mPendingActions; + + PendingAction* LastOrNewPendingCompositionUpdate() { + if (!mPendingActions.IsEmpty()) { + PendingAction& lastAction = mPendingActions.LastElement(); + if (lastAction.mType == PendingAction::Type::eCompositionUpdate) { + return &lastAction; + } + } + PendingAction* newAction = mPendingActions.AppendElement(); + newAction->mType = PendingAction::Type::eCompositionUpdate; + newAction->mRanges = new TextRangeArray(); + newAction->mIncomplete = true; + return newAction; + } + + /** + * IsLastPendingActionCompositionEndAt() checks whether the previous pending + * action is committing composition whose range starts from aStart and its + * length is aLength. In other words, this checks whether new composition + * which will replace same range as previous pending commit can be merged + * with the previous composition. + * + * @param aStart The inserted offset you expected. + * @param aLength The inserted text length you expected. + * @return true if the last pending action is + * eCompositionEnd and it inserted the text + * between aStart and aStart + aLength. + */ + bool IsLastPendingActionCompositionEndAt(LONG aStart, LONG aLength) const { + if (mPendingActions.IsEmpty()) { + return false; + } + const PendingAction& pendingLastAction = mPendingActions.LastElement(); + return pendingLastAction.mType == PendingAction::Type::eCompositionEnd && + pendingLastAction.mSelectionStart == aStart && + pendingLastAction.mData.Length() == static_cast<ULONG>(aLength); + } + + bool IsPendingCompositionUpdateIncomplete() const { + if (mPendingActions.IsEmpty()) { + return false; + } + const PendingAction& lastAction = mPendingActions.LastElement(); + return lastAction.mType == PendingAction::Type::eCompositionUpdate && + lastAction.mIncomplete; + } + + void CompleteLastActionIfStillIncomplete() { + if (!IsPendingCompositionUpdateIncomplete()) { + return; + } + RecordCompositionUpdateAction(); + } + + void RemoveLastCompositionUpdateActions() { + while (!mPendingActions.IsEmpty()) { + const PendingAction& lastAction = mPendingActions.LastElement(); + if (lastAction.mType != PendingAction::Type::eCompositionUpdate) { + break; + } + mPendingActions.RemoveLastElement(); + } + } + + // When On*Composition() is called without document lock, we need to flush + // the recorded actions at quitting the method. + // AutoPendingActionAndContentFlusher class is usedful for it. + class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher final { + public: + explicit AutoPendingActionAndContentFlusher(TSFTextStore* aTextStore) + : mTextStore(aTextStore) { + MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock); + if (!mTextStore->IsReadWriteLocked()) { + mTextStore->mIsRecordingActionsWithoutLock = true; + } + } + + ~AutoPendingActionAndContentFlusher() { + if (!mTextStore->mIsRecordingActionsWithoutLock) { + return; + } + mTextStore->FlushPendingActions(); + mTextStore->mIsRecordingActionsWithoutLock = false; + } + + private: + AutoPendingActionAndContentFlusher() {} + + RefPtr<TSFTextStore> mTextStore; + }; + + class Content final { + public: + Content(TSFTextStore& aTSFTextStore, const nsAString& aText) + : mText(aText), + mLastComposition(aTSFTextStore.mComposition), + mComposition(aTSFTextStore.mComposition), + mSelection(aTSFTextStore.mSelectionForTSF) {} + + void OnLayoutChanged() { mMinModifiedOffset.reset(); } + + // OnCompositionEventsHandled() is called when all pending composition + // events are handled in the focused content which may be in a remote + // process. + void OnCompositionEventsHandled() { mLastComposition = mComposition; } + + const nsDependentSubstring GetSelectedText() const; + const nsDependentSubstring GetSubstring(uint32_t aStart, + uint32_t aLength) const; + void ReplaceSelectedTextWith(const nsAString& aString); + void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString); + + void StartComposition(ITfCompositionView* aCompositionView, + const PendingAction& aCompStart, + bool aPreserveSelection); + /** + * RestoreCommittedComposition() restores the committed string as + * composing string. If InsertTextAtSelection() or something is called + * before a call of OnStartComposition() or previous composition is + * committed and new composition is restarted to clean up the commited + * string, there is a pending compositionend. In this case, we need to + * cancel the pending compositionend and continue the composition. + * + * @param aCompositionView The composition view. + * @param aCanceledCompositionEnd The pending compositionend which is + * canceled for restarting the composition. + */ + void RestoreCommittedComposition( + ITfCompositionView* aCompositionView, + const PendingAction& aCanceledCompositionEnd); + void EndComposition(const PendingAction& aCompEnd); + + const nsString& TextRef() const { return mText; } + const Maybe<OffsetAndData<LONG>>& LastComposition() const { + return mLastComposition; + } + const Maybe<uint32_t>& MinModifiedOffset() const { + return mMinModifiedOffset; + } + const Maybe<StartAndEndOffsets<LONG>>& LatestCompositionRange() const { + return mLatestCompositionRange; + } + + // Returns true if layout of the character at the aOffset has not been + // calculated. + bool IsLayoutChangedAt(uint32_t aOffset) const { + return IsLayoutChanged() && (mMinModifiedOffset.value() <= aOffset); + } + // Returns true if layout of the content has been changed, i.e., the new + // layout has not been calculated. + bool IsLayoutChanged() const { return mMinModifiedOffset.isSome(); } + bool HasOrHadComposition() const { + return mLatestCompositionRange.isSome(); + } + + Maybe<TSFTextStore::Composition>& Composition() { return mComposition; } + Maybe<TSFTextStore::Selection>& Selection() { return mSelection; } + + friend std::ostream& operator<<(std::ostream& aStream, + const Content& aContent) { + aStream << "{ mText=" + << PrintStringDetail(aContent.mText, + PrintStringDetail::kMaxLengthForEditor) + .get() + << ", mLastComposition=" << aContent.mLastComposition + << ", mLatestCompositionRange=" + << aContent.mLatestCompositionRange + << ", mMinModifiedOffset=" << aContent.mMinModifiedOffset << " }"; + return aStream; + } + + private: + nsString mText; + + // mLastComposition may store the composition string and its start offset + // when the document is locked. This is necessary to compute + // mMinTextModifiedOffset. + Maybe<OffsetAndData<LONG>> mLastComposition; + + Maybe<TSFTextStore::Composition>& mComposition; + Maybe<TSFTextStore::Selection>& mSelection; + + // The latest composition's start and end offset. + Maybe<StartAndEndOffsets<LONG>> mLatestCompositionRange; + + // The minimum offset of modified part of the text. + Maybe<uint32_t> mMinModifiedOffset; + }; + // mContentForTSF is cache of content. The information is expected by TSF + // and TIP. Therefore, this is useful for answering the query from TSF or + // TIP. + // This is initialized by ContentForTSF() automatically (therefore, don't + // access this member directly except at calling Clear(), IsInitialized(), + // IsLayoutChangeAfter() or IsLayoutChanged()). + // This is cleared when: + // - When there is no composition, the document is unlocked. + // - When there is a composition, all dispatched events are handled by + // the focused editor which may be in a remote process. + // So, if two compositions are created very quickly, this cache may not be + // cleared between eCompositionCommit(AsIs) and eCompositionStart. + Maybe<Content> mContentForTSF; + + Maybe<Content>& ContentForTSF(); + + // CanAccessActualContentDirectly() returns true when TSF/TIP can access + // actual content directly. In other words, mContentForTSF and/or + // mSelectionForTSF doesn't cache content or they matches with actual + // contents due to no pending text/selection change notifications. + bool CanAccessActualContentDirectly() const; + + // While mContentForTSF is valid, this returns the text stored by it. + // Otherwise, return the current text content retrieved by eQueryTextContent. + bool GetCurrentText(nsAString& aTextContent); + + class MouseTracker final { + public: + static const DWORD kInvalidCookie = static_cast<DWORD>(-1); + + MouseTracker(); + + HRESULT Init(TSFTextStore* aTextStore); + HRESULT AdviseSink(TSFTextStore* aTextStore, ITfRangeACP* aTextRange, + ITfMouseSink* aMouseSink); + void UnadviseSink(); + + bool IsUsing() const { return mSink != nullptr; } + DWORD Cookie() const { return mCookie; } + bool OnMouseButtonEvent(ULONG aEdge, ULONG aQuadrant, DWORD aButtonStatus); + const Maybe<StartAndEndOffsets<LONG>> Range() const { return mRange; } + + private: + RefPtr<ITfMouseSink> mSink; + Maybe<StartAndEndOffsets<LONG>> mRange; + DWORD mCookie; + }; + // mMouseTrackers is an array to store each information of installed + // ITfMouseSink instance. + nsTArray<MouseTracker> mMouseTrackers; + + // The input scopes for this context, defaults to IS_DEFAULT. + nsTArray<InputScope> mInputScopes; + + // 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 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; + static already_AddRefed<ITfThreadMgr> GetThreadMgr(); + // sMessagePump is QI'ed from sThreadMgr + static StaticRefPtr<ITfMessagePump> sMessagePump; + + public: + // Expose GetMessagePump() for WinUtils. + static already_AddRefed<ITfMessagePump> GetMessagePump(); + + private: + // sKeystrokeMgr is QI'ed from sThreadMgr + static StaticRefPtr<ITfKeystrokeMgr> sKeystrokeMgr; + // TSF display attribute manager + static StaticRefPtr<ITfDisplayAttributeMgr> sDisplayAttrMgr; + static already_AddRefed<ITfDisplayAttributeMgr> GetDisplayAttributeMgr(); + // TSF category manager + static StaticRefPtr<ITfCategoryMgr> sCategoryMgr; + static already_AddRefed<ITfCategoryMgr> GetCategoryMgr(); + // Compartment for (Get|Set)IMEOpenState() + static StaticRefPtr<ITfCompartment> sCompartmentForOpenClose; + static already_AddRefed<ITfCompartment> GetCompartmentForOpenClose(); + + // Current text store which is managing a keyboard enabled editor (i.e., + // editable editor). Currently only ONE TSFTextStore instance is ever used, + // although Create is called when an editor is focused and Destroy called + // when the focused editor is blurred. + static StaticRefPtr<TSFTextStore> sEnabledTextStore; + + // For IME (keyboard) disabled state: + static StaticRefPtr<ITfDocumentMgr> sDisabledDocumentMgr; + static StaticRefPtr<ITfContext> sDisabledContext; + + static StaticRefPtr<ITfInputProcessorProfiles> sInputProcessorProfiles; + static already_AddRefed<ITfInputProcessorProfiles> + GetInputProcessorProfiles(); + + // Handling key message. + static const MSG* sHandlingKeyMsg; + + // TSF client ID for the current application + static DWORD sClientId; + + // true if an eKeyDown or eKeyUp event for sHandlingKeyMsg has already + // been dispatched. + static bool sIsKeyboardEventDispatched; +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef TSFTextStore_h_ diff --git a/widget/windows/TaskbarPreview.cpp b/widget/windows/TaskbarPreview.cpp new file mode 100644 index 0000000000..f31e187528 --- /dev/null +++ b/widget/windows/TaskbarPreview.cpp @@ -0,0 +1,409 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TaskbarPreview.h" +#include <nsITaskbarPreviewController.h> +#include <windows.h> + +#include <nsError.h> +#include <nsCOMPtr.h> +#include <nsIWidget.h> +#include <nsServiceManagerUtils.h> + +#include "nsUXThemeData.h" +#include "nsWindow.h" +#include "nsAppShell.h" +#include "TaskbarPreviewButton.h" +#include "WinUtils.h" + +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" + +// Defined in dwmapi in a header that needs a higher numbered _WINNT #define +#ifndef DWM_SIT_DISPLAYFRAME +# define DWM_SIT_DISPLAYFRAME 0x1 +#endif + +namespace mozilla { +namespace widget { + +/////////////////////////////////////////////////////////////////////////////// +// TaskbarPreview + +TaskbarPreview::TaskbarPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, + HWND aHWND, nsIDocShell* aShell) + : mTaskbar(aTaskbar), + mController(aController), + mWnd(aHWND), + mVisible(false), + mDocShell(do_GetWeakReference(aShell)) {} + +TaskbarPreview::~TaskbarPreview() { + // Avoid dangling pointer + if (sActivePreview == this) sActivePreview = nullptr; + + // Our subclass should have invoked DetachFromNSWindow already. + NS_ASSERTION( + !mWnd, + "TaskbarPreview::DetachFromNSWindow was not called before destruction"); + + // Make sure to release before potentially uninitializing COM + mTaskbar = nullptr; + + ::CoUninitialize(); +} + +nsresult TaskbarPreview::Init() { + // TaskbarPreview may outlive the WinTaskbar that created it + if (FAILED(::CoInitialize(nullptr))) { + return NS_ERROR_NOT_INITIALIZED; + } + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + return hook->AddMonitor(WM_DESTROY, MainWindowHook, this); +} + +NS_IMETHODIMP +TaskbarPreview::SetController(nsITaskbarPreviewController* aController) { + NS_ENSURE_ARG(aController); + + mController = aController; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetController(nsITaskbarPreviewController** aController) { + NS_ADDREF(*aController = mController); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetTooltip(nsAString& aTooltip) { + aTooltip = mTooltip; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetTooltip(const nsAString& aTooltip) { + mTooltip = aTooltip; + return CanMakeTaskbarCalls() ? UpdateTooltip() : NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetVisible(bool visible) { + if (mVisible == visible) return NS_OK; + mVisible = visible; + + // If the nsWindow has already been destroyed but the caller is still trying + // to use it then just pretend that everything succeeded. The caller doesn't + // actually have a way to detect this since it's the same case as when we + // CanMakeTaskbarCalls returns false. + if (!IsWindowAvailable()) return NS_OK; + + return visible ? Enable() : Disable(); +} + +NS_IMETHODIMP +TaskbarPreview::GetVisible(bool* visible) { + *visible = mVisible; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::SetActive(bool active) { + if (active) + sActivePreview = this; + else if (sActivePreview == this) + sActivePreview = nullptr; + + return CanMakeTaskbarCalls() ? ShowActive(active) : NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::GetActive(bool* active) { + *active = sActivePreview == this; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreview::Invalidate() { + if (!mVisible) return NS_OK; + + // DWM Composition is required for previews + if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return NS_OK; + + HWND previewWindow = PreviewWindow(); + return FAILED(DwmInvalidateIconicBitmaps(previewWindow)) ? NS_ERROR_FAILURE + : NS_OK; +} + +nsresult TaskbarPreview::UpdateTaskbarProperties() { + nsresult rv = UpdateTooltip(); + + // If we are the active preview and our window is the active window, restore + // our active state - otherwise some other non-preview window is now active + // and should be displayed as so. + if (sActivePreview == this) { + if (mWnd == ::GetActiveWindow()) { + nsresult rvActive = ShowActive(true); + if (NS_FAILED(rvActive)) rv = rvActive; + } else { + sActivePreview = nullptr; + } + } + return rv; +} + +nsresult TaskbarPreview::Enable() { + nsresult rv = NS_OK; + if (CanMakeTaskbarCalls()) { + rv = UpdateTaskbarProperties(); + } else if (IsWindowAvailable()) { + WindowHook* hook = GetWindowHook(); + MOZ_ASSERT(hook, + "IsWindowAvailable() should have eliminated the null case."); + hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + MainWindowHook, this); + } + return rv; +} + +nsresult TaskbarPreview::Disable() { + if (!IsWindowAvailable()) { + // Window is already destroyed + return NS_OK; + } + + WindowHook* hook = GetWindowHook(); + MOZ_ASSERT(hook, "IsWindowAvailable() should have eliminated the null case."); + (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + MainWindowHook, this); + + return NS_OK; +} + +bool TaskbarPreview::IsWindowAvailable() const { + if (mWnd) { + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); + if (win && !win->Destroyed()) { + return true; + } + } + return false; +} + +void TaskbarPreview::DetachFromNSWindow() { + if (WindowHook* hook = GetWindowHook()) { + hook->RemoveMonitor(WM_DESTROY, MainWindowHook, this); + } + mWnd = nullptr; +} + +LRESULT +TaskbarPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + switch (nMsg) { + case WM_DWMSENDICONICTHUMBNAIL: { + uint32_t width = HIWORD(lParam); + uint32_t height = LOWORD(lParam); + float aspectRatio = width / float(height); + + nsresult rv; + float preferredAspectRatio; + rv = mController->GetThumbnailAspectRatio(&preferredAspectRatio); + if (NS_FAILED(rv)) break; + + uint32_t thumbnailWidth = width; + uint32_t thumbnailHeight = height; + + if (aspectRatio > preferredAspectRatio) { + thumbnailWidth = uint32_t(thumbnailHeight * preferredAspectRatio); + } else { + thumbnailHeight = uint32_t(thumbnailWidth / preferredAspectRatio); + } + + DrawBitmap(thumbnailWidth, thumbnailHeight, false); + } break; + case WM_DWMSENDICONICLIVEPREVIEWBITMAP: { + uint32_t width, height; + nsresult rv; + rv = mController->GetWidth(&width); + if (NS_FAILED(rv)) break; + rv = mController->GetHeight(&height); + if (NS_FAILED(rv)) break; + + double scale = StaticPrefs::layout_css_devPixelsPerPx(); + if (scale <= 0.0) { + scale = WinUtils::LogToPhysFactor(PreviewWindow()); + } + DrawBitmap(NSToIntRound(scale * width), NSToIntRound(scale * height), + true); + } break; + } + return ::DefWindowProcW(PreviewWindow(), nMsg, wParam, lParam); +} + +bool TaskbarPreview::CanMakeTaskbarCalls() { + // If the nsWindow has already been destroyed and we know it but our caller + // clearly doesn't so we can't make any calls. + if (!mWnd) return false; + // Certain functions like SetTabOrder seem to require a visible window. During + // window close, the window seems to be hidden before being destroyed. + if (!::IsWindowVisible(mWnd)) return false; + if (mVisible) { + nsWindow* window = WinUtils::GetNSWindowPtr(mWnd); + NS_ASSERTION(window, "Could not get nsWindow from HWND"); + return window ? window->HasTaskbarIconBeenCreated() : false; + } + return false; +} + +WindowHook* TaskbarPreview::GetWindowHook() { + nsWindow* window = WinUtils::GetNSWindowPtr(mWnd); + NS_ASSERTION(window, "Cannot use taskbar previews in an embedded context!"); + + return window ? &window->GetWindowHook() : nullptr; +} + +void TaskbarPreview::EnableCustomDrawing(HWND aHWND, bool aEnable) { + BOOL enabled = aEnable; + DwmSetWindowAttribute(aHWND, DWMWA_FORCE_ICONIC_REPRESENTATION, &enabled, + sizeof(enabled)); + + DwmSetWindowAttribute(aHWND, DWMWA_HAS_ICONIC_BITMAP, &enabled, + sizeof(enabled)); +} + +nsresult TaskbarPreview::UpdateTooltip() { + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, + "UpdateTooltip called on invisible tab preview"); + + if (FAILED(mTaskbar->SetThumbnailTooltip(PreviewWindow(), mTooltip.get()))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +void TaskbarPreview::DrawBitmap(uint32_t width, uint32_t height, + bool isPreview) { + nsresult rv; + nsCOMPtr<nsITaskbarPreviewCallback> callback = + do_CreateInstance("@mozilla.org/widget/taskbar-preview-callback;1", &rv); + if (NS_FAILED(rv)) { + return; + } + + ((TaskbarPreviewCallback*)callback.get())->SetPreview(this); + + if (isPreview) { + ((TaskbarPreviewCallback*)callback.get())->SetIsPreview(); + mController->RequestPreview(callback); + } else { + mController->RequestThumbnail(callback, width, height); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// TaskbarPreviewCallback + +NS_IMPL_ISUPPORTS(TaskbarPreviewCallback, nsITaskbarPreviewCallback) + +/* void done (in nsISupports aCanvas, in boolean aDrawBorder); */ +NS_IMETHODIMP +TaskbarPreviewCallback::Done(nsISupports* aCanvas, bool aDrawBorder) { + // We create and destroy TaskbarTabPreviews from front end code in response + // to TabOpen and TabClose events. Each TaskbarTabPreview creates and owns a + // proxy HWND which it hands to Windows as a tab identifier. When a tab + // closes, TaskbarTabPreview Disable() method is called by front end, which + // destroys the proxy window and clears mProxyWindow which is the HWND + // returned from PreviewWindow(). So, since this is async, we should check to + // be sure the tab is still alive before doing all this gfx work and making + // dwm calls. To accomplish this we check the result of PreviewWindow(). + if (!aCanvas || !mPreview || !mPreview->PreviewWindow() || + !mPreview->IsWindowAvailable()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIContent> content(do_QueryInterface(aCanvas)); + auto canvas = dom::HTMLCanvasElement::FromNodeOrNull(content); + if (!canvas) { + return NS_ERROR_FAILURE; + } + + RefPtr<gfx::SourceSurface> source = canvas->GetSurfaceSnapshot(); + if (!source) { + return NS_ERROR_FAILURE; + } + RefPtr<gfxWindowsSurface> target = new gfxWindowsSurface( + source->GetSize(), gfx::SurfaceFormat::A8R8G8B8_UINT32); + if (!target) { + 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 = DwmSetIconicLivePreviewBitmap(mPreview->PreviewWindow(), hBitmap, + &pptClient, flags); + } else { + hr = DwmSetIconicThumbnail(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..ca21e3eeb8 --- /dev/null +++ b/widget/windows/TaskbarPreview.h @@ -0,0 +1,132 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarPreview_h__ +#define __mozilla_widget_TaskbarPreview_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "mozilla/RefPtr.h" +#include <nsITaskbarPreview.h> +#include <nsITaskbarPreviewController.h> +#include <nsString.h> +#include <nsIWeakReferenceUtils.h> +#include <nsIDocShell.h> +#include "WindowHook.h" + +namespace mozilla { +namespace widget { + +class TaskbarPreviewCallback; + +class TaskbarPreview : public nsITaskbarPreview { + public: + TaskbarPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, HWND aHWND, + nsIDocShell* aShell); + virtual nsresult Init(); + + friend class TaskbarPreviewCallback; + + NS_DECL_NSITASKBARPREVIEW + + protected: + virtual ~TaskbarPreview(); + + // Called to update ITaskbarList4 dependent properties + virtual nsresult UpdateTaskbarProperties(); + + // Invoked when the preview is made visible + virtual nsresult Enable(); + // Invoked when the preview is made invisible + virtual nsresult Disable(); + + // Detaches this preview from the nsWindow instance it's tied to + virtual void DetachFromNSWindow(); + + // Determines if the window is available and a destroy has not yet started + bool IsWindowAvailable() const; + + // Marks this preview as being active + virtual nsresult ShowActive(bool active) = 0; + // Gets a reference to the window used to handle the preview messages + virtual HWND& PreviewWindow() = 0; + + // Window procedure for the PreviewWindow (hooked for window previews) + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam); + + // Returns whether or not the taskbar icon has been created for mWnd The + // ITaskbarList4 API requires that we wait until the icon has been created + // before we can call its methods. + bool CanMakeTaskbarCalls(); + + // Gets the WindowHook for the nsWindow + WindowHook* GetWindowHook(); + + // Enables/disables custom drawing for the given window + static void EnableCustomDrawing(HWND aHWND, bool aEnable); + + // MSCOM Taskbar interface + RefPtr<ITaskbarList4> mTaskbar; + // Controller for this preview + nsCOMPtr<nsITaskbarPreviewController> mController; + // The HWND to the nsWindow that this object previews + HWND mWnd; + // Whether or not this preview is visible + bool mVisible; + + private: + // Called when the tooltip should be updated + nsresult UpdateTooltip(); + + // Requests the controller to draw into a canvas of the given width and + // height. The resulting bitmap is sent to the DWM to display. + void DrawBitmap(uint32_t width, uint32_t height, bool isPreview); + + // WindowHook procedure for hooking mWnd + static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, LRESULT* aResult); + + // Docshell corresponding to the <window> the nsWindow contains + nsWeakPtr mDocShell; + nsString mTooltip; + + // The preview currently marked as active in the taskbar. nullptr if no + // preview is active (some other window is). + static TaskbarPreview* sActivePreview; +}; + +/* + * Callback object TaskbarPreview hands to preview controllers when we + * request async thumbnail or live preview images. Controllers invoke + * this interface once they have aquired the requested image. + */ +class TaskbarPreviewCallback : public nsITaskbarPreviewCallback { + public: + TaskbarPreviewCallback() : mIsThumbnail(true) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWCALLBACK + + void SetPreview(TaskbarPreview* aPreview) { mPreview = aPreview; } + + void SetIsPreview() { mIsThumbnail = false; } + + protected: + virtual ~TaskbarPreviewCallback() {} + + private: + RefPtr<TaskbarPreview> mPreview; + bool mIsThumbnail; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarPreview_h__ */ diff --git a/widget/windows/TaskbarPreviewButton.cpp b/widget/windows/TaskbarPreviewButton.cpp new file mode 100644 index 0000000000..abbb1069a9 --- /dev/null +++ b/widget/windows/TaskbarPreviewButton.cpp @@ -0,0 +1,137 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <windows.h> +#include <strsafe.h> + +#include "TaskbarWindowPreview.h" +#include "TaskbarPreviewButton.h" +#include "nsWindowGfx.h" +#include <imgIContainer.h> + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(TaskbarPreviewButton, nsITaskbarPreviewButton, + nsISupportsWeakReference) + +TaskbarPreviewButton::TaskbarPreviewButton(TaskbarWindowPreview* preview, + uint32_t index) + : mPreview(preview), mIndex(index) {} + +TaskbarPreviewButton::~TaskbarPreviewButton() { SetVisible(false); } + +NS_IMETHODIMP +TaskbarPreviewButton::GetTooltip(nsAString& aTooltip) { + aTooltip = mTooltip; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetTooltip(const nsAString& aTooltip) { + mTooltip = aTooltip; + size_t destLength = sizeof Button().szTip / (sizeof Button().szTip[0]); + wchar_t* tooltip = &(Button().szTip[0]); + StringCchCopyNW(tooltip, destLength, mTooltip.get(), mTooltip.Length()); + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetDismissOnClick(bool* dismiss) { + *dismiss = (Button().dwFlags & THBF_DISMISSONCLICK) == THBF_DISMISSONCLICK; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetDismissOnClick(bool dismiss) { + if (dismiss) + Button().dwFlags |= THBF_DISMISSONCLICK; + else + Button().dwFlags &= ~THBF_DISMISSONCLICK; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetHasBorder(bool* hasBorder) { + *hasBorder = (Button().dwFlags & THBF_NOBACKGROUND) != THBF_NOBACKGROUND; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetHasBorder(bool hasBorder) { + if (hasBorder) + Button().dwFlags &= ~THBF_NOBACKGROUND; + else + Button().dwFlags |= THBF_NOBACKGROUND; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetDisabled(bool* disabled) { + *disabled = (Button().dwFlags & THBF_DISABLED) == THBF_DISABLED; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetDisabled(bool disabled) { + if (disabled) + Button().dwFlags |= THBF_DISABLED; + else + Button().dwFlags &= ~THBF_DISABLED; + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetImage(imgIContainer** img) { + if (mImage) + NS_ADDREF(*img = mImage); + else + *img = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetImage(imgIContainer* img) { + if (Button().hIcon) ::DestroyIcon(Button().hIcon); + if (img) { + nsresult rv; + rv = nsWindowGfx::CreateIcon( + img, false, LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), + &Button().hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } else { + Button().hIcon = nullptr; + } + return Update(); +} + +NS_IMETHODIMP +TaskbarPreviewButton::GetVisible(bool* visible) { + *visible = (Button().dwFlags & THBF_HIDDEN) != THBF_HIDDEN; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarPreviewButton::SetVisible(bool visible) { + if (visible) + Button().dwFlags &= ~THBF_HIDDEN; + else + Button().dwFlags |= THBF_HIDDEN; + return Update(); +} + +THUMBBUTTON& TaskbarPreviewButton::Button() { + return mPreview->mThumbButtons[mIndex]; +} + +nsresult TaskbarPreviewButton::Update() { + return mPreview->UpdateButton(mIndex); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/TaskbarPreviewButton.h b/widget/windows/TaskbarPreviewButton.h new file mode 100644 index 0000000000..e13b2d6771 --- /dev/null +++ b/widget/windows/TaskbarPreviewButton.h @@ -0,0 +1,47 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarPreviewButton_h__ +#define __mozilla_widget_TaskbarPreviewButton_h__ + +#include <windows.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +#include "mozilla/RefPtr.h" +#include <nsITaskbarPreviewButton.h> +#include <nsString.h> +#include "nsWeakReference.h" + +namespace mozilla { +namespace widget { + +class TaskbarWindowPreview; +class TaskbarPreviewButton : public nsITaskbarPreviewButton, + public nsSupportsWeakReference { + virtual ~TaskbarPreviewButton(); + + public: + TaskbarPreviewButton(TaskbarWindowPreview* preview, uint32_t index); + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWBUTTON + + private: + THUMBBUTTON& Button(); + nsresult Update(); + + RefPtr<TaskbarWindowPreview> mPreview; + uint32_t mIndex; + nsString mTooltip; + nsCOMPtr<imgIContainer> mImage; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarPreviewButton_h__ */ diff --git a/widget/windows/TaskbarTabPreview.cpp b/widget/windows/TaskbarTabPreview.cpp new file mode 100644 index 0000000000..a19a6bf12e --- /dev/null +++ b/widget/windows/TaskbarTabPreview.cpp @@ -0,0 +1,345 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TaskbarTabPreview.h" +#include "nsWindowGfx.h" +#include "nsUXThemeData.h" +#include "WinUtils.h" +#include <nsITaskbarPreviewController.h> + +#define TASKBARPREVIEW_HWNDID L"TaskbarTabPreviewHwnd" + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(TaskbarTabPreview, nsITaskbarTabPreview) + +const wchar_t* const kWindowClass = L"MozillaTaskbarPreviewClass"; + +TaskbarTabPreview::TaskbarTabPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, + HWND aHWND, nsIDocShell* aShell) + : TaskbarPreview(aTaskbar, aController, aHWND, aShell), + mProxyWindow(nullptr), + mIcon(nullptr), + mRegistered(false) {} + +TaskbarTabPreview::~TaskbarTabPreview() { + if (mIcon) { + ::DestroyIcon(mIcon); + mIcon = nullptr; + } + + // We need to ensure that proxy window disappears or else Bad Things happen. + if (mProxyWindow) Disable(); + + NS_ASSERTION(!mProxyWindow, "Taskbar proxy window was not destroyed!"); + + if (IsWindowAvailable()) { + DetachFromNSWindow(); + } else { + mWnd = nullptr; + } +} + +nsresult TaskbarTabPreview::Init() { + nsresult rv = TaskbarPreview::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + + return hook->AddMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); +} + +nsresult TaskbarTabPreview::ShowActive(bool active) { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), + "ShowActive called on invisible window or before taskbar calls " + "can be made for this window"); + return FAILED( + mTaskbar->SetTabActive(active ? mProxyWindow : nullptr, mWnd, 0)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +HWND& TaskbarTabPreview::PreviewWindow() { return mProxyWindow; } + +nativeWindow TaskbarTabPreview::GetHWND() { return mProxyWindow; } + +void TaskbarTabPreview::EnsureRegistration() { + NS_ASSERTION(mVisible && CanMakeTaskbarCalls(), + "EnsureRegistration called when it is not safe to do so"); + + (void)UpdateTaskbarProperties(); +} + +NS_IMETHODIMP +TaskbarTabPreview::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetTitle(const nsAString& aTitle) { + mTitle = aTitle; + return mVisible ? UpdateTitle() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::SetIcon(imgIContainer* icon) { + HICON hIcon = nullptr; + if (icon) { + nsresult rv; + rv = nsWindowGfx::CreateIcon( + icon, false, LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mIcon) ::DestroyIcon(mIcon); + mIcon = hIcon; + mIconImage = icon; + return mVisible ? UpdateIcon() : NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::GetIcon(imgIContainer** icon) { + NS_IF_ADDREF(*icon = mIconImage); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarTabPreview::Move(nsITaskbarTabPreview* aNext) { + if (aNext == this) return NS_ERROR_INVALID_ARG; + mNext = aNext; + return CanMakeTaskbarCalls() ? UpdateNext() : NS_OK; +} + +nsresult TaskbarTabPreview::UpdateTaskbarProperties() { + if (mRegistered) return NS_OK; + + if (FAILED(mTaskbar->RegisterTab(mProxyWindow, mWnd))) + return NS_ERROR_FAILURE; + + nsresult rv = UpdateNext(); + NS_ENSURE_SUCCESS(rv, rv); + rv = TaskbarPreview::UpdateTaskbarProperties(); + mRegistered = true; + return rv; +} + +LRESULT +TaskbarTabPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + RefPtr<TaskbarTabPreview> kungFuDeathGrip(this); + switch (nMsg) { + case WM_CREATE: + TaskbarPreview::EnableCustomDrawing(mProxyWindow, true); + return 0; + case WM_CLOSE: + mController->OnClose(); + return 0; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_ACTIVE) { + // Activate the tab the user selected then restore the main window, + // keeping normal/max window state intact. + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + nsWindow* win = WinUtils::GetNSWindowPtr(mWnd); + if (win) { + nsWindow* parent = win->GetTopLevelWindow(true); + if (parent) { + parent->Show(true); + } + } + } + } + return 0; + case WM_GETICON: + return (LRESULT)mIcon; + case WM_SYSCOMMAND: + // Send activation events to the top level window and select the proper + // tab through the controller. + if (wParam == SC_RESTORE || wParam == SC_MAXIMIZE) { + bool activateWindow; + nsresult rv = mController->OnActivate(&activateWindow); + if (NS_SUCCEEDED(rv) && activateWindow) { + // Note, restoring an iconic, maximized window here will only + // activate the maximized window. This is not a bug, it's default + // windows behavior. + ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + } + return 0; + } + // Forward everything else to the top level window. Do not forward + // close since that's intended for the tab. When the preview proxy + // closes, we'll close the tab above. + return wParam == SC_CLOSE + ? ::DefWindowProcW(mProxyWindow, WM_SYSCOMMAND, wParam, lParam) + : ::SendMessageW(mWnd, WM_SYSCOMMAND, wParam, lParam); + return 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); + if (WindowHook* hook = GetWindowHook()) { + hook->RemoveMonitor(WM_WINDOWPOSCHANGED, MainWindowHook, this); + } + TaskbarPreview::DetachFromNSWindow(); +} + +/* static */ +bool TaskbarTabPreview::MainWindowHook(void* aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, + LRESULT* aResult) { + if (nMsg == WM_WINDOWPOSCHANGED) { + TaskbarTabPreview* preview = reinterpret_cast<TaskbarTabPreview*>(aContext); + WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam); + if (SWP_FRAMECHANGED == (pos->flags & SWP_FRAMECHANGED)) + preview->UpdateProxyWindowStyle(); + } else { + MOZ_ASSERT_UNREACHABLE( + "Style changed hook fired on non-style changed " + "message"); + } + return false; +} + +void TaskbarTabPreview::UpdateProxyWindowStyle() { + if (!mProxyWindow) return; + + DWORD minMaxMask = WS_MINIMIZE | WS_MAXIMIZE; + DWORD windowStyle = GetWindowLongW(mWnd, GWL_STYLE); + + DWORD proxyStyle = GetWindowLongW(mProxyWindow, GWL_STYLE); + proxyStyle &= ~minMaxMask; + proxyStyle |= windowStyle & minMaxMask; + SetWindowLongW(mProxyWindow, GWL_STYLE, proxyStyle); + + DWORD exStyle = + (WS_MAXIMIZE == (windowStyle & WS_MAXIMIZE)) ? WS_EX_TOOLWINDOW : 0; + SetWindowLongW(mProxyWindow, GWL_EXSTYLE, exStyle); +} + +nsresult TaskbarTabPreview::UpdateTitle() { + NS_ASSERTION(mVisible, "UpdateTitle called on invisible preview"); + + if (!::SetWindowTextW(mProxyWindow, mTitle.get())) return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult TaskbarTabPreview::UpdateIcon() { + NS_ASSERTION(mVisible, "UpdateIcon called on invisible preview"); + + ::SendMessageW(mProxyWindow, WM_SETICON, ICON_SMALL, (LPARAM)mIcon); + + return NS_OK; +} + +nsresult TaskbarTabPreview::UpdateNext() { + NS_ASSERTION(CanMakeTaskbarCalls() && mVisible, + "UpdateNext called on invisible tab preview"); + HWND hNext = nullptr; + if (mNext) { + bool visible; + nsresult rv = mNext->GetVisible(&visible); + + NS_ENSURE_SUCCESS(rv, rv); + + // Can only move next to enabled previews + if (!visible) return NS_ERROR_FAILURE; + + hNext = (HWND)mNext->GetHWND(); + + // hNext must be registered with the taskbar if the call is to succeed + mNext->EnsureRegistration(); + } + if (FAILED(mTaskbar->SetTabOrder(mProxyWindow, hNext))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/TaskbarTabPreview.h b/widget/windows/TaskbarTabPreview.h new file mode 100644 index 0000000000..9a15760557 --- /dev/null +++ b/widget/windows/TaskbarTabPreview.h @@ -0,0 +1,70 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarTabPreview_h__ +#define __mozilla_widget_TaskbarTabPreview_h__ + +#include "nsITaskbarTabPreview.h" +#include "TaskbarPreview.h" + +namespace mozilla { +namespace widget { + +class TaskbarTabPreview : public nsITaskbarTabPreview, public TaskbarPreview { + virtual ~TaskbarTabPreview(); + + public: + TaskbarTabPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, HWND aHWND, + nsIDocShell* aShell); + virtual nsresult Init() override; + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARTABPREVIEW + NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::) + + private: + virtual nsresult ShowActive(bool active); + virtual HWND& PreviewWindow(); + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK GlobalWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, + LPARAM lParam); + + virtual nsresult UpdateTaskbarProperties(); + virtual nsresult Enable(); + virtual nsresult Disable(); + virtual void DetachFromNSWindow(); + + // WindowHook procedure for hooking mWnd + static bool MainWindowHook(void* aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, LRESULT* aResult); + + // Bug 520807 - we need to update the proxy window style based on the main + // window's style to workaround a bug with the way the DWM displays the + // previews. + void UpdateProxyWindowStyle(); + + nsresult UpdateTitle(); + nsresult UpdateIcon(); + nsresult UpdateNext(); + + // Handle to the toplevel proxy window + HWND mProxyWindow; + nsString mTitle; + nsCOMPtr<imgIContainer> mIconImage; + // Cached Windows icon of mIconImage + HICON mIcon; + // Preview that follows this preview in the taskbar (left-to-right order) + nsCOMPtr<nsITaskbarTabPreview> mNext; + // True if this preview has been registered with the taskbar + bool mRegistered; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarTabPreview_h__ */ diff --git a/widget/windows/TaskbarWindowPreview.cpp b/widget/windows/TaskbarWindowPreview.cpp new file mode 100644 index 0000000000..670a27bbef --- /dev/null +++ b/widget/windows/TaskbarWindowPreview.cpp @@ -0,0 +1,323 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include <nsITaskbarPreviewController.h> +#include "TaskbarWindowPreview.h" +#include "WindowHook.h" +#include "nsUXThemeData.h" +#include "TaskbarPreviewButton.h" +#include "nsWindow.h" +#include "nsWindowGfx.h" + +namespace mozilla { +namespace widget { + +namespace { +bool WindowHookProc(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam, + LPARAM lParam, LRESULT* aResult) { + TaskbarWindowPreview* preview = + reinterpret_cast<TaskbarWindowPreview*>(aContext); + *aResult = preview->WndProc(nMsg, wParam, lParam); + return true; +} +} // namespace + +NS_IMPL_ISUPPORTS(TaskbarWindowPreview, nsITaskbarWindowPreview, + nsITaskbarProgress, nsITaskbarOverlayIconController, + nsISupportsWeakReference) + +/** + * These correspond directly to the states defined in nsITaskbarProgress.idl, so + * they should be kept in sync. + */ +static TBPFLAG sNativeStates[] = {TBPF_NOPROGRESS, TBPF_INDETERMINATE, + TBPF_NORMAL, TBPF_ERROR, TBPF_PAUSED}; + +TaskbarWindowPreview::TaskbarWindowPreview( + ITaskbarList4* aTaskbar, nsITaskbarPreviewController* aController, + HWND aHWND, nsIDocShell* aShell) + : TaskbarPreview(aTaskbar, aController, aHWND, aShell), + mCustomDrawing(false), + mHaveButtons(false), + mState(TBPF_NOPROGRESS), + mCurrentValue(0), + mMaxValue(0), + mOverlayIcon(nullptr) { + // Window previews are visible by default + (void)SetVisible(true); + + memset(mThumbButtons, 0, sizeof mThumbButtons); + for (int32_t i = 0; i < nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS; i++) { + mThumbButtons[i].dwMask = THB_FLAGS | THB_ICON | THB_TOOLTIP; + mThumbButtons[i].iId = i; + mThumbButtons[i].dwFlags = THBF_HIDDEN; + } +} + +TaskbarWindowPreview::~TaskbarWindowPreview() { + if (mOverlayIcon) { + ::DestroyIcon(mOverlayIcon); + mOverlayIcon = nullptr; + } + + if (IsWindowAvailable()) { + DetachFromNSWindow(); + } else { + mWnd = nullptr; + } +} + +nsresult TaskbarWindowPreview::Init() { + nsresult rv = TaskbarPreview::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + if (CanMakeTaskbarCalls()) { + return NS_OK; + } + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + + return hook->AddMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + TaskbarWindowHook, this); +} + +nsresult TaskbarWindowPreview::ShowActive(bool active) { + return FAILED(mTaskbar->ActivateTab(active ? mWnd : nullptr)) + ? NS_ERROR_FAILURE + : NS_OK; +} + +HWND& TaskbarWindowPreview::PreviewWindow() { return mWnd; } + +nsresult TaskbarWindowPreview::GetButton(uint32_t index, + nsITaskbarPreviewButton** _retVal) { + if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsITaskbarPreviewButton> button( + do_QueryReferent(mWeakButtons[index])); + + if (!button) { + // Lost reference + button = new TaskbarPreviewButton(this, index); + if (!button) { + return NS_ERROR_OUT_OF_MEMORY; + } + mWeakButtons[index] = do_GetWeakReference(button); + } + + if (!mHaveButtons) { + mHaveButtons = true; + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + (void)hook->AddHook(WM_COMMAND, WindowHookProc, this); + + if (mVisible && FAILED(mTaskbar->ThumbBarAddButtons( + mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, + mThumbButtons))) { + return NS_ERROR_FAILURE; + } + } + button.forget(_retVal); + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetEnableCustomDrawing(bool aEnable) { + if (aEnable == mCustomDrawing) return NS_OK; + + WindowHook* hook = GetWindowHook(); + if (!hook) { + return NS_ERROR_NOT_AVAILABLE; + } + + mCustomDrawing = aEnable; + TaskbarPreview::EnableCustomDrawing(mWnd, aEnable); + + if (aEnable) { + (void)hook->AddHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this); + (void)hook->AddHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc, + this); + } else { + (void)hook->RemoveHook(WM_DWMSENDICONICLIVEPREVIEWBITMAP, WindowHookProc, + this); + (void)hook->RemoveHook(WM_DWMSENDICONICTHUMBNAIL, WindowHookProc, this); + } + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::GetEnableCustomDrawing(bool* aEnable) { + *aEnable = mCustomDrawing; + return NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetProgressState(nsTaskbarProgressState aState, + uint64_t aCurrentValue, + uint64_t aMaxValue) { + NS_ENSURE_ARG_RANGE(aState, nsTaskbarProgressState(0), + nsTaskbarProgressState(ArrayLength(sNativeStates) - 1)); + + TBPFLAG nativeState = sNativeStates[aState]; + if (nativeState == TBPF_NOPROGRESS || nativeState == TBPF_INDETERMINATE) { + NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG); + } + + if (aCurrentValue > aMaxValue) return NS_ERROR_ILLEGAL_VALUE; + + mState = nativeState; + mCurrentValue = aCurrentValue; + mMaxValue = aMaxValue; + + // Only update if we can + return CanMakeTaskbarCalls() ? UpdateTaskbarProgress() : NS_OK; +} + +NS_IMETHODIMP +TaskbarWindowPreview::SetOverlayIcon(imgIContainer* aStatusIcon, + const nsAString& aStatusDescription) { + nsresult rv; + if (aStatusIcon) { + // The image shouldn't be animated + bool isAnimated; + rv = aStatusIcon->GetAnimated(&isAnimated); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_FALSE(isAnimated, NS_ERROR_INVALID_ARG); + } + + HICON hIcon = nullptr; + if (aStatusIcon) { + rv = nsWindowGfx::CreateIcon( + aStatusIcon, false, LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mOverlayIcon) ::DestroyIcon(mOverlayIcon); + mOverlayIcon = hIcon; + mIconDescription = aStatusDescription; + + // Only update if we can + return CanMakeTaskbarCalls() ? UpdateOverlayIcon() : NS_OK; +} + +nsresult TaskbarWindowPreview::UpdateTaskbarProperties() { + if (mHaveButtons) { + if (FAILED(mTaskbar->ThumbBarAddButtons( + mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons))) + return NS_ERROR_FAILURE; + } + nsresult rv = UpdateTaskbarProgress(); + NS_ENSURE_SUCCESS(rv, rv); + rv = UpdateOverlayIcon(); + NS_ENSURE_SUCCESS(rv, rv); + return TaskbarPreview::UpdateTaskbarProperties(); +} + +nsresult TaskbarWindowPreview::UpdateTaskbarProgress() { + HRESULT hr = mTaskbar->SetProgressState(mWnd, mState); + if (SUCCEEDED(hr) && mState != TBPF_NOPROGRESS && + mState != TBPF_INDETERMINATE) + hr = mTaskbar->SetProgressValue(mWnd, mCurrentValue, mMaxValue); + + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult TaskbarWindowPreview::UpdateOverlayIcon() { + HRESULT hr = + mTaskbar->SetOverlayIcon(mWnd, mOverlayIcon, mIconDescription.get()); + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; +} + +LRESULT +TaskbarWindowPreview::WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) { + RefPtr<TaskbarWindowPreview> kungFuDeathGrip(this); + switch (nMsg) { + case WM_COMMAND: { + uint32_t id = LOWORD(wParam); + uint32_t index = id; + nsCOMPtr<nsITaskbarPreviewButton> button; + nsresult rv = GetButton(index, getter_AddRefs(button)); + if (NS_SUCCEEDED(rv)) mController->OnClick(button); + } + return 0; + } + return TaskbarPreview::WndProc(nMsg, wParam, lParam); +} + +/* static */ +bool TaskbarWindowPreview::TaskbarWindowHook(void* aContext, HWND hWnd, + UINT nMsg, WPARAM wParam, + LPARAM lParam, LRESULT* aResult) { + NS_ASSERTION(nMsg == nsAppShell::GetTaskbarButtonCreatedMessage(), + "Window hook proc called with wrong message"); + TaskbarWindowPreview* preview = + reinterpret_cast<TaskbarWindowPreview*>(aContext); + // Now we can make all the calls to mTaskbar + preview->UpdateTaskbarProperties(); + return false; +} + +nsresult TaskbarWindowPreview::Enable() { + nsresult rv = TaskbarPreview::Enable(); + NS_ENSURE_SUCCESS(rv, rv); + + return FAILED(mTaskbar->AddTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK; +} + +nsresult TaskbarWindowPreview::Disable() { + nsresult rv = TaskbarPreview::Disable(); + NS_ENSURE_SUCCESS(rv, rv); + + return FAILED(mTaskbar->DeleteTab(mWnd)) ? NS_ERROR_FAILURE : NS_OK; +} + +void TaskbarWindowPreview::DetachFromNSWindow() { + // Remove the hooks we have for drawing + SetEnableCustomDrawing(false); + + if (WindowHook* hook = GetWindowHook()) { + (void)hook->RemoveHook(WM_COMMAND, WindowHookProc, this); + (void)hook->RemoveMonitor(nsAppShell::GetTaskbarButtonCreatedMessage(), + TaskbarWindowHook, this); + } + TaskbarPreview::DetachFromNSWindow(); +} + +nsresult TaskbarWindowPreview::UpdateButtons() { + NS_ASSERTION(mVisible, "UpdateButtons called on invisible preview"); + + if (FAILED(mTaskbar->ThumbBarUpdateButtons( + mWnd, nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS, mThumbButtons))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult TaskbarWindowPreview::UpdateButton(uint32_t index) { + if (index >= nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS) + return NS_ERROR_INVALID_ARG; + if (mVisible) { + if (FAILED(mTaskbar->ThumbBarUpdateButtons(mWnd, 1, &mThumbButtons[index]))) + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/TaskbarWindowPreview.h b/widget/windows/TaskbarWindowPreview.h new file mode 100644 index 0000000000..97c074197c --- /dev/null +++ b/widget/windows/TaskbarWindowPreview.h @@ -0,0 +1,85 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_widget_TaskbarWindowPreview_h__ +#define __mozilla_widget_TaskbarWindowPreview_h__ + +#include "nsITaskbarWindowPreview.h" +#include "nsITaskbarProgress.h" +#include "nsITaskbarOverlayIconController.h" +#include "TaskbarPreview.h" +#include "nsWeakReference.h" + +namespace mozilla { +namespace widget { + +class TaskbarPreviewButton; +class TaskbarWindowPreview : public TaskbarPreview, + public nsITaskbarWindowPreview, + public nsITaskbarProgress, + public nsITaskbarOverlayIconController, + public nsSupportsWeakReference { + virtual ~TaskbarWindowPreview(); + + public: + TaskbarWindowPreview(ITaskbarList4* aTaskbar, + nsITaskbarPreviewController* aController, HWND aHWND, + nsIDocShell* aShell); + virtual nsresult Init() override; + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARWINDOWPREVIEW + NS_DECL_NSITASKBARPROGRESS + NS_DECL_NSITASKBAROVERLAYICONCONTROLLER + NS_FORWARD_NSITASKBARPREVIEW(TaskbarPreview::) + + virtual LRESULT WndProc(UINT nMsg, WPARAM wParam, LPARAM lParam) override; + + private: + virtual nsresult ShowActive(bool active) override; + virtual HWND& PreviewWindow() override; + + virtual nsresult UpdateTaskbarProperties() override; + virtual nsresult Enable() override; + virtual nsresult Disable() override; + virtual void DetachFromNSWindow() override; + nsresult UpdateButton(uint32_t index); + nsresult UpdateButtons(); + + // Is custom drawing enabled? + bool mCustomDrawing; + // Have we made any buttons? + bool mHaveButtons; + // Windows button format + THUMBBUTTON mThumbButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS]; + // Pointers to our button class (cached instances) + nsWeakPtr mWeakButtons[nsITaskbarWindowPreview::NUM_TOOLBAR_BUTTONS]; + + // Called to update ITaskbarList4 dependent properties + nsresult UpdateTaskbarProgress(); + nsresult UpdateOverlayIcon(); + + // The taskbar progress + TBPFLAG mState; + ULONGLONG mCurrentValue; + ULONGLONG mMaxValue; + + // Taskbar overlay icon + HICON mOverlayIcon; + nsString mIconDescription; + + // WindowHook procedure for hooking mWnd for taskbar progress and icon stuff + static bool TaskbarWindowHook(void* aContext, HWND hWnd, UINT nMsg, + WPARAM wParam, LPARAM lParam, LRESULT* aResult); + + friend class TaskbarPreviewButton; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_TaskbarWindowPreview_h__ */ diff --git a/widget/windows/ToastNotification.cpp b/widget/windows/ToastNotification.cpp new file mode 100644 index 0000000000..916f770590 --- /dev/null +++ b/widget/windows/ToastNotification.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ToastNotification.h" + +#include "mozilla/WindowsVersion.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "ToastNotificationHandler.h" +#include "WinTaskbar.h" + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(ToastNotification, nsIAlertsService, nsIObserver, + nsISupportsWeakReference) + +ToastNotification::ToastNotification() = default; + +ToastNotification::~ToastNotification() = default; + +nsresult ToastNotification::Init() { + if (!IsWin8OrLater()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsAutoString uid; + if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) { + // Windows Toast Notification requires AppId + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsresult rv = + NS_NewNamedThread("ToastBgThread", getter_AddRefs(mBackgroundThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIObserverService> obsServ = + do_GetService("@mozilla.org/observer-service;1"); + if (obsServ) { + obsServ->AddObserver(this, "quit-application", true); + } + + return NS_OK; +} + +nsresult ToastNotification::BackgroundDispatch(nsIRunnable* runnable) { + return mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ToastNotification::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // Got quit-application + // The handlers destructors will do the right thing (de-register with + // Windows). + for (auto iter = mActiveHandlers.Iter(); !iter.Done(); iter.Next()) { + RefPtr<ToastNotificationHandler> handler = iter.UserData(); + iter.Remove(); + + // Break the cycle between the handler and the MSCOM notification so the + // handler's destructor will be called. + handler->UnregisterHandler(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ToastNotification::ShowAlertNotification( + const nsAString& aImageUrl, const nsAString& aAlertTitle, + const nsAString& aAlertText, bool aAlertTextClickable, + const nsAString& aAlertCookie, nsIObserver* aAlertListener, + const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing, + bool aRequireInteraction) { + nsCOMPtr<nsIAlertNotification> alert = + do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); + if (NS_WARN_IF(!alert)) { + return NS_ERROR_FAILURE; + } + nsresult rv = + alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText, + aAlertTextClickable, aAlertCookie, aBidi, aLang, aData, + aPrincipal, aInPrivateBrowsing, aRequireInteraction); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return ShowAlert(alert, aAlertListener); +} + +NS_IMETHODIMP +ToastNotification::ShowPersistentNotification(const nsAString& aPersistentData, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + return ShowAlert(aAlert, aAlertListener); +} + +NS_IMETHODIMP +ToastNotification::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + if (NS_WARN_IF(!aAlert)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoString cookie; + nsresult rv = aAlert->GetCookie(cookie); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString name; + rv = aAlert->GetName(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString title; + rv = aAlert->GetTitle(title); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString text; + rv = aAlert->GetText(text); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool textClickable; + rv = aAlert->GetTextClickable(&textClickable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString hostPort; + rv = aAlert->GetSource(hostPort); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<ToastNotificationHandler> oldHandler = mActiveHandlers.Get(name); + + RefPtr<ToastNotificationHandler> handler = new ToastNotificationHandler( + this, aAlertListener, name, cookie, title, text, hostPort, textClickable); + mActiveHandlers.Put(name, RefPtr{handler}); + + rv = handler->InitAlertAsync(aAlert); + if (NS_WARN_IF(NS_FAILED(rv))) { + mActiveHandlers.Remove(name); + handler->UnregisterHandler(); + return rv; + } + + // If there was a previous handler with the same name then unregister it. + if (oldHandler) { + oldHandler->UnregisterHandler(); + } + + return NS_OK; +} + +NS_IMETHODIMP +ToastNotification::CloseAlert(const nsAString& aAlertName) { + RefPtr<ToastNotificationHandler> handler; + if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { + return NS_OK; + } + mActiveHandlers.Remove(aAlertName); + handler->UnregisterHandler(); + return NS_OK; +} + +bool ToastNotification::IsActiveHandler(const nsAString& aAlertName, + ToastNotificationHandler* aHandler) { + RefPtr<ToastNotificationHandler> handler; + if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { + return false; + } + return handler == aHandler; +} + +void ToastNotification::RemoveHandler(const nsAString& aAlertName, + ToastNotificationHandler* aHandler) { + // The alert may have been replaced; only remove it from the active + // handlers map if it's the same. + if (IsActiveHandler(aAlertName, aHandler)) { + // Terrible things happen if the destructor of a handler is called inside + // the hashtable .Remove() method. Wait until we have returned from there. + RefPtr<ToastNotificationHandler> kungFuDeathGrip(aHandler); + mActiveHandlers.Remove(aAlertName); + aHandler->UnregisterHandler(); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/ToastNotification.h b/widget/windows/ToastNotification.h new file mode 100644 index 0000000000..31d2e548dd --- /dev/null +++ b/widget/windows/ToastNotification.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 ToastNotification_h__ +#define ToastNotification_h__ + +#include "nsIAlertsService.h" +#include "nsIObserver.h" +#include "nsIThread.h" +#include "nsRefPtrHashtable.h" +#include "nsWeakReference.h" + +namespace mozilla { +namespace widget { + +class ToastNotificationHandler; + +class ToastNotification final : public nsIAlertsService, + public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_NSIALERTSSERVICE + NS_DECL_NSIOBSERVER + NS_DECL_ISUPPORTS + + ToastNotification(); + + nsresult Init(); + + bool IsActiveHandler(const nsAString& aAlertName, + ToastNotificationHandler* aHandler); + void RemoveHandler(const nsAString& aAlertName, + ToastNotificationHandler* aHandler); + + nsresult BackgroundDispatch(nsIRunnable* runnable); + + protected: + virtual ~ToastNotification(); + + nsRefPtrHashtable<nsStringHashKey, ToastNotificationHandler> mActiveHandlers; + + nsCOMPtr<nsIThread> mBackgroundThread; +}; + +} // namespace widget +} // namespace mozilla + +#endif diff --git a/widget/windows/ToastNotificationHandler.cpp b/widget/windows/ToastNotificationHandler.cpp new file mode 100644 index 0000000000..923b6da38e --- /dev/null +++ b/widget/windows/ToastNotificationHandler.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sts=2 sw=2 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ToastNotificationHandler.h" + +#include "WidgetUtils.h" +#include "WinTaskbar.h" +#include "WinUtils.h" +#include "imgIContainer.h" +#include "imgIRequest.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/gfx/2D.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "nsIUUIDGenerator.h" +#include "nsIWidget.h" +#include "nsIWindowMediator.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsProxyRelease.h" + +#include "ToastNotification.h" + +namespace mozilla { +namespace widget { + +typedef ABI::Windows::Foundation::ITypedEventHandler< + ABI::Windows::UI::Notifications::ToastNotification*, IInspectable*> + ToastActivationHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler< + ABI::Windows::UI::Notifications::ToastNotification*, + ABI::Windows::UI::Notifications::ToastDismissedEventArgs*> + ToastDismissedHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler< + ABI::Windows::UI::Notifications::ToastNotification*, + ABI::Windows::UI::Notifications::ToastFailedEventArgs*> + ToastFailedHandler; + +using namespace ABI::Windows::Data::Xml::Dom; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::UI::Notifications; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace mozilla; + +NS_IMPL_ISUPPORTS(ToastNotificationHandler, nsIAlertNotificationImageListener) + +static bool SetNodeValueString(const nsString& aString, IXmlNode* node, + IXmlDocument* xml) { + ComPtr<IXmlText> inputText; + if (NS_WARN_IF(FAILED(xml->CreateTextNode( + HStringReference(static_cast<const wchar_t*>(aString.get())).Get(), + &inputText)))) { + return false; + } + ComPtr<IXmlNode> inputTextNode; + if (NS_WARN_IF(FAILED(inputText.As(&inputTextNode)))) { + return false; + } + ComPtr<IXmlNode> appendedChild; + if (NS_WARN_IF( + FAILED(node->AppendChild(inputTextNode.Get(), &appendedChild)))) { + return false; + } + return true; +} + +static bool SetAttribute(IXmlElement* element, const HSTRING name, + const nsAString& value) { + HSTRING valueStr = HStringReference(static_cast<const wchar_t*>( + PromiseFlatString(value).get())) + .Get(); + if (NS_WARN_IF(FAILED(element->SetAttribute(name, valueStr)))) { + return false; + } + return true; +} + +static bool AddActionNode(IXmlDocument* toastXml, IXmlNode* actionsNode, + const nsAString& actionTitle, + const nsAString& actionArgs) { + ComPtr<IXmlElement> action; + HRESULT hr = + toastXml->CreateElement(HStringReference(L"action").Get(), &action); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + if (NS_WARN_IF(!SetAttribute(action.Get(), HStringReference(L"content").Get(), + actionTitle))) { + return false; + } + + if (NS_WARN_IF(!SetAttribute( + action.Get(), HStringReference(L"arguments").Get(), actionArgs))) { + return false; + } + if (NS_WARN_IF(!SetAttribute(action.Get(), + HStringReference(L"placement").Get(), + u"contextmenu"_ns))) { + return false; + } + + // Add <action> to <actions> + ComPtr<IXmlNode> actionNode; + hr = action.As(&actionNode); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IXmlNode> appendedChild; + hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + return true; +} + +static ComPtr<IToastNotificationManagerStatics> +GetToastNotificationManagerStatics() { + ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics; + if (NS_WARN_IF(FAILED(GetActivationFactory( + HStringReference( + RuntimeClass_Windows_UI_Notifications_ToastNotificationManager) + .Get(), + &toastNotificationManagerStatics)))) { + return nullptr; + } + + return toastNotificationManagerStatics; +} + +ToastNotificationHandler::~ToastNotificationHandler() { + if (mImageRequest) { + mImageRequest->Cancel(NS_BINDING_ABORTED); + mImageRequest = nullptr; + } + + if (mHasImage) { + DebugOnly<nsresult> rv = mImageFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Cannot remove temporary image file"); + } + + UnregisterHandler(); +} + +void ToastNotificationHandler::UnregisterHandler() { + if (mNotification && mNotifier) { + mNotification->remove_Dismissed(mDismissedToken); + mNotification->remove_Activated(mActivatedToken); + mNotification->remove_Failed(mFailedToken); + mNotifier->Hide(mNotification.Get()); + } + + mNotification = nullptr; + mNotifier = nullptr; + + SendFinished(); +} + +ComPtr<IXmlDocument> ToastNotificationHandler::InitializeXmlForTemplate( + ToastTemplateType templateType) { + ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics = + GetToastNotificationManagerStatics(); + + ComPtr<IXmlDocument> toastXml; + toastNotificationManagerStatics->GetTemplateContent(templateType, &toastXml); + + return toastXml; +} + +nsresult ToastNotificationHandler::InitAlertAsync( + nsIAlertNotification* aAlert) { + return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr, + getter_AddRefs(mImageRequest)); +} + +bool ToastNotificationHandler::ShowAlert() { + if (!mBackend->IsActiveHandler(mName, this)) { + return true; + } + + ToastTemplateType toastTemplate; + if (mHostPort.IsEmpty()) { + toastTemplate = + mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText03 + : ToastTemplateType::ToastTemplateType_ToastText03; + } else { + toastTemplate = + mHasImage ? ToastTemplateType::ToastTemplateType_ToastImageAndText04 + : ToastTemplateType::ToastTemplateType_ToastText04; + } + + ComPtr<IXmlDocument> toastXml = InitializeXmlForTemplate(toastTemplate); + if (!toastXml) { + return false; + } + + HRESULT hr; + + if (mHasImage) { + ComPtr<IXmlNodeList> toastImageElements; + hr = toastXml->GetElementsByTagName(HStringReference(L"image").Get(), + &toastImageElements); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + ComPtr<IXmlNode> imageNode; + hr = toastImageElements->Item(0, &imageNode); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + ComPtr<IXmlElement> image; + hr = imageNode.As(&image); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + if (NS_WARN_IF(!SetAttribute(image.Get(), HStringReference(L"src").Get(), + mImageUri))) { + return false; + } + } + + ComPtr<IXmlNodeList> toastTextElements; + hr = toastXml->GetElementsByTagName(HStringReference(L"text").Get(), + &toastTextElements); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IXmlNode> titleTextNodeRoot; + hr = toastTextElements->Item(0, &titleTextNodeRoot); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + ComPtr<IXmlNode> msgTextNodeRoot; + hr = toastTextElements->Item(1, &msgTextNodeRoot); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + if (NS_WARN_IF(!SetNodeValueString(mTitle, titleTextNodeRoot.Get(), + toastXml.Get()))) { + return false; + } + if (NS_WARN_IF( + !SetNodeValueString(mMsg, msgTextNodeRoot.Get(), toastXml.Get()))) { + return false; + } + + ComPtr<IXmlNodeList> toastElements; + hr = toastXml->GetElementsByTagName(HStringReference(L"toast").Get(), + &toastElements); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IXmlNode> toastNodeRoot; + hr = toastElements->Item(0, &toastNodeRoot); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IXmlElement> actions; + hr = toastXml->CreateElement(HStringReference(L"actions").Get(), &actions); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IXmlNode> actionsNode; + hr = actions.As(&actionsNode); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + nsCOMPtr<nsIStringBundleService> sbs = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + if (NS_WARN_IF(!sbs)) { + return false; + } + + nsCOMPtr<nsIStringBundle> bundle; + sbs->CreateBundle("chrome://alerts/locale/alert.properties", + getter_AddRefs(bundle)); + if (NS_WARN_IF(!bundle)) { + return false; + } + + if (!mHostPort.IsEmpty()) { + AutoTArray<nsString, 1> formatStrings = {mHostPort}; + + ComPtr<IXmlNode> urlTextNodeRoot; + hr = toastTextElements->Item(2, &urlTextNodeRoot); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + nsAutoString urlReference; + bundle->FormatStringFromName("source.label", formatStrings, urlReference); + + if (NS_WARN_IF(!SetNodeValueString(urlReference, urlTextNodeRoot.Get(), + toastXml.Get()))) { + return false; + } + + if (IsWin10AnniversaryUpdateOrLater()) { + ComPtr<IXmlElement> placementText; + hr = urlTextNodeRoot.As(&placementText); + if (SUCCEEDED(hr)) { + // placement is supported on Windows 10 Anniversary Update or later + SetAttribute(placementText.Get(), HStringReference(L"placement").Get(), + u"attribution"_ns); + } + } + + nsAutoString disableButtonTitle; + bundle->FormatStringFromName("webActions.disableForOrigin.label", + formatStrings, disableButtonTitle); + + AddActionNode(toastXml.Get(), actionsNode.Get(), disableButtonTitle, + u"snooze"_ns); + } + + nsAutoString settingsButtonTitle; + bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle); + AddActionNode(toastXml.Get(), actionsNode.Get(), settingsButtonTitle, + u"settings"_ns); + + ComPtr<IXmlNode> appendedChild; + hr = toastNodeRoot->AppendChild(actionsNode.Get(), &appendedChild); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + return CreateWindowsNotificationFromXml(toastXml.Get()); +} + +bool ToastNotificationHandler::CreateWindowsNotificationFromXml( + IXmlDocument* aXml) { + ComPtr<IToastNotificationFactory> factory; + HRESULT hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification) + .Get(), + &factory); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + hr = factory->CreateToastNotification(aXml, &mNotification); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + RefPtr<ToastNotificationHandler> self = this; + + hr = mNotification->add_Activated( + Callback<ToastActivationHandler>([self](IToastNotification* aNotification, + IInspectable* aInspectable) { + return self->OnActivate(aNotification, aInspectable); + }).Get(), + &mActivatedToken); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + hr = mNotification->add_Dismissed( + Callback<ToastDismissedHandler>([self](IToastNotification* aNotification, + IToastDismissedEventArgs* aArgs) { + return self->OnDismiss(aNotification, aArgs); + }).Get(), + &mDismissedToken); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + hr = mNotification->add_Failed( + Callback<ToastFailedHandler>([self](IToastNotification* aNotification, + IToastFailedEventArgs* aArgs) { + return self->OnFail(aNotification, aArgs); + }).Get(), + &mFailedToken); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + ComPtr<IToastNotificationManagerStatics> toastNotificationManagerStatics = + GetToastNotificationManagerStatics(); + if (NS_WARN_IF(!toastNotificationManagerStatics)) { + return false; + } + + nsAutoString uid; + if (NS_WARN_IF(!WinTaskbar::GetAppUserModelID(uid))) { + return false; + } + + HSTRING uidStr = + HStringReference(static_cast<const wchar_t*>(uid.get())).Get(); + hr = toastNotificationManagerStatics->CreateToastNotifierWithId(uidStr, + &mNotifier); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + hr = mNotifier->Show(mNotification.Get()); + if (NS_WARN_IF(FAILED(hr))) { + return false; + } + + if (mAlertListener) { + mAlertListener->Observe(nullptr, "alertshow", mCookie.get()); + } + + return true; +} + +void ToastNotificationHandler::SendFinished() { + if (!mSentFinished && mAlertListener) { + mAlertListener->Observe(nullptr, "alertfinished", mCookie.get()); + } + + mSentFinished = true; +} + +HRESULT +ToastNotificationHandler::OnActivate(IToastNotification* notification, + IInspectable* inspectable) { + if (mAlertListener) { + nsAutoString argString; + if (inspectable) { + ComPtr<IToastActivatedEventArgs> eventArgs; + HRESULT hr = inspectable->QueryInterface( + __uuidof(IToastActivatedEventArgs), (void**)&eventArgs); + if (SUCCEEDED(hr)) { + HSTRING arguments; + hr = eventArgs->get_Arguments(&arguments); + if (SUCCEEDED(hr)) { + uint32_t len = 0; + const wchar_t* buffer = WindowsGetStringRawBuffer(arguments, &len); + if (buffer) { + argString.Assign(buffer, len); + } + } + } + } + + if (argString.EqualsLiteral("settings")) { + mAlertListener->Observe(nullptr, "alertsettingscallback", mCookie.get()); + } else if (argString.EqualsLiteral("snooze")) { + mAlertListener->Observe(nullptr, "alertdisablecallback", mCookie.get()); + } else if (mClickable) { + // When clicking toast, focus moves to another process, but we want to set + // focus on Firefox process. + nsCOMPtr<nsIWindowMediator> winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (winMediator) { + nsCOMPtr<mozIDOMWindowProxy> navWin; + winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + if (navWin) { + nsCOMPtr<nsIWidget> widget = + WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(navWin)); + if (widget) { + SetForegroundWindow( + static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW))); + } + } + } + mAlertListener->Observe(nullptr, "alertclickcallback", mCookie.get()); + } + } + mBackend->RemoveHandler(mName, this); + return S_OK; +} + +HRESULT +ToastNotificationHandler::OnDismiss(IToastNotification* notification, + IToastDismissedEventArgs* aArgs) { + SendFinished(); + mBackend->RemoveHandler(mName, this); + return S_OK; +} + +HRESULT +ToastNotificationHandler::OnFail(IToastNotification* notification, + IToastFailedEventArgs* aArgs) { + SendFinished(); + mBackend->RemoveHandler(mName, this); + return S_OK; +} + +nsresult ToastNotificationHandler::TryShowAlert() { + if (NS_WARN_IF(!ShowAlert())) { + mBackend->RemoveHandler(mName, this); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +ToastNotificationHandler::OnImageMissing(nsISupports*) { + return TryShowAlert(); +} + +NS_IMETHODIMP +ToastNotificationHandler::OnImageReady(nsISupports*, imgIRequest* aRequest) { + nsresult rv = AsyncSaveImage(aRequest); + if (NS_FAILED(rv)) { + return TryShowAlert(); + } + return rv; +} + +nsresult ToastNotificationHandler::AsyncSaveImage(imgIRequest* aRequest) { + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mImageFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mImageFile->Append(u"notificationimages"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mImageFile->Create(nsIFile::DIRECTORY_TYPE, 0500); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + return rv; + } + + nsCOMPtr<nsIUUIDGenerator> idGen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsID uuid; + rv = idGen->GenerateUUIDInPlace(&uuid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + char uuidChars[NSID_LENGTH]; + uuid.ToProvidedString(uuidChars); + // Remove the brackets at the beginning and ending of the generated UUID. + nsAutoCString uuidStr(Substring(uuidChars + 1, uuidChars + NSID_LENGTH - 2)); + uuidStr.AppendLiteral(".bmp"); + mImageFile->AppendNative(uuidStr); + + nsCOMPtr<imgIContainer> imgContainer; + rv = aRequest->GetImage(getter_AddRefs(imgContainer)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsMainThreadPtrHandle<ToastNotificationHandler> self( + new nsMainThreadPtrHolder<ToastNotificationHandler>( + "ToastNotificationHandler", this)); + + nsCOMPtr<nsIFile> imageFile(mImageFile); + RefPtr<mozilla::gfx::SourceSurface> surface = imgContainer->GetFrame( + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "ToastNotificationHandler::AsyncWriteBitmap", + [self, imageFile, surface]() -> void { + nsresult rv; + if (!surface) { + rv = NS_ERROR_FAILURE; + } else { + rv = WinUtils::WriteBitmap(imageFile, surface); + } + + nsCOMPtr<nsIRunnable> cbRunnable = NS_NewRunnableFunction( + "ToastNotificationHandler::AsyncWriteBitmapCb", + [self, rv]() -> void { + auto handler = const_cast<ToastNotificationHandler*>(self.get()); + handler->OnWriteBitmapFinished(rv); + }); + + NS_DispatchToMainThread(cbRunnable); + }); + + return mBackend->BackgroundDispatch(r); +} + +void ToastNotificationHandler::OnWriteBitmapFinished(nsresult rv) { + if (NS_SUCCEEDED(rv)) { + OnWriteBitmapSuccess(); + } + TryShowAlert(); +} + +nsresult ToastNotificationHandler::OnWriteBitmapSuccess() { + nsresult rv; + + nsCOMPtr<nsIURI> fileURI; + rv = NS_NewFileURI(getter_AddRefs(fileURI), mImageFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString uriStr; + rv = fileURI->GetSpec(uriStr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + AppendUTF8toUTF16(uriStr, mImageUri); + + mHasImage = true; + + return NS_OK; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/ToastNotificationHandler.h b/widget/windows/ToastNotificationHandler.h new file mode 100644 index 0000000000..106756d9ce --- /dev/null +++ b/widget/windows/ToastNotificationHandler.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ToastNotificationHandler_h__ +#define ToastNotificationHandler_h__ + +#include <windows.ui.notifications.h> +#include <windows.data.xml.dom.h> +#include <wrl.h> +#include "nsCOMPtr.h" +#include "nsIAlertsService.h" +#include "nsICancelable.h" +#include "nsIFile.h" +#include "nsString.h" + +namespace mozilla { +namespace widget { + +class ToastNotification; + +class ToastNotificationHandler final + : public nsIAlertNotificationImageListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER + + ToastNotificationHandler(ToastNotification* backend, + nsIObserver* aAlertListener, const nsAString& aName, + const nsAString& aCookie, const nsAString& aTitle, + const nsAString& aMsg, const nsAString& aHostPort, + bool aClickable) + : mBackend(backend), + mHasImage(false), + mAlertListener(aAlertListener), + mName(aName), + mCookie(aCookie), + mTitle(aTitle), + mMsg(aMsg), + mHostPort(aHostPort), + mClickable(aClickable), + mSentFinished(!aAlertListener) {} + + nsresult InitAlertAsync(nsIAlertNotification* aAlert); + + void OnWriteBitmapFinished(nsresult rv); + + void UnregisterHandler(); + + protected: + virtual ~ToastNotificationHandler(); + + typedef ABI::Windows::Data::Xml::Dom::IXmlDocument IXmlDocument; + typedef ABI::Windows::UI::Notifications::IToastNotifier IToastNotifier; + typedef ABI::Windows::UI::Notifications::IToastNotification + IToastNotification; + typedef ABI::Windows::UI::Notifications::IToastDismissedEventArgs + IToastDismissedEventArgs; + typedef ABI::Windows::UI::Notifications::IToastFailedEventArgs + IToastFailedEventArgs; + typedef ABI::Windows::UI::Notifications::ToastTemplateType ToastTemplateType; + + Microsoft::WRL::ComPtr<IToastNotification> mNotification; + Microsoft::WRL::ComPtr<IToastNotifier> mNotifier; + + RefPtr<ToastNotification> mBackend; + + nsCOMPtr<nsICancelable> mImageRequest; + nsCOMPtr<nsIFile> mImageFile; + nsString mImageUri; + bool mHasImage; + + EventRegistrationToken mActivatedToken; + EventRegistrationToken mDismissedToken; + EventRegistrationToken mFailedToken; + + nsCOMPtr<nsIObserver> mAlertListener; + nsString mName; + nsString mCookie; + nsString mTitle; + nsString mMsg; + nsString mHostPort; + bool mClickable; + bool mSentFinished; + + nsresult TryShowAlert(); + bool ShowAlert(); + nsresult AsyncSaveImage(imgIRequest* aRequest); + nsresult OnWriteBitmapSuccess(); + void SendFinished(); + + bool CreateWindowsNotificationFromXml(IXmlDocument* aToastXml); + Microsoft::WRL::ComPtr<IXmlDocument> InitializeXmlForTemplate( + ToastTemplateType templateType); + + HRESULT OnActivate(IToastNotification* notification, + IInspectable* inspectable); + HRESULT OnDismiss(IToastNotification* notification, + IToastDismissedEventArgs* aArgs); + HRESULT OnFail(IToastNotification* notification, + IToastFailedEventArgs* aArgs); +}; + +} // namespace widget +} // namespace mozilla + +#endif diff --git a/widget/windows/UrlmonHeaderOnlyUtils.h b/widget/windows/UrlmonHeaderOnlyUtils.h new file mode 100644 index 0000000000..dd9209f78f --- /dev/null +++ b/widget/windows/UrlmonHeaderOnlyUtils.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_UrlmonHeaderOnlyUtils_h +#define mozilla_UrlmonHeaderOnlyUtils_h + +#include "mozilla/ShellHeaderOnlyUtils.h" + +namespace mozilla { + +/** + * We used to validate a uri with SHParseDisplayName to mitigate the Windows + * bug (Bug 394974). However, Bug 1573051 revealed an issue that a fragment, + * a string following a hash mark (#), is dropped when we extract a string + * from PIDL. This is the intended behavior of Windows. + * + * To deal with the fragment issue as well as keeping our mitigation, we + * decided to use CreateUri to validate a uri string, but we also keep using + * SHParseDisplayName as a pre-check. This is because there are several + * cases where CreateUri succeeds while SHParseDisplayName fails such as + * a non-existent file: uri. + * + * To minimize the impact of introducing CreateUri into the validation logic, + * we try to mimic the logic of windows_storage!IUriToPidl (ieframe!IUriToPidl + * in Win7) which is executed behind SHParseDisplayName. + * What IUriToPidl does is: + * 1) If a given uri has a fragment, removes a fragment. + * 2) Takes an absolute uri if it's available in the given uri, otherwise + * takes a raw uri. + * + * As we need to get a full uri including a fragment, this function does 2). + */ +inline LauncherResult<_bstr_t> UrlmonValidateUri(const wchar_t* aUri) { + LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri); + if (pidlResult.isErr()) { + return pidlResult.propagateErr(); + } + + // The value of |flags| is the same value as used in ieframe!_EnsureIUri in + // Win7, which is called behind SHParseDisplayName. In Win10, on the other + // hand, an flag 0x03000000 is also passed to CreateUri, but we don't + // specify it because it's undocumented and unknown. + constexpr DWORD flags = + Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE | + Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI | + Uri_CREATE_IE_SETTINGS; + RefPtr<IUri> uri; + HRESULT hr; + SAFECALL_URLMON_FUNC(CreateUri, aUri, flags, 0, getter_AddRefs(uri)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + _bstr_t bstrUri; + + hr = uri->GetAbsoluteUri(bstrUri.GetAddress()); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + if (hr == S_FALSE) { + hr = uri->GetRawUri(bstrUri.GetAddress()); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + } + + return bstrUri; +} + +} // namespace mozilla + +#endif // mozilla_UrlmonHeaderOnlyUtils_h diff --git a/widget/windows/WidgetTraceEvent.cpp b/widget/windows/WidgetTraceEvent.cpp new file mode 100644 index 0000000000..4509791d59 --- /dev/null +++ b/widget/windows/WidgetTraceEvent.cpp @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Windows widget support for event loop instrumentation. + * See toolkit/xre/EventTracer.cpp for more details. + */ + +#include <stdio.h> +#include <windows.h> + +#include "mozilla/RefPtr.h" +#include "mozilla/WidgetTraceEvent.h" +#include "nsAppShellCID.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIAppShellService.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsISupportsImpl.h" +#include "nsIWidget.h" +#include "nsIAppWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsWindowDefs.h" + +namespace { + +// Used for signaling the background thread from the main thread. +HANDLE sEventHandle = nullptr; + +// We need a runnable in order to find the hidden window on the main +// thread. +class HWNDGetter : public mozilla::Runnable { + public: + HWNDGetter() : Runnable("HWNDGetter"), hidden_window_hwnd(nullptr) {} + + HWND hidden_window_hwnd; + + NS_IMETHOD Run() override { + // Jump through some hoops to locate the hidden window. + nsCOMPtr<nsIAppShellService> appShell( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + nsCOMPtr<nsIAppWindow> hiddenWindow; + + nsresult rv = appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShell> docShell; + rv = hiddenWindow->GetDocShell(getter_AddRefs(docShell)); + if (NS_FAILED(rv) || !docShell) { + return rv; + } + + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(docShell)); + + if (!baseWindow) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIWidget> widget; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + + if (!widget) return NS_ERROR_FAILURE; + + hidden_window_hwnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW); + + return NS_OK; + } +}; + +HWND GetHiddenWindowHWND() { + // Need to dispatch this to the main thread because plenty of + // the things it wants to access are main-thread-only. + RefPtr<HWNDGetter> getter = new HWNDGetter(); + NS_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..3f0dc92a5b --- /dev/null +++ b/widget/windows/WinCompositorWidget.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WinCompositorWidget.h" + +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsWindow.h" +#include "VsyncDispatcher.h" +#include "WinCompositorWindowThread.h" +#include "VRShMem.h" + +#include <ddraw.h> + +namespace mozilla { +namespace widget { + +using namespace mozilla::gfx; +using namespace mozilla; + +WinCompositorWidget::WinCompositorWidget( + const WinCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions) + : CompositorWidget(aOptions), + mSetParentCompleted(false), + mWidgetKey(aInitData.widgetKey()), + mWnd(reinterpret_cast<HWND>(aInitData.hWnd())), + mCompositorWnds(nullptr, nullptr) { + MOZ_ASSERT(mWnd && ::IsWindow(mWnd)); +} + +WinCompositorWidget::~WinCompositorWidget() { DestroyCompositorWindow(); } + +uintptr_t WinCompositorWidget::GetWidgetKey() { return mWidgetKey; } + +void WinCompositorWidget::EnsureCompositorWindow() { + if (mCompositorWnds.mCompositorWnd || mCompositorWnds.mInitialParentWnd) { + return; + } + + mCompositorWnds = WinCompositorWindowThread::CreateCompositorWindow(); + UpdateCompositorWnd(mCompositorWnds.mCompositorWnd, mWnd); + + MOZ_ASSERT(mCompositorWnds.mCompositorWnd); + MOZ_ASSERT(mCompositorWnds.mInitialParentWnd); +} + +void WinCompositorWidget::DestroyCompositorWindow() { + if (!mCompositorWnds.mCompositorWnd && !mCompositorWnds.mInitialParentWnd) { + return; + } + WinCompositorWindowThread::DestroyCompositorWindow(mCompositorWnds); + mCompositorWnds = WinCompositorWnds(nullptr, nullptr); +} + +void WinCompositorWidget::UpdateCompositorWndSizeIfNecessary() { + if (!mCompositorWnds.mCompositorWnd) { + return; + } + + LayoutDeviceIntSize size = GetClientSize(); + if (mLastCompositorWndSize == size) { + return; + } + + // This code is racing with the compositor, which needs to reparent + // the compositor surface to the actual window (mWnd). To avoid racing + // mutations, we refuse to proceed until ::SetParent() is called in parent + // process. After the ::SetParent() call, composition is scheduled in + // CompositorWidgetParent::UpdateCompositorWnd(). + if (!mSetParentCompleted) { + // ::SetParent() is not completed yet. + return; + } + + MOZ_ASSERT(mWnd == ::GetParent(mCompositorWnds.mCompositorWnd)); + + // Force a resize and redraw (but not a move, activate, etc.). + if (!::SetWindowPos( + mCompositorWnds.mCompositorWnd, nullptr, 0, 0, size.width, + size.height, + SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER | SWP_NOZORDER)) { + return; + } + + mLastCompositorWndSize = size; +} + +// Creates a new instance of FxROutputHandler so that this compositor widget +// can send its output to Firefox Reality for Desktop. +void WinCompositorWidget::RequestFxrOutput() { + MOZ_ASSERT(mFxrHandler == nullptr); + + mFxrHandler.reset(new FxROutputHandler()); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h new file mode 100644 index 0000000000..440897be09 --- /dev/null +++ b/widget/windows/WinCompositorWidget.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_WinCompositorWidget_h +#define widget_windows_WinCompositorWidget_h + +#include "CompositorWidget.h" +#include "gfxASurface.h" +#include "mozilla/Atomics.h" +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/Mutex.h" +#include "mozilla/widget/WinCompositorWindowThread.h" +#include "FxROutputHandler.h" +#include "nsIWidget.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate { + public: + // Callbacks for nsWindow. + virtual void EnterPresentLock() = 0; + virtual void LeavePresentLock() = 0; + virtual void OnDestroyWindow() = 0; + virtual bool OnWindowResize(const LayoutDeviceIntSize& aSize) = 0; + virtual void OnWindowModeChange(nsSizeMode aSizeMode) = 0; + + // Transparency handling. + virtual void UpdateTransparency(nsTransparencyMode aMode) = 0; + virtual void ClearTransparentWindow() = 0; + + // CompositorWidgetDelegate Overrides + + PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override { + return this; + } +}; + +class WinCompositorWidgetInitData; + +// This is the Windows-specific implementation of CompositorWidget. For +// the most part it only requires an HWND, however it maintains extra state +// for transparent windows, as well as for synchronizing WM_SETTEXT messages +// with the compositor. +class WinCompositorWidget : public CompositorWidget { + public: + WinCompositorWidget(const WinCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions); + ~WinCompositorWidget() override; + + // CompositorWidget Overrides + + uintptr_t GetWidgetKey() override; + WinCompositorWidget* AsWindows() override { return this; } + + HWND GetHwnd() const { + return mCompositorWnds.mCompositorWnd ? mCompositorWnds.mCompositorWnd + : mWnd; + } + + HWND GetCompositorHwnd() const { return mCompositorWnds.mCompositorWnd; } + + void EnsureCompositorWindow(); + void DestroyCompositorWindow(); + void UpdateCompositorWndSizeIfNecessary(); + + void RequestFxrOutput(); + bool HasFxrOutputHandler() const { return mFxrHandler != nullptr; } + FxROutputHandler* GetFxrOutputHandler() const { return mFxrHandler.get(); } + + virtual bool HasGlass() const = 0; + + virtual void UpdateCompositorWnd(const HWND aCompositorWnd, + const HWND aParentWnd) = 0; + virtual void SetRootLayerTreeID(const layers::LayersId& aRootLayerTreeId) = 0; + + protected: + bool mSetParentCompleted; + + private: + uintptr_t mWidgetKey; + HWND mWnd; + + WinCompositorWnds mCompositorWnds; + LayoutDeviceIntSize mLastCompositorWndSize; + + UniquePtr<FxROutputHandler> mFxrHandler; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_WinCompositorWidget_h diff --git a/widget/windows/WinCompositorWindowThread.cpp b/widget/windows/WinCompositorWindowThread.cpp new file mode 100644 index 0000000000..f712522bc7 --- /dev/null +++ b/widget/windows/WinCompositorWindowThread.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/platform_thread.h" +#include "WinCompositorWindowThread.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/StaticPtr.h" +#include "transport/runnable_utils.h" +#include "mozilla/StaticPrefs_apz.h" + +#if WINVER < 0x0602 +# define WS_EX_NOREDIRECTIONBITMAP 0x00200000L +#endif + +namespace mozilla { +namespace widget { + +static StaticRefPtr<WinCompositorWindowThread> sWinCompositorWindowThread; + +WinCompositorWindowThread::WinCompositorWindowThread(base::Thread* aThread) + : mThread(aThread) {} + +WinCompositorWindowThread::~WinCompositorWindowThread() { delete mThread; } + +/* static */ +WinCompositorWindowThread* WinCompositorWindowThread::Get() { + return sWinCompositorWindowThread; +} + +/* static */ +void WinCompositorWindowThread::Start() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sWinCompositorWindowThread); + + base::Thread* thread = new base::Thread("WinCompositor"); + + base::Thread::Options options; + // HWND requests ui thread. + options.message_loop_type = MessageLoop::TYPE_UI; + + if (!thread->StartWithOptions(options)) { + delete thread; + return; + } + + sWinCompositorWindowThread = new WinCompositorWindowThread(thread); +} + +/* static */ +void WinCompositorWindowThread::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sWinCompositorWindowThread); + + layers::SynchronousTask task("WinCompositorWindowThread"); + RefPtr<Runnable> runnable = WrapRunnable( + RefPtr<WinCompositorWindowThread>(sWinCompositorWindowThread.get()), + &WinCompositorWindowThread::ShutDownTask, &task); + sWinCompositorWindowThread->Loop()->PostTask(runnable.forget()); + task.Wait(); + + sWinCompositorWindowThread = nullptr; +} + +void WinCompositorWindowThread::ShutDownTask(layers::SynchronousTask* aTask) { + layers::AutoCompleteTask complete(aTask); + MOZ_ASSERT(IsInCompositorWindowThread()); +} + +/* static */ +MessageLoop* WinCompositorWindowThread::Loop() { + return sWinCompositorWindowThread + ? sWinCompositorWindowThread->mThread->message_loop() + : nullptr; +} + +/* static */ +bool WinCompositorWindowThread::IsInCompositorWindowThread() { + return sWinCompositorWindowThread && + sWinCompositorWindowThread->mThread->thread_id() == + PlatformThread::CurrentId(); +} + +const wchar_t kClassNameCompositorInitalParent[] = + L"MozillaCompositorInitialParentClass"; +const wchar_t kClassNameCompositor[] = L"MozillaCompositorWindowClass"; + +ATOM g_compositor_inital_parent_window_class; +ATOM g_compositor_window_class; + +// This runs on the window owner thread. +void InitializeInitialParentWindowClass() { + if (g_compositor_inital_parent_window_class) { + return; + } + + WNDCLASSW wc; + wc.style = 0; + wc.lpfnWndProc = ::DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = kClassNameCompositorInitalParent; + g_compositor_inital_parent_window_class = ::RegisterClassW(&wc); +} + +// This runs on the window owner thread. +void InitializeWindowClass() { + if (g_compositor_window_class) { + return; + } + + WNDCLASSW wc; + wc.style = CS_OWNDC; + wc.lpfnWndProc = ::DefWindowProcW; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(nullptr); + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = kClassNameCompositor; + g_compositor_window_class = ::RegisterClassW(&wc); +} + +/* static */ +WinCompositorWnds WinCompositorWindowThread::CreateCompositorWindow() { + MOZ_ASSERT(Loop()); + + if (!Loop()) { + return WinCompositorWnds(nullptr, nullptr); + } + + layers::SynchronousTask task("Create compositor window"); + + HWND initialParentWnd = nullptr; + HWND compositorWnd = nullptr; + + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "WinCompositorWindowThread::CreateCompositorWindow::Runnable", [&]() { + layers::AutoCompleteTask complete(&task); + + InitializeInitialParentWindowClass(); + InitializeWindowClass(); + + // Create initial parent window. + // We could not directly create a compositor window with a main window + // as parent window, so instead create it with a temporary placeholder + // parent. Its parent is set as main window in UI process. + initialParentWnd = + ::CreateWindowEx(WS_EX_TOOLWINDOW, kClassNameCompositorInitalParent, + nullptr, WS_POPUP | WS_DISABLED, 0, 0, 1, 1, + nullptr, 0, GetModuleHandle(nullptr), 0); + if (!initialParentWnd) { + gfxCriticalNoteOnce << "Inital parent window failed " + << ::GetLastError(); + return; + } + + DWORD extendedStyle = WS_EX_NOPARENTNOTIFY | WS_EX_NOREDIRECTIONBITMAP; + + if (!StaticPrefs::apz_windows_force_disable_direct_manipulation()) { + extendedStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT; + } + + compositorWnd = ::CreateWindowEx( + extendedStyle, kClassNameCompositor, nullptr, + WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, 1, 1, + initialParentWnd, 0, GetModuleHandle(nullptr), 0); + if (!compositorWnd) { + gfxCriticalNoteOnce << "Compositor window failed " + << ::GetLastError(); + } + }); + + Loop()->PostTask(runnable.forget()); + + task.Wait(); + + return WinCompositorWnds(compositorWnd, initialParentWnd); +} + +/* static */ +void WinCompositorWindowThread::DestroyCompositorWindow( + WinCompositorWnds aWnds) { + MOZ_ASSERT(aWnds.mCompositorWnd); + MOZ_ASSERT(aWnds.mInitialParentWnd); + MOZ_ASSERT(Loop()); + + if (!Loop()) { + return; + } + + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "WinCompositorWidget::CreateNativeWindow::Runnable", [aWnds]() { + ::DestroyWindow(aWnds.mCompositorWnd); + ::DestroyWindow(aWnds.mInitialParentWnd); + }); + + Loop()->PostTask(runnable.forget()); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinCompositorWindowThread.h b/widget/windows/WinCompositorWindowThread.h new file mode 100644 index 0000000000..c1462711ce --- /dev/null +++ b/widget/windows/WinCompositorWindowThread.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_WinCompositorWindowThread_h +#define widget_windows_WinCompositorWindowThread_h + +#include "base/thread.h" +#include "base/message_loop.h" + +namespace mozilla { + +namespace layers { +class SynchronousTask; +} + +namespace widget { + +struct WinCompositorWnds { + HWND mCompositorWnd; + HWND mInitialParentWnd; + WinCompositorWnds(HWND aCompositorWnd, HWND aInitialParentWnd) + : mCompositorWnd(aCompositorWnd), mInitialParentWnd(aInitialParentWnd) {} +}; + +class WinCompositorWindowThread final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + WinCompositorWindowThread) + + public: + /// Can be called from any thread. + static WinCompositorWindowThread* Get(); + + /// Can only be called from the main thread. + static void Start(); + + /// Can only be called from the main thread. + static void ShutDown(); + + /// Can be called from any thread. + static MessageLoop* Loop(); + + /// Can be called from any thread. + static bool IsInCompositorWindowThread(); + + /// Can be called from any thread. + static WinCompositorWnds CreateCompositorWindow(); + + /// Can be called from any thread. + static void DestroyCompositorWindow(WinCompositorWnds aWnds); + + private: + explicit WinCompositorWindowThread(base::Thread* aThread); + ~WinCompositorWindowThread(); + + void ShutDownTask(layers::SynchronousTask* aTask); + + base::Thread* const mThread; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_windows_WinCompositorWindowThread_h diff --git a/widget/windows/WinContentSystemParameters.cpp b/widget/windows/WinContentSystemParameters.cpp new file mode 100644 index 0000000000..f15e9aa0cc --- /dev/null +++ b/widget/windows/WinContentSystemParameters.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "WinContentSystemParameters.h" +#include "WinUtils.h" +#include "nsUXThemeData.h" +#include "mozilla/Mutex.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" + +namespace mozilla { +namespace widget { +using dom::SystemParameterKVPair; + +static_assert(uint8_t(SystemParameterId::Count) < 32, + "Too many SystemParameterId for " + "WinContentSystemParameters::Detail::validCachedValueBitfield"); + +struct WinContentSystemParameters::Detail { + OffTheBooksMutex mutex{"WinContentSystemParameters::Detail::mutex"}; + // A bitfield indicating which cached ids are valid + uint32_t validCachedValueBitfield{0}; + bool cachedIsPerMonitorDPIAware{false}; + float cachedSystemDPI{0.0f}; + bool cachedFlatMenusEnabled{false}; + + // Almost-always true in Windows 7, always true starting in Windows 8 + bool cachedIsAppThemed{true}; +}; + +// static +WinContentSystemParameters* WinContentSystemParameters::GetSingleton() { + static WinContentSystemParameters sInstance{}; + return &sInstance; +} + +WinContentSystemParameters::WinContentSystemParameters() + : mDetail(std::make_unique<Detail>()) {} + +WinContentSystemParameters::~WinContentSystemParameters() {} + +bool WinContentSystemParameters::IsPerMonitorDPIAware() { + MOZ_ASSERT(XRE_IsContentProcess()); + + OffTheBooksMutexAutoLock lock(mDetail->mutex); + MOZ_RELEASE_ASSERT( + IsCachedValueValid(SystemParameterId::IsPerMonitorDPIAware)); + return mDetail->cachedIsPerMonitorDPIAware; +} + +float WinContentSystemParameters::SystemDPI() { + MOZ_ASSERT(XRE_IsContentProcess()); + + OffTheBooksMutexAutoLock lock(mDetail->mutex); + MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::SystemDPI)); + return mDetail->cachedSystemDPI; +} + +bool WinContentSystemParameters::AreFlatMenusEnabled() { + MOZ_ASSERT(XRE_IsContentProcess()); + + OffTheBooksMutexAutoLock lock(mDetail->mutex); + MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::FlatMenusEnabled)); + return mDetail->cachedFlatMenusEnabled; +} + +bool WinContentSystemParameters::IsAppThemed() { + MOZ_ASSERT(XRE_IsContentProcess()); + + OffTheBooksMutexAutoLock lock(mDetail->mutex); + MOZ_RELEASE_ASSERT(IsCachedValueValid(SystemParameterId::IsAppThemed)); + return mDetail->cachedIsAppThemed; +} + +void WinContentSystemParameters::SetContentValueInternal( + const SystemParameterKVPair& aKVPair) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_RELEASE_ASSERT(aKVPair.id() < uint8_t(SystemParameterId::Count)); + + SetCachedValueValid(SystemParameterId(aKVPair.id()), true); + + mDetail->mutex.AssertCurrentThreadOwns(); + + switch (SystemParameterId(aKVPair.id())) { + case SystemParameterId::IsPerMonitorDPIAware: + mDetail->cachedIsPerMonitorDPIAware = aKVPair.value(); + return; + + case SystemParameterId::SystemDPI: + mDetail->cachedSystemDPI = aKVPair.value(); + return; + + case SystemParameterId::FlatMenusEnabled: + mDetail->cachedFlatMenusEnabled = aKVPair.value(); + return; + + case SystemParameterId::IsAppThemed: + mDetail->cachedIsAppThemed = aKVPair.value(); + return; + + case SystemParameterId::Count: + MOZ_CRASH("Invalid SystemParameterId"); + } + MOZ_CRASH("Unhandled SystemParameterId"); +} + +void WinContentSystemParameters::SetContentValues( + const nsTArray<dom::SystemParameterKVPair>& values) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + OffTheBooksMutexAutoLock lock(mDetail->mutex); + for (auto& kvPair : values) { + SetContentValueInternal(kvPair); + } +} + +bool WinContentSystemParameters::GetParentValueInternal( + SystemParameterId aId, SystemParameterKVPair* aKVPair) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(aKVPair); + + aKVPair->id() = uint8_t(aId); + + switch (aId) { + case SystemParameterId::IsPerMonitorDPIAware: + aKVPair->value() = WinUtils::IsPerMonitorDPIAware(); + return true; + + case SystemParameterId::SystemDPI: + aKVPair->value() = WinUtils::SystemDPI(); + return true; + + case SystemParameterId::FlatMenusEnabled: + aKVPair->value() = nsUXThemeData::AreFlatMenusEnabled(); + return true; + + case SystemParameterId::IsAppThemed: + aKVPair->value() = nsUXThemeData::IsAppThemed(); + return true; + + case SystemParameterId::Count: + MOZ_CRASH("Invalid SystemParameterId"); + } + MOZ_CRASH("Unhandled SystemParameterId"); +} + +nsTArray<dom::SystemParameterKVPair> +WinContentSystemParameters::GetParentValues() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsTArray<dom::SystemParameterKVPair> results; + for (uint8_t i = 0; i < uint8_t(SystemParameterId::Count); ++i) { + dom::SystemParameterKVPair kvPair{}; + GetParentValueInternal(SystemParameterId(i), &kvPair); + results.AppendElement(std::move(kvPair)); + } + return results; +} + +void WinContentSystemParameters::OnThemeChanged() { + MOZ_ASSERT(XRE_IsParentProcess()); + + nsTArray<dom::SystemParameterKVPair> updates; + + { + dom::SystemParameterKVPair kvPair{}; + GetParentValueInternal(SystemParameterId::FlatMenusEnabled, &kvPair); + updates.AppendElement(std::move(kvPair)); + } + + { + dom::SystemParameterKVPair kvPair{}; + GetParentValueInternal(SystemParameterId::IsAppThemed, &kvPair); + updates.AppendElement(std::move(kvPair)); + } + + nsTArray<dom::ContentParent*> contentProcesses{}; + dom::ContentParent::GetAll(contentProcesses); + for (auto contentProcess : contentProcesses) { + Unused << contentProcess->SendUpdateSystemParameters(updates); + } +} + +bool WinContentSystemParameters::IsCachedValueValid(SystemParameterId aId) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(uint8_t(aId) < uint8_t(SystemParameterId::Count)); + mDetail->mutex.AssertCurrentThreadOwns(); + uint32_t mask = uint32_t(1) << uint8_t(aId); + return (mDetail->validCachedValueBitfield & mask) != 0; +} + +void WinContentSystemParameters::SetCachedValueValid(SystemParameterId aId, + bool aIsValid) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(uint8_t(aId) < uint8_t(SystemParameterId::Count)); + mDetail->mutex.AssertCurrentThreadOwns(); + uint32_t mask = uint32_t(1) << uint8_t(aId); + + if (aIsValid) { + mDetail->validCachedValueBitfield |= mask; + } else { + mDetail->validCachedValueBitfield &= ~mask; + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinContentSystemParameters.h b/widget/windows/WinContentSystemParameters.h new file mode 100644 index 0000000000..daa7db2115 --- /dev/null +++ b/widget/windows/WinContentSystemParameters.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_widget_WinContentSystemParameters_h +#define mozilla_widget_WinContentSystemParameters_h +#include <memory> +#include <cinttypes> +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +class SystemParameterKVPair; + +} + +namespace widget { + +enum class SystemParameterId : uint8_t { + IsPerMonitorDPIAware = 0, + SystemDPI, + FlatMenusEnabled, + IsAppThemed, + Count, +}; + +class WinContentSystemParameters { + public: + static WinContentSystemParameters* GetSingleton(); + + bool IsPerMonitorDPIAware(); + + float SystemDPI(); + + bool AreFlatMenusEnabled(); + + bool IsAppThemed(); + + void SetContentValues(const nsTArray<dom::SystemParameterKVPair>& values); + + nsTArray<dom::SystemParameterKVPair> GetParentValues(); + + void OnThemeChanged(); + + WinContentSystemParameters(const WinContentSystemParameters&) = delete; + WinContentSystemParameters(WinContentSystemParameters&&) = delete; + WinContentSystemParameters& operator=(const WinContentSystemParameters&) = + delete; + WinContentSystemParameters& operator=(WinContentSystemParameters&&) = delete; + + private: + WinContentSystemParameters(); + ~WinContentSystemParameters(); + + void SetContentValueInternal(const dom::SystemParameterKVPair& aKVPair); + + bool GetParentValueInternal(SystemParameterId aId, + dom::SystemParameterKVPair* aKVPair); + + bool IsCachedValueValid(SystemParameterId aId); + void SetCachedValueValid(SystemParameterId aId, bool aIsValid); + + struct Detail; + std::unique_ptr<Detail> mDetail; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_WinContentSystemParameters_h diff --git a/widget/windows/WinHeaderOnlyUtils.h b/widget/windows/WinHeaderOnlyUtils.h new file mode 100644 index 0000000000..2bab787869 --- /dev/null +++ b/widget/windows/WinHeaderOnlyUtils.h @@ -0,0 +1,742 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WinHeaderOnlyUtils_h +#define mozilla_WinHeaderOnlyUtils_h + +#include <windows.h> +#include <winerror.h> +#include <winnt.h> +#include <winternl.h> +#include <objbase.h> + +#include <stdlib.h> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/Tuple.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindowsHelpers.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "nsIFile.h" +# include "nsString.h" +#endif // defined(MOZILLA_INTERNAL_API) + +/** + * This header is intended for self-contained, header-only, utility code for + * Win32. It may be used outside of xul.dll, in places such as firefox.exe or + * mozglue.dll. If your code creates dependencies on Mozilla libraries, you + * should put it elsewhere. + */ + +#if _WIN32_WINNT < _WIN32_WINNT_WIN8 +typedef struct _FILE_ID_INFO { + ULONGLONG VolumeSerialNumber; + FILE_ID_128 FileId; +} FILE_ID_INFO; + +# define FileIdInfo ((FILE_INFO_BY_HANDLE_CLASS)18) + +#endif // _WIN32_WINNT < _WIN32_WINNT_WIN8 + +#if !defined(STATUS_SUCCESS) +# define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif // !defined(STATUS_SUCCESS) + +// Our data indicates a few users of Win7 x86 hit failure to load urlmon.dll +// for unknown reasons. Since we don't always require urlmon.dll on Win7, +// we delay-load it, which causes a crash if loading urlmon.dll fails. This +// macro is to safely load and call urlmon's API graciously without crash. +#if defined(_X86_) +# define SAFECALL_URLMON_FUNC(FuncName, ...) \ + do { \ + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( \ + &::FuncName)> \ + func(L"urlmon.dll", #FuncName); \ + hr = \ + func ? func(__VA_ARGS__) : HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND); \ + } while (0) +#else +# define SAFECALL_URLMON_FUNC(FuncName, ...) hr = ::FuncName(__VA_ARGS__) +#endif + +namespace mozilla { + +class WindowsError final { + private: + // HRESULT and NTSTATUS are both typedefs of LONG, so we cannot use + // overloading to properly differentiate between the two. Instead we'll use + // static functions to convert the various error types to HRESULTs before + // instantiating. + explicit constexpr WindowsError(HRESULT aHResult) : mHResult(aHResult) {} + + public: + using UniqueString = UniquePtr<WCHAR[], LocalFreeDeleter>; + + static constexpr WindowsError FromNtStatus(NTSTATUS aNtStatus) { + if (aNtStatus == STATUS_SUCCESS) { + // Special case: we don't want to set FACILITY_NT_BIT + // (HRESULT_FROM_NT does not handle this case, unlike HRESULT_FROM_WIN32) + return WindowsError(S_OK); + } + + return WindowsError(HRESULT_FROM_NT(aNtStatus)); + } + + static constexpr WindowsError FromHResult(HRESULT aHResult) { + return WindowsError(aHResult); + } + + static constexpr WindowsError FromWin32Error(DWORD aWin32Err) { + return WindowsError(HRESULT_FROM_WIN32(aWin32Err)); + } + + static WindowsError FromLastError() { + return FromWin32Error(::GetLastError()); + } + + static WindowsError CreateSuccess() { return WindowsError(S_OK); } + + static WindowsError CreateGeneric() { + return FromWin32Error(ERROR_UNIDENTIFIED_ERROR); + } + + bool IsSuccess() const { return SUCCEEDED(mHResult); } + + bool IsFailure() const { return FAILED(mHResult); } + + bool IsAvailableAsWin32Error() const { + return IsAvailableAsNtStatus() || + HRESULT_FACILITY(mHResult) == FACILITY_WIN32; + } + + bool IsAvailableAsNtStatus() const { + return mHResult == S_OK || (mHResult & FACILITY_NT_BIT); + } + + bool IsAvailableAsHResult() const { return true; } + + UniqueString AsString() const { + LPWSTR rawMsgBuf = nullptr; + constexpr DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + DWORD result = + ::FormatMessageW(flags, nullptr, mHResult, 0, + reinterpret_cast<LPWSTR>(&rawMsgBuf), 0, nullptr); + if (!result) { + return nullptr; + } + + return UniqueString(rawMsgBuf); + } + + HRESULT AsHResult() const { return mHResult; } + + // Not all HRESULTs are convertible to Win32 Errors, so we use Maybe + Maybe<DWORD> AsWin32Error() const { + if (mHResult == S_OK) { + return Some(static_cast<DWORD>(ERROR_SUCCESS)); + } + + if (HRESULT_FACILITY(mHResult) == FACILITY_WIN32) { + // This is the inverse of HRESULT_FROM_WIN32 + return Some(static_cast<DWORD>(HRESULT_CODE(mHResult))); + } + + // The NTSTATUS facility is a special case and thus does not utilize the + // HRESULT_FACILITY and HRESULT_CODE macros. + if (mHResult & FACILITY_NT_BIT) { + return Some(NtStatusToWin32Error( + static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT))); + } + + return Nothing(); + } + + // Not all HRESULTs are convertible to NTSTATUS, so we use Maybe + Maybe<NTSTATUS> AsNtStatus() const { + if (mHResult == S_OK) { + return Some(STATUS_SUCCESS); + } + + // The NTSTATUS facility is a special case and thus does not utilize the + // HRESULT_FACILITY and HRESULT_CODE macros. + if (mHResult & FACILITY_NT_BIT) { + return Some(static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT)); + } + + return Nothing(); + } + + constexpr bool operator==(const WindowsError& aOther) const { + return mHResult == aOther.mHResult; + } + + constexpr bool operator!=(const WindowsError& aOther) const { + return mHResult != aOther.mHResult; + } + + static DWORD NtStatusToWin32Error(NTSTATUS aNtStatus) { + static const StaticDynamicallyLinkedFunctionPtr<decltype( + &RtlNtStatusToDosError)> + pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError"); + + MOZ_ASSERT(!!pRtlNtStatusToDosError); + if (!pRtlNtStatusToDosError) { + return ERROR_UNIDENTIFIED_ERROR; + } + + return pRtlNtStatusToDosError(aNtStatus); + } + + private: + // We store the error code as an HRESULT because they can encode both Win32 + // error codes and NTSTATUS codes. + HRESULT mHResult; +}; + +namespace detail { +template <> +struct UnusedZero<WindowsError> { + using StorageType = WindowsError; + + static constexpr bool value = true; + static constexpr StorageType nullValue = WindowsError::FromHResult(S_OK); + + static constexpr void AssertValid(StorageType aValue) {} + static constexpr const WindowsError& Inspect(const StorageType& aValue) { + return aValue; + } + static constexpr WindowsError Unwrap(StorageType aValue) { return aValue; } + static constexpr StorageType Store(WindowsError aValue) { return aValue; } +}; +} // namespace detail + +enum DetourResultCode : uint32_t { + RESULT_OK = 0, + INTERCEPTOR_MOD_NULL, + INTERCEPTOR_MOD_INACCESSIBLE, + INTERCEPTOR_PROC_NULL, + INTERCEPTOR_PROC_INACCESSIBLE, + DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR, + DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR, + DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR, + DETOUR_PATCHER_DO_RESERVE_ERROR, + DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR, + DETOUR_PATCHER_INVALID_TRAMPOLINE, + DETOUR_PATCHER_WRITE_POINTER_ERROR, + DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR, + FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR, + MMPOLICY_RESERVE_INVALIDARG, + MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE, + MMPOLICY_RESERVE_CREATEFILEMAPPING, + MMPOLICY_RESERVE_MAPVIEWOFFILE, + MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR, + MMPOLICY_RESERVE_FINDREGION_INVALIDLEN, + MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE, + MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR, + MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION, + MMPOLICY_RESERVE_FINAL_RESERVE_ERROR, +}; + +#if defined(NIGHTLY_BUILD) +struct DetourError { + // We have a 16-bytes buffer, but only minimum bytes to detour per + // architecture are copied. See CreateTrampoline in PatcherDetour.h. + DetourResultCode mErrorCode; + uint8_t mOrigBytes[16]; + explicit DetourError(DetourResultCode aError) + : mErrorCode(aError), mOrigBytes{} {} + DetourError(DetourResultCode aError, DWORD aWin32Error) + : mErrorCode(aError), mOrigBytes{} { + static_assert(sizeof(mOrigBytes) >= sizeof(aWin32Error), + "Can't fit a DWORD in mOrigBytes"); + *reinterpret_cast<DWORD*>(mOrigBytes) = aWin32Error; + } + operator WindowsError() const { + return WindowsError::FromHResult(mErrorCode); + } +}; +#endif // defined(NIGHTLY_BUILD) + +template <typename T> +using WindowsErrorResult = Result<T, WindowsError>; + +struct LauncherError { + LauncherError(const char* aFile, int aLine, WindowsError aWin32Error) + : mFile(aFile), mLine(aLine), mError(aWin32Error) {} + +#if defined(NIGHTLY_BUILD) + LauncherError(const char* aFile, int aLine, + const Maybe<DetourError>& aDetourError) + : mFile(aFile), + mLine(aLine), + mError(aDetourError.isSome() ? aDetourError.value() + : WindowsError::CreateGeneric()), + mDetourError(aDetourError) {} +#endif // defined(NIGHTLY_BUILD) + + const char* mFile; + int mLine; + WindowsError mError; +#if defined(NIGHTLY_BUILD) + Maybe<DetourError> mDetourError; +#endif // defined(NIGHTLY_BUILD) + + bool operator==(const LauncherError& aOther) const { + return mError == aOther.mError; + } + + bool operator!=(const LauncherError& aOther) const { + return mError != aOther.mError; + } + + bool operator==(const WindowsError& aOther) const { return mError == aOther; } + + bool operator!=(const WindowsError& aOther) const { return mError != aOther; } +}; + +#if defined(MOZ_USE_LAUNCHER_ERROR) + +template <typename T> +using LauncherResult = Result<T, LauncherError>; + +template <typename T> +using LauncherResultWithLineInfo = LauncherResult<T>; + +using WindowsErrorType = LauncherError; + +#else + +template <typename T> +using LauncherResult = WindowsErrorResult<T>; + +template <typename T> +using LauncherResultWithLineInfo = Result<T, LauncherError>; + +using WindowsErrorType = WindowsError; + +#endif // defined(MOZ_USE_LAUNCHER_ERROR) + +using LauncherVoidResult = LauncherResult<Ok>; + +using LauncherVoidResultWithLineInfo = LauncherResultWithLineInfo<Ok>; + +#if defined(MOZ_USE_LAUNCHER_ERROR) + +# define LAUNCHER_ERROR_GENERIC() \ + ::mozilla::Err(::mozilla::LauncherError( \ + __FILE__, __LINE__, ::mozilla::WindowsError::CreateGeneric())) + +# if defined(NIGHTLY_BUILD) +# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) \ + ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err)) +# else +# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC() +# endif // defined(NIGHTLY_BUILD) + +# define LAUNCHER_ERROR_FROM_WIN32(err) \ + ::mozilla::Err(::mozilla::LauncherError( \ + __FILE__, __LINE__, ::mozilla::WindowsError::FromWin32Error(err))) + +# define LAUNCHER_ERROR_FROM_LAST() \ + ::mozilla::Err(::mozilla::LauncherError( \ + __FILE__, __LINE__, ::mozilla::WindowsError::FromLastError())) + +# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \ + ::mozilla::Err(::mozilla::LauncherError( \ + __FILE__, __LINE__, ::mozilla::WindowsError::FromNtStatus(ntstatus))) + +# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \ + ::mozilla::Err(::mozilla::LauncherError( \ + __FILE__, __LINE__, ::mozilla::WindowsError::FromHResult(hresult))) + +// This macro wraps the supplied WindowsError with a LauncherError +# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \ + ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err)) + +#else + +# define LAUNCHER_ERROR_GENERIC() \ + ::mozilla::Err(::mozilla::WindowsError::CreateGeneric()) + +# define LAUNCHER_ERROR_FROM_DETOUR_ERROR(err) LAUNCHER_ERROR_GENERIC() + +# define LAUNCHER_ERROR_FROM_WIN32(err) \ + ::mozilla::Err(::mozilla::WindowsError::FromWin32Error(err)) + +# define LAUNCHER_ERROR_FROM_LAST() \ + ::mozilla::Err(::mozilla::WindowsError::FromLastError()) + +# define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \ + ::mozilla::Err(::mozilla::WindowsError::FromNtStatus(ntstatus)) + +# define LAUNCHER_ERROR_FROM_HRESULT(hresult) \ + ::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult)) + +# define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) ::mozilla::Err(err) + +#endif // defined(MOZ_USE_LAUNCHER_ERROR) + +// How long to wait for a created process to become available for input, +// to prevent that process's windows being forced to the background. +// This is used across update, restart, and the launcher. +const DWORD kWaitForInputIdleTimeoutMS = 10 * 1000; + +/** + * Wait for a child GUI process to become "idle." Idle means that the process + * has created its message queue and has begun waiting for user input. + * + * Note that this must only be used when the child process is going to display + * GUI! Otherwise you're going to be waiting for a very long time ;-) + * + * @return true if we successfully waited for input idle; + * false if we timed out or failed to wait. + */ +inline bool WaitForInputIdle(HANDLE aProcess, + DWORD aTimeoutMs = kWaitForInputIdleTimeoutMS) { + const DWORD kSleepTimeMs = 10; + const DWORD waitStart = aTimeoutMs == INFINITE ? 0 : ::GetTickCount(); + DWORD elapsed = 0; + + while (true) { + if (aTimeoutMs != INFINITE) { + elapsed = ::GetTickCount() - waitStart; + } + + if (elapsed >= aTimeoutMs) { + return false; + } + + // ::WaitForInputIdle() doesn't always set the last-error code on failure + ::SetLastError(ERROR_SUCCESS); + + DWORD waitResult = ::WaitForInputIdle(aProcess, aTimeoutMs - elapsed); + if (!waitResult) { + return true; + } + + if (waitResult == WAIT_FAILED && + ::GetLastError() == ERROR_NOT_GUI_PROCESS) { + ::Sleep(kSleepTimeMs); + continue; + } + + return false; + } +} + +enum class PathType { + eNtPath, + eDosPath, +}; + +class FileUniqueId final { + public: + explicit FileUniqueId(const wchar_t* aPath, PathType aPathType) + : mId(FILE_ID_INFO()) { + if (!aPath) { + mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG); + return; + } + + nsAutoHandle file; + + switch (aPathType) { + default: + mId = LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG); + MOZ_ASSERT_UNREACHABLE("Unhandled PathType"); + return; + + case PathType::eNtPath: { + UNICODE_STRING unicodeString; + ::RtlInitUnicodeString(&unicodeString, aPath); + OBJECT_ATTRIBUTES objectAttributes; + InitializeObjectAttributes(&objectAttributes, &unicodeString, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + IO_STATUS_BLOCK ioStatus = {}; + HANDLE ntHandle; + NTSTATUS status = ::NtOpenFile( + &ntHandle, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &objectAttributes, + &ioStatus, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); + // We don't need to check |ntHandle| for INVALID_HANDLE_VALUE here, + // as that value is set by the Win32 layer. + if (!NT_SUCCESS(status)) { + mId = LAUNCHER_ERROR_FROM_NTSTATUS(status); + return; + } + + file.own(ntHandle); + break; + } + + case PathType::eDosPath: { + file.own(::CreateFileW( + aPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr)); + if (file == INVALID_HANDLE_VALUE) { + mId = LAUNCHER_ERROR_FROM_LAST(); + return; + } + + break; + } + } + + GetId(file); + } + + explicit FileUniqueId(const nsAutoHandle& aFile) : mId(FILE_ID_INFO()) { + GetId(aFile); + } + + ~FileUniqueId() = default; + + bool IsError() const { return mId.isErr(); } + + const WindowsErrorType& GetError() const { return mId.inspectErr(); } + + FileUniqueId(FileUniqueId&& aOther) = default; + FileUniqueId& operator=(FileUniqueId&& aOther) = delete; + + bool operator==(const FileUniqueId& aOther) const { + return mId.isOk() && aOther.mId.isOk() && + !memcmp(&mId.inspect(), &aOther.mId.inspect(), sizeof(FILE_ID_INFO)); + } + + bool operator!=(const FileUniqueId& aOther) const { + return !((*this) == aOther); + } + + private: + void GetId(const nsAutoHandle& aFile) { + FILE_ID_INFO fileIdInfo = {}; + if (IsWin8OrLater()) { + if (::GetFileInformationByHandleEx(aFile.get(), FileIdInfo, &fileIdInfo, + sizeof(fileIdInfo))) { + mId = fileIdInfo; + return; + } + // Only NTFS and ReFS support FileIdInfo. So we have to fallback if + // GetFileInformationByHandleEx failed. + } + + BY_HANDLE_FILE_INFORMATION info = {}; + if (!::GetFileInformationByHandle(aFile.get(), &info)) { + mId = LAUNCHER_ERROR_FROM_LAST(); + return; + } + + fileIdInfo.VolumeSerialNumber = info.dwVolumeSerialNumber; + memcpy(&fileIdInfo.FileId.Identifier[0], &info.nFileIndexLow, + sizeof(DWORD)); + memcpy(&fileIdInfo.FileId.Identifier[sizeof(DWORD)], &info.nFileIndexHigh, + sizeof(DWORD)); + mId = fileIdInfo; + } + + private: + LauncherResult<FILE_ID_INFO> mId; +}; + +class MOZ_RAII AutoVirtualProtect final { + public: + AutoVirtualProtect(void* aAddress, size_t aLength, DWORD aProtFlags, + HANDLE aTargetProcess = ::GetCurrentProcess()) + : mAddress(aAddress), + mLength(aLength), + mTargetProcess(aTargetProcess), + mPrevProt(0), + mError(WindowsError::CreateSuccess()) { + if (!::VirtualProtectEx(aTargetProcess, aAddress, aLength, aProtFlags, + &mPrevProt)) { + mError = WindowsError::FromLastError(); + } + } + + ~AutoVirtualProtect() { + if (mError.IsFailure()) { + return; + } + + ::VirtualProtectEx(mTargetProcess, mAddress, mLength, mPrevProt, + &mPrevProt); + } + + explicit operator bool() const { return mError.IsSuccess(); } + + WindowsError GetError() const { return mError; } + + DWORD PrevProt() const { return mPrevProt; } + + AutoVirtualProtect(const AutoVirtualProtect&) = delete; + AutoVirtualProtect(AutoVirtualProtect&&) = delete; + AutoVirtualProtect& operator=(const AutoVirtualProtect&) = delete; + AutoVirtualProtect& operator=(AutoVirtualProtect&&) = delete; + + private: + void* mAddress; + size_t mLength; + HANDLE mTargetProcess; + DWORD mPrevProt; + WindowsError mError; +}; + +inline UniquePtr<wchar_t[]> GetFullModulePath(HMODULE aModule) { + DWORD bufLen = MAX_PATH; + mozilla::UniquePtr<wchar_t[]> buf; + DWORD retLen; + + while (true) { + buf = mozilla::MakeUnique<wchar_t[]>(bufLen); + retLen = ::GetModuleFileNameW(aModule, buf.get(), bufLen); + if (!retLen) { + return nullptr; + } + + if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + bufLen *= 2; + continue; + } + + break; + } + + // Upon success, retLen *excludes* the null character + ++retLen; + + // Since we're likely to have a bunch of unused space in buf, let's + // reallocate a string to the actual size of the file name. + auto result = mozilla::MakeUnique<wchar_t[]>(retLen); + if (wcscpy_s(result.get(), retLen, buf.get())) { + return nullptr; + } + + return result; +} + +inline UniquePtr<wchar_t[]> GetFullBinaryPath() { + return GetFullModulePath(nullptr); +} + +class ModuleVersion final { + public: + constexpr ModuleVersion() : mVersion(0ULL) {} + + explicit ModuleVersion(const VS_FIXEDFILEINFO& aFixedInfo) + : mVersion((static_cast<uint64_t>(aFixedInfo.dwFileVersionMS) << 32) | + static_cast<uint64_t>(aFixedInfo.dwFileVersionLS)) {} + + explicit ModuleVersion(const uint64_t aVersion) : mVersion(aVersion) {} + + ModuleVersion(const ModuleVersion& aOther) : mVersion(aOther.mVersion) {} + + uint64_t AsInteger() const { return mVersion; } + + operator uint64_t() const { return AsInteger(); } + + Tuple<uint16_t, uint16_t, uint16_t, uint16_t> AsTuple() const { + uint16_t major = static_cast<uint16_t>((mVersion >> 48) & 0xFFFFU); + uint16_t minor = static_cast<uint16_t>((mVersion >> 32) & 0xFFFFU); + uint16_t patch = static_cast<uint16_t>((mVersion >> 16) & 0xFFFFU); + uint16_t build = static_cast<uint16_t>(mVersion & 0xFFFFU); + + return MakeTuple(major, minor, patch, build); + } + + explicit operator bool() const { return !!mVersion; } + + bool operator<(const ModuleVersion& aOther) const { + return mVersion < aOther.mVersion; + } + + bool operator<(const uint64_t& aOther) const { return mVersion < aOther; } + + ModuleVersion& operator=(const uint64_t aIntVersion) { + mVersion = aIntVersion; + return *this; + } + + private: + uint64_t mVersion; +}; + +inline LauncherResult<ModuleVersion> GetModuleVersion( + const wchar_t* aModuleFullPath) { + DWORD verInfoLen = ::GetFileVersionInfoSizeW(aModuleFullPath, nullptr); + if (!verInfoLen) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + auto verInfoBuf = MakeUnique<BYTE[]>(verInfoLen); + if (!::GetFileVersionInfoW(aModuleFullPath, 0, verInfoLen, + verInfoBuf.get())) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + UINT fixedInfoLen; + VS_FIXEDFILEINFO* fixedInfo = nullptr; + if (!::VerQueryValueW(verInfoBuf.get(), L"\\", + reinterpret_cast<LPVOID*>(&fixedInfo), &fixedInfoLen)) { + // VerQueryValue may fail if the resource does not exist. This is not an + // error; we'll return 0 in this case. + return ModuleVersion(0ULL); + } + + return ModuleVersion(*fixedInfo); +} + +inline LauncherResult<ModuleVersion> GetModuleVersion(HMODULE aModule) { + UniquePtr<wchar_t[]> fullPath(GetFullModulePath(aModule)); + if (!fullPath) { + return LAUNCHER_ERROR_GENERIC(); + } + + return GetModuleVersion(fullPath.get()); +} + +#if defined(MOZILLA_INTERNAL_API) +inline LauncherResult<ModuleVersion> GetModuleVersion(nsIFile* aFile) { + if (!aFile) { + return LAUNCHER_ERROR_FROM_HRESULT(E_INVALIDARG); + } + + nsAutoString fullPath; + nsresult rv = aFile->GetPath(fullPath); + if (NS_FAILED(rv)) { + return LAUNCHER_ERROR_GENERIC(); + } + + return GetModuleVersion(fullPath.get()); +} +#endif // defined(MOZILLA_INTERNAL_API) + +struct CoTaskMemFreeDeleter { + void operator()(void* aPtr) { ::CoTaskMemFree(aPtr); } +}; + +inline LauncherResult<TOKEN_ELEVATION_TYPE> GetElevationType( + const nsAutoHandle& aToken) { + DWORD retLen; + TOKEN_ELEVATION_TYPE elevationType; + if (!::GetTokenInformation(aToken.get(), TokenElevationType, &elevationType, + sizeof(elevationType), &retLen)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + return elevationType; +} + +} // namespace mozilla + +#endif // mozilla_WinHeaderOnlyUtils_h diff --git a/widget/windows/WinIMEHandler.cpp b/widget/windows/WinIMEHandler.cpp new file mode 100644 index 0000000000..e5733cc8d6 --- /dev/null +++ b/widget/windows/WinIMEHandler.cpp @@ -0,0 +1,1180 @@ +/* -*- 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/TextEvents.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindowDefs.h" +#include "WinTextEventDispatcherListener.h" + +#include "TSFTextStore.h" + +#include "OSKInputPaneManager.h" +#include "nsLookAndFeel.h" +#include "nsWindow.h" +#include "WinUtils.h" +#include "nsIWindowsRegKey.h" +#include "nsIWindowsUIUtils.h" + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif // #ifdef ACCESSIBILITY + +#include "shellapi.h" +#include "shlobj.h" +#include "powrprof.h" +#include "setupapi.h" +#include "cfgmgr32.h" + +#include "FxRWindowManager.h" +#include "VRShMem.h" +#include "moz_external_vr.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::sMaybeEditable = false; +bool IMEHandler::sForceDisableCurrentIMM_IME = false; +bool IMEHandler::sNativeCaretIsCreated = false; +bool IMEHandler::sHasNativeCaretBeenRequested = false; + +bool IMEHandler::sIsInTSFMode = false; +bool IMEHandler::sIsIMMEnabled = true; +bool IMEHandler::sAssociateIMCOnlyWhenIMM_IMEActive = false; +decltype(SetInputScopes)* IMEHandler::sSetInputScopes = nullptr; + +static POWER_PLATFORM_ROLE sPowerPlatformRole = PlatformRoleUnspecified; +static bool sDeterminedPowerPlatformRole = false; + +// static +void IMEHandler::Initialize() { + TSFTextStore::Initialize(); + sIsInTSFMode = TSFTextStore::IsInTSFMode(); + sIsIMMEnabled = + !sIsInTSFMode || 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")); + } + } + + IMMHandler::Initialize(); + + sForceDisableCurrentIMM_IME = IMMHandler::IsActiveIMEInBlockList(); +} + +// static +void IMEHandler::Terminate() { + if (sIsInTSFMode) { + TSFTextStore::Terminate(); + sIsInTSFMode = false; + } + + IMMHandler::Terminate(); + WinTextEventDispatcherListener::Shutdown(); +} + +// static +void* IMEHandler::GetNativeData(nsWindow* aWindow, uint32_t aDataType) { + if (aDataType == NS_RAW_NATIVE_IME_CONTEXT) { + if (IsTSFAvailable()) { + return TSFTextStore::GetThreadManager(); + } + IMEContext context(aWindow); + if (context.IsValid()) { + return context.get(); + } + // If IMC isn't associated with the window, IME is disabled on the window + // now. In such case, we should return default IMC instead. + const IMEContext& defaultIMC = aWindow->DefaultIMC(); + if (defaultIMC.IsValid()) { + return defaultIMC.get(); + } + // If there is no default IMC, we should return the pointer to the window + // since if we return nullptr, IMEStateManager cannot manage composition + // with TextComposition instance. This is possible if no IME is installed, + // but composition may occur with dead key sequence. + return aWindow; + } + + void* result = TSFTextStore::GetNativeData(aDataType); + if (!result || !(*(static_cast<void**>(result)))) { + return nullptr; + } + // XXX During the TSF module test, sIsInTSFMode must be true. After that, + // the value should be restored but currently, there is no way for that. + // When the TSF test is enabled again, we need to fix this. Perhaps, + // sending a message can fix this. + sIsInTSFMode = true; + return result; +} + +// static +bool IMEHandler::ProcessRawKeyMessage(const MSG& aMsg) { + if (IsTSFAvailable()) { + return TSFTextStore::ProcessRawKeyMessage(aMsg); + } + return false; // noting to do in IMM mode. +} + +// static +bool IMEHandler::ProcessMessage(nsWindow* aWindow, UINT aMessage, + WPARAM& aWParam, LPARAM& aLParam, + MSGResult& aResult) { + // If we're putting native caret over our caret, Windows dispatches + // EVENT_OBJECT_LOCATIONCHANGE event on other applications which hook + // the event with ::SetWinEventHook() and handles WM_GETOBJECT for + // OBJID_CARET (this is request of caret from such applications) instead + // of us. If a11y module is active, it observes every our caret change + // and put native caret over it automatically. However, if other + // applications require only caret information, activating a11y module is + // overwork and such applications may requires carets only in editors. + // Therefore, if it'd be possible, IMEHandler should put native caret over + // our caret, but there is a problem. Some versions of ATOK (Japanese TIP) + // refer native caret and if there is, the behavior is worse than the + // behavior without native caret. Therefore, we shouldn't put native caret + // as far as possible. + if (!sHasNativeCaretBeenRequested && aMessage == WM_GETOBJECT && + static_cast<DWORD>(aLParam) == OBJID_CARET) { + // So, when we receive first WM_GETOBJECT for OBJID_CARET, let's start to + // create native caret for such applications. + sHasNativeCaretBeenRequested = true; + // If an editable element has focus, we can put native caret now. + // XXX Should we avoid doing this if there is composition? + MaybeCreateNativeCaret(aWindow); + } + + if (IsTSFAvailable()) { + TSFTextStore::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); + if (aResult.mConsumed) { + return true; + } + // If we don't support IMM in TSF mode, we don't use IMMHandler. + if (!sIsIMMEnabled) { + return false; + } + // IME isn't implemented with IMM, IMMHandler shouldn't handle any + // messages. + if (!IsIMMActive()) { + return false; + } + } + + bool keepGoing = + IMMHandler::ProcessMessage(aWindow, aMessage, aWParam, aLParam, aResult); + + // If user changes active IME to an IME which is listed in our block list, + // we should disassociate IMC from the window for preventing the IME to work + // and crash. + if (aMessage == WM_INPUTLANGCHANGE) { + bool disableIME = IMMHandler::IsActiveIMEInBlockList(); + if (disableIME != sForceDisableCurrentIMM_IME) { + bool enable = + !disableIME && WinUtils::IsIMEEnabled(aWindow->InputContextRef()); + AssociateIMEContext(aWindow, enable); + sForceDisableCurrentIMM_IME = disableIME; + } + } + + return keepGoing; +} + +// static +bool IMEHandler::IsA11yHandlingNativeCaret() { +#ifndef ACCESSIBILITY + return false; +#else // #ifndef ACCESSIBILITY + // Let's assume that when there is the service, it handles native caret. + return GetAccService() != nullptr; +#endif // #ifndef ACCESSIBILITY #else +} + +// static +bool IMEHandler::IsIMMActive() { return TSFTextStore::IsIMM_IMEActive(); } + +// static +bool IMEHandler::IsComposing() { + if (IsTSFAvailable()) { + return TSFTextStore::IsComposing() || IMMHandler::IsComposing(); + } + + return IMMHandler::IsComposing(); +} + +// static +bool IMEHandler::IsComposingOn(nsWindow* aWindow) { + if (IsTSFAvailable()) { + return TSFTextStore::IsComposingOn(aWindow) || + IMMHandler::IsComposingOn(aWindow); + } + + return IMMHandler::IsComposingOn(aWindow); +} + +// static +nsresult IMEHandler::NotifyIME(nsWindow* aWindow, + const IMENotification& aIMENotification) { + if (IsTSFAvailable()) { + switch (aIMENotification.mMessage) { + case NOTIFY_IME_OF_SELECTION_CHANGE: { + nsresult rv = TSFTextStore::OnSelectionChange(aIMENotification); + // If IMM IME is active, we need to notify IMMHandler of updating + // composition change. It will adjust candidate window position or + // composition window position. + bool isIMMActive = IsIMMActive(); + if (isIMMActive) { + IMMHandler::OnUpdateComposition(aWindow); + } + IMMHandler::OnSelectionChange(aWindow, aIMENotification, isIMMActive); + return rv; + } + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + // If IMM IME is active, we need to notify IMMHandler of updating + // composition change. It will adjust candidate window position or + // composition window position. + if (IsIMMActive()) { + IMMHandler::OnUpdateComposition(aWindow); + } else { + TSFTextStore::OnUpdateComposition(); + } + return NS_OK; + case NOTIFY_IME_OF_TEXT_CHANGE: + return TSFTextStore::OnTextChange(aIMENotification); + case NOTIFY_IME_OF_FOCUS: { + sFocusedWindow = aWindow; + IMMHandler::OnFocusChange(true, aWindow); + nsresult rv = TSFTextStore::OnFocusChange(true, aWindow, + aWindow->GetInputContext()); + MaybeCreateNativeCaret(aWindow); + IMEHandler::MaybeShowOnScreenKeyboard(aWindow, + aWindow->GetInputContext()); + return rv; + } + case NOTIFY_IME_OF_BLUR: + sFocusedWindow = nullptr; + IMEHandler::MaybeDismissOnScreenKeyboard(aWindow); + IMMHandler::OnFocusChange(false, aWindow); + return TSFTextStore::OnFocusChange(false, aWindow, + aWindow->GetInputContext()); + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + // If IMM IME is active, we should send a mouse button event via IMM. + if (IsIMMActive()) { + return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification); + } + return TSFTextStore::OnMouseButtonEvent(aIMENotification); + case REQUEST_TO_COMMIT_COMPOSITION: + if (TSFTextStore::IsComposingOn(aWindow)) { + TSFTextStore::CommitComposition(false); + } else if (IsIMMActive()) { + IMMHandler::CommitComposition(aWindow); + } + return NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + if (TSFTextStore::IsComposingOn(aWindow)) { + TSFTextStore::CommitComposition(true); + } else if (IsIMMActive()) { + IMMHandler::CancelComposition(aWindow); + } + return NS_OK; + case NOTIFY_IME_OF_POSITION_CHANGE: + return TSFTextStore::OnLayoutChange(); + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + } + + switch (aIMENotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: + IMMHandler::CommitComposition(aWindow); + return NS_OK; + case REQUEST_TO_CANCEL_COMPOSITION: + IMMHandler::CancelComposition(aWindow); + return NS_OK; + case NOTIFY_IME_OF_POSITION_CHANGE: + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: + IMMHandler::OnUpdateComposition(aWindow); + return NS_OK; + case NOTIFY_IME_OF_SELECTION_CHANGE: + IMMHandler::OnSelectionChange(aWindow, aIMENotification, true); + // IMMHandler::OnSelectionChange() cannot work without its singleton + // instance. Therefore, IMEHandler needs to create native caret instead + // if it's necessary. + MaybeCreateNativeCaret(aWindow); + return NS_OK; + case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT: + return IMMHandler::OnMouseButtonEvent(aWindow, aIMENotification); + case NOTIFY_IME_OF_FOCUS: + sFocusedWindow = aWindow; + IMMHandler::OnFocusChange(true, aWindow); + IMEHandler::MaybeShowOnScreenKeyboard(aWindow, + aWindow->GetInputContext()); + MaybeCreateNativeCaret(aWindow); + return NS_OK; + case NOTIFY_IME_OF_BLUR: + sFocusedWindow = nullptr; + IMEHandler::MaybeDismissOnScreenKeyboard(aWindow); + IMMHandler::OnFocusChange(false, aWindow); + // If a plugin gets focus while TSF has focus, we need to notify TSF of + // the blur. + if (TSFTextStore::ThinksHavingFocus()) { + return TSFTextStore::OnFocusChange(false, aWindow, + aWindow->GetInputContext()); + } + return NS_OK; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +// static +IMENotificationRequests IMEHandler::GetIMENotificationRequests() { + if (IsTSFAvailable()) { + if (!sIsIMMEnabled) { + return TSFTextStore::GetIMENotificationRequests(); + } + // Even if TSF is available, the active IME may be an IMM-IME. + // Unfortunately, changing the result of GetIMENotificationRequests() while + // an editor has focus isn't supported by IMEContentObserver nor + // ContentCacheInParent. Therefore, we need to request whole notifications + // which are necessary either IMMHandler or TSFTextStore. + return IMMHandler::GetIMENotificationRequests() | + TSFTextStore::GetIMENotificationRequests(); + } + + return IMMHandler::GetIMENotificationRequests(); +} + +// static +TextEventDispatcherListener* +IMEHandler::GetNativeTextEventDispatcherListener() { + return WinTextEventDispatcherListener::GetInstance(); +} + +// static +bool IMEHandler::GetOpenState(nsWindow* aWindow) { + if (IsTSFAvailable() && !IsIMMActive()) { + return TSFTextStore::GetIMEOpenState(); + } + + IMEContext context(aWindow); + return context.GetOpenState(); +} + +// static +void IMEHandler::OnDestroyWindow(nsWindow* aWindow) { + // When focus is in remote process, but the window is being destroyed, we + // need to clean up TSFTextStore here since NOTIFY_IME_OF_BLUR won't reach + // here because BrowserParent already lost the reference to the nsWindow when + // it receives from the remote process. + if (sFocusedWindow == aWindow) { + MOZ_ASSERT(aWindow->GetInputContext().IsOriginContentProcess(), + "input context of focused widget should've been set by a remote " + "process " + "if IME focus isn't cleared before destroying the widget"); + NotifyIME(aWindow, IMENotification(NOTIFY_IME_OF_BLUR)); + } + + // We need to do nothing here for TSF. Just restore the default context + // if it's been disassociated. + if (!sIsInTSFMode) { + // MSDN says we need to set IS_DEFAULT to avoid memory leak when we use + // SetInputScopes API. Use an empty string to do this. + SetInputScopeForIMM32(aWindow, u""_ns, u""_ns, false); + } + AssociateIMEContext(aWindow, true); +} + +// static +bool IMEHandler::NeedsToAssociateIMC() { + return !sForceDisableCurrentIMM_IME && + (!sAssociateIMCOnlyWhenIMM_IMEActive || !IsIMMActive()); +} + +// static +void IMEHandler::SetInputContext(nsWindow* aWindow, InputContext& aInputContext, + const InputContextAction& aAction) { + sLastContextActionCause = aAction.mCause; + // FYI: If there is no composition, this call will do nothing. + NotifyIME(aWindow, IMENotification(REQUEST_TO_COMMIT_COMPOSITION)); + + if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) { + IMEHandler::MaybeDismissOnScreenKeyboard(aWindow, Sync::Yes); + } else if (aAction.UserMightRequestOpenVKB()) { + IMEHandler::MaybeShowOnScreenKeyboard(aWindow, aInputContext); + } + + bool enable = WinUtils::IsIMEEnabled(aInputContext); + bool adjustOpenState = (enable && aInputContext.mIMEState.mOpen != + IMEState::DONT_CHANGE_OPEN_STATE); + bool open = + (adjustOpenState && aInputContext.mIMEState.mOpen == IMEState::OPEN); + + // Note that even while a plugin has focus, we need to notify TSF of that. + if (sIsInTSFMode) { + TSFTextStore::SetInputContext(aWindow, aInputContext, aAction); + if (IsTSFAvailable()) { + if (sIsIMMEnabled) { + // Associate IMC with aWindow only when it's necessary. + AssociateIMEContext(aWindow, enable && NeedsToAssociateIMC()); + } + if (adjustOpenState) { + TSFTextStore::SetIMEOpenState(open); + } + return; + } + } else { + // Set at least InputScope even when TextStore is not available. + SetInputScopeForIMM32(aWindow, aInputContext.mHTMLInputType, + aInputContext.mHTMLInputInputmode, + aInputContext.mInPrivateBrowsing); + } + + 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) { + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aWindow->GetWindowHandle(), + "IMEHandler::SetInputContext() requires non-nullptr HWND"); + + static bool sInitialized = false; + if (!sInitialized) { + sInitialized = true; + // Some TIPs like QQ Input (Simplified Chinese) may need normal window + // (i.e., windows except message window) when initializing themselves. + // Therefore, we need to initialize TSF/IMM modules after first normal + // window is created. InitInputContext() should be called immediately + // after creating each normal window, so, here is a good place to + // initialize these modules. + Initialize(); + } + + // For a11y, the default enabled state should be 'enabled'. + aInputContext.mIMEState.mEnabled = IMEEnabled::Enabled; + + if (sIsInTSFMode) { + TSFTextStore::SetInputContext( + aWindow, aInputContext, + InputContextAction(InputContextAction::CAUSE_UNKNOWN, + InputContextAction::WIDGET_CREATED)); + // IME context isn't necessary in pure TSF mode. + if (!sIsIMMEnabled) { + AssociateIMEContext(aWindow, false); + } + return; + } + +#ifdef DEBUG + // NOTE: IMC may be null if IMM module isn't installed. + IMEContext context(aWindow); + MOZ_ASSERT(context.IsValid() || !CurrentKeyboardLayoutHasIME()); +#endif // #ifdef DEBUG +} + +#ifdef DEBUG +// static +bool IMEHandler::CurrentKeyboardLayoutHasIME() { + if (sIsInTSFMode) { + return TSFTextStore::CurrentKeyboardLayoutHasIME(); + } + + return IMMHandler::IsIMEAvailable(); +} +#endif // #ifdef DEBUG + +// static +void IMEHandler::OnKeyboardLayoutChanged() { + // Be aware, this method won't be called until TSFStaticSink starts to + // observe active TIP change. If you need to be notified of this, you + // need to create TSFStaticSink::Observe() or something and call it + // TSFStaticSink::EnsureInitActiveTIPKeyboard() forcibly. + + if (!sIsIMMEnabled || !IsTSFAvailable()) { + return; + } + + // We don't need to do anything when sAssociateIMCOnlyWhenIMM_IMEActive is + // false because IMContext won't be associated/disassociated when changing + // active keyboard layout/IME. + if (!sAssociateIMCOnlyWhenIMM_IMEActive) { + 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, + bool aInPrivateBrowsing) { + if (sIsInTSFMode || !sSetInputScopes || aWindow->Destroyed()) { + return; + } + AutoTArray<InputScope, 3> scopes; + + // IME may refer only first input scope, but we will append inputmode's + // input scopes since IME may refer it like Chrome. + AppendInputScopeFromType(aHTMLInputType, scopes); + AppendInputScopeFromInputmode(aHTMLInputInputmode, scopes); + + if (aInPrivateBrowsing) { + scopes.AppendElement(IS_PRIVATE); + } + + if (scopes.IsEmpty()) { + // At least, 1 item is necessary. + scopes.AppendElement(IS_DEFAULT); + } + + sSetInputScopes(aWindow->GetWindowHandle(), scopes.Elements(), + scopes.Length(), nullptr, 0, nullptr, nullptr); +} + +// static +void IMEHandler::AppendInputScopeFromInputmode(const nsAString& aInputmode, + nsTArray<InputScope>& aScopes) { + if (aInputmode.EqualsLiteral("mozAwesomebar")) { + // Even if Awesomebar has focus, user may not input URL directly. + // However, on-screen keyboard for URL should be shown because it has + // some useful additional keys like ".com" and they are not hindrances + // even when inputting non-URL text, e.g., words to search something in + // the web. On the other hand, a lot of Microsoft's IMEs and Google + // Japanese Input make their open state "closed" automatically if we + // notify them of URL as the input scope. However, this is very annoying + // for the users when they try to input some words to search the web or + // bookmark/history items. Therefore, if they are active, we need to + // notify them of the default input scope for avoiding this issue. + // FYI: We cannot check active TIP without TSF. Therefore, if it's + // not in TSF mode, this will check only if active IMM-IME is Google + // Japanese Input. Google Japanese Input is a TIP of TSF basically. + // However, if the OS is Win7 or it's installed on Win7 but has not + // been updated yet even after the OS is upgraded to Win8 or later, + // it's installed as IMM-IME. + if (TSFTextStore::ShouldSetInputScopeOfURLBarToDefault()) { + return; + } + // Don't append IS_SEARCH here for showing on-screen keyboard for URL. + if (!aScopes.Contains(IS_URL)) { + aScopes.AppendElement(IS_URL); + } + return; + } + + // https://html.spec.whatwg.org/dev/interaction.html#attr-inputmode + if (aInputmode.EqualsLiteral("url")) { + if (!aScopes.Contains(IS_SEARCH)) { + aScopes.AppendElement(IS_URL); + } + return; + } + if (aInputmode.EqualsLiteral("email")) { + if (!aScopes.Contains(IS_EMAIL_SMTPEMAILADDRESS)) { + aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS); + } + return; + } + if (aInputmode.EqualsLiteral("tel")) { + if (!aScopes.Contains(IS_TELEPHONE_FULLTELEPHONENUMBER)) { + aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER); + } + if (!aScopes.Contains(IS_TELEPHONE_LOCALNUMBER)) { + aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER); + } + return; + } + if (aInputmode.EqualsLiteral("numeric")) { + if (!aScopes.Contains(IS_DIGITS)) { + aScopes.AppendElement(IS_DIGITS); + } + return; + } + if (aInputmode.EqualsLiteral("decimal")) { + if (!aScopes.Contains(IS_NUMBER)) { + aScopes.AppendElement(IS_NUMBER); + } + return; + } + if (aInputmode.EqualsLiteral("search")) { + if (!aScopes.Contains(IS_SEARCH)) { + aScopes.AppendElement(IS_SEARCH); + } + return; + } +} + +// static +void IMEHandler::AppendInputScopeFromType(const nsAString& aHTMLInputType, + nsTArray<InputScope>& aScopes) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html + if (aHTMLInputType.EqualsLiteral("url")) { + aScopes.AppendElement(IS_URL); + return; + } + if (aHTMLInputType.EqualsLiteral("search")) { + aScopes.AppendElement(IS_SEARCH); + return; + } + if (aHTMLInputType.EqualsLiteral("email")) { + aScopes.AppendElement(IS_EMAIL_SMTPEMAILADDRESS); + return; + } + if (aHTMLInputType.EqualsLiteral("password")) { + aScopes.AppendElement(IS_PASSWORD); + return; + } + if (aHTMLInputType.EqualsLiteral("datetime") || + aHTMLInputType.EqualsLiteral("datetime-local")) { + aScopes.AppendElement(IS_DATE_FULLDATE); + aScopes.AppendElement(IS_TIME_FULLTIME); + return; + } + if (aHTMLInputType.EqualsLiteral("date") || + aHTMLInputType.EqualsLiteral("month") || + aHTMLInputType.EqualsLiteral("week")) { + aScopes.AppendElement(IS_DATE_FULLDATE); + return; + } + if (aHTMLInputType.EqualsLiteral("time")) { + aScopes.AppendElement(IS_TIME_FULLTIME); + return; + } + if (aHTMLInputType.EqualsLiteral("tel")) { + aScopes.AppendElement(IS_TELEPHONE_FULLTELEPHONENUMBER); + aScopes.AppendElement(IS_TELEPHONE_LOCALNUMBER); + return; + } + if (aHTMLInputType.EqualsLiteral("number")) { + aScopes.AppendElement(IS_NUMBER); + return; + } +} + +// static +void IMEHandler::MaybeShowOnScreenKeyboard(nsWindow* aWindow, + const InputContext& aInputContext) { + if (aInputContext.mHTMLInputInputmode.EqualsLiteral("none")) { + return; + } +#ifdef NIGHTLY_BUILD + if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) { + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(), + mozilla::gfx::VRFxEventState::FOCUS); + return; + } +#endif // NIGHTLY_BUILD + if (!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(aWindow); +} + +// static +void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow, Sync aSync) { +#ifdef NIGHTLY_BUILD + if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) { + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(), + mozilla::gfx::VRFxEventState::BLUR); + } +#endif // NIGHTLY_BUILD + if (!IsWin8OrLater()) { + return; + } + + if (aSync == Sync::Yes) { + DismissOnScreenKeyboard(aWindow); + return; + } + + RefPtr<nsWindow> window(aWindow); + NS_DispatchToCurrentThreadQueue( + NS_NewRunnableFunction("IMEHandler::MaybeDismissOnScreenKeyboard", + [window]() { + if (window->Destroyed()) { + return; + } + if (!sFocusedWindow) { + DismissOnScreenKeyboard(window); + } + }), + EventQueuePriority::Idle); +} + +// static +bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack, + const std::wstring& aNeedle) { + std::wstring lowerCaseHaystack(aHaystack); + std::wstring lowerCaseNeedle(aNeedle); + std::transform(lowerCaseHaystack.begin(), lowerCaseHaystack.end(), + lowerCaseHaystack.begin(), ::tolower); + std::transform(lowerCaseNeedle.begin(), lowerCaseNeedle.end(), + lowerCaseNeedle.begin(), ::tolower); + return wcsstr(lowerCaseHaystack.c_str(), lowerCaseNeedle.c_str()) == + lowerCaseHaystack.c_str(); +} + +// Returns false if a physical keyboard is detected on Windows 8 and up, +// or there is some other reason why an onscreen keyboard is not necessary. +// Returns true if no keyboard is found and this device looks like it needs +// an on-screen keyboard for text input. +// static +bool IMEHandler::NeedOnScreenKeyboard() { + // 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::IsHandlingUserInput(sLastContextActionCause)) { + return false; + } + + // This function should be only invoked for machines with touch screens. + if ((::GetSystemMetrics(SM_DIGITIZER) & NID_INTEGRATED_TOUCH) != + NID_INTEGRATED_TOUCH) { + Preferences::SetString(kOskDebugReason, L"IKPOS: Touch screen not found."); + return false; + } + + // If the device is docked, the user is treating the device as a PC. + if (::GetSystemMetrics(SM_SYSTEMDOCKED) != 0) { + Preferences::SetString(kOskDebugReason, L"IKPOS: System docked."); + return false; + } + + // To determine whether a keyboard is present on the device, we do the + // following:- + // 1. If the platform role is that of a mobile or slate device, check the + // system metric SM_CONVERTIBLESLATEMODE to see if it is being used + // in slate mode. If it is, also check that the last input was a touch. + // If all of this is true, then we should show the on-screen keyboard. + + // 2. If step 1 didn't determine we should show the keyboard, we check if + // this device has keyboards attached to it. + + // Check if the device is being used as a laptop or a tablet. This can be + // checked by first checking the role of the device and then the + // corresponding system metric (SM_CONVERTIBLESLATEMODE). If it is being + // used as a tablet then we want the OSK to show up. + if (!sDeterminedPowerPlatformRole) { + sDeterminedPowerPlatformRole = true; + sPowerPlatformRole = WinUtils::GetPowerPlatformRole(); + } + + // If this a mobile or slate (tablet) device, check if it is in slate mode. + // If the last input was touch, ignore whether or not a keyboard is present. + if ((sPowerPlatformRole == PlatformRoleMobile || + sPowerPlatformRole == PlatformRoleSlate) && + ::GetSystemMetrics(SM_CONVERTIBLESLATEMODE) == 0 && + sLastContextActionCause == InputContextAction::CAUSE_TOUCH) { + Preferences::SetString( + kOskDebugReason, + L"IKPOS: Mobile/Slate Platform role, in slate mode with touch event."); + return true; + } + + return !IMEHandler::IsKeyboardPresentOnSlate(); +} + +// Uses the Setup APIs to enumerate the attached keyboards and returns true +// if the keyboard count is 1 or more. While this will work in most cases +// it won't work if there are devices which expose keyboard interfaces which +// are attached to the machine. +// Based on IsKeyboardPresentOnSlate() in Chromium's base/win/win_util.cc. +// static +bool IMEHandler::IsKeyboardPresentOnSlate() { + const GUID KEYBOARD_CLASS_GUID = { + 0x4D36E96B, + 0xE325, + 0x11CE, + {0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18}}; + + // Query for all the keyboard devices. + HDEVINFO device_info = ::SetupDiGetClassDevs(&KEYBOARD_CLASS_GUID, nullptr, + nullptr, DIGCF_PRESENT); + if (device_info == INVALID_HANDLE_VALUE) { + Preferences::SetString(kOskDebugReason, L"IKPOS: No keyboard info."); + return false; + } + + // Enumerate all keyboards and look for ACPI\PNP and HID\VID devices. If + // the count is more than 1 we assume that a keyboard is present. This is + // under the assumption that there will always be one keyboard device. + for (DWORD i = 0;; ++i) { + SP_DEVINFO_DATA device_info_data = {0}; + device_info_data.cbSize = sizeof(device_info_data); + if (!::SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) { + break; + } + + // Get the device ID. + wchar_t device_id[MAX_DEVICE_ID_LEN]; + CONFIGRET status = ::CM_Get_Device_ID(device_info_data.DevInst, device_id, + MAX_DEVICE_ID_LEN, 0); + if (status == CR_SUCCESS) { + static const std::wstring BT_HID_DEVICE = L"HID\\{00001124"; + static const std::wstring BT_HOGP_DEVICE = L"HID\\{00001812"; + // To reduce the scope of the hack we only look for ACPI and HID\\VID + // prefixes in the keyboard device ids. + if (IMEHandler::WStringStartsWithCaseInsensitive(device_id, L"ACPI") || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + L"HID\\VID") || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + BT_HID_DEVICE) || + IMEHandler::WStringStartsWithCaseInsensitive(device_id, + BT_HOGP_DEVICE)) { + // The heuristic we are using is to check the count of keyboards and + // return true if the API's report one or more keyboards. Please note + // that this will break for non keyboard devices which expose a + // keyboard PDO. + Preferences::SetString(kOskDebugReason, + L"IKPOS: Keyboard presence confirmed."); + return true; + } + } + } + Preferences::SetString(kOskDebugReason, + L"IKPOS: Lack of keyboard confirmed."); + return false; +} + +// static +bool IMEHandler::IsInTabletMode() { + 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 ReadEnableDesktopModeAutoInvoke(uint32_t aRoot, + nsIWindowsRegKey* aRegKey, + uint32_t& aValue) { + nsresult rv; + rv = aRegKey->Open(aRoot, u"SOFTWARE\\Microsoft\\TabletTip\\1.7"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + Preferences::SetString(kOskDebugReason, + L"AIOSKIDM: failed opening regkey."); + return false; + } + // EnableDesktopModeAutoInvoke is an opt-in option from the Windows + // Settings to "Automatically show the touch keyboard in windowed apps + // when there's no keyboard attached to your device." If the user has + // opted-in to this behavior, the tablet-mode requirement is skipped. + rv = aRegKey->ReadIntValue(u"EnableDesktopModeAutoInvoke"_ns, &aValue); + if (NS_FAILED(rv)) { + Preferences::SetString(kOskDebugReason, + L"AIOSKIDM: failed reading value of regkey."); + return false; + } + return true; +} + +// static +bool IMEHandler::AutoInvokeOnScreenKeyboardInDesktopMode() { + nsresult rv; + nsCOMPtr<nsIWindowsRegKey> regKey( + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + if (NS_WARN_IF(NS_FAILED(rv))) { + Preferences::SetString(kOskDebugReason, + L"AIOSKIDM: " + L"nsIWindowsRegKey not available"); + return false; + } + + uint32_t value; + if (!ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + regKey, value) && + !ReadEnableDesktopModeAutoInvoke(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + regKey, value)) { + return false; + } + if (!!value) { + Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=true."); + } else { + Preferences::SetString(kOskDebugReason, L"AIOSKIDM: regkey value=false."); + } + return !!value; +} + +// Based on DisplayVirtualKeyboard() in Chromium's base/win/win_util.cc. +// static +void IMEHandler::ShowOnScreenKeyboard(nsWindow* aWindow) { + if (IsWin10AnniversaryUpdateOrLater()) { + OSKInputPaneManager::ShowOnScreenKeyboard(aWindow->GetWindowHandle()); + return; + } + + nsAutoString cachedPath; + nsresult result = Preferences::GetString(kOskPathPrefName, cachedPath); + if (NS_FAILED(result) || cachedPath.IsEmpty()) { + wchar_t path[MAX_PATH]; + // The path to TabTip.exe is defined at the following registry key. + // This is pulled out of the 64-bit registry hive directly. + 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 = 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(nsWindow* aWindow) { + // Dismiss the virtual keyboard if it's open + if (IsWin10AnniversaryUpdateOrLater()) { + OSKInputPaneManager::DismissOnScreenKeyboard(aWindow->GetWindowHandle()); + return; + } + + 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; +} + +bool IMEHandler::MaybeCreateNativeCaret(nsWindow* aWindow) { + MOZ_ASSERT(aWindow); + + if (IsA11yHandlingNativeCaret()) { + return false; + } + + if (!sHasNativeCaretBeenRequested) { + // If we have not received WM_GETOBJECT for OBJID_CARET, there may be new + // application which requires our caret information. For kicking its + // window event proc, we should fire a window event here. + // (If there is such application, sHasNativeCaretBeenRequested will be set + // to true later.) + // FYI: If we create native caret and move its position, native caret + // causes EVENT_OBJECT_LOCATIONCHANGE event with OBJID_CARET and + // OBJID_CLIENT. + ::NotifyWinEvent(EVENT_OBJECT_LOCATIONCHANGE, aWindow->GetWindowHandle(), + OBJID_CARET, OBJID_CLIENT); + return false; + } + + MaybeDestroyNativeCaret(); + + // If focused content is not text editable, we don't support caret + // caret information without a11y module. + if (!aWindow->GetInputContext().mIMEState.IsEditable()) { + return false; + } + + WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, aWindow); + aWindow->InitEvent(queryCaretRectEvent); + + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + queryCaretRectEvent.InitForQueryCaretRect(0, options); + + aWindow->DispatchWindowEvent(&queryCaretRectEvent); + if (NS_WARN_IF(queryCaretRectEvent.Failed())) { + return false; + } + + return CreateNativeCaret(aWindow, queryCaretRectEvent.mReply->mRect); +} + +bool IMEHandler::CreateNativeCaret(nsWindow* aWindow, + const LayoutDeviceIntRect& aCaretRect) { + MOZ_ASSERT(aWindow); + + MOZ_ASSERT(!IsA11yHandlingNativeCaret()); + + sNativeCaretIsCreated = + ::CreateCaret(aWindow->GetWindowHandle(), nullptr, aCaretRect.Width(), + aCaretRect.Height()); + if (!sNativeCaretIsCreated) { + return false; + } + nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false); + if (NS_WARN_IF(!toplevelWindow)) { + MaybeDestroyNativeCaret(); + return false; + } + + LayoutDeviceIntPoint caretPosition(aCaretRect.TopLeft()); + if (toplevelWindow != aWindow) { + caretPosition += toplevelWindow->WidgetToScreenOffset(); + caretPosition -= aWindow->WidgetToScreenOffset(); + } + + ::SetCaretPos(caretPosition.x, caretPosition.y); + return true; +} + +void IMEHandler::MaybeDestroyNativeCaret() { + if (!sNativeCaretIsCreated) { + return; + } + ::DestroyCaret(); + sNativeCaretIsCreated = false; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinIMEHandler.h b/widget/windows/WinIMEHandler.h new file mode 100644 index 0000000000..5ccb62a7c7 --- /dev/null +++ b/widget/windows/WinIMEHandler.h @@ -0,0 +1,245 @@ +/* -*- 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 { + private: + /** + * Initialize() initializes both TSF modules and IMM modules. Some TIPs + * may require a normal window (i.e., not message window) belonging to + * this process. Therefore, this is called immediately after first normal + * window is created. + */ + static void Initialize(); + + public: + static void Terminate(); + + /** + * Returns TSF related native data or native IME context. + */ + static void* GetNativeData(nsWindow* aWindow, uint32_t aDataType); + + /** + * ProcessRawKeyMessage() message is called before calling TranslateMessage() + * and DispatchMessage(). If this returns true, the message is consumed. + * Then, caller must not perform TranslateMessage() nor DispatchMessage(). + */ + static bool ProcessRawKeyMessage(const MSG& aMsg); + + /** + * When the message is not needed to handle anymore by the caller, this + * returns true. Otherwise, false. + */ + static bool ProcessMessage(nsWindow* aWindow, UINT aMessage, WPARAM& aWParam, + LPARAM& aLParam, MSGResult& aResult); + + /** + * IsA11yHandlingNativeCaret() returns true if a11y is handling + * native caret. In such case, IME modules shouldn't touch native caret. + **/ + static bool IsA11yHandlingNativeCaret(); + + /** + * NeedsToCreateNativeCaret() returns true if IME handler needs to create + * native caret for other applications which requests OBJID_CARET with + * WM_GETOBJECT and a11y module isn't active (if a11y module is active, + * it always creates native caret, i.e., even if no editor has focus). + */ + static bool NeedsToCreateNativeCaret() { + return sHasNativeCaretBeenRequested && !IsA11yHandlingNativeCaret(); + } + + /** + * CreateNativeCaret() create native caret if this has been created it. + * + * @param aWindow The window which owns the caret. + * @param aCaretRect The caret rect relative to aWindow. + */ + static bool CreateNativeCaret(nsWindow* aWindow, + const LayoutDeviceIntRect& aCaretRect); + + /** + * MaybeDestroyNativeCaret() destroies native caret if it has been created + * by IMEHandler. + */ + static void MaybeDestroyNativeCaret(); + + /** + * HasNativeCaret() returns true if there is native caret and it was created + * by IMEHandler. + */ + static bool HasNativeCaret() { return sNativeCaretIsCreated; } + + /** + * When there is a composition, returns true. Otherwise, false. + */ + static bool IsComposing(); + + /** + * When there is a composition and it's in the window, returns true. + * Otherwise, false. + */ + static bool IsComposingOn(nsWindow* aWindow); + + /** + * Notifies IME of the notification (a request or an event). + */ + static nsresult NotifyIME(nsWindow* aWindow, + const IMENotification& aIMENotification); + + /** + * Returns notification requests of IME. + */ + static IMENotificationRequests GetIMENotificationRequests(); + + /** + * Returns native text event dispatcher listener. + */ + static TextEventDispatcherListener* GetNativeTextEventDispatcherListener(); + + /** + * Returns IME open state on the window. + */ + static bool GetOpenState(nsWindow* aWindow); + + /** + * Called when the window is destroying. + */ + static void OnDestroyWindow(nsWindow* aWindow); + + /** + * Called when nsIWidget::SetInputContext() is called before the window's + * InputContext is modified actually. + */ + static void SetInputContext(nsWindow* aWindow, InputContext& aInputContext, + const InputContextAction& aAction); + + /** + * Associate or disassociate IME context to/from the aWindowBase. + */ + static void AssociateIMEContext(nsWindowBase* aWindowBase, bool aEnable); + + /** + * Called when the window is created. + */ + static void InitInputContext(nsWindow* aWindow, InputContext& aInputContext); + + /** + * This is called by TSFStaticSink when active IME is changed. + */ + static void OnKeyboardLayoutChanged(); + +#ifdef DEBUG + /** + * Returns true when current keyboard layout has IME. Otherwise, false. + */ + static bool CurrentKeyboardLayoutHasIME(); +#endif // #ifdef DEBUG + + /** + * Append InputScope values from inputmode string. + */ + static void AppendInputScopeFromInputmode(const nsAString& aInputmode, + nsTArray<InputScope>& aScopes); + + /** + * Append InputScope values from type attreibute string of input element + */ + static void AppendInputScopeFromType(const nsAString& aInputType, + nsTArray<InputScope>& aScopes); + + private: + static nsWindow* sFocusedWindow; + static InputContextAction::Cause sLastContextActionCause; + + static bool sMaybeEditable; + static bool sForceDisableCurrentIMM_IME; + static bool sNativeCaretIsCreated; + static bool sHasNativeCaretBeenRequested; + + /** + * MaybeCreateNativeCaret() may create native caret over our caret if + * focused content is text editable and we need to create native caret + * for other applications. + * + * @param aWindow The window which owns the native caret. + */ + static bool MaybeCreateNativeCaret(nsWindow* aWindow); + + static decltype(SetInputScopes)* sSetInputScopes; + static void SetInputScopeForIMM32(nsWindow* aWindow, + const nsAString& aHTMLInputType, + const nsAString& aHTMLInputInputmode, + bool aInPrivateBrowsing); + static bool sIsInTSFMode; + // If sIMMEnabled is false, any IME messages are not handled in TSF mode. + // Additionally, IME context is always disassociated from focused window. + static bool sIsIMMEnabled; + static bool sAssociateIMCOnlyWhenIMM_IMEActive; + + static bool IsTSFAvailable() { return sIsInTSFMode; } + static bool IsIMMActive(); + + static void MaybeShowOnScreenKeyboard(nsWindow* aWindow, + const InputContext& aInputContext); + enum class Sync { Yes, No }; + static void MaybeDismissOnScreenKeyboard(nsWindow* aWindow, + Sync aSync = Sync::No); + static bool WStringStartsWithCaseInsensitive(const std::wstring& aHaystack, + const std::wstring& aNeedle); + static bool NeedOnScreenKeyboard(); + static bool IsKeyboardPresentOnSlate(); + static bool IsInTabletMode(); + static bool AutoInvokeOnScreenKeyboardInDesktopMode(); + static bool NeedsToAssociateIMC(); + + /** + * Show the Windows on-screen keyboard. Only allowed for + * chrome documents and Windows 8 and higher. + */ + static void ShowOnScreenKeyboard(nsWindow* aWindow); + + /** + * Dismiss the Windows on-screen keyboard. Only allowed for + * Windows 8 and higher. + */ + static void DismissOnScreenKeyboard(nsWindow* aWindow); + + /** + * Get the HWND for the on-screen keyboard, if it's up. Only + * allowed for Windows 8 and higher. + */ + static HWND GetOnScreenKeyboardWindow(); +}; + +} // 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..3eec16397a --- /dev/null +++ b/widget/windows/WinMessages.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinMessages_h_ +#define mozilla_widget_WinMessages_h_ + +/***************************************************************************** + * MOZ_WM_* messages + ****************************************************************************/ + +// A magic APP message that can be sent to quit, sort of like a +// QUERYENDSESSION/ENDSESSION, but without the query. +#define MOZ_WM_APP_QUIT (WM_APP + 0x0300) +// Used as a "tracer" event to probe event loop latency. +#define MOZ_WM_TRACE (WM_APP + 0x0301) +// accessibility priming +#define MOZ_WM_STARTA11Y (WM_APP + 0x0302) +// Our internal message for WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL and +// WM_HSCROLL +#define MOZ_WM_MOUSEVWHEEL (WM_APP + 0x0310) +#define MOZ_WM_MOUSEHWHEEL (WM_APP + 0x0311) +#define MOZ_WM_VSCROLL (WM_APP + 0x0312) +#define MOZ_WM_HSCROLL (WM_APP + 0x0313) +#define MOZ_WM_MOUSEWHEEL_FIRST MOZ_WM_MOUSEVWHEEL +#define MOZ_WM_MOUSEWHEEL_LAST MOZ_WM_HSCROLL +// If a popup window is being activated, we try to reactivate the previous +// window with this message. +#define MOZ_WM_REACTIVATE (WM_APP + 0x0314) +// If TSFTextStore needs to notify TSF/TIP of layout change later, this +// message is posted. +#define MOZ_WM_NOTIY_TSF_OF_LAYOUT_CHANGE (WM_APP + 0x0315) +// Internal message used in correcting backwards clock skew +#define MOZ_WM_SKEWFIX (WM_APP + 0x0316) + +// Internal message used for rolling up popups for dmanip events +#define MOZ_WM_DMANIP (WM_APP + 0x0317) + +// 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) + +// 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) + +// Drop shadow window style +#define CS_XP_DROPSHADOW 0x00020000 + +#ifndef APPCOMMAND_BROWSER_BACKWARD +# define APPCOMMAND_BROWSER_BACKWARD 1 +# define APPCOMMAND_BROWSER_FORWARD 2 +# define APPCOMMAND_BROWSER_REFRESH 3 +# define APPCOMMAND_BROWSER_STOP 4 +# define APPCOMMAND_BROWSER_SEARCH 5 +# define APPCOMMAND_BROWSER_FAVORITES 6 +# define APPCOMMAND_BROWSER_HOME 7 + +# define APPCOMMAND_MEDIA_NEXTTRACK 11 +# define APPCOMMAND_MEDIA_PREVIOUSTRACK 12 +# define APPCOMMAND_MEDIA_STOP 13 +# define APPCOMMAND_MEDIA_PLAY_PAUSE 14 + +/* + * Additional commands currently not in use. + * + *#define APPCOMMAND_VOLUME_MUTE 8 + *#define APPCOMMAND_VOLUME_DOWN 9 + *#define APPCOMMAND_VOLUME_UP 10 + *#define APPCOMMAND_LAUNCH_MAIL 15 + *#define APPCOMMAND_LAUNCH_MEDIA_SELECT 16 + *#define APPCOMMAND_LAUNCH_APP1 17 + *#define APPCOMMAND_LAUNCH_APP2 18 + *#define APPCOMMAND_BASS_DOWN 19 + *#define APPCOMMAND_BASS_BOOST 20 + *#define APPCOMMAND_BASS_UP 21 + *#define APPCOMMAND_TREBLE_DOWN 22 + *#define APPCOMMAND_TREBLE_UP 23 + *#define FAPPCOMMAND_MOUSE 0x8000 + *#define FAPPCOMMAND_KEY 0 + *#define FAPPCOMMAND_OEM 0x1000 + */ + +# define GET_APPCOMMAND_LPARAM(lParam) \ + ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK)) + +/* + *#define GET_DEVICE_LPARAM(lParam) ((WORD)(HIWORD(lParam) & + *FAPPCOMMAND_MASK)) #define GET_MOUSEORKEY_LPARAM GET_DEVICE_LPARAM + *#define GET_FLAGS_LPARAM(lParam) (LOWORD(lParam)) + *#define GET_KEYSTATE_LPARAM(lParam) GET_FLAGS_LPARAM(lParam) + */ +#endif // #ifndef APPCOMMAND_BROWSER_BACKWARD + +#endif // #ifndef mozilla_widget_WinMessages_h_ diff --git a/widget/windows/WinModifierKeyState.h b/widget/windows/WinModifierKeyState.h new file mode 100644 index 0000000000..0788c25939 --- /dev/null +++ b/widget/windows/WinModifierKeyState.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WinModifierKeyState_h_ +#define mozilla_widget_WinModifierKeyState_h_ + +#include "mozilla/RefPtr.h" +#include "mozilla/EventForwards.h" +#include "nsStringFwd.h" +#include <windows.h> + +namespace mozilla { +namespace widget { + +class MOZ_STACK_CLASS ModifierKeyState final { + public: + ModifierKeyState(); + explicit ModifierKeyState(Modifiers aModifiers); + + void Update(); + + void Unset(Modifiers aRemovingModifiers); + void Set(Modifiers aAddingModifiers); + + void InitInputEvent(WidgetInputEvent& aInputEvent) const; + + // Do not create IsAltGr() because it's unclear whether: + // - AltGr key is actually pressed. + // - Both Ctrl and Alt keys are pressed when a keyboard layout which + // has AltGr key. + // - Both Ctrl and Alt keys are pressed when a keyboard layout which + // does not have AltGr key. + bool IsShift() const; + bool IsControl() const; + bool IsAlt() const; + bool IsWin() const; + + bool MaybeMatchShortcutKey() const; + + bool IsCapsLocked() const; + bool IsNumLocked() const; + bool IsScrollLocked() const; + + MOZ_ALWAYS_INLINE Modifiers GetModifiers() const { return mModifiers; } + + private: + Modifiers mModifiers; + + void InitMouseEvent(WidgetInputEvent& aMouseEvent) const; +}; + +const nsCString ToString(const ModifierKeyState& aModifierKeyState); + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef mozilla_widget_WinModifierKeyState_h_ diff --git a/widget/windows/WinMouseScrollHandler.cpp b/widget/windows/WinMouseScrollHandler.cpp new file mode 100644 index 0000000000..1da2ba624e --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.cpp @@ -0,0 +1,1702 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "mozilla/Logging.h" + +#include "WinMouseScrollHandler.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "KeyboardLayout.h" +#include "WinUtils.h" +#include "nsGkAtoms.h" +#include "nsIDOMWindowUtils.h" + +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/WheelEventBinding.h" +#include "mozilla/StaticPrefs_mousewheel.h" + +#include <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, + aNativeMessage); + + for (uint32_t i = 0; i < keySequence.Length(); ++i) { + uint8_t key = keySequence[i].mGeneral; + uint8_t keySpecific = keySequence[i].mSpecific; + kbdState[key] = 0x81; // key is down and toggled on if appropriate + if (keySpecific) { + kbdState[keySpecific] = 0x81; + } + } + + if (!sInstance->mSynthesizingEvent) { + sInstance->mSynthesizingEvent = new SynthesizingEvent(); + } + + POINTS pts; + pts.x = static_cast<SHORT>(pt.x); + pts.y = static_cast<SHORT>(pt.y); + return sInstance->mSynthesizingEvent->Synthesize(pts, target, aNativeMessage, + wParam, lParam, kbdState); +} + +/* static */ +void MouseScrollHandler::InitEvent(nsWindowBase* aWidget, + WidgetGUIEvent& aEvent, LPARAM* aPoint) { + NS_ENSURE_TRUE_VOID(aWidget); + + // If a point is provided, use it; otherwise, get current message point or + // synthetic point + POINTS pointOnScreen; + if (aPoint != nullptr) { + pointOnScreen = MAKEPOINTS(*aPoint); + } else { + pointOnScreen = GetCurrentMessagePos(); + } + + // InitEvent expects the point to be in window coordinates, so translate the + // point from screen coordinates. + POINT pointOnWindow; + POINTSTOPOINT(pointOnWindow, pointOnScreen); + ::ScreenToClient(aWidget->GetWindowHandle(), &pointOnWindow); + + LayoutDeviceIntPoint point; + point.x = pointOnWindow.x; + point.y = pointOnWindow.y; + + aWidget->InitEvent(aEvent, &point); +} + +/* static */ +ModifierKeyState MouseScrollHandler::GetModifierKeyState(UINT aMessage) { + ModifierKeyState result; + // Assume the Control key is down if the Elantech touchpad has sent the + // mis-ordered WM_KEYDOWN/WM_MOUSEWHEEL messages. (See the comment in + // MouseScrollHandler::Device::Elantech::HandleKeyMessage().) + if ((aMessage == MOZ_WM_MOUSEVWHEEL || aMessage == WM_MOUSEWHEEL) && + !result.IsControl() && Device::Elantech::IsZooming()) { + // XXX Do we need to unset MODIFIER_SHIFT, MODIFIER_ALT, MODIFIER_OS too? + // If one of them are true, the default action becomes not zooming. + result.Unset(MODIFIER_ALTGRAPH); + result.Set(MODIFIER_CONTROL); + } + return result; +} + +POINT +MouseScrollHandler::ComputeMessagePos(UINT aMessage, WPARAM aWParam, + LPARAM aLParam) { + POINT point; + if (Device::SetPoint::IsGetMessagePosResponseValid(aMessage, aWParam, + aLParam)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::ComputeMessagePos: Using ::GetCursorPos()")); + ::GetCursorPos(&point); + } else { + POINTS pts = GetCurrentMessagePos(); + point.x = pts.x; + point.y = pts.y; + } + return point; +} + +void MouseScrollHandler::ProcessNativeMouseWheelMessage(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"); + + // Some odd touchpad utils sets focus to window under the mouse cursor. + // this emulates the odd behavior for debug. + if (mUserPrefs.ShouldEmulateToMakeWindowUnderCursorForeground() && + (aMessage == WM_MOUSEWHEEL || aMessage == WM_MOUSEHWHEEL) && + ::GetForegroundWindow() != destWindow->GetWindowHandle()) { + ::SetForegroundWindow(destWindow->GetWindowHandle()); + } + + // 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, + aLParam)) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: dispatching " + "eWheel event")); + aWidget->DispatchWheelEvent(&wheelEvent); + if (aWidget->Destroyed()) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: The window was destroyed " + "by eWheel event")); + mLastEventInfo.ResetTransaction(); + return; + } + } else { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleMouseWheelMessage: eWheel event is not " + "dispatched")); + } +} + +void MouseScrollHandler::HandleScrollMessageAsMouseWheelMessage( + 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 = dom::WheelEvent_Binding::DOM_DELTA_PAGE; + break; + + case SB_LINEUP: + delta = -1.0; + lineOrPageDelta = -1; + case SB_LINEDOWN: + wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE; + break; + + default: + return; + } + modKeyState.InitInputEvent(wheelEvent); + + // Current mouse position may not be same as when the original message + // is received. However, this data is not available with the original + // message, which is why nullptr is passed in. We need to know the actual + // mouse cursor position when the original message was received. + InitEvent(aWidget, wheelEvent, nullptr); + + MOZ_LOG( + gMouseScrollLog, LogLevel::Info, + ("MouseScroll::HandleScrollMessageAsMouseWheelMessage: aWidget=%p, " + "aMessage=MOZ_WM_%sSCROLL, aWParam=0x%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, LPARAM aLParam) { + MOZ_ASSERT(aWheelEvent.mMessage == eWheel); + + if (StaticPrefs::mousewheel_ignore_cursor_position_in_lparam()) { + InitEvent(aWidget, aWheelEvent, nullptr); + } else { + InitEvent(aWidget, aWheelEvent, &aLParam); + } + + aModKeyState.InitInputEvent(aWheelEvent); + + // Our positive delta value means to bottom or right. + // But positive native delta value means to top or right. + // Use orienter for computing our delta value with native delta value. + int32_t orienter = mIsVertical ? -1 : 1; + + aWheelEvent.mDeltaMode = mIsPage ? dom::WheelEvent_Binding::DOM_DELTA_PAGE + : dom::WheelEvent_Binding::DOM_DELTA_LINE; + + double& 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 != dom::WheelEvent_Binding::DOM_DELTA_LINE) { + // If the scroll delta mode isn't per line scroll, we shouldn't allow to + // override the system scroll speed setting. + aWheelEvent.mAllowToOverrideSystemScrollSpeed = false; + } 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, this is unexpected on Vista or " + "later")); + // XXX Should we use DefaultScrollChars()? + mScrollChars = 1; + } + + if (mScrollChars > WHEEL_DELTA) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::SystemSettings::InitScrollChars(): the result of " + "::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS) is too large: %d", + mScrollChars)); + // See the comments for the case mScrollLines > WHEEL_DELTA. + mScrollChars = WHEEL_PAGESCROLL; + } + + return oldValue != mScrollChars; +} + +void MouseScrollHandler::SystemSettings::MarkDirty() { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::SystemSettings::MarkDirty(): " + "Marking SystemSettings dirty")); + mInitialized = false; + // When system settings are changed, we should reset current transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +void MouseScrollHandler::SystemSettings::RefreshCache() { + bool isChanged = InitScrollLines(); + isChanged = InitScrollChars() || isChanged; + if (!isChanged) { + return; + } + // If the scroll amount is changed, we should reset current transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +void MouseScrollHandler::SystemSettings::TrustedScrollSettingsDriver() { + if (!mInitialized) { + return; + } + + // if the cache is initialized with prefs, we don't need to refresh it. + if (mIsReliableScrollLines && mIsReliableScrollChars) { + return; + } + + MouseScrollHandler::UserPrefs& userPrefs = + MouseScrollHandler::sInstance->mUserPrefs; + + // If system settings cache is disabled, we should always refresh them. + if (!userPrefs.IsSystemSettingCacheEnabled()) { + RefreshCache(); + return; + } + + // If pref is set to as "always trust the cache", we shouldn't refresh them + // in any environments. + if (userPrefs.IsSystemSettingCacheForciblyEnabled()) { + return; + } + + // If SynTP of Synaptics or Apoint of Alps is installed, it may hook + // ::SystemParametersInfo() and returns different value from system settings. + if (Device::SynTP::IsDriverInstalled() || + Device::Apoint::IsDriverInstalled()) { + RefreshCache(); + return; + } + + // XXX We're not sure about other touchpad drivers... +} + +bool MouseScrollHandler::SystemSettings:: + IsOverridingSystemScrollSpeedAllowed() { + return mScrollLines == DefaultScrollLines() && + 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::RegisterPrefixCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to register callback for mousewheel."); +} + +MouseScrollHandler::UserPrefs::~UserPrefs() { + DebugOnly<nsresult> rv = + Preferences::UnregisterPrefixCallback(OnChange, "mousewheel.", this); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to unregister callback for mousewheel."); +} + +void MouseScrollHandler::UserPrefs::Init() { + if (mInitialized) { + return; + } + + mInitialized = true; + + mScrollMessageHandledAsWheelMessage = + Preferences::GetBool("mousewheel.emulate_at_wm_scroll", false); + mEnableSystemSettingCache = + Preferences::GetBool("mousewheel.system_settings_cache.enabled", true); + mForceEnableSystemSettingCache = Preferences::GetBool( + "mousewheel.system_settings_cache.force_enabled", false); + mEmulateToMakeWindowUnderCursorForeground = Preferences::GetBool( + "mousewheel.debug.make_window_under_cursor_foreground", false); + mOverriddenVerticalScrollAmount = + Preferences::GetInt("mousewheel.windows.vertical_amount_override", -1); + mOverriddenHorizontalScrollAmount = + Preferences::GetInt("mousewheel.windows.horizontal_amount_override", -1); + mMouseScrollTransactionTimeout = Preferences::GetInt( + "mousewheel.windows.transaction.timeout", DEFAULT_TIMEOUT_DURATION); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::UserPrefs::Init(): initialized, " + "mScrollMessageHandledAsWheelMessage=%s, " + "mEnableSystemSettingCache=%s, " + "mForceEnableSystemSettingCache=%s, " + "mEmulateToMakeWindowUnderCursorForeground=%s, " + "mOverriddenVerticalScrollAmount=%d, " + "mOverriddenHorizontalScrollAmount=%d, " + "mMouseScrollTransactionTimeout=%d", + GetBoolName(mScrollMessageHandledAsWheelMessage), + GetBoolName(mEnableSystemSettingCache), + GetBoolName(mForceEnableSystemSettingCache), + GetBoolName(mEmulateToMakeWindowUnderCursorForeground), + mOverriddenVerticalScrollAmount, mOverriddenHorizontalScrollAmount, + mMouseScrollTransactionTimeout)); +} + +void MouseScrollHandler::UserPrefs::MarkDirty() { + MOZ_LOG( + gMouseScrollLog, LogLevel::Info, + ("MouseScrollHandler::UserPrefs::MarkDirty(): Marking UserPrefs dirty")); + mInitialized = false; + // Some prefs might override system settings, so, we should mark them dirty. + MouseScrollHandler::sInstance->mSystemSettings.MarkDirty(); + // When user prefs for mousewheel are changed, we should reset current + // transaction. + MOZ_ASSERT(sInstance, + "Must not be called at initializing MouseScrollHandler"); + MouseScrollHandler::sInstance->mLastEventInfo.ResetTransaction(); +} + +/****************************************************************************** + * + * Device + * + ******************************************************************************/ + +/* static */ +bool MouseScrollHandler::Device::GetWorkaroundPref(const char* aPrefName, + bool aValueIfAutomatic) { + if (!aPrefName) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Failed, aPrefName is " + "NULL")); + return aValueIfAutomatic; + } + + int32_t lHackValue = 0; + if (NS_FAILED(Preferences::GetInt(aPrefName, &lHackValue))) { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Preferences::GetInt() " + "failed," + " aPrefName=\"%s\", aValueIfAutomatic=%s", + aPrefName, GetBoolName(aValueIfAutomatic))); + return aValueIfAutomatic; + } + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::GetWorkaroundPref(): Succeeded, " + "aPrefName=\"%s\", aValueIfAutomatic=%s, lHackValue=%d", + aPrefName, GetBoolName(aValueIfAutomatic), lHackValue)); + + switch (lHackValue) { + case 0: // disabled + return false; + case 1: // enabled + return true; + default: // -1: autodetect + return aValueIfAutomatic; + } +} + +/* static */ +void MouseScrollHandler::Device::Init() { + // FYI: Thinkpad's TrackPoint is Apoint of Alps and UltraNav is SynTP of + // Synaptics. So, those drivers' information should be initialized + // before calling methods of TrackPoint and UltraNav. + SynTP::Init(); + Elantech::Init(); + Apoint::Init(); + + sFakeScrollableWindowNeeded = GetWorkaroundPref( + "ui.trackpoint_hack.enabled", (TrackPoint::IsDriverInstalled() || + UltraNav::IsObsoleteDriverInstalled())); + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Init(): sFakeScrollableWindowNeeded=%s", + GetBoolName(sFakeScrollableWindowNeeded))); +} + +/****************************************************************************** + * + * Device::SynTP + * + ******************************************************************************/ + +/* static */ +void MouseScrollHandler::Device::SynTP::Init() { + if (sInitialized) { + return; + } + + sInitialized = true; + sMajorVersion = 0; + sMinorVersion = -1; + + wchar_t buf[40]; + 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 appCommandEvent( + true, (aWParam == VK_NEXT) ? nsGkAtoms::Forward : nsGkAtoms::Back, + aWidget); + + // In this scenario, the coordinate of the event isn't supplied, so pass + // nullptr as an argument to indicate using the coordinate from the last + // available window message. + InitEvent(aWidget, appCommandEvent, nullptr); + aWidget->DispatchWindowEvent(&appCommandEvent); + } else { + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): Consumed")); + } + return true; // consume the message (doesn't need to dispatch key events) + } + + // Version 8 of the Elantech touchpad driver sends these messages for + // zoom gestures: + // + // WM_KEYDOWN virtual_key = 0xCC time = 10 + // WM_KEYDOWN virtual_key = VK_CONTROL time = 10 + // WM_MOUSEWHEEL time = ::GetTickCount() + // WM_KEYUP virtual_key = VK_CONTROL time = 10 + // WM_KEYUP virtual_key = 0xCC time = 10 + // + // The result of this is that we process all of the WM_KEYDOWN/WM_KEYUP + // messages first because their timestamps make them appear to have + // been sent before the WM_MOUSEWHEEL message. To work around this, + // we store the current time when we process the WM_KEYUP message and + // assume that any WM_MOUSEWHEEL message with a timestamp before that + // time is one that should be processed as if the Control key was down. + if (sUsePinchHack && aMsg == WM_KEYUP && aWParam == VK_CONTROL && + ::GetMessageTime() == 10) { + // We look only at the bottom 31 bits of the system tick count since + // GetMessageTime returns a LONG, which is signed, so we want values + // that are more easily comparable. + sZoomUntil = ::GetTickCount() & 0x7FFFFFFF; + + MOZ_LOG(gMouseScrollLog, LogLevel::Info, + ("MouseScroll::Device::Elantech::HandleKeyMessage(): sZoomUntil=%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..5e915d630d --- /dev/null +++ b/widget/windows/WinMouseScrollHandler.h @@ -0,0 +1,576 @@ +/* -*- 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, + LPARAM* aPoint); + + /** + * GetModifierKeyState() returns current modifier key state. + * Note that some devices need some hack for the modifier key state. + * This method does it automatically. + * + * @param aMessage Handling message. + */ + static ModifierKeyState GetModifierKeyState(UINT aMessage); + + /** + * MozGetMessagePos() returns the mouse cursor position when GetMessage() + * was called last time. However, if we're sending a native message, + * this returns the specified cursor position by + * SynthesizeNativeMouseScrollEvent(). + */ + static POINTS GetCurrentMessagePos(); + + /** + * ProcessNativeMouseWheelMessage() processes WM_MOUSEWHEEL and + * WM_MOUSEHWHEEL. Additionally, processes WM_VSCROLL and WM_HSCROLL if they + * should be processed as mouse wheel message. + * This method posts MOZ_WM_MOUSEVWHEEL, MOZ_WM_MOUSEHWHEEL, + * MOZ_WM_VSCROLL or MOZ_WM_HSCROLL if we need to dispatch mouse scroll + * events. That avoids deadlock with plugin process. + * + * @param aWidget A window which receives the message. + * @param aMessage WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_VSCROLL or + * WM_HSCROLL. + * @param aWParam The wParam value of the message. + * @param aLParam The lParam value of the message. + */ + void ProcessNativeMouseWheelMessage(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, LPARAM aLParam); + + private: + static int32_t RoundDelta(double aDelta); + + int32_t mAccumulatedDelta; + }; + + LastEventInfo mLastEventInfo; + + class SystemSettings { + public: + SystemSettings() : mInitialized(false) {} + + void Init(); + void MarkDirty(); + void NotifyUserPrefsMayOverrideSystemSettings(); + + // On some environments, SystemParametersInfo() may be hooked by touchpad + // utility or something. In such case, when user changes active pointing + // device to another one, the result of SystemParametersInfo() may be + // changed without WM_SETTINGCHANGE message. For avoiding this trouble, + // we need to modify cache of system settings at every wheel message + // handling if we meet known device whose utility may hook the API. + void TrustedScrollSettingsDriver(); + + // 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; + } + + bool ShouldEmulateToMakeWindowUnderCursorForeground() { + Init(); + return mEmulateToMakeWindowUnderCursorForeground; + } + + int32_t GetOverriddenVerticalScrollAmout() { + Init(); + return mOverriddenVerticalScrollAmount; + } + + int32_t GetOverriddenHorizontalScrollAmout() { + Init(); + return mOverriddenHorizontalScrollAmount; + } + + int32_t GetMouseScrollTransactionTimeout() { + Init(); + return mMouseScrollTransactionTimeout; + } + + private: + void Init(); + + static void OnChange(const char* aPrefName, void* aSelf) { + static_cast<UserPrefs*>(aSelf)->MarkDirty(); + } + + bool mInitialized; + bool mScrollMessageHandledAsWheelMessage; + bool mEnableSystemSettingCache; + bool mForceEnableSystemSettingCache; + bool mEmulateToMakeWindowUnderCursorForeground; + int32_t mOverriddenVerticalScrollAmount; + int32_t mOverriddenHorizontalScrollAmount; + int32_t mMouseScrollTransactionTimeout; + }; + + UserPrefs mUserPrefs; + + class SynthesizingEvent { + public: + SynthesizingEvent() + : mWnd(nullptr), + mMessage(0), + mWParam(0), + mLParam(0), + mStatus(NOT_SYNTHESIZING) {} + + ~SynthesizingEvent() {} + + static bool IsSynthesizing(); + + nsresult Synthesize(const POINTS& aCursorPoint, HWND aWnd, UINT aMessage, + WPARAM aWParam, LPARAM aLParam, + const BYTE (&aKeyStates)[256]); + + void NativeMessageReceived(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..b6b1de225a --- /dev/null +++ b/widget/windows/WinNativeEventData.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_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/WinPointerEvents.cpp b/widget/windows/WinPointerEvents.cpp new file mode 100644 index 0000000000..62d2aa0515 --- /dev/null +++ b/widget/windows/WinPointerEvents.cpp @@ -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/. */ + +/* + * WinPointerEvents - Helper functions to retrieve PointerEvent's attributes + */ + +#include "nscore.h" +#include "WinPointerEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/dom/MouseEventBinding.h" + +using namespace mozilla; +using namespace mozilla::widget; + +const wchar_t WinPointerEvents::kPointerLibraryName[] = L"user32.dll"; +HMODULE WinPointerEvents::sLibraryHandle = nullptr; +WinPointerEvents::GetPointerTypePtr WinPointerEvents::getPointerType = nullptr; +WinPointerEvents::GetPointerInfoPtr WinPointerEvents::getPointerInfo = nullptr; +WinPointerEvents::GetPointerPenInfoPtr WinPointerEvents::getPointerPenInfo = + nullptr; + +WinPointerEvents::WinPointerEvents() { InitLibrary(); } + +/* Load and shutdown */ +void WinPointerEvents::InitLibrary() { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!IsWin8OrLater()) { + // Only Win8 or later supports WM_POINTER* + return; + } + if (getPointerType) { + // Return if we already initialized the PointerEvent related interfaces + return; + } + sLibraryHandle = ::LoadLibraryW(kPointerLibraryName); + MOZ_ASSERT(sLibraryHandle, "cannot load pointer library"); + if (sLibraryHandle) { + getPointerType = + (GetPointerTypePtr)GetProcAddress(sLibraryHandle, "GetPointerType"); + getPointerInfo = + (GetPointerInfoPtr)GetProcAddress(sLibraryHandle, "GetPointerInfo"); + getPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress( + sLibraryHandle, "GetPointerPenInfo"); + } + + if (!getPointerType || !getPointerInfo || !getPointerPenInfo) { + MOZ_ASSERT(false, "get PointerEvent interfaces failed"); + getPointerType = nullptr; + getPointerInfo = nullptr; + getPointerPenInfo = nullptr; + return; + } +} + +bool WinPointerEvents::ShouldHandleWinPointerMessages(UINT aMsg, + WPARAM aWParam) { + MOZ_ASSERT(aMsg == WM_POINTERDOWN || aMsg == WM_POINTERUP || + aMsg == WM_POINTERUPDATE || aMsg == WM_POINTERLEAVE); + if (!sLibraryHandle || !StaticPrefs::dom_w3c_pointer_events_enabled()) { + return false; + } + + // We only handle WM_POINTER* when the input source is pen. This is because + // we need some information (e.g. tiltX, tiltY) which can't be retrieved by + // WM_*BUTTONDOWN. + uint32_t pointerId = GetPointerId(aWParam); + POINTER_INPUT_TYPE pointerType = PT_POINTER; + if (!GetPointerType(pointerId, &pointerType)) { + MOZ_ASSERT(false, "cannot find PointerType"); + return false; + } + return (pointerType == PT_PEN); +} + +bool WinPointerEvents::GetPointerType(uint32_t aPointerId, + POINTER_INPUT_TYPE* aPointerType) { + if (!getPointerType) { + return false; + } + return getPointerType(aPointerId, aPointerType); +} + +POINTER_INPUT_TYPE +WinPointerEvents::GetPointerType(uint32_t aPointerId) { + POINTER_INPUT_TYPE pointerType = PT_POINTER; + Unused << GetPointerType(aPointerId, &pointerType); + return pointerType; +} + +bool WinPointerEvents::GetPointerInfo(uint32_t aPointerId, + POINTER_INFO* aPointerInfo) { + if (!getPointerInfo) { + return false; + } + return getPointerInfo(aPointerId, aPointerInfo); +} + +bool WinPointerEvents::GetPointerPenInfo(uint32_t aPointerId, + POINTER_PEN_INFO* aPenInfo) { + if (!getPointerPenInfo) { + return false; + } + return getPointerPenInfo(aPointerId, aPenInfo); +} + +bool WinPointerEvents::ShouldEnableInkCollector() { + // We need InkCollector on Win7. For Win8 or later, we handle WM_POINTER* for + // pen. + return !IsWin8OrLater(); +} + +bool WinPointerEvents::ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam) { + MOZ_ASSERT(aMsg == WM_POINTERDOWN); + // Only roll up popups when we handling WM_POINTER* to fire Gecko + // WidgetMouseEvent and suppress Windows WM_*BUTTONDOWN. + return ShouldHandleWinPointerMessages(aMsg, aWParam) && + ShouldFirePointerEventByWinPointerMessages(); +} + +bool WinPointerEvents::ShouldFirePointerEventByWinPointerMessages() { + MOZ_ASSERT(sLibraryHandle && StaticPrefs::dom_w3c_pointer_events_enabled()); + return StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages(); +} + +WinPointerInfo* WinPointerEvents::GetCachedPointerInfo(UINT aMsg, + WPARAM aWParam) { + if (!sLibraryHandle || !StaticPrefs::dom_w3c_pointer_events_enabled() || + MOUSE_INPUT_SOURCE() != dom::MouseEvent_Binding::MOZ_SOURCE_PEN || + ShouldFirePointerEventByWinPointerMessages()) { + return nullptr; + } + switch (aMsg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + return &mPenPointerDownInfo; + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + return &mPenPointerDownInfo; + case WM_MOUSEMOVE: + return &mPenPointerUpdateInfo; + default: + MOZ_ASSERT(false); + } + return nullptr; +} + +void WinPointerEvents::ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam) { + MOZ_ASSERT( + !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages()); + // Windows doesn't support chorded buttons for pen, so we can simply keep the + // latest information from pen generated pointer messages and use them when + // handling mouse messages. Used different pointer info for pointerdown, + // pointerupdate, and pointerup because Windows doesn't always interleave + // pointer messages and mouse messages. + switch (aMsg) { + case WM_POINTERDOWN: + ConvertAndCachePointerInfo(aWParam, &mPenPointerDownInfo); + break; + case WM_POINTERUP: + ConvertAndCachePointerInfo(aWParam, &mPenPointerUpInfo); + break; + case WM_POINTERUPDATE: + ConvertAndCachePointerInfo(aWParam, &mPenPointerUpdateInfo); + break; + default: + break; + } +} + +void WinPointerEvents::ConvertAndCachePointerInfo(WPARAM aWParam, + WinPointerInfo* aInfo) { + MOZ_ASSERT( + !StaticPrefs::dom_w3c_pointer_events_dispatch_by_pointer_messages()); + aInfo->pointerId = GetPointerId(aWParam); + MOZ_ASSERT(GetPointerType(aInfo->pointerId) == PT_PEN); + POINTER_PEN_INFO penInfo; + GetPointerPenInfo(aInfo->pointerId, &penInfo); + aInfo->tiltX = penInfo.tiltX; + aInfo->tiltY = penInfo.tiltY; + // Windows defines the pen pressure is normalized to a range between 0 and + // 1024. Convert it to float. + aInfo->mPressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0; +} diff --git a/widget/windows/WinPointerEvents.h b/widget/windows/WinPointerEvents.h new file mode 100644 index 0000000000..6bded07d06 --- /dev/null +++ b/widget/windows/WinPointerEvents.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinPointerEvents_h__ +#define WinPointerEvents_h__ + +#include "mozilla/MouseEvents.h" +#include "nsWindowBase.h" + +// Define PointerEvent related macros and structures when building code on +// Windows version before Win8. +#if WINVER < 0x0602 + +// These definitions are copied from WinUser.h. Some of them are not used but +// keep them here for future usage. +# define WM_NCPOINTERUPDATE 0x0241 +# define WM_NCPOINTERDOWN 0x0242 +# define WM_NCPOINTERUP 0x0243 +# define WM_POINTERUPDATE 0x0245 +# define WM_POINTERDOWN 0x0246 +# define WM_POINTERUP 0x0247 +# define WM_POINTERENTER 0x0249 +# define WM_POINTERLEAVE 0x024A +# define WM_POINTERACTIVATE 0x024B +# define WM_POINTERCAPTURECHANGED 0x024C +# define WM_TOUCHHITTESTING 0x024D +# define WM_POINTERWHEEL 0x024E +# define WM_POINTERHWHEEL 0x024F +# define DM_POINTERHITTEST 0x0250 + +typedef UINT32 PEN_FLAGS; +# define PEN_FLAG_NONE 0x00000000 // Default +# define PEN_FLAG_BARREL 0x00000001 // The barrel button is pressed +# define PEN_FLAG_INVERTED 0x00000002 // The pen is inverted +# define PEN_FLAG_ERASER 0x00000004 // The eraser button is pressed + +typedef UINT32 PEN_MASK; +# define PEN_MASK_NONE \ + 0x00000000 // Default - none of the optional fields are valid +# define PEN_MASK_PRESSURE 0x00000001 // The pressure field is valid +# define PEN_MASK_ROTATION 0x00000002 // The rotation field is valid +# define PEN_MASK_TILT_X 0x00000004 // The tiltX field is valid +# define PEN_MASK_TILT_Y 0x00000008 // The tiltY field is valid + +typedef struct tagPOINTER_PEN_INFO { + POINTER_INFO pointerInfo; + PEN_FLAGS penFlags; + PEN_MASK penMask; + UINT32 pressure; + UINT32 rotation; + INT32 tiltX; + INT32 tiltY; +} POINTER_PEN_INFO; + +/* + * Flags that appear in pointer input message parameters + */ +# define POINTER_MESSAGE_FLAG_NEW 0x00000001 // New pointer +# define POINTER_MESSAGE_FLAG_INRANGE 0x00000002 // Pointer has not departed +# define POINTER_MESSAGE_FLAG_INCONTACT 0x00000004 // Pointer is in contact +# define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 // Primary action +# define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 // Secondary action +# define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 // Third button +# define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 // Fourth button +# define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 // Fifth button +# define POINTER_MESSAGE_FLAG_PRIMARY 0x00002000 // Pointer is primary +# define POINTER_MESSAGE_FLAG_CONFIDENCE \ + 0x00004000 // Pointer is considered unlikely to be accidental +# define POINTER_MESSAGE_FLAG_CANCELED \ + 0x00008000 // Pointer is departing in an abnormal manner + +/* + * Macros to retrieve information from pointer input message parameters + */ +# define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) +# define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) \ + (((DWORD)HIWORD(wParam) & (flag)) == (flag)) +# define IS_POINTER_NEW_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW) +# define IS_POINTER_INRANGE_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE) +# define IS_POINTER_INCONTACT_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT) +# define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON) +# define IS_POINTER_SECONDBUTTON_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON) +# define IS_POINTER_THIRDBUTTON_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON) +# define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON) +# define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON) +# define IS_POINTER_PRIMARY_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY) +# define HAS_POINTER_CONFIDENCE_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE) +# define IS_POINTER_CANCELED_WPARAM(wParam) \ + IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED) + +/* + * WM_POINTERACTIVATE return codes + */ +# define PA_ACTIVATE MA_ACTIVATE +# define PA_NOACTIVATE MA_NOACTIVATE + +#endif // WINVER < 0x0602 + +/****************************************************************************** + * WinPointerInfo + * + * This is a helper class to handle WM_POINTER*. It only supports Win8 or later. + * + ******************************************************************************/ +class WinPointerInfo final : public mozilla::WidgetPointerHelper { + public: + WinPointerInfo() : WidgetPointerHelper(), mPressure(0), mButtons(0) {} + + WinPointerInfo(uint32_t aPointerId, uint32_t aTiltX, uint32_t aTiltY, + float aPressure, int16_t aButtons) + : WidgetPointerHelper(aPointerId, aTiltX, aTiltY), + mPressure(aPressure), + mButtons(aButtons) {} + + float mPressure; + int16_t mButtons; +}; + +class WinPointerEvents final { + public: + explicit WinPointerEvents(); + + public: + bool ShouldHandleWinPointerMessages(UINT aMsg, WPARAM aWParam); + + uint32_t GetPointerId(WPARAM aWParam) { + return GET_POINTERID_WPARAM(aWParam); + } + bool GetPointerType(uint32_t aPointerId, POINTER_INPUT_TYPE* aPointerType); + POINTER_INPUT_TYPE GetPointerType(uint32_t aPointerId); + bool GetPointerInfo(uint32_t aPointerId, POINTER_INFO* aPointerInfo); + bool GetPointerPenInfo(uint32_t aPointerId, POINTER_PEN_INFO* aPenInfo); + bool ShouldEnableInkCollector(); + bool ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam); + bool ShouldFirePointerEventByWinPointerMessages(); + WinPointerInfo* GetCachedPointerInfo(UINT aMsg, WPARAM aWParam); + void ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam); + void ConvertAndCachePointerInfo(WPARAM aWParam, WinPointerInfo* aInfo); + + private: + // Function prototypes + typedef BOOL(WINAPI* GetPointerTypePtr)(uint32_t aPointerId, + POINTER_INPUT_TYPE* aPointerType); + typedef BOOL(WINAPI* GetPointerInfoPtr)(uint32_t aPointerId, + POINTER_INFO* aPointerInfo); + typedef BOOL(WINAPI* GetPointerPenInfoPtr)(uint32_t aPointerId, + POINTER_PEN_INFO* aPenInfo); + + void InitLibrary(); + + static HMODULE sLibraryHandle; + static const wchar_t kPointerLibraryName[]; + // Static function pointers + static GetPointerTypePtr getPointerType; + static GetPointerInfoPtr getPointerInfo; + static GetPointerPenInfoPtr getPointerPenInfo; + WinPointerInfo mPenPointerDownInfo; + WinPointerInfo mPenPointerUpInfo; + WinPointerInfo mPenPointerUpdateInfo; +}; + +#endif // #ifndef WinPointerEvents_h__ diff --git a/widget/windows/WinTaskbar.cpp b/widget/windows/WinTaskbar.cpp new file mode 100644 index 0000000000..d751cb2895 --- /dev/null +++ b/widget/windows/WinTaskbar.cpp @@ -0,0 +1,437 @@ +/* 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 <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 "nsAppRunner.h" +#include "nsXREDirProvider.h" +#include <io.h> +#include <propvarutil.h> +#include <propkey.h> +#include <shellapi.h> + +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; + + RefPtr<IPropertyStore> pPropStore; + if (FAILED(SHGetPropertyStoreForWindow(toplevelHWND, IID_IPropertyStore, + getter_AddRefs(pPropStore)))) { + return NS_ERROR_INVALID_ARG; + } + + PROPVARIANT pv; + if (FAILED(InitPropVariantFromString(aIdentifier.get(), &pv))) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + if (FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv)) || + FAILED(pPropStore->Commit())) { + rv = NS_ERROR_FAILURE; + } + + PropVariantClear(&pv); + + return rv; +} + +/////////////////////////////////////////////////////////////////////////////// +// default nsITaskbarPreviewController + +class DefaultController final : public nsITaskbarPreviewController { + ~DefaultController() {} + HWND mWnd; + + public: + explicit DefaultController(HWND hWnd) : mWnd(hWnd) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSITASKBARPREVIEWCONTROLLER +}; + +NS_IMETHODIMP +DefaultController::GetWidth(uint32_t* aWidth) { + RECT r; + ::GetClientRect(mWnd, &r); + *aWidth = r.right; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetHeight(uint32_t* aHeight) { + RECT r; + ::GetClientRect(mWnd, &r); + *aHeight = r.bottom; + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::GetThumbnailAspectRatio(float* aThumbnailAspectRatio) { + uint32_t width, height; + GetWidth(&width); + GetHeight(&height); + if (!height) height = 1; + + *aThumbnailAspectRatio = width / float(height); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::RequestThumbnail(nsITaskbarPreviewCallback* aCallback, + uint32_t width, uint32_t height) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::RequestPreview(nsITaskbarPreviewCallback* aCallback) { + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClose(void) { + MOZ_ASSERT_UNREACHABLE( + "OnClose should not be called for " + "TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnActivate(bool* rAcceptActivation) { + *rAcceptActivation = true; + MOZ_ASSERT_UNREACHABLE( + "OnActivate should not be called for " + "TaskbarWindowPreviews"); + return NS_OK; +} + +NS_IMETHODIMP +DefaultController::OnClick(nsITaskbarPreviewButton* button) { return NS_OK; } + +NS_IMPL_ISUPPORTS(DefaultController, nsITaskbarPreviewController) +} // namespace + +namespace mozilla { +namespace widget { + +/////////////////////////////////////////////////////////////////////////////// +// nsIWinTaskbar + +NS_IMPL_ISUPPORTS(WinTaskbar, nsIWinTaskbar) + +bool WinTaskbar::Initialize() { + if (mTaskbar) return true; + + ::CoInitialize(nullptr); + HRESULT hr = + ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, + IID_ITaskbarList4, (void**)&mTaskbar); + if (FAILED(hr)) return false; + + hr = mTaskbar->HrInit(); + if (FAILED(hr)) { + // This may fail with shell extensions like blackbox installed. + NS_WARNING("Unable to initialize taskbar"); + NS_RELEASE(mTaskbar); + return false; + } + return true; +} + +WinTaskbar::WinTaskbar() : mTaskbar(nullptr) {} + +WinTaskbar::~WinTaskbar() { + if (mTaskbar) { // match successful Initialize() call + NS_RELEASE(mTaskbar); + ::CoUninitialize(); + } +} + +// static +bool WinTaskbar::GetAppUserModelID(nsAString& aDefaultGroupId) { + // If an ID has already been set then use that. + PWSTR id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&id))) { + aDefaultGroupId.Assign(id); + CoTaskMemFree(id); + } + + // 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->GetPersistentDescriptor(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. + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + nsCString appName; + if (appInfo && NS_SUCCEEDED(appInfo->GetName(appName))) { + nsAutoString regKey; + regKey.AssignLiteral("Software\\Mozilla\\"); + AppendASCIItoUTF16(appName, regKey); + regKey.AppendLiteral("\\TaskBarIDs"); + + WCHAR path[MAX_PATH]; + if (GetModuleFileNameW(nullptr, path, MAX_PATH)) { + wchar_t* slash = wcsrchr(path, '\\'); + if (!slash) return false; + *slash = '\0'; // no trailing slash + + // 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); + } + } + } + + // If we haven't found an ID yet then use the install hash. In xpcshell tests + // the directory provider may not have been initialized so bypass in this + // case. + if (aDefaultGroupId.IsEmpty() && gDirServiceProvider) { + gDirServiceProvider->GetInstallHash(aDefaultGroupId); + } + + return !aDefaultGroupId.IsEmpty(); +} + +NS_IMETHODIMP +WinTaskbar::GetDefaultGroupId(nsAString& aDefaultGroupId) { + if (!GetAppUserModelID(aDefaultGroupId)) return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +// (static) Called from AppShell +bool WinTaskbar::RegisterAppUserModelID() { + nsAutoString uid; + if (!GetAppUserModelID(uid)) return false; + + return SUCCEEDED(SetCurrentProcessExplicitAppUserModelID(uid.get())); +} + +NS_IMETHODIMP +WinTaskbar::GetAvailable(bool* aAvailable) { + // ITaskbarList4::HrInit() may fail with shell extensions like blackbox + // installed. Initialize early to return available=false in those cases. + *aAvailable = Initialize(); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::CreateTaskbarTabPreview(nsIDocShell* shell, + nsITaskbarPreviewController* controller, + nsITaskbarTabPreview** _retval) { + if (!Initialize()) return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + NS_ENSURE_ARG_POINTER(controller); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + + RefPtr<TaskbarTabPreview> preview( + new TaskbarTabPreview(mTaskbar, controller, toplevelHWND, shell)); + if (!preview) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = preview->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarWindowPreview(nsIDocShell* shell, + nsITaskbarWindowPreview** _retval) { + if (!Initialize()) return NS_ERROR_NOT_AVAILABLE; + + NS_ENSURE_ARG_POINTER(shell); + + HWND toplevelHWND = ::GetAncestor(GetHWNDFromDocShell(shell), GA_ROOT); + + if (!toplevelHWND) return NS_ERROR_INVALID_ARG; + + nsWindow* window = WinUtils::GetNSWindowPtr(toplevelHWND); + + if (!window) return NS_ERROR_FAILURE; + + nsCOMPtr<nsITaskbarWindowPreview> preview = window->GetTaskbarPreview(); + if (!preview) { + RefPtr<DefaultController> defaultController = + new DefaultController(toplevelHWND); + + TaskbarWindowPreview* previewRaw = new TaskbarWindowPreview( + mTaskbar, defaultController, toplevelHWND, shell); + if (!previewRaw) { + return NS_ERROR_OUT_OF_MEMORY; + } + + preview = previewRaw; + + nsresult rv = previewRaw->Init(); + if (NS_FAILED(rv)) { + return rv; + } + window->SetTaskbarPreview(preview); + } + + preview.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP +WinTaskbar::GetTaskbarProgress(nsIDocShell* shell, + nsITaskbarProgress** _retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::GetOverlayIconController( + nsIDocShell* shell, nsITaskbarOverlayIconController** _retval) { + nsCOMPtr<nsITaskbarWindowPreview> preview; + nsresult rv = GetTaskbarWindowPreview(shell, getter_AddRefs(preview)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(preview, _retval); +} + +NS_IMETHODIMP +WinTaskbar::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..44102f8b39 --- /dev/null +++ b/widget/windows/WinTaskbar.h @@ -0,0 +1,45 @@ +/* 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..aeed8be01b --- /dev/null +++ b/widget/windows/WinTextEventDispatcherListener.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "KeyboardLayout.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/widget/IMEData.h" +#include "nsWindow.h" +#include "WinIMEHandler.h" +#include "WinTextEventDispatcherListener.h" + +namespace mozilla { +namespace widget { + +StaticRefPtr<WinTextEventDispatcherListener> + WinTextEventDispatcherListener::sInstance; + +// static +WinTextEventDispatcherListener* WinTextEventDispatcherListener::GetInstance() { + if (!sInstance) { + sInstance = new WinTextEventDispatcherListener(); + } + return sInstance.get(); +} + +void WinTextEventDispatcherListener::Shutdown() { sInstance = nullptr; } + +NS_IMPL_ISUPPORTS(WinTextEventDispatcherListener, TextEventDispatcherListener, + nsISupportsWeakReference) + +WinTextEventDispatcherListener::WinTextEventDispatcherListener() {} + +WinTextEventDispatcherListener::~WinTextEventDispatcherListener() {} + +NS_IMETHODIMP +WinTextEventDispatcherListener::NotifyIME( + TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) { + nsWindow* window = static_cast<nsWindow*>(aTextEventDispatcher->GetWidget()); + if (NS_WARN_IF(!window)) { + return NS_ERROR_FAILURE; + } + return IMEHandler::NotifyIME(window, aNotification); +} + +NS_IMETHODIMP_(IMENotificationRequests) +WinTextEventDispatcherListener::GetIMENotificationRequests() { + return IMEHandler::GetIMENotificationRequests(); +} + +NS_IMETHODIMP_(void) +WinTextEventDispatcherListener::OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) { + // XXX When input transaction is being stolen by add-on, what should we do? +} + +NS_IMETHODIMP_(void) +WinTextEventDispatcherListener::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, + void* aData) { + static_cast<NativeKey*>(aData)->WillDispatchKeyboardEvent(aKeyboardEvent, + aIndexOfKeypress); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinTextEventDispatcherListener.h b/widget/windows/WinTextEventDispatcherListener.h new file mode 100644 index 0000000000..d799d0b280 --- /dev/null +++ b/widget/windows/WinTextEventDispatcherListener.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinTextEventDispatcherListener_h_ +#define WinTextEventDispatcherListener_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextEventDispatcherListener.h" + +namespace mozilla { +namespace widget { + +/** + * On Windows, it's enough TextEventDispatcherListener to be a singleton + * because we have only one input context per process (IMM can create + * multiple IM context but we don't support such behavior). + */ + +class WinTextEventDispatcherListener final + : public TextEventDispatcherListener { + public: + static WinTextEventDispatcherListener* GetInstance(); + static void Shutdown(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) override; + NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override; + NS_IMETHOD_(void) + OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override; + NS_IMETHOD_(void) + WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, void* aData) override; + + private: + WinTextEventDispatcherListener(); + virtual ~WinTextEventDispatcherListener(); + + static StaticRefPtr<WinTextEventDispatcherListener> sInstance; +}; + +} // namespace widget +} // namespace mozilla + +#endif // #ifndef WinTextEventDispatcherListener_h_ diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp new file mode 100644 index 0000000000..14706d86a4 --- /dev/null +++ b/widget/windows/WinUtils.cpp @@ -0,0 +1,2272 @@ +/* -*- 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 "GeckoProfiler.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" +#include "InputDeviceUtils.h" +#include "KeyboardLayout.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/Unused.h" +#include "nsIContentPolicy.h" +#include "nsIWindowsUIUtils.h" +#include "nsContentUtils.h" + +#include "mozilla/Logging.h" + +#include "nsString.h" +#include "nsDirectoryServiceUtils.h" +#include "imgIContainer.h" +#include "imgITools.h" +#include "nsNetUtil.h" +#include "nsIOutputStream.h" +#include "nsNetCID.h" +#include "prtime.h" +#ifdef MOZ_PLACES +# include "nsIFaviconService.h" +#endif +#include "nsIDownloader.h" +#include "nsIChannel.h" +#include "nsIThread.h" +#include "MainThreadUtils.h" +#include "nsLookAndFeel.h" +#include "nsUnicharUtils.h" +#include "nsWindowsHelpers.h" +#include "WinContentSystemParameters.h" + +#include <textstor.h> +#include "TSFTextStore.h" + +#include <shlobj.h> +#include <shlwapi.h> + +mozilla::LazyLogModule gWindowsLog("Widget"); + +#define LOG_E(...) MOZ_LOG(gWindowsLog, LogLevel::Error, (__VA_ARGS__)) +#define LOG_D(...) MOZ_LOG(gWindowsLog, LogLevel::Debug, (__VA_ARGS__)) + +using namespace mozilla::gfx; + +namespace mozilla { +namespace widget { + +#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(AsyncDeleteAllFaviconsFromDisk, nsIRunnable) + +const char FaviconHelper::kJumpListCacheDir[] = "jumpListCache"; +const char FaviconHelper::kShortcutCacheDir[] = "shortcutCache"; + +// 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; +GetSystemMetricsForDpiProc WinUtils::sGetSystemMetricsForDpi = NULL; + +/* static */ +void WinUtils::Initialize() { + if (IsWin10OrLater()) { + HMODULE user32Dll = ::GetModuleHandleW(L"user32"); + if (user32Dll) { + auto getThreadDpiAwarenessContext = + (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress( + user32Dll, "GetThreadDpiAwarenessContext"); + auto areDpiAwarenessContextsEqual = + (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress( + user32Dll, "AreDpiAwarenessContextsEqual"); + if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual && + areDpiAwarenessContextsEqual( + getThreadDpiAwarenessContext(), + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { + // Only per-monitor v1 requires these workarounds. + sEnableNonClientDpiScaling = + (EnableNonClientDpiScalingProc)::GetProcAddress( + user32Dll, "EnableNonClientDpiScaling"); + sSetThreadDpiAwarenessContext = + (SetThreadDpiAwarenessContextProc)::GetProcAddress( + user32Dll, "SetThreadDpiAwarenessContext"); + } + + sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress( + user32Dll, "GetSystemMetricsForDpi"); + } + } +} + +// static +LRESULT WINAPI WinUtils::NonClientDpiScalingDefWindowProcW(HWND hWnd, UINT msg, + WPARAM wParam, + LPARAM lParam) { + // NOTE: this function was copied out into the body of the pre-XUL skeleton + // UI window proc (PreXULSkeletonUI.cpp). If this function changes at any + // point, we should probably factor this out and use it from both locations. + if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) { + sEnableNonClientDpiScaling(hWnd); + } + return ::DefWindowProcW(hWnd, msg, wParam, lParam); +} + +// static +void WinUtils::LogW(const wchar_t* fmt, ...) { + va_list args = nullptr; + if (!lstrlenW(fmt)) { + return; + } + va_start(args, fmt); + int buflen = _vscwprintf(fmt, args); + wchar_t* buffer = new wchar_t[buflen + 1]; + if (!buffer) { + va_end(args); + return; + } + vswprintf(buffer, buflen, fmt, args); + va_end(args); + + // MSVC, including remote debug sessions + OutputDebugStringW(buffer); + OutputDebugStringW(L"\n"); + + int len = + WideCharToMultiByte(CP_ACP, 0, buffer, -1, nullptr, 0, nullptr, nullptr); + if (len) { + char* utf8 = new char[len]; + if (WideCharToMultiByte(CP_ACP, 0, buffer, -1, utf8, len, nullptr, + nullptr) > 0) { + // desktop console + printf("%s\n", utf8); + NS_ASSERTION(gWindowsLog, + "Called WinUtils Log() but Widget " + "log module doesn't exist!"); + MOZ_LOG(gWindowsLog, LogLevel::Error, (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 +float WinUtils::SystemDPI() { + if (XRE_IsContentProcess()) { + return WinContentSystemParameters::GetSingleton()->SystemDPI(); + } + // The result of GetDeviceCaps won't change dynamically, as it predates + // per-monitor DPI and support for on-the-fly resolution changes. + // Therefore, we only need to look it up once. + static float dpi = 0; + if (dpi <= 0) { + HDC screenDC = GetDC(nullptr); + dpi = GetDeviceCaps(screenDC, LOGPIXELSY); + ReleaseDC(nullptr, screenDC); + } + + // Bug 1012487 - dpi can be 0 when the Screen DC is used off the + // main thread on windows. For now just assume a 100% DPI for this + // drawing call. + // XXX - fixme! + return dpi > 0 ? dpi : 96; +} + +// static +double WinUtils::SystemScaleFactor() { return SystemDPI() / 96.0; } + +#if WINVER < 0x603 +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() { + // 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() { + if (XRE_IsContentProcess()) { + return WinContentSystemParameters::GetSingleton()->IsPerMonitorDPIAware(); + } + static bool perMonitorDPIAware = SlowIsPerMonitorDPIAware(); + return perMonitorDPIAware; +} + +/* static */ +float WinUtils::MonitorDPI(HMONITOR aMonitor) { + if (IsPerMonitorDPIAware()) { + UINT dpiX, dpiY = 96; + sGetDpiForMonitor(aMonitor ? aMonitor : GetPrimaryMonitor(), + MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + return dpiY; + } + + // We're not per-monitor aware, use system DPI instead. + return SystemDPI(); +} + +/* static */ +double WinUtils::LogToPhysFactor(HMONITOR aMonitor) { + return MonitorDPI(aMonitor) / 96.0; +} + +/* static */ +int32_t WinUtils::LogToPhys(HMONITOR aMonitor, double aValue) { + return int32_t(NS_round(aValue * LogToPhysFactor(aMonitor))); +} + +/* static */ +HMONITOR +WinUtils::GetPrimaryMonitor() { + const POINT pt = {0, 0}; + return ::MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); +} + +/* static */ +HMONITOR +WinUtils::MonitorFromRect(const gfx::Rect& rect) { + // convert coordinates from desktop to device pixels for MonitorFromRect + double dpiScale = + IsPerMonitorDPIAware() ? 1.0 : LogToPhysFactor(GetPrimaryMonitor()); + + RECT globalWindowBounds = {NSToIntRound(dpiScale * rect.X()), + NSToIntRound(dpiScale * rect.Y()), + NSToIntRound(dpiScale * (rect.XMost())), + NSToIntRound(dpiScale * (rect.YMost()))}; + + return ::MonitorFromRect(&globalWindowBounds, MONITOR_DEFAULTTONEAREST); +} + +/* static */ +bool WinUtils::HasSystemMetricsForDpi() { + return (sGetSystemMetricsForDpi != NULL); +} + +/* static */ +int WinUtils::GetSystemMetricsForDpi(int nIndex, UINT dpi) { + if (HasSystemMetricsForDpi()) { + return sGetSystemMetricsForDpi(nIndex, dpi); + } else { + double scale = IsPerMonitorDPIAware() ? dpi / SystemDPI() : 1.0; + return NSToIntRound(::GetSystemMetrics(nIndex) * scale); + } +} + +/* static */ +gfx::MarginDouble WinUtils::GetUnwriteableMarginsForDeviceInInches(HDC aHdc) { + if (!aHdc) { + return gfx::MarginDouble(); + } + + int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY); + int marginTop = ::GetDeviceCaps(aHdc, PHYSICALOFFSETY); + int printableAreaHeight = ::GetDeviceCaps(aHdc, VERTRES); + int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT); + + double marginTopInch = double(marginTop) / pixelsPerInchY; + + double printableAreaHeightInch = double(printableAreaHeight) / pixelsPerInchY; + double physicalHeightInch = double(physicalHeight) / pixelsPerInchY; + double marginBottomInch = + physicalHeightInch - printableAreaHeightInch - marginTopInch; + + int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX); + int marginLeft = ::GetDeviceCaps(aHdc, PHYSICALOFFSETX); + int printableAreaWidth = ::GetDeviceCaps(aHdc, HORZRES); + int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH); + + double marginLeftInch = double(marginLeft) / pixelsPerInchX; + + double printableAreaWidthInch = double(printableAreaWidth) / pixelsPerInchX; + double physicalWidthInch = double(physicalWidth) / pixelsPerInchX; + double marginRightInch = + physicalWidthInch - printableAreaWidthInch - marginLeftInch; + + return gfx::MarginDouble(marginTopInch, marginRightInch, marginBottomInch, + marginLeftInch); +} + +#ifdef ACCESSIBILITY +/* static */ +a11y::Accessible* WinUtils::GetRootAccessibleForHWND(HWND aHwnd) { + nsWindow* window = GetNSWindowPtr(aHwnd); + if (!window) { + return nullptr; + } + + return window->GetAccessible(); +} +#endif // ACCESSIBILITY + +/* static */ +bool WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage, UINT aOption) { + RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump(); + if (msgPump) { + BOOL ret = FALSE; + HRESULT hr = msgPump->PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, + aOption, &ret); + NS_ENSURE_TRUE(SUCCEEDED(hr), false); + return ret; + } + return ::PeekMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, aOption); +} + +/* static */ +bool WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage, + UINT aLastMessage) { + RefPtr<ITfMessagePump> msgPump = TSFTextStore::GetMessagePump(); + if (msgPump) { + BOOL ret = FALSE; + HRESULT hr = + msgPump->GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage, &ret); + NS_ENSURE_TRUE(SUCCEEDED(hr), false); + return ret; + } + return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage); +} + +#if defined(ACCESSIBILITY) +static DWORD GetWaitFlags() { + DWORD result = MWMO_INPUTAVAILABLE; + if (XRE_IsContentProcess()) { + result |= MWMO_ALERTABLE; + } + return result; +} +#endif + +/* static */ +void WinUtils::WaitForMessage(DWORD aTimeoutMs) { +#if defined(ACCESSIBILITY) + static const DWORD waitFlags = GetWaitFlags(); +#else + const DWORD waitFlags = MWMO_INPUTAVAILABLE; +#endif + + const DWORD waitStart = ::GetTickCount(); + DWORD elapsed = 0; + while (true) { + if (aTimeoutMs != INFINITE) { + elapsed = ::GetTickCount() - waitStart; + } + if (elapsed >= aTimeoutMs) { + break; + } + DWORD result; + { + AUTO_PROFILER_THREAD_SLEEP; + result = ::MsgWaitForMultipleObjectsEx(0, NULL, aTimeoutMs - elapsed, + MOZ_QS_ALLEVENT, waitFlags); + } + NS_WARNING_ASSERTION(result != WAIT_FAILED, "Wait failed"); + if (result == WAIT_TIMEOUT) { + break; + } +#if defined(ACCESSIBILITY) + if (result == WAIT_IO_COMPLETION) { + if (NS_IsMainThread()) { + // We executed an APC that would have woken up the hang monitor. Since + // there are no more APCs pending and we are now going to sleep again, + // we should notify the hang monitor. + mozilla::BackgroundHangMonitor().NotifyWait(); + } + continue; + } +#endif // defined(ACCESSIBILITY) + + // Sent messages (via SendMessage and friends) are processed differently + // than queued messages (via PostMessage); the destination window procedure + // of the sent message is called during (Get|Peek)Message. Since PeekMessage + // does not tell us whether it processed any sent messages, we need to query + // this ahead of time. + bool haveSentMessagesPending = + (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; + + MSG msg = {0}; + if (haveSentMessagesPending || + ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE)) { + break; + } + // The message is intended for another thread that has been synchronized + // with our input queue; yield to give other threads an opportunity to + // process the message. This should prevent busy waiting if resumed due + // to another thread's message. + ::SwitchToThread(); + } +} + +/* static */ +bool WinUtils::GetRegistryKey(HKEY aRoot, char16ptr_t aKeyName, + char16ptr_t aValueName, wchar_t* aBuffer, + DWORD aBufferLength) { + MOZ_ASSERT(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 = dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE; + LPARAM lParamExtraInfo = ::GetMessageExtraInfo(); + if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) { + inputSource = (lParamExtraInfo & TABLET_INK_TOUCH) + ? dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH + : dom::MouseEvent_Binding::MOZ_SOURCE_PEN; + } + return static_cast<uint16_t>(inputSource); +} + +/* static */ +uint16_t WinUtils::GetMousePointerID() { + LPARAM lParamExtraInfo = ::GetMessageExtraInfo(); + return lParamExtraInfo & TABLET_INK_ID_MASK; +} + +/* static */ +bool WinUtils::GetIsMouseFromTouch(EventMessage aEventMessage) { + const uint32_t MOZ_T_I_SIGNATURE = TABLET_INK_TOUCH | TABLET_INK_SIGNATURE; + const uint32_t MOZ_T_I_CHECK_TCH = TABLET_INK_TOUCH | TABLET_INK_CHECK; + return ((aEventMessage == eMouseMove || aEventMessage == eMouseDown || + aEventMessage == eMouseUp || aEventMessage == eMouseAuxClick || + aEventMessage == eMouseDoubleClick) && + (GetMessageExtraInfo() & MOZ_T_I_SIGNATURE) == MOZ_T_I_CHECK_TCH); +} + +/* static */ +MSG WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) { + MSG msg; + msg.message = aMessage; + msg.wParam = wParam; + msg.lParam = lParam; + msg.hwnd = aWnd; + return msg; +} + +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 + * @param aRunnable : Executed in the aIOThread when the favicon cache is + * avaiable + ************************************************************************/ + +AsyncFaviconDataReady::AsyncFaviconDataReady( + nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread, const bool aURLShortcut, + already_AddRefed<nsIRunnable> aRunnable) + : mNewURI(aNewURI), + mIOThread(aIOThread), + mRunnable(aRunnable), + 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_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_INTERNAL_IMAGE); + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDownloadObserver> downloadObserver = new myDownloadObserver; + nsCOMPtr<nsIStreamListener> listener; + rv = NS_NewDownloader(getter_AddRefs(listener), downloadObserver, icoFile); + NS_ENSURE_SUCCESS(rv, rv); + + return channel->AsyncOpen(listener); +} + +NS_IMETHODIMP +AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen, + const uint8_t* aData, + const nsACString& aMimeType, + uint16_t aWidth) { + if (!aDataLen || !aData) { + if (mURLShortcut) { + OnFaviconDataNotAvailable(); + } + + return NS_OK; + } + + nsCOMPtr<nsIFile> icoFile; + nsresult rv = + FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString path; + rv = icoFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Decode the image from the format it was returned to us in (probably PNG) + nsCOMPtr<imgIContainer> container; + nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); + rv = imgtool->DecodeImageFromBuffer(reinterpret_cast<const char*>(aData), + aDataLen, aMimeType, + getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<SourceSurface> surface = container->GetFrame( + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + RefPtr<DataSourceSurface> dataSurface; + IntSize size; + + if (mURLShortcut && + (surface->GetSize().width < 48 || surface->GetSize().height < 48)) { + // Create a 48x48 surface and paint the icon into the central rect. + size.width = std::max(surface->GetSize().width, 48); + size.height = std::max(surface->GetSize().height, 48); + dataSurface = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + return NS_ERROR_FAILURE; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, + dataSurface->GetFormat()); + if (!dt) { + gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in " + "CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + dt->FillRect(Rect(0, 0, size.width, size.height), + ColorPattern(ToDeviceColor(sRGBColor::OpaqueWhite()))); + IntPoint point; + point.x = (size.width - surface->GetSize().width) / 2; + point.y = (size.height - surface->GetSize().height) / 2; + dt->DrawSurface(surface, + Rect(point.x, point.y, surface->GetSize().width, + surface->GetSize().height), + Rect(Point(0, 0), Size(surface->GetSize().width, + surface->GetSize().height))); + + dataSurface->Unmap(); + } else { + // By using the input image surface's size, we may end up encoding + // to a different size than a 16x16 (or bigger for higher DPI) ICO, but + // Windows will resize appropriately for us. If we want to encode ourselves + // one day because we like our resizing better, we'd have to manually + // resize the image here and use GetSystemMetrics w/ SM_CXSMICON and + // SM_CYSMICON. We don't support resizing images asynchronously at the + // moment anyway so getting the DPI aware icon size won't help. + size.width = surface->GetSize().width; + size.height = surface->GetSize().height; + dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + } + + // Allocate a new buffer that we own and can use out of line in + // another thread. + UniquePtr<uint8_t[]> data = SurfaceToPackedBGRA(dataSurface); + if (!data) { + return NS_ERROR_OUT_OF_MEMORY; + } + int32_t stride = 4 * size.width; + + // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer + nsCOMPtr<nsIRunnable> event = + new AsyncEncodeAndWriteIcon(path, std::move(data), stride, size.width, + size.height, mRunnable.forget()); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; +} +#endif + +// Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer passed +// in +AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon( + const nsAString& aIconPath, UniquePtr<uint8_t[]> aBuffer, uint32_t aStride, + uint32_t aWidth, uint32_t aHeight, already_AddRefed<nsIRunnable> aRunnable) + : mIconPath(aIconPath), + mBuffer(std::move(aBuffer)), + mRunnable(aRunnable), + mStride(aStride), + mWidth(aWidth), + mHeight(aHeight) {} + +NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() { + MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread."); + + // Note that since we're off the main thread we can't use + // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget() + RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface( + mBuffer.get(), mStride, IntSize(mWidth, mHeight), + SurfaceFormat::B8G8R8A8); + + FILE* file = _wfopen(mIconPath.get(), L"wb"); + if (!file) { + // Maybe the directory doesn't exist; try creating it, then fopen again. + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> comFile = do_CreateInstance("@mozilla.org/file/local;1"); + if (comFile) { + rv = comFile->InitWithPath(mIconPath); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> dirPath; + comFile->GetParent(getter_AddRefs(dirPath)); + if (dirPath) { + rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + file = _wfopen(mIconPath.get(), L"wb"); + if (!file) { + rv = NS_ERROR_FAILURE; + } + } + } + } + } + if (!file) { + return rv; + } + } + nsresult rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns, + gfxUtils::eBinaryEncode, file); + fclose(file); + NS_ENSURE_SUCCESS(rv, rv); + + if (mRunnable) { + mRunnable->Run(); + } + return rv; +} + +AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() {} + +AsyncDeleteAllFaviconsFromDisk::AsyncDeleteAllFaviconsFromDisk( + bool aIgnoreRecent) + : mIgnoreRecent(aIgnoreRecent) { + // We can't call FaviconHelper::GetICOCacheSecondsTimeout() on non-main + // threads, as it reads a pref, so cache its value here. + mIcoNoDeleteSeconds = FaviconHelper::GetICOCacheSecondsTimeout() + 600; + + // Prepare the profile directory cache on the main thread, to ensure we wont + // do this on non-main threads. + Unused << NS_GetSpecialDirectory("ProfLDS", + getter_AddRefs(mJumpListCacheDir)); +} + +NS_IMETHODIMP AsyncDeleteAllFaviconsFromDisk::Run() { + if (!mJumpListCacheDir) { + return NS_ERROR_FAILURE; + } + // Construct the path of our jump list cache + nsresult rv = mJumpListCacheDir->AppendNative( + nsDependentCString(FaviconHelper::kJumpListCacheDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDirectoryEnumerator> entries; + rv = mJumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through each directory entry and remove all ICO files found + do { + nsCOMPtr<nsIFile> currFile; + if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile) + break; + + nsAutoString path; + if (NS_FAILED(currFile->GetPath(path))) continue; + + if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { + // Check if the cached ICO file exists + bool exists; + if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue; + + if (mIgnoreRecent) { + // Check to make sure the icon wasn't just recently created. + // If it was created recently, don't delete it yet. + int64_t fileModTime = 0; + rv = currFile->GetLastModifiedTime(&fileModTime); + fileModTime /= PR_MSEC_PER_SEC; + // If the icon is older than the regeneration time (+ 10 min to be + // safe), then it's old and we can get rid of it. + // This code is only hit directly after a regeneration. + int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC); + if (NS_FAILED(rv) || (nowTime - fileModTime) < mIcoNoDeleteSeconds) { + continue; + } + } + + // We found an ICO file that exists, so we should remove it + currFile->Remove(false); + } + } while (true); + + return NS_OK; +} + +AsyncDeleteAllFaviconsFromDisk::~AsyncDeleteAllFaviconsFromDisk() {} + +/* + * (static) If the data is available, will return the path on disk where + * the favicon for page aFaviconPageURI is stored. If the favicon does not + * exist, or its cache is expired, this method will kick off an async request + * for the icon so that next time the method is called it will be available. + * @param aFaviconPageURI The URI of the page to obtain + * @param aICOFilePath The path of the icon file + * @param aIOThread The thread to perform the Fetch on + * @param aURLShortcut to distinguish between jumplistcache(false) and + * shortcutcache(true) + * @param aRunnable Executed in the aIOThread when the favicon cache is + * avaiable + */ +nsresult FaviconHelper::ObtainCachedIconFile( + nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath, + nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut, + already_AddRefed<nsIRunnable> aRunnable) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; + // Obtain the ICO file path + nsCOMPtr<nsIFile> icoFile; + nsresult rv = GetOutputIconPath(aFaviconPageURI, icoFile, aURLShortcut); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if the cached ICO file already exists + bool exists; + rv = icoFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + // Obtain the file's last modification date in seconds + int64_t fileModTime = 0; + rv = icoFile->GetLastModifiedTime(&fileModTime); + fileModTime /= PR_MSEC_PER_SEC; + int32_t icoReCacheSecondsTimeout = GetICOCacheSecondsTimeout(); + int64_t nowTime = PR_Now() / int64_t(PR_USEC_PER_SEC); + + // If the last mod call failed or the icon is old then re-cache it + // This check is in case the favicon of a page changes + // the next time we try to build the jump list, the data will be available. + if (NS_FAILED(rv) || (nowTime - fileModTime) > icoReCacheSecondsTimeout) { + CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread, + aURLShortcut, runnable.forget()); + return NS_ERROR_NOT_AVAILABLE; + } + } else { + // The file does not exist yet, obtain it async from the favicon service so + // that the next time we try to build the jump list it'll be available. + CacheIconFileFromFaviconURIAsync(aFaviconPageURI, icoFile, aIOThread, + aURLShortcut, runnable.forget()); + return NS_ERROR_NOT_AVAILABLE; + } + + // The icoFile is filled with a path that exists, get its path + rv = icoFile->GetPath(aICOFilePath); + return rv; +} + +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, + already_AddRefed<nsIRunnable> aRunnable) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; +#ifdef MOZ_PLACES + // Obtain the favicon service and get the favicon for the specified page + nsCOMPtr<nsIFaviconService> favIconSvc( + do_GetService("@mozilla.org/browser/favicon-service;1")); + NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFaviconDataCallback> callback = + new mozilla::widget::AsyncFaviconDataReady( + aFaviconPageURI, aIOThread, aURLShortcut, runnable.forget()); + + favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0); +#endif + return NS_OK; +} + +// Obtains the jump list 'ICO cache timeout in seconds' pref +int32_t FaviconHelper::GetICOCacheSecondsTimeout() { + // Only obtain the setting at most once from the pref service. + // In the rare case that 2 threads call this at the same + // time it is no harm and we will simply obtain the pref twice. + // None of the taskbar list prefs are currently updated via a + // pref observer so I think this should suffice. + const int32_t kSecondsPerDay = 86400; + static bool alreadyObtained = false; + static int32_t icoReCacheSecondsTimeout = kSecondsPerDay; + if (alreadyObtained) { + return icoReCacheSecondsTimeout; + } + + // Obtain the pref + const char PREF_ICOTIMEOUT[] = "browser.taskbar.lists.icoTimeoutInSeconds"; + icoReCacheSecondsTimeout = + Preferences::GetInt(PREF_ICOTIMEOUT, kSecondsPerDay); + alreadyObtained = true; + return icoReCacheSecondsTimeout; +} + +/* static */ +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 */ +LayoutDeviceIntRegion WinUtils::ConvertHRGNToRegion(HRGN aRgn) { + NS_ASSERTION(aRgn, "Don't pass NULL region here"); + + LayoutDeviceIntRegion rgn; + + DWORD size = ::GetRegionData(aRgn, 0, nullptr); + AutoTArray<uint8_t, 100> buffer; + buffer.SetLength(size); + + RGNDATA* data = reinterpret_cast<RGNDATA*>(buffer.Elements()); + if (!::GetRegionData(aRgn, size, data)) return rgn; + + if (data->rdh.nCount > MAX_RECTS_IN_REGION) { + rgn = ToIntRect(data->rdh.rcBound); + return rgn; + } + + RECT* rects = reinterpret_cast<RECT*>(data->Buffer); + for (uint32_t i = 0; i < data->rdh.nCount; ++i) { + RECT* r = rects + i; + rgn.Or(rgn, ToIntRect(*r)); + } + + return rgn; +} + +LayoutDeviceIntRect WinUtils::ToIntRect(const RECT& aRect) { + return LayoutDeviceIntRect(aRect.left, aRect.top, aRect.right - aRect.left, + aRect.bottom - aRect.top); +} + +/* static */ +bool WinUtils::IsIMEEnabled(const InputContext& aInputContext) { + return IsIMEEnabled(aInputContext.mIMEState.mEnabled); +} + +/* static */ +bool WinUtils::IsIMEEnabled(IMEEnabled aIMEState) { + return aIMEState == IMEEnabled::Enabled; +} + +/* static */ +void WinUtils::SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, + uint32_t aModifiers, UINT aMessage) { + MOZ_ASSERT(!(aModifiers & nsIWidget::ALTGRAPH) || + !(aModifiers & (nsIWidget::CTRL_L | nsIWidget::ALT_R))); + if (aMessage == WM_KEYUP) { + // If AltGr is released, ControlLeft key is released first, then, + // AltRight key is released. + if (aModifiers & nsIWidget::ALTGRAPH) { + aArray->AppendElement( + KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft)); + aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight)); + } + for (uint32_t i = ArrayLength(sModifierKeyMap); i; --i) { + const uint32_t* map = sModifierKeyMap[i - 1]; + if (aModifiers & map[0]) { + aArray->AppendElement(KeyPair(map[1], map[2], map[3])); + } + } + } else { + for (uint32_t i = 0; i < ArrayLength(sModifierKeyMap); ++i) { + const uint32_t* map = sModifierKeyMap[i]; + if (aModifiers & map[0]) { + aArray->AppendElement(KeyPair(map[1], map[2], map[3])); + } + } + // If AltGr is pressed, ControlLeft key is pressed first, then, + // AltRight key is pressed. + if (aModifiers & nsIWidget::ALTGRAPH) { + aArray->AppendElement( + KeyPair(VK_CONTROL, VK_LCONTROL, ScanCode::eControlLeft)); + aArray->AppendElement(KeyPair(VK_MENU, VK_RMENU, ScanCode::eAltRight)); + } + } +} + +/* static */ +nsresult WinUtils::WriteBitmap(nsIFile* aFile, imgIContainer* aImage) { + RefPtr<SourceSurface> surface = aImage->GetFrame( + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + + return WriteBitmap(aFile, surface); +} + +/* static */ +nsresult WinUtils::WriteBitmap(nsIFile* aFile, SourceSurface* surface) { + nsresult rv; + + // For either of the following formats we want to set the biBitCount member + // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap + // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored + // for the BI_RGB value we use for the biCompression member. + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == SurfaceFormat::B8G8R8X8); + + RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + int32_t width = dataSurface->GetSize().width; + int32_t height = dataSurface->GetSize().height; + int32_t bytesPerPixel = 4 * sizeof(uint8_t); + uint32_t bytesPerRow = bytesPerPixel * width; + bool hasAlpha = surface->GetFormat() == SurfaceFormat::B8G8R8A8; + + // initialize these bitmap structs which we will later + // serialize directly to the head of the bitmap file + BITMAPV4HEADER bmi; + memset(&bmi, 0, sizeof(BITMAPV4HEADER)); + bmi.bV4Size = sizeof(BITMAPV4HEADER); + bmi.bV4Width = width; + bmi.bV4Height = height; + bmi.bV4Planes = 1; + bmi.bV4BitCount = (WORD)bytesPerPixel * 8; + bmi.bV4V4Compression = hasAlpha ? BI_BITFIELDS : BI_RGB; + bmi.bV4SizeImage = bytesPerRow * height; + bmi.bV4CSType = LCS_sRGB; + if (hasAlpha) { + bmi.bV4RedMask = 0x00FF0000; + bmi.bV4GreenMask = 0x0000FF00; + bmi.bV4BlueMask = 0x000000FF; + bmi.bV4AlphaMask = 0xFF000000; + } + + BITMAPFILEHEADER bf; + DWORD colormask[3]; + bf.bfType = 0x4D42; // 'BM' + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPV4HEADER) + + (hasAlpha ? sizeof(colormask) : 0); + bf.bfSize = bf.bfOffBits + bmi.bV4SizeImage; + + // get a file output stream + nsCOMPtr<nsIOutputStream> stream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return NS_ERROR_FAILURE; + } + + // write the bitmap headers and rgb pixel data to the file + rv = NS_ERROR_FAILURE; + if (stream) { + uint32_t written; + stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written); + if (written == sizeof(BITMAPFILEHEADER)) { + stream->Write((const char*)&bmi, sizeof(BITMAPV4HEADER), &written); + if (written == sizeof(BITMAPV4HEADER)) { + if (hasAlpha) { + // color mask + colormask[0] = 0x00FF0000; + colormask[1] = 0x0000FF00; + colormask[2] = 0x000000FF; + + stream->Write((const char*)colormask, sizeof(colormask), &written); + } + if (!hasAlpha || written == sizeof(colormask)) { + // write out the image data backwards because the desktop won't + // show bitmaps with negative heights for top-to-bottom + uint32_t i = map.mStride * height; + do { + i -= map.mStride; + stream->Write(((const char*)map.mData) + i, bytesPerRow, &written); + if (written == bytesPerRow) { + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + break; + } + } while (i != 0); + } + } + } + + stream->Close(); + } + + dataSurface->Unmap(); + + return rv; +} + +// This is in use here and in dom/events/TouchEvent.cpp +/* static */ +uint32_t WinUtils::IsTouchDeviceSupportPresent() { + int32_t touchCapabilities = ::GetSystemMetrics(SM_DIGITIZER); + return (touchCapabilities & NID_READY) && + (touchCapabilities & (NID_EXTERNAL_TOUCH | NID_INTEGRATED_TOUCH)); +} + +/* static */ +uint32_t WinUtils::GetMaxTouchPoints() { + if (IsTouchDeviceSupportPresent()) { + return GetSystemMetrics(SM_MAXIMUMTOUCHES); + } + return 0; +} + +/* static */ +POWER_PLATFORM_ROLE +WinUtils::GetPowerPlatformRole() { + typedef POWER_PLATFORM_ROLE(WINAPI * + PowerDeterminePlatformRoleEx)(ULONG Version); + static PowerDeterminePlatformRoleEx power_determine_platform_role = + reinterpret_cast<PowerDeterminePlatformRoleEx>(::GetProcAddress( + ::LoadLibraryW(L"PowrProf.dll"), "PowerDeterminePlatformRoleEx")); + + POWER_PLATFORM_ROLE powerPlatformRole = PlatformRoleUnspecified; + if (!power_determine_platform_role) { + return powerPlatformRole; + } + + return power_determine_platform_role(POWER_PLATFORM_ROLE_V2); +} + +static bool IsWindows10TabletMode() { + nsCOMPtr<nsIWindowsUIUtils> uiUtils( + do_GetService("@mozilla.org/windows-ui-utils;1")); + if (NS_WARN_IF(!uiUtils)) { + return false; + } + bool isInTabletMode = false; + uiUtils->GetInTabletMode(&isInTabletMode); + return isInTabletMode; +} + +static bool CallGetAutoRotationState(AR_STATE* aRotationState) { + typedef BOOL(WINAPI * GetAutoRotationStateFunc)(PAR_STATE pState); + static GetAutoRotationStateFunc get_auto_rotation_state_func = + reinterpret_cast<GetAutoRotationStateFunc>(::GetProcAddress( + GetModuleHandleW(L"user32.dll"), "GetAutoRotationState")); + if (get_auto_rotation_state_func) { + ZeroMemory(aRotationState, sizeof(AR_STATE)); + return get_auto_rotation_state_func(aRotationState); + } + return false; +} + +static bool IsTabletDevice() { + // Guarantees that: + // - The device has a touch screen. + // - It is used as a tablet which means that it has no keyboard connected. + // On Windows 10 it means that it is verifying with ConvertibleSlateMode. + + if (!IsWin8OrLater()) { + return false; + } + + if (IsWindows10TabletMode()) { + return true; + } + + if (!GetSystemMetrics(SM_MAXIMUMTOUCHES)) { + return false; + } + + // If the device is docked, the user is treating the device as a PC. + if (GetSystemMetrics(SM_SYSTEMDOCKED)) { + return false; + } + + // If the device is not supporting rotation, it's unlikely to be a tablet, + // a convertible or a detachable. See: + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx + AR_STATE rotation_state; + if (CallGetAutoRotationState(&rotation_state) && + (rotation_state & (AR_NOT_SUPPORTED | AR_LAPTOP | AR_NOSENSOR))) { + return false; + } + + // PlatformRoleSlate was added in Windows 8+. + POWER_PLATFORM_ROLE role = WinUtils::GetPowerPlatformRole(); + if (role == PlatformRoleMobile || role == PlatformRoleSlate) { + return !GetSystemMetrics(SM_CONVERTIBLESLATEMODE); + } + return false; +} + +static bool IsMousePresent() { + if (!::GetSystemMetrics(SM_MOUSEPRESENT)) { + return false; + } + + DWORD count = InputDeviceUtils::CountMouseDevices(); + if (!count) { + return false; + } + + // If there is a mouse device and if this machine is a tablet or has a + // digitizer, that's counted as the mouse device. + // FIXME: Bug 1495938: We should drop this heuristic way once we find out a + // reliable way to tell there is no mouse or not. + if (count == 1 && + (WinUtils::IsTouchDeviceSupportPresent() || IsTabletDevice())) { + return false; + } + + return true; +} + +/* static */ +PointerCapabilities WinUtils::GetPrimaryPointerCapabilities() { + if (IsTabletDevice()) { + return PointerCapabilities::Coarse; + } + + if (IsMousePresent()) { + return PointerCapabilities::Fine | PointerCapabilities::Hover; + } + + if (IsTouchDeviceSupportPresent()) { + return PointerCapabilities::Coarse; + } + + return PointerCapabilities::None; +} + +/* static */ +PointerCapabilities WinUtils::GetAllPointerCapabilities() { + PointerCapabilities result = PointerCapabilities::None; + + if (IsTabletDevice() || IsTouchDeviceSupportPresent()) { + result |= PointerCapabilities::Coarse; + } + + if (IsMousePresent()) { + result |= PointerCapabilities::Fine | PointerCapabilities::Hover; + } + + return result; +} + +/* static */ +bool WinUtils::ResolveJunctionPointsAndSymLinks(std::wstring& aPath) { + LOG_D("ResolveJunctionPointsAndSymLinks: Resolving path: %S", aPath.c_str()); + + wchar_t path[MAX_PATH] = {0}; + + nsAutoHandle handle(::CreateFileW( + aPath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr)); + + if (handle == INVALID_HANDLE_VALUE) { + LOG_E("Failed to open file handle to resolve path. GetLastError=%d", + GetLastError()); + return false; + } + + DWORD pathLen = GetFinalPathNameByHandleW( + handle, path, MAX_PATH, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (pathLen == 0 || pathLen >= MAX_PATH) { + LOG_E("GetFinalPathNameByHandleW failed. GetLastError=%d", GetLastError()); + return false; + } + aPath = path; + + // GetFinalPathNameByHandle sticks a '\\?\' in front of the path, + // but that confuses some APIs so strip it off. It will also put + // '\\?\UNC\' in front of network paths, we convert that to '\\'. + if (aPath.compare(0, 7, L"\\\\?\\UNC") == 0) { + aPath.erase(2, 6); + } else if (aPath.compare(0, 4, L"\\\\?\\") == 0) { + aPath.erase(0, 4); + } + + LOG_D("ResolveJunctionPointsAndSymLinks: Resolved path to: %S", + aPath.c_str()); + return true; +} + +/* static */ +bool WinUtils::ResolveJunctionPointsAndSymLinks(nsIFile* aPath) { + MOZ_ASSERT(aPath); + + nsAutoString filePath; + nsresult rv = aPath->GetPath(filePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + std::wstring resolvedPath(filePath.get()); + if (!ResolveJunctionPointsAndSymLinks(resolvedPath)) { + return false; + } + + rv = aPath->InitWithPath(nsDependentString(resolvedPath.c_str())); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +/* static */ +bool WinUtils::RunningFromANetworkDrive() { + wchar_t exePath[MAX_PATH]; + if (!::GetModuleFileNameW(nullptr, exePath, MAX_PATH)) { + return false; + } + + std::wstring exeString(exePath); + if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(exeString)) { + return false; + } + + wchar_t volPath[MAX_PATH]; + if (!::GetVolumePathNameW(exeString.c_str(), volPath, MAX_PATH)) { + return false; + } + + return (::GetDriveTypeW(volPath) == DRIVE_REMOTE); +} + +/* static */ +bool WinUtils::GetModuleFullPath(HMODULE aModuleHandle, nsAString& aPath) { + size_t bufferSize = MAX_PATH; + size_t len = 0; + while (true) { + aPath.SetLength(bufferSize); + len = (size_t)::GetModuleFileNameW( + aModuleHandle, (char16ptr_t)aPath.BeginWriting(), bufferSize); + if (!len) { + return false; + } + if (len == bufferSize && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + bufferSize *= 2; + continue; + } + aPath.Truncate(len); + break; + } + return true; +} + +/* static */ +bool WinUtils::CanonicalizePath(nsAString& aPath) { + wchar_t tempPath[MAX_PATH + 1]; + if (!PathCanonicalizeW(tempPath, + (char16ptr_t)PromiseFlatString(aPath).get())) { + return false; + } + aPath = tempPath; + MOZ_ASSERT(aPath.Length() <= MAX_PATH); + return true; +} + +/* static */ +bool WinUtils::MakeLongPath(nsAString& aPath) { + wchar_t tempPath[MAX_PATH + 1]; + DWORD longResult = + GetLongPathNameW((char16ptr_t)PromiseFlatString(aPath).get(), tempPath, + ArrayLength(tempPath)); + if (longResult > ArrayLength(tempPath)) { + // Our buffer is too short, and we're guaranteeing <= MAX_PATH results. + return false; + } else if (longResult) { + // Success. + aPath = tempPath; + MOZ_ASSERT(aPath.Length() <= MAX_PATH); + } + // GetLongPathNameW returns 0 if the path is not found or is not rooted, + // but we shouldn't consider that a failure condition. + return true; +} + +/* static */ +bool WinUtils::UnexpandEnvVars(nsAString& aPath) { + wchar_t tempPath[MAX_PATH + 1]; + // PathUnExpandEnvStringsW returns false if it doesn't make any + // substitutions. Silently continue using the unaltered path. + if (PathUnExpandEnvStringsW((char16ptr_t)PromiseFlatString(aPath).get(), + tempPath, ArrayLength(tempPath))) { + aPath = tempPath; + MOZ_ASSERT(aPath.Length() <= MAX_PATH); + } + return true; +} + +/* static */ +WinUtils::WhitelistVec WinUtils::BuildWhitelist() { + WhitelistVec result; + + Unused << result.emplaceBack( + std::make_pair(nsString(u"%ProgramFiles%"_ns), nsDependentString())); + + // When no substitution is required, set the void flag + result.back().second.SetIsVoid(true); + + Unused << result.emplaceBack( + std::make_pair(nsString(u"%SystemRoot%"_ns), nsDependentString())); + result.back().second.SetIsVoid(true); + + wchar_t tmpPath[MAX_PATH + 1] = {}; + if (GetTempPath(MAX_PATH, tmpPath)) { + // GetTempPath's result always ends with a backslash, which we don't want + uint32_t tmpPathLen = wcslen(tmpPath); + if (tmpPathLen) { + tmpPath[tmpPathLen - 1] = 0; + } + + nsAutoString cleanTmpPath(tmpPath); + if (UnexpandEnvVars(cleanTmpPath)) { + constexpr auto tempVar = u"%TEMP%"_ns; + Unused << result.emplaceBack(std::make_pair( + nsString(cleanTmpPath), nsDependentString(tempVar, 0))); + } + } + + // If we add more items to the whitelist, ensure we still don't invoke an + // unnecessary heap allocation. + MOZ_ASSERT(result.length() <= kMaxWhitelistedItems); + + return result; +} + +/** + * This function provides an array of (system path, substitution) pairs that are + * considered to be acceptable with respect to privacy, for the purposes of + * submitting within telemetry or crash reports. + * + * The substitution string's void flag may be set. If it is, no subsitution is + * necessary. Otherwise, the consumer should replace the system path with the + * substitution. + * + * @see PreparePathForTelemetry for an example of its usage. + */ +/* static */ +const WinUtils::WhitelistVec& WinUtils::GetWhitelistedPaths() { + static WhitelistVec sWhitelist([]() -> WhitelistVec { + auto setClearFn = [ptr = &sWhitelist]() -> void { + RunOnShutdown([ptr]() -> void { ptr->clear(); }, + ShutdownPhase::ShutdownFinal); + }; + + if (NS_IsMainThread()) { + setClearFn(); + } else { + SchedulerGroup::Dispatch( + TaskCategory::Other, + NS_NewRunnableFunction("WinUtils::GetWhitelistedPaths", + std::move(setClearFn))); + } + + return BuildWhitelist(); + }()); + return sWhitelist; +} + +/** + * This function is located here (as opposed to nsSystemInfo or elsewhere) + * because we need to gather this information as early as possible during + * startup. + */ +/* static */ +bool WinUtils::GetAppInitDLLs(nsAString& aOutput) { + aOutput.Truncate(); + HKEY hkey = NULL; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", + 0, KEY_QUERY_VALUE, &hkey)) { + return false; + } + nsAutoRegKey key(hkey); + LONG status; + const wchar_t kLoadAppInitDLLs[] = L"LoadAppInit_DLLs"; + DWORD loadAppInitDLLs = 0; + DWORD loadAppInitDLLsLen = sizeof(loadAppInitDLLs); + status = RegQueryValueExW(hkey, kLoadAppInitDLLs, nullptr, nullptr, + (LPBYTE)&loadAppInitDLLs, &loadAppInitDLLsLen); + if (status != ERROR_SUCCESS) { + return false; + } + if (!loadAppInitDLLs) { + // If loadAppInitDLLs is zero then AppInit_DLLs is disabled. + // In this case we'll return true along with an empty output string. + return true; + } + DWORD numBytes = 0; + const wchar_t kAppInitDLLs[] = L"AppInit_DLLs"; + // Query for required buffer size + status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, nullptr, + &numBytes); + if (status != ERROR_SUCCESS) { + return false; + } + // Allocate the buffer and query for the actual data + mozilla::UniquePtr<wchar_t[]> data = + mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t)); + status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr, + (LPBYTE)data.get(), &numBytes); + if (status != ERROR_SUCCESS) { + return false; + } + // For each token, split up the filename components and then check the + // name of the file. + const wchar_t kDelimiters[] = L", "; + wchar_t* tokenContext = nullptr; + wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext); + while (token) { + nsAutoString cleanPath(token); + // Since these paths are short paths originating from the registry, we need + // to canonicalize them, lengthen them, and sanitize them before we can + // check them against the whitelist + if (PreparePathForTelemetry(cleanPath)) { + if (!aOutput.IsEmpty()) { + aOutput += L";"; + } + aOutput += cleanPath; + } + token = wcstok_s(nullptr, kDelimiters, &tokenContext); + } + return true; +} + +/* static */ +bool WinUtils::PreparePathForTelemetry(nsAString& aPath, + PathTransformFlags aFlags) { + if (aFlags & PathTransformFlags::Canonicalize) { + if (!CanonicalizePath(aPath)) { + return false; + } + } + if (aFlags & PathTransformFlags::Lengthen) { + if (!MakeLongPath(aPath)) { + return false; + } + } + if (aFlags & PathTransformFlags::UnexpandEnvVars) { + if (!UnexpandEnvVars(aPath)) { + return false; + } + } + + const WhitelistVec& whitelistedPaths = GetWhitelistedPaths(); + + for (uint32_t i = 0; i < whitelistedPaths.length(); ++i) { + const nsString& testPath = whitelistedPaths[i].first; + const nsDependentString& substitution = whitelistedPaths[i].second; + if (StringBeginsWith(aPath, testPath, nsCaseInsensitiveStringComparator)) { + if (!substitution.IsVoid()) { + aPath.Replace(0, testPath.Length(), substitution); + } + return true; + } + } + + // For non-whitelisted paths, we strip the path component and just leave + // the filename. We can't use nsLocalFile to do this because these paths may + // begin with environment variables, and nsLocalFile doesn't like + // non-absolute paths. + const nsString& flatPath = PromiseFlatString(aPath); + LPCWSTR leafStart = ::PathFindFileNameW(flatPath.get()); + ptrdiff_t cutLen = leafStart - flatPath.get(); + if (cutLen) { + aPath.Cut(0, cutLen); + } else if (aFlags & PathTransformFlags::RequireFilePath) { + return false; + } + + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h new file mode 100644 index 0000000000..c23c0d243f --- /dev/null +++ b/widget/windows/WinUtils.h @@ -0,0 +1,657 @@ +/* -*- 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" +#include "mozilla/Vector.h" +#include "mozilla/WindowsDpiAwareness.h" +#include "mozilla/gfx/2D.h" + +/** + * NS_INLINE_DECL_IUNKNOWN_REFCOUNTING should be used for defining and + * implementing AddRef() and Release() of IUnknown interface. + * This depends on xpcom/base/nsISupportsImpl.h. + */ + +#define NS_INLINE_DECL_IUNKNOWN_REFCOUNTING(_class) \ + public: \ + STDMETHODIMP_(ULONG) AddRef() { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++mRefCnt; \ + NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \ + return static_cast<ULONG>(mRefCnt.get()); \ + } \ + STDMETHODIMP_(ULONG) Release() { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, \ + "Release called on object that has already been released!"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --mRefCnt; \ + NS_LOG_RELEASE(this, mRefCnt, #_class); \ + if (mRefCnt == 0) { \ + NS_ASSERT_OWNINGTHREAD(_class); \ + mRefCnt = 1; /* stabilize */ \ + delete this; \ + return 0; \ + } \ + return static_cast<ULONG>(mRefCnt.get()); \ + } \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +class nsWindow; +class nsWindowBase; +struct KeyPair; + +namespace mozilla { +enum class PointerCapabilities : uint8_t; +#if defined(ACCESSIBILITY) +namespace a11y { +class Accessible; +} // namespace a11y +#endif // defined(ACCESSIBILITY) + +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; + static GetSystemMetricsForDpiProc sGetSystemMetricsForDpi; + + 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(); + /** + * Get the DPI of the given monitor if it's per-monitor DPI aware, otherwise + * return the system DPI. + */ + static float MonitorDPI(HMONITOR aMonitor); + static float SystemDPI(); + /** + * Functions to convert between logical pixels as used by most Windows APIs + * and physical (device) pixels. + */ + static double LogToPhysFactor(HMONITOR aMonitor); + static double LogToPhysFactor(HWND aWnd) { + // 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); + + static bool HasSystemMetricsForDpi(); + static int GetSystemMetricsForDpi(int nIndex, UINT dpi); + + /** + * @param aHdc HDC for printer + * @return unwritable margins for currently set page on aHdc or empty margins + * if aHdc is null + */ + static gfx::MarginDouble GetUnwriteableMarginsForDeviceInInches(HDC aHdc); + + /** + * 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 MouseEvent_Binding::MOZ_SOURCE_*. This method MUST be called during + * mouse message handling. + */ + static uint16_t GetMouseInputSource(); + + /** + * Windows also fires mouse window messages for pens and touches, so we should + * retrieve their pointer ID on receiving mouse events as well. Please refer + * to + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx + */ + static uint16_t GetMousePointerID(); + + static bool GetIsMouseFromTouch(EventMessage aEventType); + + /** + * 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 LayoutDeviceIntRegion. + * + * aRgn the HRGN to convert. + * returns the LayoutDeviceIntRegion. + */ + static LayoutDeviceIntRegion ConvertHRGNToRegion(HRGN aRgn); + + /** + * ToIntRect converts a Windows RECT to a LayoutDeviceIntRect. + * + * aRect the RECT to convert. + * returns the LayoutDeviceIntRect. + */ + static LayoutDeviceIntRect ToIntRect(const RECT& aRect); + + /** + * 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(IMEEnabled aIMEState); + + /** + * Returns modifier key array for aModifiers. This is for + * nsIWidget::SynthethizeNative*Event(). + */ + static void SetupKeyModifiersSequence(nsTArray<KeyPair>* aArray, + uint32_t aModifiers, UINT aMessage); + + /** + * Does device have touch support + */ + static uint32_t IsTouchDeviceSupportPresent(); + + /** + * The maximum number of simultaneous touch contacts supported by the device. + * In the case of devices with multiple digitizers (e.g. multiple touch + * screens), the value will be the maximum of the set of maximum supported + * contacts by each individual digitizer. + */ + static uint32_t GetMaxTouchPoints(); + + /** + * Returns the windows power platform role, which is useful for detecting + * tablets. + */ + static POWER_PLATFORM_ROLE GetPowerPlatformRole(); + + // For pointer and hover media queries features. + static PointerCapabilities GetPrimaryPointerCapabilities(); + // For any-pointer and any-hover media queries features. + static PointerCapabilities GetAllPointerCapabilities(); + + /** + * Fully resolves a path to its final path name. So if path contains + * junction points or symlinks to other folders, we'll resolve the path + * fully to the actual path that the links target. + * + * @param aPath path to be resolved. + * @return true if successful, including if nothing needs to be changed. + * false if something failed or aPath does not exist, aPath will + * remain unchanged. + */ + static bool ResolveJunctionPointsAndSymLinks(std::wstring& aPath); + static bool ResolveJunctionPointsAndSymLinks(nsIFile* aPath); + + /** + * Returns true if executable's path is on a network drive. + */ + static bool RunningFromANetworkDrive(); + + static void Initialize(); + + static nsresult WriteBitmap(nsIFile* aFile, + mozilla::gfx::SourceSurface* surface); + // This function is a helper, but it cannot be called from the main thread. + // Use the one above! + static nsresult WriteBitmap(nsIFile* aFile, imgIContainer* aImage); + + /** + * Wrapper for ::GetModuleFileNameW(). + * @param aModuleHandle [in] The handle of a loaded module + * @param aPath [out] receives the full path of the module specified + * by aModuleBase. + * @return true if aPath was successfully populated. + */ + static bool GetModuleFullPath(HMODULE aModuleHandle, nsAString& aPath); + + /** + * Wrapper for PathCanonicalize(). + * Upon success, the resulting output string length is <= MAX_PATH. + * @param aPath [in,out] The path to transform. + * @return true on success, false on failure. + */ + static bool CanonicalizePath(nsAString& aPath); + + /** + * Converts short paths (e.g. "C:\\PROGRA~1\\XYZ") to full paths. + * Upon success, the resulting output string length is <= MAX_PATH. + * @param aPath [in,out] The path to transform. + * @return true on success, false on failure. + */ + static bool MakeLongPath(nsAString& aPath); + + /** + * Wrapper for PathUnExpandEnvStringsW(). + * Upon success, the resulting output string length is <= MAX_PATH. + * @param aPath [in,out] The path to transform. + * @return true on success, false on failure. + */ + static bool UnexpandEnvVars(nsAString& aPath); + + /** + * Retrieve a semicolon-delimited list of DLL files derived from AppInit_DLLs + */ + static bool GetAppInitDLLs(nsAString& aOutput); + + enum class PathTransformFlags : uint32_t { + Canonicalize = 1, + Lengthen = 2, + UnexpandEnvVars = 4, + RequireFilePath = 8, + + Default = 7, // Default omits RequireFilePath + }; + + /** + * Given a path, transforms it in preparation to be reported via telemetry. + * That can include canonicalization, converting short to long paths, + * unexpanding environment strings, and removing potentially sensitive data + * from the path. + * + * @param aPath [in,out] The path to transform. + * @param aFlags [in] Specifies which transformations to perform, allowing + * the caller to skip operations they know have already been + * performed. + * @return true on success, false on failure. + */ + static bool PreparePathForTelemetry( + nsAString& aPath, + PathTransformFlags aFlags = PathTransformFlags::Default); + + static const size_t kMaxWhitelistedItems = 3; + using WhitelistVec = + Vector<std::pair<nsString, nsDependentString>, kMaxWhitelistedItems>; + + static const WhitelistVec& GetWhitelistedPaths(); + + private: + static WhitelistVec BuildWhitelist(); + + public: +#ifdef ACCESSIBILITY + static a11y::Accessible* GetRootAccessibleForHWND(HWND aHwnd); +#endif +}; + +#ifdef MOZ_PLACES +class AsyncFaviconDataReady final : public nsIFaviconDataCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFAVICONDATACALLBACK + + AsyncFaviconDataReady(nsIURI* aNewURI, nsCOMPtr<nsIThread>& aIOThread, + const bool aURLShortcut, + already_AddRefed<nsIRunnable> aRunnable); + nsresult OnFaviconDataNotAvailable(void); + + private: + ~AsyncFaviconDataReady() {} + + nsCOMPtr<nsIURI> mNewURI; + nsCOMPtr<nsIThread> mIOThread; + nsCOMPtr<nsIRunnable> mRunnable; + const bool mURLShortcut; +}; +#endif + +/** + * Asynchronously tries add the list to the build + */ +class AsyncEncodeAndWriteIcon : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + // Warning: AsyncEncodeAndWriteIcon assumes ownership of the aData buffer + // passed in + AsyncEncodeAndWriteIcon(const nsAString& aIconPath, + UniquePtr<uint8_t[]> aData, uint32_t aStride, + uint32_t aWidth, uint32_t aHeight, + already_AddRefed<nsIRunnable> aRunnable); + + private: + virtual ~AsyncEncodeAndWriteIcon(); + + nsAutoString mIconPath; + UniquePtr<uint8_t[]> mBuffer; + nsCOMPtr<nsIRunnable> mRunnable; + uint32_t mStride; + uint32_t mWidth; + uint32_t mHeight; +}; + +class AsyncDeleteAllFaviconsFromDisk : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + + explicit AsyncDeleteAllFaviconsFromDisk(bool aIgnoreRecent = false); + + private: + virtual ~AsyncDeleteAllFaviconsFromDisk(); + + int32_t mIcoNoDeleteSeconds; + bool mIgnoreRecent; + nsCOMPtr<nsIFile> mJumpListCacheDir; +}; + +class FaviconHelper { + public: + static const char kJumpListCacheDir[]; + static const char kShortcutCacheDir[]; + static nsresult ObtainCachedIconFile( + nsCOMPtr<nsIURI> aFaviconPageURI, nsString& aICOFilePath, + nsCOMPtr<nsIThread>& aIOThread, bool aURLShortcut, + already_AddRefed<nsIRunnable> aRunnable = nullptr); + + 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, + already_AddRefed<nsIRunnable> aRunnable); + + static int32_t GetICOCacheSecondsTimeout(); +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(WinUtils::PathTransformFlags); + +} // 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..06773f21b7 --- /dev/null +++ b/widget/windows/WindowHook.cpp @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowHook.h" +#include "nsWindow.h" +#include "nsWindowDefs.h" + +namespace mozilla { +namespace widget { + +nsresult WindowHook::AddHook(UINT nMsg, Callback callback, void* context) { + MessageData* data = LookupOrCreate(nMsg); + + if (!data) return NS_ERROR_OUT_OF_MEMORY; + + // Ensure we don't overwrite another hook + NS_ENSURE_TRUE(nullptr == data->hook.cb, NS_ERROR_UNEXPECTED); + + data->hook = CallbackData(callback, context); + + return NS_OK; +} + +nsresult WindowHook::RemoveHook(UINT nMsg, Callback callback, void* context) { + CallbackData cbdata(callback, context); + MessageData* data = Lookup(nMsg); + if (!data) return NS_ERROR_UNEXPECTED; + if (data->hook != cbdata) return NS_ERROR_UNEXPECTED; + data->hook = CallbackData(); + + DeleteIfEmpty(data); + return NS_OK; +} + +nsresult WindowHook::AddMonitor(UINT nMsg, Callback callback, void* context) { + MessageData* data = LookupOrCreate(nMsg); + return (data && data->monitors.AppendElement(CallbackData(callback, context), + fallible)) + ? NS_OK + : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult WindowHook::RemoveMonitor(UINT nMsg, Callback callback, + void* context) { + CallbackData cbdata(callback, context); + MessageData* data = Lookup(nMsg); + if (!data) return NS_ERROR_UNEXPECTED; + CallbackDataArray::index_type idx = data->monitors.IndexOf(cbdata); + if (idx == CallbackDataArray::NoIndex) return NS_ERROR_UNEXPECTED; + data->monitors.RemoveElementAt(idx); + DeleteIfEmpty(data); + return NS_OK; +} + +WindowHook::MessageData* WindowHook::Lookup(UINT nMsg) { + MessageDataArray::index_type idx; + for (idx = 0; idx < mMessageData.Length(); idx++) { + MessageData& data = mMessageData[idx]; + if (data.nMsg == nMsg) return &data; + } + return nullptr; +} + +WindowHook::MessageData* WindowHook::LookupOrCreate(UINT nMsg) { + MessageData* data = Lookup(nMsg); + if (!data) { + data = mMessageData.AppendElement(); + + if (!data) return nullptr; + + data->nMsg = nMsg; + } + return data; +} + +void WindowHook::DeleteIfEmpty(MessageData* data) { + // Never remove a MessageData that has still a hook or monitor entries. + if (data->hook || !data->monitors.IsEmpty()) return; + + MessageDataArray::index_type idx; + idx = data - mMessageData.Elements(); + NS_ASSERTION( + idx < mMessageData.Length(), + "Attempted to delete MessageData that doesn't belong to this array!"); + mMessageData.RemoveElementAt(idx); +} + +bool WindowHook::Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, + MSGResult& aResult) { + MessageData* data = Lookup(nMsg); + if (!data) return false; + + uint32_t length = data->monitors.Length(); + for (uint32_t midx = 0; midx < length; midx++) { + data->monitors[midx].Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult); + } + + aResult.mConsumed = + data->hook.Invoke(hWnd, nMsg, wParam, lParam, &aResult.mResult); + return aResult.mConsumed; +} + +bool WindowHook::CallbackData::Invoke(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam, LRESULT* aResult) { + if (!cb) return false; + return cb(context, hWnd, msg, wParam, lParam, aResult); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WindowHook.h b/widget/windows/WindowHook.h new file mode 100644 index 0000000000..1d1f4b02da --- /dev/null +++ b/widget/windows/WindowHook.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_WindowHook_h__ +#define __mozilla_WindowHook_h__ + +#include <windows.h> + +#include <nsHashKeys.h> +#include <nsClassHashtable.h> +#include <nsTArray.h> + +#include "nsAppShell.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +struct MSGResult; + +class WindowHook { + public: + // It is expected that most callbacks will return false + typedef bool (*Callback)(void* aContext, HWND hWnd, UINT nMsg, WPARAM wParam, + LPARAM lParam, LRESULT* aResult); + + nsresult AddHook(UINT nMsg, Callback callback, void* context); + nsresult RemoveHook(UINT nMsg, Callback callback, void* context); + nsresult AddMonitor(UINT nMsg, Callback callback, void* context); + nsresult RemoveMonitor(UINT nMsg, Callback callback, void* context); + + private: + struct CallbackData { + Callback cb; + void* context; + + CallbackData() : cb(nullptr), context(nullptr) {} + CallbackData(Callback cb, void* ctx) : cb(cb), context(ctx) {} + bool Invoke(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, + LRESULT* aResult); + bool operator==(const CallbackData& rhs) const { + return cb == rhs.cb && context == rhs.context; + } + bool operator!=(const CallbackData& rhs) const { return !(*this == rhs); } + explicit operator bool() const { return !!cb; } + }; + + typedef nsTArray<CallbackData> CallbackDataArray; + struct MessageData { + UINT nMsg; + CallbackData hook; + CallbackDataArray monitors; + }; + + bool Notify(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, + MSGResult& aResult); + + MessageData* Lookup(UINT nMsg); + MessageData* LookupOrCreate(UINT nMsg); + void DeleteIfEmpty(MessageData* data); + + typedef nsTArray<MessageData> MessageDataArray; + MessageDataArray mMessageData; + + // For Notify + friend class ::nsWindow; +}; + +} // namespace widget +} // namespace mozilla + +#endif // __mozilla_WindowHook_h__ diff --git a/widget/windows/WindowsConsole.cpp b/widget/windows/WindowsConsole.cpp new file mode 100644 index 0000000000..16005a642b --- /dev/null +++ b/widget/windows/WindowsConsole.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowsConsole.h" + +#include <windows.h> +#include <fcntl.h> + +namespace mozilla { + +static void AssignStdHandle(const char* aPath, const char* aMode, FILE* aStream, + DWORD aStdHandle) { + // Visual Studio's _fileno() returns -2 for the standard + // streams if they aren't associated with an output stream. + const int fd = _fileno(aStream); + if (fd == -2) { + freopen(aPath, aMode, aStream); + return; + } + if (fd < 0) { + return; + } + + const HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); + if (handle == INVALID_HANDLE_VALUE) { + return; + } + + const HANDLE oldHandle = GetStdHandle(aStdHandle); + if (handle == oldHandle) { + return; + } + + SetStdHandle(aStdHandle, handle); +} + +// This code attaches the process to the appropriate console. +void UseParentConsole() { + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + // Redirect the standard streams to the existing console, but + // only if they haven't been redirected to a valid file. + AssignStdHandle("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE); + // There is no CONERR$, so use CONOUT$ for stderr as well. + AssignStdHandle("CONOUT$", "w", stderr, STD_ERROR_HANDLE); + AssignStdHandle("CONIN$", "r", stdin, STD_INPUT_HANDLE); + } +} + +} // namespace mozilla diff --git a/widget/windows/WindowsConsole.h b/widget/windows/WindowsConsole.h new file mode 100644 index 0000000000..4b8ecf1823 --- /dev/null +++ b/widget/windows/WindowsConsole.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsConsole_h +#define mozilla_WindowsConsole_h + +namespace mozilla { + +// This code attaches the process to the appropriate console. +void UseParentConsole(); + +} // namespace mozilla + +#endif // mozilla_WindowsConsole_h diff --git a/widget/windows/WindowsEMF.cpp b/widget/windows/WindowsEMF.cpp new file mode 100644 index 0000000000..71e3631bea --- /dev/null +++ b/widget/windows/WindowsEMF.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowsEMF.h" + +namespace mozilla { +namespace widget { + +WindowsEMF::WindowsEMF() : mEmf(nullptr), mDC(nullptr) {} + +WindowsEMF::~WindowsEMF() { ReleaseAllResource(); } + +bool WindowsEMF::InitForDrawing(const wchar_t* aMetafilePath /* = nullptr */) { + ReleaseAllResource(); + + mDC = ::CreateEnhMetaFile(nullptr, aMetafilePath, nullptr, nullptr); + return !!mDC; +} + +bool WindowsEMF::InitFromFileContents(const wchar_t* aMetafilePath) { + MOZ_ASSERT(aMetafilePath); + ReleaseAllResource(); + + mEmf = ::GetEnhMetaFileW(aMetafilePath); + return !!mEmf; +} + +bool WindowsEMF::InitFromFileContents(LPBYTE aBytes, UINT aSize) { + MOZ_ASSERT(aBytes && aSize != 0); + ReleaseAllResource(); + + mEmf = SetEnhMetaFileBits(aSize, aBytes); + + return !!mEmf; +} + +bool WindowsEMF::FinishDocument() { + if (mDC) { + mEmf = ::CloseEnhMetaFile(mDC); + mDC = nullptr; + } + return !!mEmf; +} + +void WindowsEMF::ReleaseEMFHandle() { + if (mEmf) { + ::DeleteEnhMetaFile(mEmf); + mEmf = nullptr; + } +} + +void WindowsEMF::ReleaseAllResource() { + FinishDocument(); + ReleaseEMFHandle(); +} + +bool WindowsEMF::Playback(HDC aDeviceContext, const RECT& aRect) { + DebugOnly<bool> result = FinishDocument(); + MOZ_ASSERT(result, "This function should be used after InitXXX."); + + return ::PlayEnhMetaFile(aDeviceContext, mEmf, &aRect) != 0; +} + +bool WindowsEMF::SaveToFile() { + DebugOnly<bool> result = FinishDocument(); + MOZ_ASSERT(result, "This function should be used after InitXXX."); + + ReleaseEMFHandle(); + return true; +} + +UINT WindowsEMF::GetEMFContentSize() { + DebugOnly<bool> result = FinishDocument(); + MOZ_ASSERT(result, "This function should be used after InitXXX."); + + return GetEnhMetaFileBits(mEmf, 0, NULL); +} + +bool WindowsEMF::GetEMFContentBits(LPBYTE aBytes) { + DebugOnly<bool> result = FinishDocument(); + MOZ_ASSERT(result, "This function should be used after InitXXX."); + + UINT emfSize = GetEMFContentSize(); + if (GetEnhMetaFileBits(mEmf, emfSize, aBytes) != emfSize) { + return false; + } + + return true; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/windows/WindowsEMF.h b/widget/windows/WindowsEMF.h new file mode 100644 index 0000000000..3a7a20173c --- /dev/null +++ b/widget/windows/WindowsEMF.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_WIDGET_WINDOWSEMF_H +#define MOZILLA_WIDGET_WINDOWSEMF_H + +/* include windows.h for the HDC definitions that we need. */ +#include <windows.h> + +namespace mozilla { +namespace widget { + +/** + * Windows Enhance Metafile: https://en.wikipedia.org/wiki/Windows_Metafile + * A metafile, also called a vector image, is an image that is stored as a + * sequence of drawing commands and settings. The commands and settings + * recorded in a Metafile object can be stored in memory or saved to a file. + * + * The metafile device context is used for all drawing operations required to + * create the picture. When the system processes a GDI function associated with + * a metafile DC, it converts the function into the appropriate data and stores + * this data in a record appended to the metafile. + */ +class WindowsEMF { + public: + WindowsEMF(); + ~WindowsEMF(); + + /** + * Initializes the object with the path of a file where the EMF data stream + * should be stored. Callers are then expected to call GetDC() to draw output + * before going on to call Playback() or SaveToFile() to generate the EMF + * output. + */ + bool InitForDrawing(const wchar_t* aMetafilePath = nullptr); + + /** + * Initializes the object with an existing EMF file. Consumers cannot use + * GetDC() to obtain an HDC to modify the file. They can only use Playback(). + */ + bool InitFromFileContents(const wchar_t* aMetafilePath); + + /** + * Creates the EMF from the specified data + * + * @param aByte Pointer to a buffer that contains EMF data. + * @param aSize Specifies the size, in bytes, of aByte. + */ + bool InitFromFileContents(PBYTE aBytes, UINT aSize); + + /** + * If this object was initiaziled using InitForDrawing() then this function + * returns an HDC that can be drawn to generate the EMF output. Otherwise it + * returns null. After finishing with the HDC, consumers could call Playback() + * to draw EMF onto the given DC or call SaveToFile() to finish writing the + * EMF file. + */ + HDC GetDC() const { + MOZ_ASSERT(mDC, + "GetDC can be used only after " + "InitForDrawing/ InitFromFileContents and before" + "Playback/ SaveToFile"); + return mDC; + } + + /** + * Play the EMF's drawing commands onto the given DC. + */ + bool Playback(HDC aDeviceContext, const RECT& aRect); + + /** + * Called to generate the EMF file once a consumer has finished drawing to + * the HDC returned by GetDC(), if initializes the object with the path of a + * file. + */ + bool SaveToFile(); + + /** + * Return the size of the enhanced metafile, in bytes. + */ + UINT GetEMFContentSize(); + + /** + * Retrieves the contents of the EMF and copies them into a buffer. + * + * @param aByte the buffer to receive the data. + */ + bool GetEMFContentBits(PBYTE aBytes); + + private: + WindowsEMF(const WindowsEMF& aEMF) = delete; + bool FinishDocument(); + void ReleaseEMFHandle(); + void ReleaseAllResource(); + + /* Compiled EMF data handle. */ + HENHMETAFILE mEmf; + HDC mDC; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* MOZILLA_WIDGET_WINDOWSEMF_H */ diff --git a/widget/windows/WindowsSMTCProvider.cpp b/widget/windows/WindowsSMTCProvider.cpp new file mode 100644 index 0000000000..a20e81ed27 --- /dev/null +++ b/widget/windows/WindowsSMTCProvider.cpp @@ -0,0 +1,731 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* mingw currently doesn't support windows.media.h, so we disable + * the whole related class until this is fixed. + * @TODO: Maybe contact MinGW Team for inclusion?*/ +#ifndef __MINGW32__ + +# include "WindowsSMTCProvider.h" + +# include <windows.h> +# include <windows.media.h> +# include <winsdkver.h> +# include <wrl.h> + +# include "nsMimeTypes.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/WidgetUtils.h" +# include "mozilla/WindowsVersion.h" +# include "mozilla/ScopeExit.h" +# include "mozilla/dom/MediaControlUtils.h" +# include "mozilla/media/MediaUtils.h" +# include "nsThreadUtils.h" + +# pragma comment(lib, "runtimeobject.lib") + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Media; +using namespace ABI::Windows::Storage::Streams; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace mozilla; + +# ifndef RuntimeClass_Windows_Media_SystemMediaTransportControls +# define RuntimeClass_Windows_Media_SystemMediaTransportControls \ + L"Windows.Media.SystemMediaTransportControls" +# endif + +# ifndef RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference +# define RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference \ + L"Windows.Storage.Streams.RandomAccessStreamReference" +# endif + +# ifndef ISystemMediaTransportControlsInterop +EXTERN_C const IID IID_ISystemMediaTransportControlsInterop; +MIDL_INTERFACE("ddb0472d-c911-4a1f-86d9-dc3d71a95f5a") +ISystemMediaTransportControlsInterop : public IInspectable { + public: + virtual HRESULT STDMETHODCALLTYPE GetForWindow( + /* [in] */ __RPC__in HWND appWindow, + /* [in] */ __RPC__in REFIID riid, + /* [iid_is][retval][out] */ + __RPC__deref_out_opt void** mediaTransportControl) = 0; +}; +# endif /* __ISystemMediaTransportControlsInterop_INTERFACE_DEFINED__ */ + +extern mozilla::LazyLogModule gMediaControlLog; + +# undef LOG +# define LOG(msg, ...) \ + MOZ_LOG(gMediaControlLog, LogLevel::Debug, \ + ("WindowSMTCProvider=%p, " msg, this, ##__VA_ARGS__)) + +static inline Maybe<mozilla::dom::MediaControlKey> TranslateKeycode( + SystemMediaTransportControlsButton keycode) { + switch (keycode) { + case SystemMediaTransportControlsButton_Play: + return Some(mozilla::dom::MediaControlKey::Play); + case SystemMediaTransportControlsButton_Pause: + return Some(mozilla::dom::MediaControlKey::Pause); + case SystemMediaTransportControlsButton_Next: + return Some(mozilla::dom::MediaControlKey::Nexttrack); + case SystemMediaTransportControlsButton_Previous: + return Some(mozilla::dom::MediaControlKey::Previoustrack); + case SystemMediaTransportControlsButton_Stop: + return Some(mozilla::dom::MediaControlKey::Stop); + case SystemMediaTransportControlsButton_FastForward: + return Some(mozilla::dom::MediaControlKey::Seekforward); + case SystemMediaTransportControlsButton_Rewind: + return Some(mozilla::dom::MediaControlKey::Seekbackward); + default: + return Nothing(); // Not supported Button + } +} + +static IAsyncInfo* GetIAsyncInfo(IAsyncOperation<unsigned int>* aAsyncOp) { + MOZ_ASSERT(aAsyncOp); + IAsyncInfo* asyncInfo; + HRESULT hr = aAsyncOp->QueryInterface(IID_IAsyncInfo, + reinterpret_cast<void**>(&asyncInfo)); + // The assertion always works since IAsyncOperation implements IAsyncInfo + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + MOZ_ASSERT(asyncInfo); + return asyncInfo; +} + +WindowsSMTCProvider::WindowsSMTCProvider() { + LOG("Creating an empty and invisible window"); + + // In order to create a SMTC-Provider, we need a hWnd, which shall be created + // dynamically from an invisible window. This leads to the following + // boilerplate code. + WNDCLASS wnd{}; + wnd.lpszClassName = L"Firefox-MediaKeys"; + wnd.hInstance = nullptr; + wnd.lpfnWndProc = DefWindowProc; + GetLastError(); // Clear the error + RegisterClass(&wnd); + MOZ_ASSERT(!GetLastError()); + + mWindow = CreateWindowExW(0, L"Firefox-MediaKeys", L"Firefox Media Keys", 0, + CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, nullptr, + nullptr, nullptr, nullptr); + MOZ_ASSERT(mWindow); + MOZ_ASSERT(!GetLastError()); +} + +WindowsSMTCProvider::~WindowsSMTCProvider() { + // Dispose the window + MOZ_ASSERT(mWindow); + if (!DestroyWindow(mWindow)) { + LOG("Failed to destroy the hidden window. Error Code: %d", GetLastError()); + } + if (!UnregisterClass(L"Firefox-MediaKeys", nullptr)) { + // Note that this is logged when the class wasn't even registered. + LOG("Failed to unregister the class. Error Code: %d", GetLastError()); + } +} + +bool WindowsSMTCProvider::IsOpened() const { return mInitialized; } + +bool WindowsSMTCProvider::Open() { + LOG("Opening Source"); + MOZ_ASSERT(!mInitialized); + + if (!IsWin8Point1OrLater()) { + LOG("Windows 8.1 or later is required for Media Key Support"); + return false; + } + + if (!InitDisplayAndControls()) { + LOG("Failed to initialize the SMTC and its display"); + return false; + } + + if (!UpdateButtons()) { + LOG("Failed to initialize the buttons"); + return false; + } + + if (!RegisterEvents()) { + LOG("Failed to register SMTC key-event listener"); + return false; + } + + if (!EnableControl(true)) { + LOG("Failed to enable SMTC control"); + return false; + } + + mInitialized = true; + SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); + return mInitialized; +} + +void WindowsSMTCProvider::Close() { + MediaControlKeySource::Close(); + // Prevent calling Set methods when init failed + if (mInitialized) { + SetPlaybackState(mozilla::dom::MediaSessionPlaybackState::None); + UnregisterEvents(); + ClearMetadata(); + // We have observed an Windows issue, if we modify `mControls` , (such as + // setting metadata, disable buttons) before disabling control, and those + // operations are not done sequentially within a same main thread task, + // then it would cause a problem where the SMTC wasn't clean up completely + // and show the executable name. + EnableControl(false); + mInitialized = false; + } +} + +void WindowsSMTCProvider::SetPlaybackState( + mozilla::dom::MediaSessionPlaybackState aState) { + MOZ_ASSERT(mInitialized); + MediaControlKeySource::SetPlaybackState(aState); + + HRESULT hr; + + // Note: we can't return the status of put_PlaybackStatus, but we can at least + // assert it. + switch (aState) { + case mozilla::dom::MediaSessionPlaybackState::Paused: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Paused); + break; + case mozilla::dom::MediaSessionPlaybackState::Playing: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Playing); + break; + case mozilla::dom::MediaSessionPlaybackState::None: + hr = mControls->put_PlaybackStatus( + ABI::Windows::Media::MediaPlaybackStatus_Stopped); + break; + // MediaPlaybackStatus still supports Closed and Changing, which we don't + // use (yet) + default: + MOZ_ASSERT_UNREACHABLE( + "Enum Inconsitency between PlaybackState and WindowsSMTCProvider"); + break; + } + + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; +} + +void WindowsSMTCProvider::SetMediaMetadata( + const mozilla::dom::MediaMetadataBase& aMetadata) { + MOZ_ASSERT(mInitialized); + SetMusicMetadata(aMetadata.mArtist.get(), aMetadata.mTitle.get(), + aMetadata.mAlbum.get()); + LoadThumbnail(aMetadata.mArtwork); +} + +void WindowsSMTCProvider::ClearMetadata() { + MOZ_ASSERT(mDisplay); + if (FAILED(mDisplay->ClearAll())) { + LOG("Failed to clear SMTC display"); + } + mImageFetchRequest.DisconnectIfExists(); + CancelPendingStoreAsyncOperation(); + mThumbnailUrl.Truncate(); + mProcessingUrl.Truncate(); + mNextImageIndex = 0; + mSupportedKeys = 0; +} + +void WindowsSMTCProvider::SetSupportedMediaKeys( + const MediaKeysArray& aSupportedKeys) { + MOZ_ASSERT(mInitialized); + + uint32_t supportedKeys = 0; + for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) { + supportedKeys |= GetMediaKeyMask(key); + } + + if (supportedKeys == mSupportedKeys) { + LOG("Supported keys stay the same"); + return; + } + + LOG("Update supported keys"); + mSupportedKeys = supportedKeys; + UpdateButtons(); +} + +void WindowsSMTCProvider::UnregisterEvents() { + if (mControls && mButtonPressedToken.value != 0) { + mControls->remove_ButtonPressed(mButtonPressedToken); + } +} + +bool WindowsSMTCProvider::RegisterEvents() { + MOZ_ASSERT(mControls); + auto self = RefPtr<WindowsSMTCProvider>(this); + auto callbackbtnPressed = Callback< + ITypedEventHandler<SystemMediaTransportControls*, + SystemMediaTransportControlsButtonPressedEventArgs*>>( + [this, self](ISystemMediaTransportControls*, + ISystemMediaTransportControlsButtonPressedEventArgs* pArgs) + -> HRESULT { + MOZ_ASSERT(pArgs); + SystemMediaTransportControlsButton btn; + + if (FAILED(pArgs->get_Button(&btn))) { + LOG("SystemMediaTransportControls: ButtonPressedEvent - Could " + "not get Button."); + return S_OK; // Propagating the error probably wouldn't help. + } + + Maybe<mozilla::dom::MediaControlKey> keyCode = TranslateKeycode(btn); + if (keyCode.isSome() && IsOpened()) { + OnButtonPressed(keyCode.value()); + } + return S_OK; + }); + + if (FAILED(mControls->add_ButtonPressed(callbackbtnPressed.Get(), + &mButtonPressedToken))) { + LOG("SystemMediaTransportControls: Failed at " + "registerEvents().add_ButtonPressed()"); + return false; + } + + return true; +} + +void WindowsSMTCProvider::OnButtonPressed( + mozilla::dom::MediaControlKey aKey) const { + if (!IsKeySupported(aKey)) { + LOG("key: %s is not supported", ToMediaControlKeyStr(aKey)); + return; + } + + for (auto& listener : mListeners) { + listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey)); + } +} + +bool WindowsSMTCProvider::EnableControl(bool aEnabled) const { + MOZ_ASSERT(mControls); + return SUCCEEDED(mControls->put_IsEnabled(aEnabled)); +} + +bool WindowsSMTCProvider::UpdateButtons() const { + static const mozilla::dom::MediaControlKey kKeys[] = { + mozilla::dom::MediaControlKey::Play, mozilla::dom::MediaControlKey::Pause, + mozilla::dom::MediaControlKey::Previoustrack, + mozilla::dom::MediaControlKey::Nexttrack}; + + bool success = true; + for (const mozilla::dom::MediaControlKey& key : kKeys) { + if (!EnableKey(key, IsKeySupported(key))) { + success = false; + LOG("Failed to set %s=%s", ToMediaControlKeyStr(key), + IsKeySupported(key) ? "true" : "false"); + } + } + + return success; +} + +bool WindowsSMTCProvider::IsKeySupported( + mozilla::dom::MediaControlKey aKey) const { + return mSupportedKeys & GetMediaKeyMask(aKey); +} + +bool WindowsSMTCProvider::EnableKey(mozilla::dom::MediaControlKey aKey, + bool aEnable) const { + MOZ_ASSERT(mControls); + switch (aKey) { + case mozilla::dom::MediaControlKey::Play: + return SUCCEEDED(mControls->put_IsPlayEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Pause: + return SUCCEEDED(mControls->put_IsPauseEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Previoustrack: + return SUCCEEDED(mControls->put_IsPreviousEnabled(aEnable)); + case mozilla::dom::MediaControlKey::Nexttrack: + return SUCCEEDED(mControls->put_IsNextEnabled(aEnable)); + default: + LOG("No button for %s", ToMediaControlKeyStr(aKey)); + return false; + } +} + +bool WindowsSMTCProvider::InitDisplayAndControls() { + // As Open() might be called multiple times, "cache" the results of the COM + // API + if (mControls && mDisplay) { + return true; + } + ComPtr<ISystemMediaTransportControlsInterop> interop; + HRESULT hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_Media_SystemMediaTransportControls) + .Get(), + interop.GetAddressOf()); + if (FAILED(hr)) { + LOG("SystemMediaTransportControls: Failed at instantiating the " + "Interop object"); + return false; + } + MOZ_ASSERT(interop); + + if (!mControls && FAILED(interop->GetForWindow( + mWindow, IID_PPV_ARGS(mControls.GetAddressOf())))) { + LOG("SystemMediaTransportControls: Failed at GetForWindow()"); + return false; + } + MOZ_ASSERT(mControls); + + if (!mDisplay && + FAILED(mControls->get_DisplayUpdater(mDisplay.GetAddressOf()))) { + LOG("SystemMediaTransportControls: Failed at get_DisplayUpdater()"); + } + + MOZ_ASSERT(mDisplay); + return true; +} + +bool WindowsSMTCProvider::SetMusicMetadata(const wchar_t* aArtist, + const wchar_t* aTitle, + const wchar_t* aAlbumArtist) { + MOZ_ASSERT(mDisplay); + MOZ_ASSERT(aArtist); + MOZ_ASSERT(aTitle); + MOZ_ASSERT(aAlbumArtist); + ComPtr<IMusicDisplayProperties> musicProps; + + HRESULT hr = mDisplay->put_Type(MediaPlaybackType::MediaPlaybackType_Music); + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + hr = mDisplay->get_MusicProperties(musicProps.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to get music properties"); + return false; + } + + hr = musicProps->put_Artist(HStringReference(aArtist).Get()); + if (FAILED(hr)) { + LOG("Failed to set the music's artist"); + return false; + } + + hr = musicProps->put_Title(HStringReference(aTitle).Get()); + if (FAILED(hr)) { + LOG("Failed to set the music's title"); + return false; + } + + hr = musicProps->put_AlbumArtist(HStringReference(aAlbumArtist).Get()); + if (FAILED(hr)) { + LOG("Failed to set the music's album"); + return false; + } + + hr = mDisplay->Update(); + if (FAILED(hr)) { + LOG("Failed to refresh the display"); + return false; + } + + return true; +} + +void WindowsSMTCProvider::LoadThumbnail( + const nsTArray<mozilla::dom::MediaImage>& aArtwork) { + MOZ_ASSERT(NS_IsMainThread()); + + // TODO: Sort the images by the preferred size or format. + mArtwork = aArtwork; + mNextImageIndex = 0; + + // Abort the loading if + // 1) thumbnail is being updated, and one in processing is in the artwork + // 2) thumbnail is not being updated, and one in use is in the artwork + if (!mProcessingUrl.IsEmpty()) { + LOG("Load thumbnail while image: %s is being processed", + NS_ConvertUTF16toUTF8(mProcessingUrl).get()); + if (mozilla::dom::IsImageIn(mArtwork, mProcessingUrl)) { + LOG("No need to load thumbnail. The one being processed is in the " + "artwork"); + return; + } + } else if (!mThumbnailUrl.IsEmpty()) { + if (mozilla::dom::IsImageIn(mArtwork, mThumbnailUrl)) { + LOG("No need to load thumbnail. The one in use is in the artwork"); + return; + } + } + + // If there is a pending image store operation, that image must be different + // from the new image will be loaded below, so the pending one should be + // cancelled. + CancelPendingStoreAsyncOperation(); + // Remove the current thumbnail on the interface + ClearThumbnail(); + // Then load the new thumbnail asynchronously + LoadImageAtIndex(mNextImageIndex++); +} + +void WindowsSMTCProvider::LoadImageAtIndex(const size_t aIndex) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aIndex >= mArtwork.Length()) { + LOG("Stop loading thumbnail. No more available images"); + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl.Truncate(); + return; + } + + const mozilla::dom::MediaImage& image = mArtwork[aIndex]; + + // TODO: No need to fetch the default image and do image processing since the + // the default image is local file and it's trustworthy. For the default + // image, we can use `CreateFromFile` to create the IRandomAccessStream. We + // should probably cache it since it could be used very often (Bug 1643102) + + if (!mozilla::dom::IsValidImageUrl(image.mSrc)) { + LOG("Skip the image with invalid URL. Try next image"); + mImageFetchRequest.DisconnectIfExists(); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + mImageFetchRequest.DisconnectIfExists(); + mProcessingUrl = image.mSrc; + + mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image); + RefPtr<WindowsSMTCProvider> self = this; + mImageFetcher->FetchImage() + ->Then( + AbstractThread::MainThread(), __func__, + [this, self](const nsCOMPtr<imgIContainer>& aImage) { + LOG("The image is fetched successfully"); + mImageFetchRequest.Complete(); + + // Although IMAGE_JPEG or IMAGE_BMP are valid types as well, but a + // png image with transparent background will be converted into a + // jpeg/bmp file with a colored background. IMAGE_PNG format seems + // to be the best choice for now. + uint32_t size = 0; + char* src = nullptr; + // Only used to hold the image data + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = mozilla::dom::GetEncodedImageBuffer( + aImage, nsLiteralCString(IMAGE_PNG), + getter_AddRefs(inputStream), &size, &src); + if (NS_FAILED(rv) || !inputStream || size == 0 || !src) { + LOG("Failed to get the image buffer info. Try next image"); + LoadImageAtIndex(mNextImageIndex++); + return; + } + + LoadImage(src, size); + }, + [this, self](bool) { + LOG("Failed to fetch image. Try next image"); + mImageFetchRequest.Complete(); + LoadImageAtIndex(mNextImageIndex++); + }) + ->Track(mImageFetchRequest); +} + +void WindowsSMTCProvider::LoadImage(const char* aImageData, + uint32_t aDataSize) { + MOZ_ASSERT(NS_IsMainThread()); + + // 1. Use mImageDataWriter to write the binary data of image into mImageStream + // 2. Refer the image by mImageStreamReference and then set it to the SMTC + // In case of the race condition between they are being destroyed and the + // async operation for image loading, mImageDataWriter, mImageStream, and + // mImageStreamReference are member variables + + HRESULT hr = ActivateInstance( + HStringReference( + RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream) + .Get(), + mImageStream.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to make mImageStream refer to an instance of " + "InMemoryRandomAccessStream"); + return; + } + + ComPtr<IOutputStream> outputStream; + hr = mImageStream.As(&outputStream); + if (FAILED(hr)) { + LOG("Failed when query IOutputStream interface from mImageStream"); + return; + } + + ComPtr<IDataWriterFactory> dataWriterFactory; + hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_Storage_Streams_DataWriter).Get(), + dataWriterFactory.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to get an activation factory for IDataWriterFactory"); + return; + } + + hr = dataWriterFactory->CreateDataWriter(outputStream.Get(), + mImageDataWriter.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to create mImageDataWriter that writes data to mImageStream"); + return; + } + + hr = mImageDataWriter->WriteBytes( + aDataSize, reinterpret_cast<BYTE*>(const_cast<char*>(aImageData))); + if (FAILED(hr)) { + LOG("Failed to write data to mImageStream"); + return; + } + + hr = mImageDataWriter->StoreAsync(&mStoreAsyncOperation); + if (FAILED(hr)) { + LOG("Failed to create a DataWriterStoreOperation for mStoreAsyncOperation"); + return; + } + + // Upon the image is stored in mImageStream, set the image to the SMTC + // interface + auto onStoreCompleted = Callback< + IAsyncOperationCompletedHandler<unsigned int>>( + [this, self = RefPtr<WindowsSMTCProvider>(this), + aImageUrl = nsString(mProcessingUrl)]( + IAsyncOperation<unsigned int>* aAsyncOp, AsyncStatus aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aStatus != AsyncStatus::Completed) { + LOG("Asynchronous operation is not completed"); + return E_ABORT; + } + + HRESULT hr = S_OK; + IAsyncInfo* asyncInfo = GetIAsyncInfo(aAsyncOp); + asyncInfo->get_ErrorCode(&hr); + if (FAILED(hr)) { + LOG("Failed to get termination status of the asynchronous operation"); + return hr; + } + + if (!UpdateThumbnail(aImageUrl)) { + LOG("Failed to update thumbnail"); + } + + // If an error occurs above: + // - If aImageUrl is not mProcessingUrl. It's fine. + // - If aImageUrl is mProcessingUrl, then mProcessingUrl won't be reset. + // Therefore the thumbnail will remain empty until a new image whose + // url is different from mProcessingUrl is loaded. + + return S_OK; + }); + + hr = mStoreAsyncOperation->put_Completed(onStoreCompleted.Get()); + if (FAILED(hr)) { + LOG("Failed to set callback on completeing the asynchronous operation"); + } +} + +bool WindowsSMTCProvider::SetThumbnail(const nsAString& aUrl) { + MOZ_ASSERT(mDisplay); + MOZ_ASSERT(mImageStream); + MOZ_ASSERT(!aUrl.IsEmpty()); + + ComPtr<IRandomAccessStreamReferenceStatics> streamRefFactory; + + HRESULT hr = GetActivationFactory( + HStringReference( + RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference) + .Get(), + streamRefFactory.GetAddressOf()); + auto cleanup = + MakeScopeExit([this, self = RefPtr<WindowsSMTCProvider>(this)] { + LOG("Clean mThumbnailUrl"); + mThumbnailUrl.Truncate(); + }); + + if (FAILED(hr)) { + LOG("Failed to get an activation factory for " + "IRandomAccessStreamReferenceStatics type"); + return false; + } + + hr = streamRefFactory->CreateFromStream(mImageStream.Get(), + mImageStreamReference.GetAddressOf()); + if (FAILED(hr)) { + LOG("Failed to create mImageStreamReference from mImageStream"); + return false; + } + + hr = mDisplay->put_Thumbnail(mImageStreamReference.Get()); + if (FAILED(hr)) { + LOG("Failed to update thumbnail"); + return false; + } + + hr = mDisplay->Update(); + if (FAILED(hr)) { + LOG("Failed to refresh display"); + return false; + } + + // No need to clean mThumbnailUrl since thumbnail is set successfully + cleanup.release(); + mThumbnailUrl = aUrl; + + return true; +} + +void WindowsSMTCProvider::ClearThumbnail() { + MOZ_ASSERT(mDisplay); + HRESULT hr = mDisplay->put_Thumbnail(nullptr); + MOZ_ASSERT(SUCCEEDED(hr)); + hr = mDisplay->Update(); + MOZ_ASSERT(SUCCEEDED(hr)); + Unused << hr; + mThumbnailUrl.Truncate(); +} + +bool WindowsSMTCProvider::UpdateThumbnail(const nsAString& aUrl) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!IsOpened()) { + LOG("Abort the thumbnail update: SMTC is closed"); + return false; + } + + if (aUrl != mProcessingUrl) { + LOG("Abort the thumbnail update: The image from %s is out of date", + NS_ConvertUTF16toUTF8(aUrl).get()); + return false; + } + + mProcessingUrl.Truncate(); + + if (!SetThumbnail(aUrl)) { + LOG("Failed to update thumbnail"); + return false; + } + + MOZ_ASSERT(mThumbnailUrl == aUrl); + LOG("The thumbnail is updated to the image from: %s", + NS_ConvertUTF16toUTF8(mThumbnailUrl).get()); + return true; +} + +void WindowsSMTCProvider::CancelPendingStoreAsyncOperation() const { + if (mStoreAsyncOperation) { + IAsyncInfo* asyncInfo = GetIAsyncInfo(mStoreAsyncOperation.Get()); + asyncInfo->Cancel(); + } +} + +#endif // __MINGW32__ diff --git a/widget/windows/WindowsSMTCProvider.h b/widget/windows/WindowsSMTCProvider.h new file mode 100644 index 0000000000..c46434e03e --- /dev/null +++ b/widget/windows/WindowsSMTCProvider.h @@ -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/. */ + +#ifndef WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_ +#define WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_ + +#ifndef __MINGW32__ + +# include <functional> +# include <Windows.Media.h> +# include <wrl.h> + +# include "mozilla/dom/FetchImageHelper.h" +# include "mozilla/dom/MediaController.h" +# include "mozilla/dom/MediaControlKeySource.h" +# include "mozilla/UniquePtr.h" + +using ISMTC = ABI::Windows::Media::ISystemMediaTransportControls; +using SMTCProperty = ABI::Windows::Media::SystemMediaTransportControlsProperty; +using ISMTCDisplayUpdater = + ABI::Windows::Media::ISystemMediaTransportControlsDisplayUpdater; + +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Storage::Streams::IDataWriter; +using ABI::Windows::Storage::Streams::IRandomAccessStream; +using ABI::Windows::Storage::Streams::IRandomAccessStreamReference; +using Microsoft::WRL::ComPtr; + +class WindowsSMTCProvider final : public mozilla::dom::MediaControlKeySource { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowsSMTCProvider, override) + + public: + WindowsSMTCProvider(); + + bool IsOpened() const override; + bool Open() override; + void Close() override; + + void SetPlaybackState( + mozilla::dom::MediaSessionPlaybackState aState) override; + + void SetMediaMetadata( + const mozilla::dom::MediaMetadataBase& aMetadata) override; + + void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override; + + private: + ~WindowsSMTCProvider(); + void UnregisterEvents(); + bool RegisterEvents(); + + void OnButtonPressed(mozilla::dom::MediaControlKey aKey) const; + // Enable the SMTC interface + bool EnableControl(bool aEnabled) const; + // Sets the play, pause, next, previous buttons on the SMTC interface by + // mSupportedKeys + bool UpdateButtons() const; + bool IsKeySupported(mozilla::dom::MediaControlKey aKey) const; + bool EnableKey(mozilla::dom::MediaControlKey aKey, bool aEnable) const; + + bool InitDisplayAndControls(); + + // Sets the Metadata for the currently playing media and sets the playback + // type to "MUSIC" + bool SetMusicMetadata(const wchar_t* aArtist, const wchar_t* aTitle, + const wchar_t* aAlbumArtist); + + // Sets one of the artwork to the SMTC interface asynchronously + void LoadThumbnail(const nsTArray<mozilla::dom::MediaImage>& aArtwork); + // Stores the image at index aIndex of the mArtwork to the Thumbnail + // asynchronously + void LoadImageAtIndex(const size_t aIndex); + // Stores the raw binary data of an image to mImageStream and set it to the + // Thumbnail asynchronously + void LoadImage(const char* aImageData, uint32_t aDataSize); + // Sets the Thumbnail to the image stored in mImageStream + bool SetThumbnail(const nsAString& aUrl); + void ClearThumbnail(); + + bool UpdateThumbnail(const nsAString& aUrl); + void CancelPendingStoreAsyncOperation() const; + + void ClearMetadata(); + + bool mInitialized = false; + + // A bit table indicating what keys are enabled + uint32_t mSupportedKeys = 0; + + ComPtr<ISMTC> mControls; + ComPtr<ISMTCDisplayUpdater> mDisplay; + + // Use mImageDataWriter to write the binary data of image into mImageStream + // and refer the image by mImageStreamReference and then set it to the SMTC + // interface + ComPtr<IDataWriter> mImageDataWriter; + ComPtr<IRandomAccessStream> mImageStream; + ComPtr<IRandomAccessStreamReference> mImageStreamReference; + ComPtr<IAsyncOperation<unsigned int>> mStoreAsyncOperation; + + // mThumbnailUrl is the url of the current Thumbnail + // mProcessingUrl is the url that is being processed. The process starts from + // fetching an image from the url and then storing the fetched image to the + // mImageStream. If mProcessingUrl is not empty, it means there is an image is + // in processing + // mThumbnailUrl and mProcessingUrl won't be set at the same time and they can + // only be touched on main thread + nsString mThumbnailUrl; + nsString mProcessingUrl; + + // mArtwork can only be used in main thread in case of data racing + CopyableTArray<mozilla::dom::MediaImage> mArtwork; + size_t mNextImageIndex; + + mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher; + mozilla::MozPromiseRequestHolder<mozilla::dom::ImagePromise> + mImageFetchRequest; + + HWND mWindow; // handle to the invisible window + + // EventRegistrationTokens are used to have a handle on a callback (to remove + // it again) + EventRegistrationToken mButtonPressedToken; +}; + +#endif // __MINGW32__ +#endif // WIDGET_WINDOWS_WINDOWSSTMCPROVIDER_H_ diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp new file mode 100644 index 0000000000..a61dc245de --- /dev/null +++ b/widget/windows/WindowsUIUtils.cpp @@ -0,0 +1,461 @@ +/* -*- 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 <wrl.h> + +#include "nsServiceManagerUtils.h" + +#include "WindowsUIUtils.h" + +#include "nsIObserverService.h" +#include "nsIAppShellService.h" +#include "nsAppShellCID.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/Services.h" +#include "mozilla/WidgetUtils.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/media/MediaUtils.h" +#include "nsString.h" +#include "nsIWidget.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "nsWindowGfx.h" +#include "Units.h" + +/* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it + * until it's fixed. */ +#ifndef __MINGW32__ + +# include <windows.ui.viewmanagement.h> + +# pragma comment(lib, "runtimeobject.lib") + +using namespace ABI::Windows::UI; +using namespace ABI::Windows::UI::ViewManagement; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::ApplicationModel::DataTransfer; + +/* 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 +}; +} +} // namespace UI +} // namespace Windows +} // namespace ABI + +# 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); +} // namespace ViewManagement +} // namespace UI +} // namespace Windows +} // namespace ABI +# 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 + +# ifndef __IDataTransferManagerInterop_INTERFACE_DEFINED__ +# define __IDataTransferManagerInterop_INTERFACE_DEFINED__ + +typedef interface IDataTransferManagerInterop IDataTransferManagerInterop; + +MIDL_INTERFACE("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8") +IDataTransferManagerInterop : public IUnknown { + public: + virtual HRESULT STDMETHODCALLTYPE GetForWindow( + HWND appWindow, REFIID riid, void** dataTransferManager) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowShareUIForWindow(HWND appWindow) = 0; +}; + +# endif + +# if !defined( \ + ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__) +# define ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__ + +MIDL_INTERFACE("13a24ec8-9382-536f-852a-3045e1b29a3b") +IDataPackage4 : public IInspectable { + public: + virtual HRESULT STDMETHODCALLTYPE add_ShareCanceled( + __FITypedEventHandler_2_Windows__CApplicationModel__CDataTransfer__CDataPackage_IInspectable * + handler, + EventRegistrationToken * token) = 0; + virtual HRESULT STDMETHODCALLTYPE remove_ShareCanceled( + EventRegistrationToken token) = 0; +}; + +# endif + +#endif + +using namespace mozilla; + +WindowsUIUtils::WindowsUIUtils() : mInTabletMode(eTabletModeUnknown) {} + +WindowsUIUtils::~WindowsUIUtils() {} + +/* + * Implement the nsISupports methods... + */ +NS_IMPL_ISUPPORTS(WindowsUIUtils, nsIWindowsUIUtils) + +NS_IMETHODIMP +WindowsUIUtils::GetSystemSmallIconSize(int32_t* aSize) { + NS_ENSURE_ARG(aSize); + + mozilla::LayoutDeviceIntSize size = + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon); + *aSize = std::max(size.width, size.height); + return NS_OK; +} + +NS_IMETHODIMP +WindowsUIUtils::GetSystemLargeIconSize(int32_t* aSize) { + NS_ENSURE_ARG(aSize); + + mozilla::LayoutDeviceIntSize size = + nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon); + *aSize = std::max(size.width, size.height); + return NS_OK; +} + +NS_IMETHODIMP +WindowsUIUtils::SetWindowIcon(mozIDOMWindowProxy* aWindow, + imgIContainer* aSmallIcon, + imgIContainer* aBigIcon) { + NS_ENSURE_ARG(aWindow); + + nsCOMPtr<nsIWidget> widget = + nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget(); + nsWindow* window = static_cast<nsWindow*>(widget.get()); + + nsresult rv; + + if (aSmallIcon) { + HICON hIcon = nullptr; + rv = nsWindowGfx::CreateIcon( + aSmallIcon, false, mozilla::LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + + window->SetSmallIcon(hIcon); + } + + if (aBigIcon) { + HICON hIcon = nullptr; + rv = nsWindowGfx::CreateIcon( + aBigIcon, false, mozilla::LayoutDeviceIntPoint(), + nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &hIcon); + NS_ENSURE_SUCCESS(rv, rv); + + window->SetBigIcon(hIcon); + } + + return NS_OK; +} + +NS_IMETHODIMP +WindowsUIUtils::GetInTabletMode(bool* aResult) { + if (mInTabletMode == eTabletModeUnknown) { + UpdateTabletModeState(); + } + *aResult = mInTabletMode == eTabletModeOn; + return NS_OK; +} + +NS_IMETHODIMP +WindowsUIUtils::UpdateTabletModeState() { +#ifndef __MINGW32__ + if (!IsWin10OrLater()) { + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIWidget> widget; + nsCOMPtr<mozIDOMWindowProxy> navWin; + + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + if (NS_FAILED(rv) || !navWin) { + // Fall back to the hidden window + nsCOMPtr<nsIAppShellService> appShell( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + + rv = appShell->GetHiddenDOMWindow(getter_AddRefs(navWin)); + if (NS_FAILED(rv) || !navWin) { + return rv; + } + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + widget = widget::WidgetUtils::DOMWindowToWidget(win); + + 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(); + observerService->NotifyObservers( + nullptr, "tablet-mode-change", + ((mInTabletMode == eTabletModeOn) ? u"tablet-mode" + : u"normal-mode")); + } + } + } + } +#endif + + return NS_OK; +} + +#ifndef __MINGW32__ +struct HStringDeleter { + typedef HSTRING pointer; + void operator()(pointer aString) { WindowsDeleteString(aString); } +}; + +typedef UniquePtr<HSTRING, HStringDeleter> HStringUniquePtr; + +Result<HStringUniquePtr, HRESULT> ConvertToWindowsString( + const nsAString& aStr) { + HSTRING rawStr; + HRESULT hr = WindowsCreateString(PromiseFlatString(aStr).get(), aStr.Length(), + &rawStr); + if (FAILED(hr)) { + return Err(hr); + } + return HStringUniquePtr(rawStr); +} + +Result<Ok, nsresult> RequestShare( + const std::function<HRESULT(IDataRequestedEventArgs* pArgs)>& aCallback) { + if (!IsWin10OrLater()) { + return Err(NS_ERROR_FAILURE); + } + + HWND hwnd = GetForegroundWindow(); + if (!hwnd) { + return Err(NS_ERROR_FAILURE); + } + + ComPtr<IDataTransferManagerInterop> dtmInterop; + ComPtr<IDataTransferManager> dtm; + + HRESULT hr = RoGetActivationFactory( + HStringReference( + RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager) + .Get(), + IID_PPV_ARGS(&dtmInterop)); + if (FAILED(hr) || + FAILED(dtmInterop->GetForWindow(hwnd, IID_PPV_ARGS(&dtm)))) { + return Err(NS_ERROR_FAILURE); + } + + auto callback = Callback< + ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>( + [aCallback](IDataTransferManager*, + IDataRequestedEventArgs* pArgs) -> HRESULT { + return aCallback(pArgs); + }); + + EventRegistrationToken dataRequestedToken; + if (FAILED(dtm->add_DataRequested(callback.Get(), &dataRequestedToken)) || + FAILED(dtmInterop->ShowShareUIForWindow(hwnd))) { + return Err(NS_ERROR_FAILURE); + } + + return Ok(); +} +#endif + +RefPtr<SharePromise> WindowsUIUtils::Share(nsAutoString aTitle, + nsAutoString aText, + nsAutoString aUrl) { + auto promiseHolder = MakeRefPtr< + mozilla::media::Refcountable<MozPromiseHolder<SharePromise>>>(); + RefPtr<SharePromise> promise = promiseHolder->Ensure(__func__); + +#ifndef __MINGW32__ + auto result = RequestShare([promiseHolder, title = std::move(aTitle), + text = std::move(aText), url = std::move(aUrl)]( + IDataRequestedEventArgs* pArgs) { + ComPtr<IDataRequest> spDataRequest; + ComPtr<IDataPackage> spDataPackage; + ComPtr<IDataPackage2> spDataPackage2; + ComPtr<IDataPackage3> spDataPackage3; + ComPtr<IDataPackagePropertySet> spDataPackageProperties; + + if (FAILED(pArgs->get_Request(&spDataRequest)) || + FAILED(spDataRequest->get_Data(&spDataPackage)) || + FAILED(spDataPackage.As(&spDataPackage2)) || + FAILED(spDataPackage.As(&spDataPackage3)) || + FAILED(spDataPackage->get_Properties(&spDataPackageProperties))) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + + /* + * Windows always requires a title, and an empty string does not work. + * Thus we trick the API by passing a whitespace when we have no title. + * https://docs.microsoft.com/en-us/windows/uwp/app-to-app/share-data + */ + auto wTitle = ConvertToWindowsString((title.IsVoid() || title.Length() == 0) + ? nsAutoString(u" "_ns) + : title); + if (wTitle.isErr() || + FAILED(spDataPackageProperties->put_Title(wTitle.unwrap().get()))) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + + // Assign even if empty, as Windows requires some data to share + auto wText = ConvertToWindowsString(text); + if (wText.isErr() || FAILED(spDataPackage->SetText(wText.unwrap().get()))) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + + if (!url.IsVoid()) { + auto wUrl = ConvertToWindowsString(url); + if (wUrl.isErr()) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return wUrl.unwrapErr(); + } + + ComPtr<IUriRuntimeClassFactory> uriFactory; + ComPtr<IUriRuntimeClass> uri; + + auto hr = GetActivationFactory( + HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(), + &uriFactory); + + if (FAILED(hr) || + FAILED(uriFactory->CreateUri(wUrl.unwrap().get(), &uri)) || + FAILED(spDataPackage2->SetWebLink(uri.Get()))) { + promiseHolder->RejectIfExists(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + } + + auto completedCallback = + Callback<ITypedEventHandler<DataPackage*, ShareCompletedEventArgs*>>( + [promiseHolder](IDataPackage*, + IShareCompletedEventArgs*) -> HRESULT { + promiseHolder->ResolveIfExists(true, __func__); + return S_OK; + }); + + EventRegistrationToken dataRequestedToken; + if (FAILED(spDataPackage3->add_ShareCompleted(completedCallback.Get(), + &dataRequestedToken))) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + + ComPtr<IDataPackage4> spDataPackage4; + if (SUCCEEDED(spDataPackage.As(&spDataPackage4))) { + // Use SharedCanceled API only on supported versions of Windows + // So that the older ones can still use ShareUrl() + + auto canceledCallback = + Callback<ITypedEventHandler<DataPackage*, IInspectable*>>( + [promiseHolder](IDataPackage*, IInspectable*) -> HRESULT { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return S_OK; + }); + + if (FAILED(spDataPackage4->add_ShareCanceled(canceledCallback.Get(), + &dataRequestedToken))) { + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); + return E_FAIL; + } + } + + return S_OK; + }); + if (result.isErr()) { + promiseHolder->Reject(result.unwrapErr(), __func__); + } +#else + promiseHolder->Reject(NS_ERROR_FAILURE, __func__); +#endif + + return promise; +} + +NS_IMETHODIMP +WindowsUIUtils::ShareUrl(const nsAString& aUrlToShare, + const nsAString& aShareTitle) { + nsAutoString text; + text.SetIsVoid(true); + WindowsUIUtils::Share(nsAutoString(aShareTitle), text, + nsAutoString(aUrlToShare)); + return NS_OK; +} diff --git a/widget/windows/WindowsUIUtils.h b/widget/windows/WindowsUIUtils.h new file mode 100644 index 0000000000..b8743c5538 --- /dev/null +++ b/widget/windows/WindowsUIUtils.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_widget_WindowsUIUtils_h__ +#define mozilla_widget_WindowsUIUtils_h__ + +#include "nsIWindowsUIUtils.h" +#include "nsString.h" +#include "mozilla/MozPromise.h" + +typedef mozilla::MozPromise<bool, nsresult, /* IsExclusive */ true> + SharePromise; + +enum TabletModeState { eTabletModeUnknown = 0, eTabletModeOff, eTabletModeOn }; + +class WindowsUIUtils final : public nsIWindowsUIUtils { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWSUIUTILS + + WindowsUIUtils(); + + static RefPtr<SharePromise> Share(nsAutoString aTitle, nsAutoString aText, + nsAutoString aUrl); + + protected: + ~WindowsUIUtils(); + + TabletModeState mInTabletMode; +}; + +#endif // mozilla_widget_WindowsUIUtils_h__ diff --git a/widget/windows/components.conf b/widget/windows/components.conf new file mode 100644 index 0000000000..c37637445b --- /dev/null +++ b/widget/windows/components.conf @@ -0,0 +1,219 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Headers = [ + '/widget/windows/nsWidgetFactory.h', +] + +InitFunc = 'nsWidgetWindowsModuleCtor' +UnloadFunc = 'nsWidgetWindowsModuleDtor' + +Classes = [ + { + 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}', + 'contract_ids': ['@mozilla.org/gfx/screenmanager;1'], + 'singleton': True, + 'type': 'mozilla::widget::ScreenManager', + 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton', + 'headers': ['/widget/ScreenManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS, + }, + { + 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}', + 'contract_ids': ['@mozilla.org/widget/appshell/win;1'], + 'headers': ['/widget/windows/nsWidgetFactory.h'], + 'legacy_constructor': 'nsAppShellConstructor', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS, + }, + { + 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}', + 'contract_ids': ['@mozilla.org/widget/useridleservice;1'], + 'singleton': True, + 'type': 'nsUserIdleServiceWin', + 'constructor': 'nsUserIdleServiceWin::GetInstance', + 'headers': ['/widget/windows/nsUserIdleServiceWin.h', 'nsUserIdleService.h'], + }, + { + 'cid': '{b148eed2-236d-11d3-b35c-00a0cc3c1cde}', + 'contract_ids': ['@mozilla.org/sound;1'], + 'singleton': True, + 'type': 'nsISound', + 'constructor': 'nsSound::GetInstance', + 'headers': ['/widget/windows/nsSound.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}', + 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'], + 'type': 'nsClipboardHelper', + 'headers': ['/widget/nsClipboardHelper.h'], + }, + { + 'cid': '{b8e5bc54-a22f-4eb2-b061-24cb6d19c15f}', + 'contract_ids': ['@mozilla.org/windows-taskbar;1'], + 'type': 'mozilla::widget::WinTaskbar', + 'headers': ['/widget/windows/WinTaskbar.h'], + }, + { + 'cid': '{73a5946f-608d-454f-9d33-0b8f8c7294b6}', + 'contract_ids': ['@mozilla.org/windows-jumplistbuilder;1'], + 'type': 'mozilla::widget::JumpListBuilder', + 'headers': ['/widget/windows/JumpListBuilder.h'], + }, + { + 'cid': '{2b9a1f2c-27ce-45b6-8d4e-755d0e34f8db}', + 'contract_ids': ['@mozilla.org/windows-jumplistitem;1'], + 'type': 'mozilla::widget::JumpListItem', + 'headers': ['/widget/windows/JumpListItem.h'], + }, + { + 'cid': '{21f1f13b-f75a-42ad-867a-d91ad694447e}', + 'contract_ids': ['@mozilla.org/windows-jumplistseparator;1'], + 'type': 'mozilla::widget::JumpListSeparator', + 'headers': ['/widget/windows/JumpListItem.h'], + }, + { + 'cid': '{f72c5dc4-5a12-47be-be28-ab105f33b08f}', + 'contract_ids': ['@mozilla.org/windows-jumplistlink;1'], + 'type': 'mozilla::widget::JumpListLink', + 'headers': ['/widget/windows/JumpListItem.h'], + }, + { + 'cid': '{b16656b2-5187-498f-abf4-56346126bfdb}', + 'contract_ids': ['@mozilla.org/windows-jumplistshortcut;1'], + 'type': 'mozilla::widget::JumpListShortcut', + 'headers': ['/widget/windows/JumpListItem.h'], + }, + { + 'cid': '{e04a55e8-fee3-4ea2-a98b-41d2621adc3c}', + 'contract_ids': ['@mozilla.org/windows-ui-utils;1'], + 'type': 'WindowsUIUtils', + 'headers': ['/widget/windows/WindowsUIUtils.h'], + }, + { + 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/transferable;1'], + 'type': 'nsTransferable', + 'headers': ['/widget/nsTransferable.h'], + }, + { + 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'], + 'type': 'nsHTMLFormatConverter', + 'headers': ['/widget/nsHTMLFormatConverter.h'], + }, + { + 'cid': '{8b5314bb-db01-11d2-96ce-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/dragservice;1'], + 'type': 'nsDragService', + 'headers': ['/widget/windows/nsDragService.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{9a0cb62b-d638-4faf-9588-ae96f5e29093}', + 'contract_ids': ['@mozilla.org/widget/taskbar-preview-callback;1'], + 'type': 'mozilla::widget::TaskbarPreviewCallback', + 'headers': ['/widget/windows/TaskbarPreview.h'], + }, + { + 'cid': '{d755a760-9f27-11df-0800-200c9a664242}', + 'contract_ids': ['@mozilla.org/gfx/info;1'], + 'type': 'mozilla::widget::GfxInfo', + 'headers': ['/widget/windows/GfxInfo.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS, + }, + { + 'cid': '{bd57cee8-1dd1-11b2-9fe7-95cf4709aea3}', + 'contract_ids': ['@mozilla.org/filepicker;1'], + 'type': 'nsFilePicker', + 'headers': ['/widget/windows/nsFilePicker.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{0f872c8c-3ee6-46bd-92a2-69652c6b474e}', + 'contract_ids': ['@mozilla.org/colorpicker;1'], + 'type': 'nsColorPicker', + 'headers': ['/widget/windows/nsColorPicker.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{1201d357-8417-4926-a694-e6408fbedcf8}', + 'contract_ids': ['@mozilla.org/sharepicker;1'], + 'type': 'nsSharePicker', + 'headers': ['/widget/windows/nsSharePicker.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'js_name': 'clipboard', + 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/clipboard;1'], + 'interfaces': ['nsIClipboard'], + 'type': 'nsIClipboard', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + 'overridable': True, + }, + { + 'cid': '{b6e1a890-b2b8-4883-a65f-9476f6185313}', + 'contract_ids': ['@mozilla.org/widget/systemstatusbar;1'], + 'singleton': True, + 'init_method': 'Init', + 'type': 'mozilla::widget::SystemStatusBar', + 'constructor': 'mozilla::widget::SystemStatusBar::GetAddRefedSingleton', + 'headers': ['/widget/windows/SystemStatusBar.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] + +if buildconfig.substs['CC_TYPE'] in ('msvc', 'clang-cl'): + Classes += [ + { + 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}', + 'contract_ids': ['@mozilla.org/system-alerts-service;1'], + 'type': 'mozilla::widget::ToastNotification', + 'headers': ['/widget/windows/ToastNotification.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + ] + +if defined('NS_PRINTING'): + Classes += [ + { + 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}', + 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'], + 'type': 'nsDeviceContextSpecWin', + 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'], + }, + { + 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}', + 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'], + 'type': 'nsPrintDialogServiceWin', + 'headers': ['/widget/windows/nsPrintDialogWin.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}', + 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'], + 'type': 'nsPrintSettingsServiceWin', + 'headers': ['/widget/windows/nsPrintSettingsServiceWin.h'], + 'init_method': 'Init', + }, + { + 'cid': '{a6cf9129-15b3-11d2-932e-00805f8add32}', + 'contract_ids': ['@mozilla.org/gfx/printerlist;1'], + 'type': 'nsPrinterListWin', + 'headers': ['/widget/windows/nsDeviceContextSpecWin.h'], + }, + { + 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}', + 'contract_ids': ['@mozilla.org/gfx/printsession;1'], + 'type': 'nsPrintSession', + 'headers': ['/widget/nsPrintSession.h'], + 'init_method': 'Init', + }, + ] diff --git a/widget/windows/moz.build b/widget/windows/moz.build new file mode 100644 index 0000000000..4b1f0102b0 --- /dev/null +++ b/widget/windows/moz.build @@ -0,0 +1,192 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Widget: Win32") + SCHEDULES.exclusive = ["windows"] + +with Files("*CompositorWidget*"): + BUG_COMPONENT = ("Core", "Graphics") + +with Files("*IMEHandler*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +with Files("*IMMHandler*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +with Files("*KeyboardLayout*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +with Files("OSK*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +with Files("*TSFTextStore*"): + BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling") + +TEST_DIRS += ["tests"] + +EXPORTS += [ + "nsdefs.h", + "WindowHook.h", + "WinUtils.h", +] + +EXPORTS.mozilla += [ + "ShellHeaderOnlyUtils.h", + "UrlmonHeaderOnlyUtils.h", + "WindowsConsole.h", + "WinHeaderOnlyUtils.h", +] + +EXPORTS.mozilla.widget += [ + "AudioSession.h", + "CompositorWidgetChild.h", + "CompositorWidgetParent.h", + "InProcessWinCompositorWidget.h", + "WinCompositorWidget.h", + "WinCompositorWindowThread.h", + "WinContentSystemParameters.h", + "WindowsEMF.h", + "WindowsSMTCProvider.h", + "WinMessages.h", + "WinModifierKeyState.h", + "WinNativeEventData.h", +] + +UNIFIED_SOURCES += [ + "AudioSession.cpp", + "CompositorWidgetChild.cpp", + "GfxInfo.cpp", + "IconLoaderHelperWin.cpp", + "IEnumFE.cpp", + "IMMHandler.cpp", + "InkCollector.cpp", + "JumpListItem.cpp", + "KeyboardLayout.cpp", + "LSPAnnotator.cpp", + "nsAppShell.cpp", + "nsClipboard.cpp", + "nsColorPicker.cpp", + "nsDataObj.cpp", + "nsDataObjCollection.cpp", + "nsDragService.cpp", + "nsLookAndFeel.cpp", + "nsNativeBasicThemeWin.cpp", + "nsNativeDragSource.cpp", + "nsNativeDragTarget.cpp", + "nsNativeThemeWin.cpp", + "nsSound.cpp", + "nsToolkit.cpp", + "nsUserIdleServiceWin.cpp", + "nsUXThemeData.cpp", + "nsWindow.cpp", + "nsWindowBase.cpp", + "nsWindowDbg.cpp", + "nsWindowGfx.cpp", + "nsWinGesture.cpp", + "RemoteBackbuffer.cpp", + "ScreenHelperWin.cpp", + "ScrollbarUtil.cpp", + "SystemStatusBar.cpp", + "TaskbarPreview.cpp", + "TaskbarPreviewButton.cpp", + "TaskbarTabPreview.cpp", + "TaskbarWindowPreview.cpp", + "WidgetTraceEvent.cpp", + "WinCompositorWindowThread.cpp", + "WindowHook.cpp", + "WindowsConsole.cpp", + "WinIMEHandler.cpp", + "WinPointerEvents.cpp", + "WinTaskbar.cpp", + "WinTextEventDispatcherListener.cpp", + "WinUtils.cpp", +] + +# The following files cannot be built in unified mode because of name clashes. +SOURCES += [ + "CompositorWidgetParent.cpp", + "InProcessWinCompositorWidget.cpp", + "JumpListBuilder.cpp", + "MediaKeysEventSourceFactory.cpp", + "nsBidiKeyboard.cpp", + "nsFilePicker.cpp", + "nsSharePicker.cpp", + "nsWidgetFactory.cpp", + "OSKInputPaneManager.cpp", + "WinCompositorWidget.cpp", + "WinContentSystemParameters.cpp", + "WindowsSMTCProvider.cpp", + "WindowsUIUtils.cpp", + "WinMouseScrollHandler.cpp", +] + +# These files redefine the winsdk api version macro and we don't want it to leak to other files. +SOURCES += [ + "DirectManipulationOwner.cpp", +] + +# Needs INITGUID and we don't allow INITGUID in unified sources since bug 970429. +SOURCES += [ + "InputDeviceUtils.cpp", + "TSFTextStore.cpp", +] + +if CONFIG["NS_PRINTING"]: + UNIFIED_SOURCES += [ + "nsDeviceContextSpecWin.cpp", + "nsPrintDialogUtil.cpp", + "nsPrintDialogWin.cpp", + "nsPrinterWin.cpp", + "nsPrintSettingsServiceWin.cpp", + "nsPrintSettingsWin.cpp", + ] + +if CONFIG["MOZ_ENABLE_SKIA_PDF"]: + UNIFIED_SOURCES += [ + "WindowsEMF.cpp", + ] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_ENABLE_SKIA_PDF"]: + LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +LOCAL_INCLUDES += [ + "/layout/forms", + "/layout/generic", + "/layout/style", + "/layout/xul", + "/toolkit/xre", + "/widget", + "/widget/headless", + "/xpcom/base", +] + +DEFINES["MOZ_UNICODE"] = True + +for var in "MOZ_ENABLE_D3D10_LAYER": + if CONFIG[var]: + DEFINES[var] = True + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] + +OS_LIBS += [ + "rpcrt4", + "urlmon", +] + +if CONFIG["CC_TYPE"] == "clang-cl": + SOURCES += [ + "ToastNotification.cpp", + "ToastNotificationHandler.cpp", + ] diff --git a/widget/windows/nsAppShell.cpp b/widget/windows/nsAppShell.cpp new file mode 100644 index 0000000000..d1d654d96f --- /dev/null +++ b/widget/windows/nsAppShell.cpp @@ -0,0 +1,781 @@ +/* -*- 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 "nsWindow.h" +#include "nsString.h" +#include "WinIMEHandler.h" +#include "mozilla/widget/AudioSession.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/Hal.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIPowerManagerService.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "GeckoProfiler.h" +#include "nsComponentManagerUtils.h" +#include "ScreenHelperWin.h" +#include "HeadlessScreenHelper.h" +#include "mozilla/widget/ScreenManager.h" +#include "mozilla/Atomics.h" + +#if defined(ACCESSIBILITY) +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/Platform.h" +#endif // defined(ACCESSIBILITY) + +using namespace mozilla; +using namespace mozilla::widget; + +#define WAKE_LOCK_LOG(...) \ + MOZ_LOG(gWinWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +static mozilla::LazyLogModule gWinWakeLockLog("WinWakeLock"); + +// This wakelock listener is used for Window7 and above. +class WinWakeLockListener final : public nsIDOMMozWakeLockListener { + public: + NS_DECL_ISUPPORTS + WinWakeLockListener() { MOZ_ASSERT(XRE_IsParentProcess()); } + + private: + ~WinWakeLockListener() { + ReleaseWakelockIfNeeded(PowerRequestDisplayRequired); + ReleaseWakelockIfNeeded(PowerRequestExecutionRequired); + } + + void SetHandle(HANDLE aHandle, POWER_REQUEST_TYPE aType) { + switch (aType) { + case PowerRequestDisplayRequired: { + if (!aHandle && mDisplayHandle) { + CloseHandle(mDisplayHandle); + } + mDisplayHandle = aHandle; + return; + } + case PowerRequestExecutionRequired: { + if (!aHandle && mNonDisplayHandle) { + CloseHandle(mNonDisplayHandle); + } + mNonDisplayHandle = aHandle; + return; + } + default: + MOZ_ASSERT_UNREACHABLE("Invalid request type"); + return; + } + } + + HANDLE GetHandle(POWER_REQUEST_TYPE aType) const { + switch (aType) { + case PowerRequestDisplayRequired: + return mDisplayHandle; + case PowerRequestExecutionRequired: + return mNonDisplayHandle; + default: + MOZ_ASSERT_UNREACHABLE("Invalid request type"); + return nullptr; + } + } + + HANDLE CreateHandle(POWER_REQUEST_TYPE aType) { + MOZ_ASSERT(!GetHandle(aType)); + REASON_CONTEXT context = {0}; + context.Version = POWER_REQUEST_CONTEXT_VERSION; + context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; + context.Reason.SimpleReasonString = RequestTypeLPWSTR(aType); + HANDLE handle = PowerCreateRequest(&context); + if (!handle) { + WAKE_LOCK_LOG("Failed to create handle for %s, error=%d", + RequestTypeStr(aType), GetLastError()); + return nullptr; + } + SetHandle(handle, aType); + return handle; + } + + LPWSTR RequestTypeLPWSTR(POWER_REQUEST_TYPE aType) const { + switch (aType) { + case PowerRequestDisplayRequired: + return const_cast<LPWSTR>(L"display request"); // -Wwritable-strings + case PowerRequestExecutionRequired: + return const_cast<LPWSTR>( + L"non-display request"); // -Wwritable-strings + default: + MOZ_ASSERT_UNREACHABLE("Invalid request type"); + return const_cast<LPWSTR>(L"unknown"); // -Wwritable-strings + } + } + + const char* RequestTypeStr(POWER_REQUEST_TYPE aType) const { + switch (aType) { + case PowerRequestDisplayRequired: + return "display request"; + case PowerRequestExecutionRequired: + return "non-display request"; + default: + MOZ_ASSERT_UNREACHABLE("Invalid request type"); + return "unknown"; + } + } + + void RequestWakelockIfNeeded(POWER_REQUEST_TYPE aType) { + if (GetHandle(aType)) { + WAKE_LOCK_LOG("Already requested lock for %s", RequestTypeStr(aType)); + return; + } + + WAKE_LOCK_LOG("Prepare a wakelock for %s", RequestTypeStr(aType)); + HANDLE handle = CreateHandle(aType); + if (!handle) { + WAKE_LOCK_LOG("Failed due to no handle for %s", RequestTypeStr(aType)); + return; + } + + if (PowerSetRequest(handle, aType)) { + WAKE_LOCK_LOG("Requested %s lock", RequestTypeStr(aType)); + } else { + WAKE_LOCK_LOG("Failed to request %s lock, error=%d", + RequestTypeStr(aType), GetLastError()); + SetHandle(nullptr, aType); + } + } + + void ReleaseWakelockIfNeeded(POWER_REQUEST_TYPE aType) { + if (!GetHandle(aType)) { + WAKE_LOCK_LOG("Already released lock for %s", RequestTypeStr(aType)); + return; + } + + WAKE_LOCK_LOG("Prepare to release wakelock for %s", RequestTypeStr(aType)); + if (!PowerClearRequest(GetHandle(aType), aType)) { + WAKE_LOCK_LOG("Failed to release %s lock, error=%d", + RequestTypeStr(aType), GetLastError()); + return; + } + SetHandle(nullptr, aType); + WAKE_LOCK_LOG("Released wakelock for %s", RequestTypeStr(aType)); + } + + NS_IMETHOD Callback(const nsAString& aTopic, + const nsAString& aState) override { + WAKE_LOCK_LOG("topic=%s, state=%s", NS_ConvertUTF16toUTF8(aTopic).get(), + NS_ConvertUTF16toUTF8(aState).get()); + if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") && + !aTopic.EqualsASCII("video-playing")) { + return NS_OK; + } + + const bool isNonDisplayLock = aTopic.EqualsASCII("audio-playing"); + bool requestLock = false; + if (isNonDisplayLock) { + requestLock = aState.EqualsASCII("locked-foreground") || + aState.EqualsASCII("locked-background"); + } else { + requestLock = aState.EqualsASCII("locked-foreground"); + } + + if (isNonDisplayLock) { + if (requestLock) { + RequestWakelockIfNeeded(PowerRequestExecutionRequired); + } else { + ReleaseWakelockIfNeeded(PowerRequestExecutionRequired); + } + } else { + if (requestLock) { + RequestWakelockIfNeeded(PowerRequestDisplayRequired); + } else { + ReleaseWakelockIfNeeded(PowerRequestDisplayRequired); + } + } + return NS_OK; + } + + // Handle would only exist when we request wakelock successfully. + HANDLE mDisplayHandle = nullptr; + HANDLE mNonDisplayHandle = nullptr; +}; +NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener) + +// This wakelock is used for the version older than Windows7. +class LegacyWinWakeLockListener final : public nsIDOMMozWakeLockListener { + public: + NS_DECL_ISUPPORTS + LegacyWinWakeLockListener() { MOZ_ASSERT(XRE_IsParentProcess()); } + + private: + ~LegacyWinWakeLockListener() {} + + NS_IMETHOD Callback(const nsAString& aTopic, + const nsAString& aState) override { + WAKE_LOCK_LOG("WinWakeLock: topic=%s, state=%s", + NS_ConvertUTF16toUTF8(aTopic).get(), + NS_ConvertUTF16toUTF8(aState).get()); + if (!aTopic.EqualsASCII("screen") && !aTopic.EqualsASCII("audio-playing") && + !aTopic.EqualsASCII("video-playing")) { + return NS_OK; + } + + // Check what kind of lock we will require, if both display lock and non + // display lock are needed, we would require display lock because it has + // higher priority. + if (aTopic.EqualsASCII("audio-playing")) { + mRequireForNonDisplayLock = aState.EqualsASCII("locked-foreground") || + aState.EqualsASCII("locked-background"); + } else if (aTopic.EqualsASCII("screen") || + aTopic.EqualsASCII("video-playing")) { + mRequireForDisplayLock = aState.EqualsASCII("locked-foreground"); + } + + if (mRequireForDisplayLock) { + WAKE_LOCK_LOG("WinWakeLock: Request display lock"); + SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); + } else if (mRequireForNonDisplayLock) { + WAKE_LOCK_LOG("WinWakeLock: Request non-display lock"); + SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS); + } else { + WAKE_LOCK_LOG("WinWakeLock: reset lock"); + SetThreadExecutionState(ES_CONTINUOUS); + } + return NS_OK; + } + + bool mRequireForDisplayLock = false; + bool mRequireForNonDisplayLock = false; +}; + +NS_IMPL_ISUPPORTS(LegacyWinWakeLockListener, nsIDOMMozWakeLockListener) +StaticRefPtr<nsIDOMMozWakeLockListener> sWakeLockListener; + +static void AddScreenWakeLockListener() { + nsCOMPtr<nsIPowerManagerService> sPowerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (sPowerManagerService) { + if (IsWin7SP1OrLater()) { + sWakeLockListener = new WinWakeLockListener(); + } else { + sWakeLockListener = new LegacyWinWakeLockListener(); + } + sPowerManagerService->AddWakeLockListener(sWakeLockListener); + } else { + NS_WARNING( + "Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } +} + +static void RemoveScreenWakeLockListener() { + nsCOMPtr<nsIPowerManagerService> sPowerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (sPowerManagerService) { + sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); + sPowerManagerService = nullptr; + sWakeLockListener = nullptr; + } +} + +class SingleNativeEventPump final : public nsIThreadObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADOBSERVER + + SingleNativeEventPump() { + MOZ_ASSERT(!XRE_UseNativeEventProcessing(), + "Should only be used when not properly processing events."); + } + + private: + ~SingleNativeEventPump() {} +}; + +NS_IMPL_ISUPPORTS(SingleNativeEventPump, nsIThreadObserver) + +NS_IMETHODIMP +SingleNativeEventPump::OnDispatchedEvent() { return NS_OK; } + +NS_IMETHODIMP +SingleNativeEventPump::OnProcessNextEvent(nsIThreadInternal* aThread, + bool aMayWait) { + MSG msg; + bool gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + if (gotMessage) { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + return NS_OK; +} + +NS_IMETHODIMP +SingleNativeEventPump::AfterProcessNextEvent(nsIThreadInternal* aThread, + bool aMayWait) { + return NS_OK; +} + +// RegisterWindowMessage values +// Native event callback message +const wchar_t* kAppShellGeckoEventId = L"nsAppShell:EventID"; +UINT sAppShellGeckoMsgId; +// Taskbar button creation message +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; + +//------------------------------------------------------------------------- + +// Note that since we're on x86-ish processors here, ReleaseAcquire is the +// semantics that normal loads and stores would use anyway. +static Atomic<size_t, ReleaseAcquire> sOutstandingNativeEventCallbacks; + +/*static*/ LRESULT CALLBACK nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + if (uMsg == sAppShellGeckoMsgId) { + // The app shell might have been destroyed between this message being + // posted and being executed, so be extra careful. + if (!sOutstandingNativeEventCallbacks) { + return TRUE; + } + + nsAppShell* as = reinterpret_cast<nsAppShell*>(lParam); + as->NativeEventCallback(); + --sOutstandingNativeEventCallbacks; + return TRUE; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +nsAppShell::~nsAppShell() { + hal::Shutdown(); + + if (mEventWnd) { + // DestroyWindow doesn't do anything when called from a non UI thread. + // Since mEventWnd was created on the UI thread, it must be destroyed on + // the UI thread. + SendMessage(mEventWnd, WM_CLOSE, 0, 0); + } + + // Cancel any outstanding native event callbacks. + sOutstandingNativeEventCallbacks = 0; +} + +#if defined(ACCESSIBILITY) + +static ULONG gUiaMsg; +static HHOOK gUiaHook; +static uint32_t gUiaAttempts; +static const uint32_t kMaxUiaAttempts = 5; + +static void InitUIADetection(); + +static LRESULT CALLBACK UiaHookProc(int aCode, WPARAM aWParam, LPARAM aLParam) { + if (aCode < 0) { + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + + auto cwp = reinterpret_cast<CWPSTRUCT*>(aLParam); + if (gUiaMsg && cwp->message == gUiaMsg) { + if (gUiaAttempts < kMaxUiaAttempts) { + ++gUiaAttempts; + + Maybe<bool> shouldCallNextHook = + a11y::Compatibility::OnUIAMessage(cwp->wParam, cwp->lParam); + if (shouldCallNextHook.isSome()) { + // We've got an instantiator. + if (!shouldCallNextHook.value()) { + // We're blocking this instantiation. We need to keep this hook set + // so that we can catch any future instantiation attempts. + return 0; + } + + // We're allowing the instantiator to proceed, so this hook is no longer + // needed. + if (::UnhookWindowsHookEx(gUiaHook)) { + gUiaHook = nullptr; + } + } else { + // Our hook might be firing after UIA; let's try reinstalling ourselves. + InitUIADetection(); + } + } else { + // We've maxed out our attempts. Let's unhook. + if (::UnhookWindowsHookEx(gUiaHook)) { + gUiaHook = nullptr; + } + } + } + + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); +} + +static void InitUIADetection() { + if (gUiaHook) { + // In this case we want to re-hook so that the hook is always called ahead + // of UIA's hook. + if (::UnhookWindowsHookEx(gUiaHook)) { + gUiaHook = nullptr; + } + } + + if (!gUiaMsg) { + // This is the message that UIA sends to trigger a command. UIA's + // CallWndProc looks for this message and then handles the request. + // Our hook gets in front of UIA's hook and examines the message first. + gUiaMsg = ::RegisterWindowMessageW(L"HOOKUTIL_MSG"); + } + + if (!gUiaHook) { + gUiaHook = ::SetWindowsHookEx(WH_CALLWNDPROC, &UiaHookProc, nullptr, + ::GetCurrentThreadId()); + } +} + +#endif // defined(ACCESSIBILITY) + +NS_IMETHODIMP +nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIObserverService> obsServ( + mozilla::services::GetObserverService()); + +#if defined(ACCESSIBILITY) + if (!strcmp(aTopic, "dll-loaded-main-thread")) { + if (a11y::PlatformDisabledState() != a11y::ePlatformIsDisabled && + !gUiaHook) { + nsDependentString dllName(aData); + + if (StringEndsWith(dllName, u"uiautomationcore.dll"_ns, + nsCaseInsensitiveStringComparator)) { + InitUIADetection(); + + // Now that we've handled the observer notification, we can remove it + obsServ->RemoveObserver(this, "dll-loaded-main-thread"); + } + } + + return NS_OK; + } +#endif // defined(ACCESSIBILITY) + + if (!strcmp(aTopic, "sessionstore-restoring-on-startup")) { + nsWindow::SetIsRestoringSession(true); + // Now that we've handled the observer notification, we can remove it + obsServ->RemoveObserver(this, "sessionstore-restoring-on-startup"); + return NS_OK; + } + + if (!strcmp(aTopic, "sessionstore-windows-restored")) { + nsWindow::SetIsRestoringSession(false); + // Now that we've handled the observer notification, we can remove it + obsServ->RemoveObserver(this, "sessionstore-windows-restored"); + return NS_OK; + } + } + + return nsBaseAppShell::Observe(aSubject, aTopic, aData); +} + +nsresult nsAppShell::Init() { + LSPAnnotate(); + + hal::Init(); + + if (XRE_IsParentProcess()) { + sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId); + NS_ASSERTION(sTaskbarButtonCreatedMsg, + "Could not register taskbar button creation message"); + } + + // The hidden message window is used for interrupting the processing of native + // events, so that we can process gecko events. Therefore, we only need it if + // we are processing native events. Disabling this is required for win32k + // syscall lockdown. + if (XRE_UseNativeEventProcessing()) { + sAppShellGeckoMsgId = ::RegisterWindowMessageW(kAppShellGeckoEventId); + NS_ASSERTION(sAppShellGeckoMsgId, + "Could not register hidden window event message!"); + + mLastNativeEventScheduled = TimeStamp::NowLoRes(); + + 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, HWND_MESSAGE, nullptr, module, nullptr); + NS_ENSURE_STATE(mEventWnd); + } else if (XRE_IsContentProcess()) { + // We're not generally processing native events, but still using GDI and we + // still have some internal windows, e.g. from calling CoInitializeEx. + // So we use a class that will do a single event pump where previously we + // might have processed multiple events to make sure any occasional messages + // to these windows are processed. This also allows any internal Windows + // messages to be processed to ensure the GDI data remains fresh. + nsCOMPtr<nsIThreadInternal> threadInt = + do_QueryInterface(NS_GetCurrentThread()); + if (threadInt) { + threadInt->SetObserver(new SingleNativeEventPump()); + } + } + + if (XRE_IsParentProcess()) { + ScreenManager& screenManager = ScreenManager::GetSingleton(); + if (gfxPlatform::IsHeadless()) { + screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>()); + } else { + screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperWin>()); + ScreenHelperWin::RefreshScreens(); + } + + nsCOMPtr<nsIObserverService> obsServ( + mozilla::services::GetObserverService()); + + obsServ->AddObserver(this, "sessionstore-restoring-on-startup", false); + obsServ->AddObserver(this, "sessionstore-windows-restored", false); + +#if defined(ACCESSIBILITY) + if (::GetModuleHandleW(L"uiautomationcore.dll")) { + InitUIADetection(); + } else { + obsServ->AddObserver(this, "dll-loaded-main-thread", false); + } +#endif // defined(ACCESSIBILITY) + } + + 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. Whole firefox + // only needs one wakelock instance, so we would only create one listener in + // chrome process to prevent requesting unnecessary wakelock. + if (XRE_IsParentProcess()) { + AddScreenWakeLockListener(); + } + + nsresult rv = nsBaseAppShell::Run(); + + if (XRE_IsParentProcess()) { + RemoveScreenWakeLockListener(); + } + + return rv; +} + +NS_IMETHODIMP +nsAppShell::Exit(void) { +#if defined(ACCESSIBILITY) + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIObserverService> obsServ( + mozilla::services::GetObserverService()); + obsServ->RemoveObserver(this, "dll-loaded-main-thread"); + + if (gUiaHook && ::UnhookWindowsHookEx(gUiaHook)) { + gUiaHook = nullptr; + } + } +#endif // defined(ACCESSIBILITY) + + 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(); + mNativeCallbackPending = false; + } else { + mNativeCallbackPending = true; + } +} + +void nsAppShell::ScheduleNativeEventCallback() { + MOZ_ASSERT(mEventWnd, + "We should have created mEventWnd in Init, if this is called."); + + // Post a message to the hidden message window + ++sOutstandingNativeEventCallbacks; + { + MutexAutoLock lock(mLastNativeEventScheduledMutex); + // Time stamp this event so we can detect cases where the event gets + // dropping in sub classes / modal loops we do not control. + mLastNativeEventScheduled = TimeStamp::NowLoRes(); + } + ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, + reinterpret_cast<LPARAM>(this)); +} + +bool nsAppShell::ProcessNextNativeEvent(bool mayWait) { + // Notify ipc we are spinning a (possibly nested) gecko event loop. + mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch(); + + bool gotMessage = false; + + do { + MSG msg; + 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::BackgroundHangMonitor().NotifyActivity(); + + if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST && + IMEHandler::ProcessRawKeyMessage(msg)) { + continue; // the message is consumed. + } + +#if defined(_X86_) + // Store Printer dialog messages for reposting on x86, because on x86 + // Windows 7 they are not processed by a window procedure, but are + // explicitly waited for in the winspool.drv code that will be further + // up the stack (winspool!WaitForCompletionMessage). These are + // undocumented Windows Message identifiers found in winspool.drv. + if (msg.message == 0x5b7a || msg.message == 0x5b7f || + msg.message == 0x5b80 || msg.message == 0x5b81) { + mMsgsToRepost.push_back(msg); + continue; + } +#endif + + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } else if (mayWait) { + // Block and wait for any posted application message + mozilla::BackgroundHangMonitor().NotifyWait(); + { + AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent::Wait", IDLE); + WinUtils::WaitForMessage(); + } + } + } while (!gotMessage && mayWait); + + // See DoProcessNextNativeEvent, mEventloopNestingLevel will be + // one when a modal loop unwinds. + if (mNativeCallbackPending && mEventloopNestingLevel == 1) + DoProcessMoreGeckoEvents(); + + // Check for starved native callbacks. If we haven't processed one + // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off. + static const mozilla::TimeDuration nativeEventStarvationLimit = + mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT); + + TimeDuration timeSinceLastNativeEventScheduled; + { + MutexAutoLock lock(mLastNativeEventScheduledMutex); + timeSinceLastNativeEventScheduled = + TimeStamp::NowLoRes() - mLastNativeEventScheduled; + } + if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) { + ScheduleNativeEventCallback(); + } + + return gotMessage; +} + +nsresult nsAppShell::AfterProcessNextEvent(nsIThreadInternal* /* unused */, + bool /* unused */) { + if (!mMsgsToRepost.empty()) { + for (MSG msg : mMsgsToRepost) { + ::PostMessageW(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + mMsgsToRepost.clear(); + } + return NS_OK; +} diff --git a/widget/windows/nsAppShell.h b/widget/windows/nsAppShell.h new file mode 100644 index 0000000000..0c68d6b0a1 --- /dev/null +++ b/widget/windows/nsAppShell.h @@ -0,0 +1,61 @@ +/* -*- 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() override; + NS_IMETHOD Exit() override; + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override; + + virtual void ScheduleNativeEventCallback(); + virtual bool ProcessNextNativeEvent(bool mayWait); + virtual ~nsAppShell(); + + static LRESULT CALLBACK EventWindowProc(HWND, UINT, WPARAM, LPARAM); + + protected: + 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..87d81d458e --- /dev/null +++ b/widget/windows/nsBidiKeyboard.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsBidiKeyboard.h" +#include "WidgetUtils.h" +#include "nsIWidget.h" +#include <tchar.h> + +NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard) + +nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard() { Reset(); } + +nsBidiKeyboard::~nsBidiKeyboard() {} + +NS_IMETHODIMP nsBidiKeyboard::Reset() { + mInitialized = false; + mHaveBidiKeyboards = false; + mLTRKeyboard[0] = '\0'; + mRTLKeyboard[0] = '\0'; + mCurrentLocaleName[0] = '\0'; + return NS_OK; +} + +NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool* aIsRTL) { + *aIsRTL = false; + + nsresult result = SetupBidiKeyboards(); + if (NS_FAILED(result)) return result; + + HKL currentLocale; + + currentLocale = ::GetKeyboardLayout(0); + *aIsRTL = IsRTLLanguage(currentLocale); + + if (!::GetKeyboardLayoutNameW(mCurrentLocaleName)) return NS_ERROR_FAILURE; + + NS_ASSERTION(*mCurrentLocaleName, + "GetKeyboardLayoutName return string length == 0"); + NS_ASSERTION((wcslen(mCurrentLocaleName) < KL_NAMELENGTH), + "GetKeyboardLayoutName return string length >= KL_NAMELENGTH"); + + // The language set by the user overrides the default language for that + // direction + if (*aIsRTL) { + wcsncpy(mRTLKeyboard, mCurrentLocaleName, KL_NAMELENGTH); + mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate + } else { + wcsncpy(mLTRKeyboard, mCurrentLocaleName, KL_NAMELENGTH); + mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate + } + + NS_ASSERTION((wcslen(mRTLKeyboard) < KL_NAMELENGTH), + "mLTRKeyboard has string length >= KL_NAMELENGTH"); + NS_ASSERTION((wcslen(mLTRKeyboard) < KL_NAMELENGTH), + "mRTLKeyboard has string length >= KL_NAMELENGTH"); + return NS_OK; +} + +NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + nsresult result = SetupBidiKeyboards(); + if (NS_FAILED(result)) return result; + + *aResult = mHaveBidiKeyboards; + return NS_OK; +} + +// Get the list of keyboard layouts available in the system +// Set mLTRKeyboard to the first LTR keyboard in the list and mRTLKeyboard to +// the first RTL keyboard in the list These defaults will be used unless the +// user explicitly sets something else. +nsresult nsBidiKeyboard::SetupBidiKeyboards() { + if (mInitialized) return mHaveBidiKeyboards ? NS_OK : NS_ERROR_FAILURE; + + int keyboards; + HKL far* buf; + HKL locale; + wchar_t localeName[KL_NAMELENGTH]; + bool isLTRKeyboardSet = false; + bool isRTLKeyboardSet = false; + + // GetKeyboardLayoutList with 0 as first parameter returns the number of + // keyboard layouts available + keyboards = ::GetKeyboardLayoutList(0, nullptr); + if (!keyboards) return NS_ERROR_FAILURE; + + // allocate a buffer to hold the list + buf = (HKL far*)malloc(keyboards * sizeof(HKL)); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; + + // Call again to fill the buffer + if (::GetKeyboardLayoutList(keyboards, buf) != keyboards) { + free(buf); + return NS_ERROR_UNEXPECTED; + } + + // Go through the list and pick a default LTR and RTL keyboard layout + while (keyboards--) { + locale = buf[keyboards]; + if (IsRTLLanguage(locale)) { + _snwprintf(mRTLKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1, + LANGIDFROMLCID((DWORD_PTR)locale)); + isRTLKeyboardSet = true; + } else { + _snwprintf(mLTRKeyboard, KL_NAMELENGTH, L"%.*x", KL_NAMELENGTH - 1, + LANGIDFROMLCID((DWORD_PTR)locale)); + isLTRKeyboardSet = true; + } + } + free(buf); + mInitialized = true; + + // If there is not at least one keyboard of each directionality, Bidi + // keyboard functionality will be disabled. + mHaveBidiKeyboards = (isRTLKeyboardSet && isLTRKeyboardSet); + if (!mHaveBidiKeyboards) return NS_ERROR_FAILURE; + + // Get the current keyboard layout and use it for either mRTLKeyboard or + // mLTRKeyboard as appropriate. If the user has many keyboard layouts + // installed this prevents us from arbitrarily resetting the current + // layout (bug 80274) + locale = ::GetKeyboardLayout(0); + if (!::GetKeyboardLayoutNameW(localeName)) return NS_ERROR_FAILURE; + + NS_ASSERTION(*localeName, "GetKeyboardLayoutName return string length == 0"); + NS_ASSERTION((wcslen(localeName) < KL_NAMELENGTH), + "GetKeyboardLayout return string length >= KL_NAMELENGTH"); + + if (IsRTLLanguage(locale)) { + wcsncpy(mRTLKeyboard, localeName, KL_NAMELENGTH); + mRTLKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate + } else { + wcsncpy(mLTRKeyboard, localeName, KL_NAMELENGTH); + mLTRKeyboard[KL_NAMELENGTH - 1] = '\0'; // null terminate + } + + NS_ASSERTION(*mRTLKeyboard, "mLTRKeyboard has string length == 0"); + NS_ASSERTION(*mLTRKeyboard, "mLTRKeyboard has string length == 0"); + + return NS_OK; +} + +// Test whether the language represented by this locale identifier is a +// right-to-left language, using bit 123 of the Unicode subset bitfield in +// the LOCALESIGNATURE +// See +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_63ub.asp +bool nsBidiKeyboard::IsRTLLanguage(HKL aLocale) { + LOCALESIGNATURE localesig; + return (::GetLocaleInfoW(PRIMARYLANGID((DWORD_PTR)aLocale), + LOCALE_FONTSIGNATURE, (LPWSTR)&localesig, + (sizeof(localesig) / sizeof(WCHAR))) && + (localesig.lsUsb[3] & 0x08000000)); +} + +// static +void nsBidiKeyboard::OnLayoutChange() { + mozilla::widget::WidgetUtils::SendBidiKeyboardInfoToContent(); +} + +// static +already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() { + return do_AddRef(new nsBidiKeyboard()); +} diff --git a/widget/windows/nsBidiKeyboard.h b/widget/windows/nsBidiKeyboard.h new file mode 100644 index 0000000000..584d4d2dee --- /dev/null +++ b/widget/windows/nsBidiKeyboard.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsBidiKeyboard +#define __nsBidiKeyboard +#include "nsIBidiKeyboard.h" +#include <windows.h> + +class nsBidiKeyboard : public nsIBidiKeyboard { + virtual ~nsBidiKeyboard(); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIBIDIKEYBOARD + + nsBidiKeyboard(); + + static void OnLayoutChange(); + + protected: + nsresult SetupBidiKeyboards(); + bool IsRTLLanguage(HKL aLocale); + + bool mInitialized; + bool mHaveBidiKeyboards; + wchar_t mLTRKeyboard[KL_NAMELENGTH]; + wchar_t mRTLKeyboard[KL_NAMELENGTH]; + wchar_t mCurrentLocaleName[KL_NAMELENGTH]; +}; + +#endif // __nsBidiKeyboard diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp new file mode 100644 index 0000000000..e274adba46 --- /dev/null +++ b/widget/windows/nsClipboard.cpp @@ -0,0 +1,1071 @@ +/* -*- 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 "nsString.h" +#include "nsNativeCharsetUtils.h" +#include "nsITransferable.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsPrimitiveHelpers.h" +#include "nsIWidget.h" +#include "nsWidgetsCID.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIFileProtocolHandler.h" +#include "nsEscape.h" +#include "nsIObserverService.h" +#include "nsMimeTypes.h" +#include "imgITools.h" + +using mozilla::LogLevel; + +static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard"); + +// 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() { + 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_WILL_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 { + dataObj->Release(); + } + 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 + nsTArray<nsCString> flavors; + aTransferable->FlavorsTransferableCanExport(flavors); + + // Walk through flavors that contain data and register them + // into the DataObj as supported flavors + for (uint32_t i = 0; i < flavors.Length(); i++) { + nsCString& flavorStr = flavors[i]; + + // When putting data onto the clipboard, we want to maintain kHTMLMime + // ("text/html") and not map it to CF_HTML here since this will be done + // below. + UINT format = GetFormat(flavorStr.get(), false); + + // Now tell the native IDataObject about both our mime type and + // the native data format + FORMATETC fe; + SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + dObj->AddDataFlavor(flavorStr.get(), &fe); + + // Do various things internal to the implementation, like map one + // flavor to another or add additional flavors based on what's required + // for the win32 impl. + if (flavorStr.EqualsLiteral(kUnicodeMime)) { + // 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 (flavorStr.EqualsLiteral(kHTMLMime)) { + // if we find text/html, also advertise win32's html flavor (which we will + // convert on our own in nsDataObj::GetText(). + FORMATETC htmlFE; + SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + dObj->AddDataFlavor(kHTMLMime, &htmlFE); + } else if (flavorStr.EqualsLiteral(kURLMime)) { + // if we're a url, in addition to also being text, we need to register + // the "file" flavors so that the win32 shell knows to create an internet + // shortcut when it sees one of these beasts. + FORMATETC shortcutFE; + SET_FORMATETC(shortcutFE, + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0, + DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0, + DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS), + 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0, + DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0, + DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kURLMime, &shortcutFE); + } else if (flavorStr.EqualsLiteral(kPNGImageMime) || + flavorStr.EqualsLiteral(kJPEGImageMime) || + flavorStr.EqualsLiteral(kJPGImageMime) || + flavorStr.EqualsLiteral(kGIFImageMime) || + flavorStr.EqualsLiteral(kNativeImageMime)) { + // if we're an image, register the native bitmap flavor + FORMATETC imageFE; + // Add DIBv5 + SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(flavorStr.get(), &imageFE); + // Add DIBv3 + SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(flavorStr.get(), &imageFE); + } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) { + // if we're a file promise flavor, also register the + // CFSTR_PREFERREDDROPEFFECT format. The data object + // returns a value of DROPEFFECTS_MOVE to the drop target + // when it asks for the value of this format. This causes + // the file to be moved from the temporary location instead + // of being copied. The right thing to do here is to call + // SetData() on the data object and set the value of this format + // to DROPEFFECTS_MOVE on this particular data object. But, + // since all the other clipboard formats follow the model of setting + // data on the data object only when the drop object calls GetData(), + // I am leaving this format's value hard coded in the data object. + // We can change this if other consumers of this format get added to this + // codebase and they need different values. + FORMATETC shortcutFE; + SET_FORMATETC(shortcutFE, + ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0, + DVASPECT_CONTENT, -1, TYMED_HGLOBAL) + dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE); + } + } + + 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 3 bytes (but don't report them in |aLen|!) + // and null them out to ensure that all of our NS_strlen calls will succeed. + // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds + // a full NUL char16_t when |*aLen| is odd. + nsresult result = NS_ERROR_FAILURE; + if (aHGBL != nullptr) { + LPSTR lpStr = (LPSTR)GlobalLock(aHGBL); + CheckedInt<uint32_t> allocSize = + CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3; + if (!allocSize.isValid()) { + return NS_ERROR_INVALID_ARG; + } + char* data = static_cast<char*>(malloc(allocSize.value())); + if (data) { + uint32_t size = allocSize.value() - 3; + memcpy(data, lpStr, size); + // null terminate for safety + data[size] = data[size + 1] = data[size + 2] = '\0'; + + GlobalUnlock(aHGBL); + *aData = data; + *aLen = size; + + result = NS_OK; + } + } else { + // We really shouldn't ever get here + // but just in case + *aData = nullptr; + *aLen = 0; + LPVOID lpMsgBuf; + + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPWSTR)&lpMsgBuf, 0, nullptr); + + // Display the string. + MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError", + MB_OK | MB_ICONINFORMATION); + + // Free the buffer. + LocalFree(lpMsgBuf); + } + + return result; +} + +//------------------------------------------------------------------------- +nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget, + UINT /*aIndex*/, UINT aFormat, + void** aData, uint32_t* aLen) { + 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; + const char* clipboardData; + if (NS_SUCCEEDED(GetGlobalData( + stm.hGlobal, (void**)&clipboardData, &allocLen))) { + nsCOMPtr<imgIContainer> container; + nsCOMPtr<imgITools> imgTools = + do_CreateInstance("@mozilla.org/image/tools;1"); + result = imgTools->DecodeImageFromBuffer( + clipboardData, allocLen, + nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD), + getter_AddRefs(container)); + if (NS_FAILED(result)) { + break; + } + + nsAutoCString mimeType; + if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) { + mimeType.Assign(IMAGE_JPEG); + } else { + mimeType.Assign(aMIMEImageFormat); + } + + nsCOMPtr<nsIInputStream> inputStream; + result = imgTools->EncodeImage(container, mimeType, u""_ns, + getter_AddRefs(inputStream)); + if (NS_FAILED(result)) { + break; + } + + if (!inputStream) { + result = NS_ERROR_FAILURE; + break; + } + + *aData = inputStream.forget().take(); + *aLen = sizeof(nsIInputStream*); + } + } + break; + + case CF_HDROP: { + // in the case of a file drop, multiple files are stashed within a + // single data object. In order to match mozilla's D&D apis, we + // just pull out the file at the requested index, pretending as + // if there really are multiple drag items. + HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal); + + UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0); + NS_ASSERTION(numFiles > 0, + "File drop flavor, but no files...hmmmm"); + NS_ASSERTION(aIndex < numFiles, + "Asked for a file index out of range of list"); + if (numFiles > 0) { + UINT fileNameLen = + ::DragQueryFileW(dropFiles, aIndex, nullptr, 0); + wchar_t* buffer = reinterpret_cast<wchar_t*>( + moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t))); + ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1); + *aData = buffer; + *aLen = fileNameLen * sizeof(char16_t); + result = NS_OK; + } + GlobalUnlock(stm.hGlobal); + + } break; + + default: { + if (fe.cfFormat == fileDescriptorFlavorA || + fe.cfFormat == fileDescriptorFlavorW || + 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) + nsTArray<nsCString> flavors; + res = aTransferable->FlavorsTransferableCanImport(flavors); + if (NS_FAILED(res)) { + return NS_ERROR_FAILURE; + } + + // Walk through flavors and see which flavor is on the clipboard them on the + // native clipboard, + for (uint32_t i = 0; i < flavors.Length(); i++) { + nsCString& flavorStr = flavors[i]; + UINT format = GetFormat(flavorStr.get()); + + // Try to get the data using the desired flavor. This might fail, but all is + // not lost. + void* data = nullptr; + uint32_t dataLen = 0; + bool dataFound = false; + if (nullptr != aDataObject) { + if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, + flavorStr.get(), &data, + &dataLen))) { + dataFound = true; + } + } else if (nullptr != aWindow) { + if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, + &data, &dataLen))) { + dataFound = true; + } + } + + // This is our second chance to try to find some data, having not found it + // when directly asking for the flavor. Let's try digging around in other + // flavors to help satisfy our craving for data. + if (!dataFound) { + if (flavorStr.EqualsLiteral(kUnicodeMime)) { + dataFound = + FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen); + } else if (flavorStr.EqualsLiteral(kURLMime)) { + // drags from other windows apps expose the native + // CFSTR_INETURL{A,W} flavor + dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen); + if (!dataFound) { + dataFound = + FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen); + } + } + } // if we try one last ditch effort to find our data + + // Hopefully by this point we've found it and can go about our business + if (dataFound) { + nsCOMPtr<nsISupports> genericDataWrapper; + if (flavorStr.EqualsLiteral(kFileMime)) { + // we have a file path in |data|. Create an nsLocalFile object. + nsDependentString filepath(reinterpret_cast<char16_t*>(data)); + nsCOMPtr<nsIFile> file; + if (NS_SUCCEEDED( + NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) { + genericDataWrapper = do_QueryInterface(file); + } + free(data); + } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) { + uint32_t dummy; + // the editor folks want CF_HTML exactly as it's on the clipboard, no + // conversions, no fancy stuff. Pull it off the clipboard, stuff it into + // a wrapper and hand it back to them. + if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) { + nsPrimitiveHelpers::CreatePrimitiveForData( + flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper)); + } else { + free(data); + continue; // something wrong with this flavor, keep looking for other + // data + } + free(data); + } else if (flavorStr.EqualsLiteral(kHTMLMime)) { + uint32_t startOfData = 0; + // The JS folks want CF_HTML exactly as it is on the clipboard, but + // minus the CF_HTML header index information. + // It also needs to be converted to UTF16 and have linebreaks changed. + if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData, + &dataLen)) { + dataLen -= startOfData; + nsPrimitiveHelpers::CreatePrimitiveForCFHTML( + static_cast<char*>(data) + startOfData, &dataLen, + getter_AddRefs(genericDataWrapper)); + } else { + free(data); + continue; // something wrong with this flavor, keep looking for other + // data + } + free(data); + } else if (flavorStr.EqualsLiteral(kJPEGImageMime) || + flavorStr.EqualsLiteral(kJPGImageMime) || + flavorStr.EqualsLiteral(kPNGImageMime)) { + nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data); + genericDataWrapper = do_QueryInterface(imageStream); + NS_IF_RELEASE(imageStream); + } else { + // Treat custom types as a string of bytes. + if (!flavorStr.EqualsLiteral(kCustomTypesMime)) { + // 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 (flavorStr.EqualsLiteral(kRTFMime)) { + // RTF on Windows is known to sometimes deliver an extra null byte. + if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') { + dataLen--; + } + } + } + + nsPrimitiveHelpers::CreatePrimitiveForData( + flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper)); + free(data); + } + + NS_ASSERTION(genericDataWrapper, + "About to put null data into the transferable"); + aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper); + res = NS_OK; + + // we found one, get out of the loop + break; + } + } // foreach flavor + + return res; +} + +// +// FindPlatformHTML +// +// Someone asked for the OS CF_HTML flavor. We give it back to them exactly +// as-is. +// +bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex, + void** outData, uint32_t* outStartOfData, + uint32_t* outDataLen) { + // Reference: MSDN doc entitled "HTML Clipboard Format" + // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854 + // CF_HTML is UTF8, not unicode. We also can't rely on it being + // null-terminated so we have to check the CF_HTML header for the correct + // length. The length we return is the bytecount from the beginning of the + // selected data to the end of the selected data, without the null + // termination. Because it's UTF8, we're guaranteed the header is ASCII. + + if (!outData || !*outData) { + return false; + } + + char version[8] = {0}; + int32_t startOfData = 0; + int32_t endOfData = 0; + int numFound = + sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version, + &startOfData, &endOfData); + + if (numFound != 3 || startOfData < -1 || endOfData < -1) { + return false; + } + + // Fixup the start and end markers if they have no context (set to -1) + if (startOfData == -1) { + startOfData = 0; + } + if (endOfData == -1) { + endOfData = *outDataLen; + } + + // Make sure we were passed sane values within our buffer size. + // (Note that we've handled all cases of negative endOfData above, so we can + // safely cast it to be unsigned here.) + if (!endOfData || startOfData >= endOfData || + static_cast<uint32_t>(endOfData) > *outDataLen) { + return false; + } + + // We want to return the buffer not offset by startOfData because it will be + // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still + // in CF_HTML format. + + // We return the byte offset from the start of the data buffer to where the + // HTML data starts. The caller might want to extract the HTML only. + *outStartOfData = startOfData; + *outDataLen = endOfData; + return true; +} + +// +// FindUnicodeFromPlainText +// +// 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 + u"\n"_ns + title); + *outDataLen = + NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + + dataFound = true; + } + } else { + // we have a normal file, use some Necko objects to get our file path + nsAutoCString urlSpec; + NS_GetURLSpecFromFile(file, urlSpec); + + // convert it to unicode and pass it out + free(*outData); + *outData = UTF8ToNewUnicode(urlSpec); + *outDataLen = + NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + dataFound = true; + } // else regular file + } + + return dataFound; +} // FindURLFromLocalFile + +// +// FindURLFromNativeURL +// +// we are looking for a URL and couldn't find it using our internal +// URL flavor, so look for it using the native URL flavor, +// CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently) +// +bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex, + void** outData, uint32_t* outDataLen) { + bool dataFound = false; + + void* tempOutData = nullptr; + uint32_t tempDataLen = 0; + + nsresult loadResult = GetNativeDataOffClipboard( + inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr, + &tempOutData, &tempDataLen); + if (NS_SUCCEEDED(loadResult) && tempOutData) { + nsDependentString urlString(static_cast<char16_t*>(tempOutData)); + // the internal mozilla URL format, text/x-moz-url, contains + // URL\ntitle. Since we don't actually have a title here, + // just repeat the URL to fake it. + *outData = ToNewUnicode(urlString + u"\n"_ns + urlString); + *outDataLen = + NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + free(tempOutData); + dataFound = true; + } else { + loadResult = GetNativeDataOffClipboard( + inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA), + nullptr, &tempOutData, &tempDataLen); + if (NS_SUCCEEDED(loadResult) && tempOutData) { + // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to + // CF_TEXT which is by definition ANSI encoded. + nsCString urlUnescapedA; + bool unescaped = + NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen, + esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA); + + nsString urlString; + if (unescaped) { + NS_CopyNativeToUnicode(urlUnescapedA, urlString); + } else { + NS_CopyNativeToUnicode( + nsDependentCString(static_cast<char*>(tempOutData), tempDataLen), + urlString); + } + + // the internal mozilla URL format, text/x-moz-url, contains + // URL\ntitle. Since we don't actually have a title here, + // just repeat the URL to fake it. + *outData = ToNewUnicode(urlString + u"\n"_ns + urlString); + *outDataLen = + NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t); + free(tempOutData); + dataFound = true; + } + } + + return dataFound; +} // FindURLFromNativeURL + +// +// ResolveShortcut +// +void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) { + nsCOMPtr<nsIFileProtocolHandler> fph; + nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIURI> uri; + rv = fph->ReadURLFile(aFile, getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return; + } + + uri->GetSpec(outURL); +} // ResolveShortcut + +// +// IsInternetShortcut +// +// A file is an Internet Shortcut if it ends with .URL +// +bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) { + return StringEndsWith(inFileName, u".url"_ns, + nsCaseInsensitiveStringComparator); +} // IsInternetShortcut + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, + int32_t aWhichClipboard) { + // 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 nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard, + bool* _retval) { + *_retval = false; + if (aWhichClipboard != kGlobalClipboard) { + return NS_OK; + } + + for (auto& flavor : aFlavorList) { +#ifdef DEBUG + if (flavor.EqualsLiteral(kTextMime)) { + NS_WARNING( + "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode " + "INSTEAD"); + } +#endif + + UINT format = GetFormat(flavor.get()); + 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 (flavor.EqualsLiteral(kUnicodeMime)) { + // 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; + break; + } + } + } + } + + return NS_OK; +} diff --git a/widget/windows/nsClipboard.h b/widget/windows/nsClipboard.h new file mode 100644 index 0000000000..ace5c85e4c --- /dev/null +++ b/widget/windows/nsClipboard.h @@ -0,0 +1,96 @@ +/* -*- 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 nsTArray<nsCString>& aFlavorList, + 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..7bc3e25d2f --- /dev/null +++ b/widget/windows/nsColorPicker.cpp @@ -0,0 +1,208 @@ +/* -*- 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" +#include "nsPIDOMWindow.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) + : mozilla::Runnable("AsyncColorChooser"), + 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_INITDIALOG) { + // "The default dialog box procedure processes the WM_INITDIALOG message + // before passing it to the hook procedure. + // For all other messages, the hook procedure receives the message first." + // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpcchookproc + // "The dialog box procedure should return TRUE to direct the system to + // set the keyboard focus to the control specified by wParam." + // https://docs.microsoft.com/en-us/windows/win32/dlgbox/wm-initdialog + return 1; + } + + if (aMsg == WM_CTLCOLORSTATIC) { + // The color picker does not expose a proper way to retrieve the current + // color, so we need to obtain it from the static control displaying the + // current color instead. + const int kCurrentColorBoxID = 709; + if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) { + gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0)); + } + } + + // Let the default dialog box procedure processes the message. + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIColorPicker + +nsColorPicker::nsColorPicker() {} + +nsColorPicker::~nsColorPicker() {} + +NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker) + +NS_IMETHODIMP +nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title, + const nsAString& aInitialColor) { + MOZ_ASSERT(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..fe568df952 --- /dev/null +++ b/widget/windows/nsColorPicker.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef 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) override; + + 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..1f4d9ecbf2 --- /dev/null +++ b/widget/windows/nsDataObj.cpp @@ -0,0 +1,2231 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/TextUtils.h" + +#include <ole2.h> +#include <shlobj.h> + +#include "nsDataObj.h" +#include "nsArrayUtils.h" +#include "nsClipboard.h" +#include "nsReadableUtils.h" +#include "nsICookieJarSettings.h" +#include "nsITransferable.h" +#include "nsISupportsPrimitives.h" +#include "IEnumFE.h" +#include "nsPrimitiveHelpers.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsIStringBundle.h" +#include "nsEscape.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "nsIOutputStream.h" +#include "nscore.h" +#include "nsDirectoryServiceDefs.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.h" +#include "nsContentUtils.h" +#include "nsIPrincipal.h" +#include "nsNativeCharsetUtils.h" +#include "nsMimeTypes.h" +#include "imgIEncoder.h" +#include "imgITools.h" + +#include "mozilla/LazyIdleThread.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::glue; +using namespace mozilla::widget; + +#define BFH_LENGTH 14 +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +//----------------------------------------------------------------------------- +// CStreamBase implementation +nsDataObj::CStreamBase::CStreamBase() : mStreamRead(0) {} + +//----------------------------------------------------------------------------- +nsDataObj::CStreamBase::~CStreamBase() {} + +NS_IMPL_ISUPPORTS(nsDataObj::CStream, nsIStreamListener) + +//----------------------------------------------------------------------------- +// CStream implementation +nsDataObj::CStream::CStream() : mChannelRead(false) {} + +//----------------------------------------------------------------------------- +nsDataObj::CStream::~CStream() {} + +//----------------------------------------------------------------------------- +// helper - initializes the stream +nsresult nsDataObj::CStream::Init(nsIURI* pSourceURI, + nsContentPolicyType aContentPolicyType, + nsIPrincipal* aRequestingPrincipal, + nsICookieJarSettings* aCookieJarSettings) { + // we can not create a channel without a requestingPrincipal + if (!aRequestingPrincipal) { + return NS_ERROR_FAILURE; + } + nsresult rv; + rv = NS_NewChannel(getter_AddRefs(mChannel), pSourceURI, aRequestingPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT, + aContentPolicyType, aCookieJarSettings, + nullptr, // PerformanceStorage + nullptr, // loadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_FROM_CACHE); + + NS_ENSURE_SUCCESS(rv, rv); + rv = mChannel->AsyncOpen(this); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// IUnknown's QueryInterface, nsISupport's AddRef and Release are shared by +// IUnknown and nsIStreamListener. +STDMETHODIMP nsDataObj::CStream::QueryInterface(REFIID refiid, + void** ppvResult) { + *ppvResult = nullptr; + if (IID_IUnknown == refiid || refiid == IID_IStream) + + { + *ppvResult = this; + } + + if (nullptr != *ppvResult) { + ((LPUNKNOWN)*ppvResult)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +// nsIStreamListener implementation +NS_IMETHODIMP +nsDataObj::CStream::OnDataAvailable( + nsIRequest* aRequest, nsIInputStream* aInputStream, + uint64_t aOffset, // offset within the stream + uint32_t aCount) // bytes available on this call +{ + // 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) { + mChannelResult = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP nsDataObj::CStream::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + mChannelRead = true; + mChannelResult = aStatusCode; + return NS_OK; +} + +// Pumps thread messages while waiting for the async listener operation to +// complete. Failing this call will fail the stream incall from Windows +// and cancel the operation. +nsresult nsDataObj::CStream::WaitForCompletion() { + // We are guaranteed OnStopRequest will get called, so this should be ok. + SpinEventLoopUntil([&]() { return mChannelRead; }); + + if (!mChannelData.Length()) mChannelResult = NS_ERROR_FAILURE; + + return mChannelResult; +} + +//----------------------------------------------------------------------------- +// IStream +STDMETHODIMP nsDataObj::CStreamBase::Clone(IStream** ppStream) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::Commit(DWORD dwFrags) { return E_NOTIMPL; } + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::CopyTo(IStream* pDestStream, + ULARGE_INTEGER nBytesToCopy, + ULARGE_INTEGER* nBytesRead, + ULARGE_INTEGER* nBytesWritten) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::LockRegion(ULARGE_INTEGER nStart, + ULARGE_INTEGER nBytes, + DWORD dwFlags) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Read(void* pvBuffer, ULONG nBytesToRead, + ULONG* nBytesRead) { + // Wait for the write into our buffer to complete via the stream listener. + // We can't respond to this by saying "call us back later". + if (NS_FAILED(WaitForCompletion())) return E_FAIL; + + // Bytes left for Windows to read out of our buffer + ULONG bytesLeft = mChannelData.Length() - mStreamRead; + // Let Windows know what we will hand back, usually this is the entire buffer + *nBytesRead = std::min(bytesLeft, nBytesToRead); + // Copy the buffer data over + memcpy(pvBuffer, ((char*)mChannelData.Elements() + mStreamRead), *nBytesRead); + // Update our bytes read tracking + mStreamRead += *nBytesRead; + return S_OK; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::Revert(void) { return E_NOTIMPL; } + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::Seek(LARGE_INTEGER nMove, DWORD dwOrigin, + ULARGE_INTEGER* nNewPos) { + if (nNewPos == nullptr) return STG_E_INVALIDPOINTER; + + if (nMove.LowPart == 0 && nMove.HighPart == 0 && + (dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR)) { + nNewPos->LowPart = 0; + nNewPos->HighPart = 0; + return S_OK; + } + + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::SetSize(ULARGE_INTEGER nNewSize) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStream::Stat(STATSTG* statstg, DWORD dwFlags) { + if (statstg == nullptr) return STG_E_INVALIDPOINTER; + + if (!mChannel || NS_FAILED(WaitForCompletion())) return E_FAIL; + + memset((void*)statstg, 0, sizeof(STATSTG)); + + if (dwFlags != STATFLAG_NONAME) { + nsCOMPtr<nsIURI> sourceURI; + if (NS_FAILED(mChannel->GetURI(getter_AddRefs(sourceURI)))) { + return E_FAIL; + } + + nsAutoCString strFileName; + nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI); + sourceURL->GetFileName(strFileName); + + if (strFileName.IsEmpty()) return E_FAIL; + + NS_UnescapeURL(strFileName); + NS_ConvertUTF8toUTF16 wideFileName(strFileName); + + uint32_t nMaxNameLength = (wideFileName.Length() * 2) + 2; + void* retBuf = CoTaskMemAlloc(nMaxNameLength); // freed by caller + if (!retBuf) return STG_E_INSUFFICIENTMEMORY; + + ZeroMemory(retBuf, nMaxNameLength); + memcpy(retBuf, wideFileName.get(), wideFileName.Length() * 2); + statstg->pwcsName = (LPOLESTR)retBuf; + } + + SYSTEMTIME st; + + statstg->type = STGTY_STREAM; + + GetSystemTime(&st); + SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime); + statstg->ctime = statstg->atime = statstg->mtime; + + statstg->cbSize.QuadPart = mChannelData.Length(); + statstg->grfMode = STGM_READ; + statstg->grfLocksSupported = LOCK_ONLYONCE; + statstg->clsid = CLSID_NULL; + + return S_OK; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::UnlockRegion(ULARGE_INTEGER nStart, + ULARGE_INTEGER nBytes, + DWORD dwFlags) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CStreamBase::Write(const void* pvBuffer, + ULONG nBytesToRead, + ULONG* nBytesRead) { + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +HRESULT nsDataObj::CreateStream(IStream** outStream) { + NS_ENSURE_TRUE(outStream, E_INVALIDARG); + + nsresult rv = NS_ERROR_FAILURE; + nsAutoString wideFileName; + nsCOMPtr<nsIURI> sourceURI; + HRESULT res; + + res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName); + if (FAILED(res)) return res; + + nsDataObj::CStream* pStream = new nsDataObj::CStream(); + NS_ENSURE_TRUE(pStream, E_OUTOFMEMORY); + + pStream->AddRef(); + + // query the requestingPrincipal from the transferable and add it to the new + // channel + nsCOMPtr<nsIPrincipal> requestingPrincipal = + mTransferable->GetRequestingPrincipal(); + MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal"); + + // Note that the cookieJarSettings could be null if the data object is for the + // image copy. We will fix this in Bug 1690532. + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + mTransferable->GetCookieJarSettings(); + + nsContentPolicyType contentPolicyType = mTransferable->GetContentPolicyType(); + rv = pStream->Init(sourceURI, contentPolicyType, requestingPrincipal, + cookieJarSettings); + if (NS_FAILED(rv)) { + pStream->Release(); + return E_FAIL; + } + *outStream = pStream; + + return S_OK; +} + +//----------------------------------------------------------------------------- +// AutoCloseEvent implementation +nsDataObj::AutoCloseEvent::AutoCloseEvent() + : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)) {} + +bool nsDataObj::AutoCloseEvent::IsInited() const { return !!mEvent; } + +void nsDataObj::AutoCloseEvent::Signal() const { ::SetEvent(mEvent); } + +DWORD nsDataObj::AutoCloseEvent::Wait(DWORD aMillisec) const { + return ::WaitForSingleObject(mEvent, aMillisec); +} + +//----------------------------------------------------------------------------- +// AutoSetEvent implementation +nsDataObj::AutoSetEvent::AutoSetEvent(NotNull<AutoCloseEvent*> aEvent) + : mEvent(aEvent) {} + +nsDataObj::AutoSetEvent::~AutoSetEvent() { Signal(); } + +void nsDataObj::AutoSetEvent::Signal() const { mEvent->Signal(); } + +bool nsDataObj::AutoSetEvent::IsWaiting() const { + return mEvent->Wait(0) == WAIT_TIMEOUT; +} + +//----------------------------------------------------------------------------- +// CMemStream implementation +Win32SRWLock nsDataObj::CMemStream::mLock; + +//----------------------------------------------------------------------------- +nsDataObj::CMemStream::CMemStream(nsHGLOBAL aGlobalMem, uint32_t aTotalLength, + already_AddRefed<AutoCloseEvent> aEvent) + : mGlobalMem(aGlobalMem), mEvent(aEvent), mTotalLength(aTotalLength) { + ::CoCreateFreeThreadedMarshaler(this, getter_AddRefs(mMarshaler)); +} + +//----------------------------------------------------------------------------- +nsDataObj::CMemStream::~CMemStream() {} + +//----------------------------------------------------------------------------- +// IUnknown +STDMETHODIMP nsDataObj::CMemStream::QueryInterface(REFIID refiid, + void** ppvResult) { + *ppvResult = nullptr; + if (refiid == IID_IUnknown || refiid == IID_IStream || + refiid == IID_IAgileObject) { + *ppvResult = this; + } else if (refiid == IID_IMarshal && mMarshaler) { + return mMarshaler->QueryInterface(refiid, ppvResult); + } + + if (nullptr != *ppvResult) { + ((LPUNKNOWN)*ppvResult)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +void nsDataObj::CMemStream::WaitForCompletion() { + if (!mEvent) { + // We are not waiting for obtaining the icon cache. + return; + } + if (!NS_IsMainThread()) { + mEvent->Wait(INFINITE); + } else { + // We should not block the main thread. + mEvent->Signal(); + } + // mEvent will always be in the signaled state here. +} + +//----------------------------------------------------------------------------- +// IStream +STDMETHODIMP nsDataObj::CMemStream::Read(void* pvBuffer, ULONG nBytesToRead, + ULONG* nBytesRead) { + // Wait until the event is signaled. + WaitForCompletion(); + + AutoExclusiveLock lock(mLock); + char* contents = reinterpret_cast<char*>(GlobalLock(mGlobalMem.get())); + if (!contents) { + return E_OUTOFMEMORY; + } + + // Bytes left for Windows to read out of our buffer + ULONG bytesLeft = mTotalLength - mStreamRead; + // Let Windows know what we will hand back, usually this is the entire buffer + *nBytesRead = std::min(bytesLeft, nBytesToRead); + // Copy the buffer data over + memcpy(pvBuffer, contents + mStreamRead, *nBytesRead); + // Update our bytes read tracking + mStreamRead += *nBytesRead; + + GlobalUnlock(mGlobalMem.get()); + return S_OK; +} + +//----------------------------------------------------------------------------- +STDMETHODIMP nsDataObj::CMemStream::Stat(STATSTG* statstg, DWORD dwFlags) { + if (statstg == nullptr) return STG_E_INVALIDPOINTER; + + memset((void*)statstg, 0, sizeof(STATSTG)); + + if (dwFlags != STATFLAG_NONAME) { + constexpr size_t kMaxNameLength = sizeof(wchar_t); + void* retBuf = CoTaskMemAlloc(kMaxNameLength); // freed by caller + if (!retBuf) return STG_E_INSUFFICIENTMEMORY; + + ZeroMemory(retBuf, kMaxNameLength); + statstg->pwcsName = (LPOLESTR)retBuf; + } + + SYSTEMTIME st; + + statstg->type = STGTY_STREAM; + + GetSystemTime(&st); + SystemTimeToFileTime((const SYSTEMTIME*)&st, (LPFILETIME)&statstg->mtime); + statstg->ctime = statstg->atime = statstg->mtime; + + statstg->cbSize.QuadPart = mTotalLength; + statstg->grfMode = STGM_READ; + statstg->grfLocksSupported = LOCK_ONLYONCE; + statstg->clsid = CLSID_NULL; + + return S_OK; +} + +/* + * 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, "nsDataObj"_ns, + LazyIdleThread::ManualShutdown); + m_enumFE = new CEnumFormatEtc(); + m_enumFE->AddRef(); + + if (uri) { + // A URI was obtained, so pass this through to the DataObject + // so it can create a SourceURL for CF_HTML flavour + uri->GetSpec(mSourceURL); + } +} +//----------------------------------------------------- +// destruction +//----------------------------------------------------- +nsDataObj::~nsDataObj() { + NS_IF_RELEASE(mTransferable); + + mDataFlavors.Clear(); + + m_enumFE->Release(); + + // Free arbitrary system formats + for (uint32_t idx = 0; idx < mDataEntryList.Length(); idx++) { + CoTaskMemFree(mDataEntryList[idx]->fe.ptd); + ReleaseStgMedium(&mDataEntryList[idx]->stgm); + CoTaskMemFree(mDataEntryList[idx]); + } +} + +//----------------------------------------------------- +// IUnknown interface methods - see inknown.h for documentation +//----------------------------------------------------- +STDMETHODIMP nsDataObj::QueryInterface(REFIID riid, void** ppv) { + *ppv = nullptr; + + if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) { + *ppv = this; + AddRef(); + return S_OK; + } else if (IID_IDataObjectAsyncCapability == riid) { + *ppv = static_cast<IDataObjectAsyncCapability*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +//----------------------------------------------------- +STDMETHODIMP_(ULONG) nsDataObj::AddRef() { + ++m_cRef; + NS_LOG_ADDREF(this, m_cRef, "nsDataObj", sizeof(*this)); + return m_cRef; +} + +namespace { +class RemoveTempFileHelper final : public nsIObserver { + public: + explicit RemoveTempFileHelper(nsIFile* aTempFile) : mTempFile(aTempFile) { + MOZ_ASSERT(mTempFile); + } + + // The attach method is seperate from the constructor as we may be addref-ing + // ourself, and we want to be sure someone has a strong reference to us. + void Attach() { + // We need to listen to both the xpcom shutdown message and our timer, and + // fire when the first of either of these two messages is received. + nsresult rv; + rv = NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, 500, + nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1"); + if (NS_WARN_IF(!observerService)) { + mTimer->Cancel(); + mTimer = nullptr; + return; + } + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~RemoveTempFileHelper() { + if (mTempFile) { + mTempFile->Remove(false); + } + } + + nsCOMPtr<nsIFile> mTempFile; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(RemoveTempFileHelper, nsIObserver); + +NS_IMETHODIMP +RemoveTempFileHelper::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // Let's be careful and make sure that we don't die immediately + RefPtr<RemoveTempFileHelper> grip = this; + + // Make sure that we aren't called again by destroying references to ourself. + nsCOMPtr<nsIObserverService> observerService = + do_GetService("@mozilla.org/observer-service;1"); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + // Remove the tempfile + if (mTempFile) { + mTempFile->Remove(false); + mTempFile = nullptr; + } + return NS_OK; +} +} // namespace + +//----------------------------------------------------- +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. + if (mCachedTempFile) { + RefPtr<RemoveTempFileHelper> helper = + new RemoveTempFileHelper(mCachedTempFile); + mCachedTempFile = nullptr; + helper->Attach(); + } + + 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; +} + +// IDataObjectAsyncCapability methods +STDMETHODIMP nsDataObj::EndOperation(HRESULT hResult, IBindCtx* pbcReserved, + DWORD dwEffects) { + mIsInOperation = FALSE; + return S_OK; +} + +STDMETHODIMP nsDataObj::GetAsyncMode(BOOL* pfIsOpAsync) { + *pfIsOpAsync = mIsAsyncMode; + + return S_OK; +} + +STDMETHODIMP nsDataObj::InOperation(BOOL* pfInAsyncOp) { + *pfInAsyncOp = mIsInOperation; + + return S_OK; +} + +STDMETHODIMP nsDataObj::SetAsyncMode(BOOL fDoOpAsync) { + mIsAsyncMode = fDoOpAsync; + return S_OK; +} + +STDMETHODIMP nsDataObj::StartOperation(IBindCtx* pbcReserved) { + mIsInOperation = TRUE; + return S_OK; +} + +// +// GetDIB +// +// Someone is asking for a bitmap. The data in the transferable will be a +// straight imgIContainer, so just QI it. +// +HRESULT +nsDataObj::GetDib(const nsACString& inFlavor, FORMATETC& aFormat, + STGMEDIUM& aSTG) { + nsCOMPtr<nsISupports> genericDataWrapper; + if (NS_FAILED( + mTransferable->GetTransferData(PromiseFlatCString(inFlavor).get(), + getter_AddRefs(genericDataWrapper)))) { + return E_FAIL; + } + + nsCOMPtr<imgIContainer> image = do_QueryInterface(genericDataWrapper); + if (!image) { + return E_FAIL; + } + + nsCOMPtr<imgITools> imgTools = + do_CreateInstance("@mozilla.org/image/tools;1"); + + nsAutoString options(u"bpp=32;"_ns); + if (aFormat.cfFormat == CF_DIBV5) { + options.AppendLiteral("version=5"); + } else { + options.AppendLiteral("version=3"); + } + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP), + options, getter_AddRefs(inputStream)); + if (NS_FAILED(rv) || !inputStream) { + return E_FAIL; + } + + nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream); + if (!encoder) { + return E_FAIL; + } + + uint32_t size = 0; + rv = encoder->GetImageBufferUsed(&size); + if (NS_FAILED(rv) || size <= BFH_LENGTH) { + return E_FAIL; + } + + char* src = nullptr; + rv = encoder->GetImageBuffer(&src); + if (NS_FAILED(rv) || !src) { + return E_FAIL; + } + + // We don't want the file header. + src += BFH_LENGTH; + size -= BFH_LENGTH; + + HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size); + if (!glob) { + return E_FAIL; + } + + char* dst = (char*)::GlobalLock(glob); + ::CopyMemory(dst, src, size); + ::GlobalUnlock(glob); + + aSTG.hGlobal = glob; + aSTG.tymed = TYMED_HGLOBAL; + return S_OK; +} + +// +// GetFileDescriptor +// + +HRESULT +nsDataObj ::GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG, + bool aIsUnicode) { + HRESULT res = S_OK; + + // How we handle this depends on if we're dealing with an internet + // shortcut, since those are done under the covers. + if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) { + if (aIsUnicode) + return GetFileDescriptor_IStreamW(aFE, aSTG); + else + return GetFileDescriptor_IStreamA(aFE, aSTG); + } else if (IsFlavourPresent(kURLMime)) { + if (aIsUnicode) + res = GetFileDescriptorInternetShortcutW(aFE, aSTG); + else + res = GetFileDescriptorInternetShortcutA(aFE, aSTG); + } else + NS_WARNING("Not yet implemented\n"); + + return res; +} // GetFileDescriptor + +// +HRESULT +nsDataObj ::GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG) { + HRESULT res = S_OK; + + // How we handle this depends on if we're dealing with an internet + // shortcut, since those are done under the covers. + if (IsFlavourPresent(kFilePromiseMime) || IsFlavourPresent(kFileMime)) + return GetFileContents_IStream(aFE, aSTG); + else if (IsFlavourPresent(kURLMime)) + return GetFileContentsInternetShortcut(aFE, aSTG); + else + NS_WARNING("Not yet implemented\n"); + + return res; + +} // GetFileContents + +// +// 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 char* aName, nsAString& 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, 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)) { + nsAutoString untitled; + if (!GetLocalizedString("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)) { + nsAutoString untitled; + if (!GetLocalizedString("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; + } + + RefPtr<AutoCloseEvent> event; + + const char* shortcutFormatStr; + int totalLen; + nsCString asciiPath; + if (!Preferences::GetBool(kShellIconPref, true)) { + shortcutFormatStr = "[InternetShortcut]\r\nURL=%s\r\n"; + const int formatLen = strlen(shortcutFormatStr) - 2; // don't include %s + totalLen = formatLen + asciiUrl.Length(); // don't include null character + } else { + nsCOMPtr<nsIFile> icoFile; + + nsAutoString aUriHash; + + event = new AutoCloseEvent(); + if (!event->IsInited()) { + return E_FAIL; + } + + RefPtr<AutoSetEvent> e = new AutoSetEvent(WrapNotNull(event)); + mozilla::widget::FaviconHelper::ObtainCachedIconFile( + aUri, aUriHash, mIOThread, true, + NS_NewRunnableFunction( + "FaviconHelper::RefreshDesktop", [e = std::move(e)] { + if (e->IsWaiting()) { + // Unblock IStream:::Read. + e->Signal(); + } else { + // We could not wait until the favicon was available. We have + // to refresh to refect the favicon. + SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, + SPI_SETNONCLIENTMETRICS, 0); + } + })); + + rv = mozilla::widget::FaviconHelper::GetOutputIconPath(aUri, icoFile, true); + NS_ENSURE_SUCCESS(rv, E_FAIL); + nsString path; + rv = icoFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, E_FAIL); + + if (IsAsciiNullTerminated(static_cast<const char16_t*>(path.get()))) { + LossyCopyUTF16toASCII(path, asciiPath); + shortcutFormatStr = + "[InternetShortcut]\r\nURL=%s\r\n" + "IDList=\r\nHotKey=0\r\nIconFile=%s\r\n" + "IconIndex=0\r\n"; + } else { + int len = + WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()), + path.Length(), nullptr, 0, nullptr, nullptr); + NS_ENSURE_TRUE(len > 0, E_FAIL); + asciiPath.SetLength(len); + WideCharToMultiByte(CP_UTF7, 0, char16ptr_t(path.BeginReading()), + path.Length(), asciiPath.BeginWriting(), len, nullptr, + nullptr); + shortcutFormatStr = + "[InternetShortcut]\r\nURL=%s\r\n" + "IDList=\r\nHotKey=0\r\nIconIndex=0\r\n" + "[InternetShortcut.W]\r\nIconFile=%s\r\n"; + } + const int formatLen = strlen(shortcutFormatStr) - 2 * 2; // no %s twice + totalLen = formatLen + asciiUrl.Length() + + asciiPath.Length(); // we don't want a null character on the end + } + + // create a global memory area and build up the file contents w/in it + nsAutoGlobalMem globalMem(nsHGLOBAL(::GlobalAlloc(GMEM_SHARE, totalLen))); + if (!globalMem) return E_OUTOFMEMORY; + + char* contents = reinterpret_cast<char*>(::GlobalLock(globalMem.get())); + if (!contents) { + return E_OUTOFMEMORY; + } + + // NOTE: we intentionally use the Microsoft version of snprintf here because + // it does NOT null + // terminate strings which reach the maximum size of the buffer. Since we know + // that the formatted length here is totalLen, this call to _snprintf will + // format the string into the buffer without appending the null character. + + if (!Preferences::GetBool(kShellIconPref, true)) { + _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get()); + } else { + _snprintf(contents, totalLen, shortcutFormatStr, asciiUrl.get(), + asciiPath.get()); + } + + ::GlobalUnlock(globalMem.get()); + + if (aFE.tymed & TYMED_ISTREAM) { + if (!mIsInOperation) { + // The drop target didn't initiate an async operation. + // We can't block CMemStream::Read. + event = nullptr; + } + RefPtr<IStream> stream = + new CMemStream(globalMem.disown(), totalLen, event.forget()); + stream.forget(&aSTG.pstm); + aSTG.tymed = TYMED_ISTREAM; + } else { + if (event && event->IsInited()) { + event->Signal(); // We can't block reading the global memory + } + aSTG.hGlobal = globalMem.disown(); + aSTG.tymed = TYMED_HGLOBAL; + } + + return S_OK; +} // GetFileContentsInternetShortcut + +// check if specified flavour is present in the transferable +bool nsDataObj ::IsFlavourPresent(const char* inFlavour) { + bool retval = false; + NS_ENSURE_TRUE(mTransferable, false); + + // get the list of flavors available in the transferable + nsTArray<nsCString> flavors; + nsresult rv = mTransferable->FlavorsTransferableCanExport(flavors); + NS_ENSURE_SUCCESS(rv, false); + + // try to find requested flavour + for (uint32_t i = 0; i < flavors.Length(); ++i) { + if (flavors[i].Equals(inFlavour)) { + retval = true; // found it! + break; + } + } // for each flavor + + return retval; +} + +HRESULT nsDataObj::GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG) { + HRESULT res = S_OK; + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + HGLOBAL hGlobalMemory = nullptr; + hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)); + if (hGlobalMemory) { + DWORD* pdw = (DWORD*)GlobalLock(hGlobalMemory); + // The PreferredDropEffect clipboard format is only registered if a + // drag/drop of an image happens from Mozilla to the desktop. We want its + // value to be DROPEFFECT_MOVE in that case so that the file is moved from + // the temporary location, not copied. This value should, ideally, be set on + // the data object via SetData() but our IDataObject implementation doesn't + // implement SetData. It adds data to the data object lazily only when the + // drop target asks for it. + *pdw = (DWORD)DROPEFFECT_MOVE; + GlobalUnlock(hGlobalMemory); + } else { + res = E_OUTOFMEMORY; + } + aSTG.hGlobal = hGlobalMemory; + return res; +} + +//----------------------------------------------------- +HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE, + STGMEDIUM& aSTG) { + void* data = nullptr; + + // 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; + nsresult rv = mTransferable->GetTransferData( + flavorStr, getter_AddRefs(genericDataWrapper)); + if (NS_FAILED(rv) || !genericDataWrapper) { + return E_FAIL; + } + + uint32_t len; + nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(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; + nsCOMPtr<nsISupports> genericDataWrapper; + + if (NS_FAILED(mTransferable->GetTransferData( + kFileMime, getter_AddRefs(genericDataWrapper)))) { + return E_FAIL; + } + nsCOMPtr<nsIFile> file(do_QueryInterface(genericDataWrapper)); + if (!file) return E_FAIL; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + nsAutoString path; + rv = file->GetPath(path); + if (NS_FAILED(rv)) return E_FAIL; + + uint32_t allocLen = path.Length() + 2; + HGLOBAL hGlobalMemory = nullptr; + char16_t* dest; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, + sizeof(DROPFILES) + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure + pDropFile->pFiles = sizeof(DROPFILES); // Offset to start of file name string + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure + dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), (allocLen - 1) * sizeof(char16_t)); + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) { + nsresult rv; + if (!mCachedTempFile) { + nsCOMPtr<nsISupports> genericDataWrapper; + + if (NS_FAILED(mTransferable->GetTransferData( + kNativeImageMime, getter_AddRefs(genericDataWrapper)))) { + return E_FAIL; + } + nsCOMPtr<imgIContainer> image(do_QueryInterface(genericDataWrapper)); + if (!image) return E_FAIL; + + nsCOMPtr<imgITools> imgTools = + do_CreateInstance("@mozilla.org/image/tools;1"); + nsCOMPtr<nsIInputStream> inputStream; + rv = imgTools->EncodeImage(image, nsLiteralCString(IMAGE_BMP), + u"bpp=32;version=3"_ns, + getter_AddRefs(inputStream)); + if (NS_FAILED(rv) || !inputStream) { + return E_FAIL; + } + + nsCOMPtr<imgIEncoder> encoder = do_QueryInterface(inputStream); + if (!encoder) { + return E_FAIL; + } + + uint32_t size = 0; + rv = encoder->GetImageBufferUsed(&size); + if (NS_FAILED(rv)) { + return E_FAIL; + } + + char* src = nullptr; + rv = encoder->GetImageBuffer(&src); + if (NS_FAILED(rv) || !src) { + return E_FAIL; + } + + // Save the bitmap to a temporary location. + nsCOMPtr<nsIFile> dropFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); + if (!dropFile) { + return E_FAIL; + } + + // Filename must be random so as not to confuse apps like + // Photoshop which handle multiple drags into a single window. + char buf[13]; + nsCString filename; + NS_MakeRandomString(buf, 8); + memcpy(buf + 8, ".bmp", 5); + filename.Append(nsDependentCString(buf, 12)); + dropFile->AppendNative(filename); + rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); + if (NS_FAILED(rv)) { + return E_FAIL; + } + + // Cache the temp file so we can delete it later and so + // it doesn't get recreated over and over on multiple calls + // which does occur from windows shell. + dropFile->Clone(getter_AddRefs(mCachedTempFile)); + + // Write the data to disk. + nsCOMPtr<nsIOutputStream> outStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); + if (NS_FAILED(rv)) { + return E_FAIL; + } + + uint32_t written = 0; + rv = outStream->Write(src, size, &written); + if (NS_FAILED(rv) || written != size) { + return E_FAIL; + } + + outStream->Close(); + } + + // Pass the file name back to the drop target so that it can access the file. + nsAutoString path; + rv = mCachedTempFile->GetPath(path); + if (NS_FAILED(rv)) return E_FAIL; + + // Two null characters are needed to terminate the file name list. + HGLOBAL hGlobalMemory = nullptr; + + uint32_t allocLen = path.Length() + 2; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, + sizeof(DROPFILES) + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure. + pDropFile->pFiles = + sizeof(DROPFILES); // Offset to start of file name char array. + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure. + char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), + (allocLen - 1) * + sizeof(char16_t)); // Copies the null character in path as well. + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +HRESULT nsDataObj::DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG) { + nsresult rv; + if (!mCachedTempFile) { + // Tempfile will need a temporary location. + nsCOMPtr<nsIFile> dropFile; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dropFile)); + if (!dropFile) return E_FAIL; + + // Filename must be random + nsCString filename; + nsAutoString wideFileName; + nsCOMPtr<nsIURI> sourceURI; + HRESULT res; + res = GetDownloadDetails(getter_AddRefs(sourceURI), wideFileName); + if (FAILED(res)) return res; + NS_CopyUnicodeToNative(wideFileName, filename); + + dropFile->AppendNative(filename); + rv = dropFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); + if (NS_FAILED(rv)) return E_FAIL; + + // Cache the temp file so we can delete it later and so + // it doesn't get recreated over and over on multiple calls + // which does occur from windows shell. + dropFile->Clone(getter_AddRefs(mCachedTempFile)); + + // Write the data to disk. + nsCOMPtr<nsIOutputStream> outStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), dropFile); + if (NS_FAILED(rv)) return E_FAIL; + + IStream* pStream = nullptr; + nsDataObj::CreateStream(&pStream); + NS_ENSURE_TRUE(pStream, E_FAIL); + + char buffer[512]; + ULONG readCount = 0; + uint32_t writeCount = 0; + while (1) { + HRESULT hres = pStream->Read(buffer, sizeof(buffer), &readCount); + if (FAILED(hres)) return E_FAIL; + if (readCount == 0) break; + rv = outStream->Write(buffer, readCount, &writeCount); + if (NS_FAILED(rv)) return E_FAIL; + } + outStream->Close(); + pStream->Release(); + } + + // Pass the file name back to the drop target so that it can access the file. + nsAutoString path; + rv = mCachedTempFile->GetPath(path); + if (NS_FAILED(rv)) return E_FAIL; + + uint32_t allocLen = path.Length() + 2; + + // Two null characters are needed to terminate the file name list. + HGLOBAL hGlobalMemory = nullptr; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = nullptr; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, + sizeof(DROPFILES) + allocLen * sizeof(char16_t)); + if (!hGlobalMemory) return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure. + pDropFile->pFiles = + sizeof(DROPFILES); // Offset to start of file name char array. + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure. + char16_t* dest = (char16_t*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), + (allocLen - 1) * + sizeof(char16_t)); // Copies the null character in path as well. + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +//----------------------------------------------------- +// Registers the DataFlavor/FE pair. +//----------------------------------------------------- +void nsDataObj::AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) { + // These two lists are the mapping to and from data flavors and FEs. + // Later, OLE will tell us it needs a certain type of FORMATETC (text, + // unicode, etc) unicode, etc), so we will look up the data flavor that + // corresponds to the FE and then ask the transferable for that type of data. + mDataFlavors.AppendElement(aDataFlavor); + m_enumFE->AddFormatEtc(aFE); +} + +//----------------------------------------------------- +// Sets the transferable object +//----------------------------------------------------- +void nsDataObj::SetTransferable(nsITransferable* aTransferable) { + NS_IF_RELEASE(mTransferable); + + mTransferable = aTransferable; + if (nullptr == mTransferable) { + return; + } + + NS_ADDREF(mTransferable); + + return; +} + +// +// ExtractURL +// +// Roots around in the transferable for the appropriate flavor that indicates +// a url and pulls out the url portion of the data. Used mostly for creating +// internet shortcuts on the desktop. The url flavor is of the format: +// +// <url> <linefeed> <page title> +// +nsresult nsDataObj ::ExtractShortcutURL(nsString& outURL) { + NS_ASSERTION(mTransferable, "We don't have a good transferable"); + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsISupports> genericURL; + if (NS_SUCCEEDED(mTransferable->GetTransferData( + kURLMime, getter_AddRefs(genericURL)))) { + nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL)); + if (urlObject) { + nsAutoString url; + urlObject->GetData(url); + outURL = url; + + // find the first linefeed in the data, that's where the url ends. trunc + // the result string at that point. + int32_t lineIndex = outURL.FindChar('\n'); + NS_ASSERTION(lineIndex > 0, + "Format for url flavor is <url> <linefeed> <page title>"); + if (lineIndex > 0) { + outURL.Truncate(lineIndex); + rv = NS_OK; + } + } + } else if (NS_SUCCEEDED(mTransferable->GetTransferData( + kURLDataMime, getter_AddRefs(genericURL))) || + NS_SUCCEEDED(mTransferable->GetTransferData( + kURLPrivateMime, getter_AddRefs(genericURL)))) { + nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL)); + if (urlObject) { + nsAutoString url; + urlObject->GetData(url); + outURL = url; + + rv = NS_OK; + } + + } // if found flavor + + return rv; + +} // ExtractShortcutURL + +// +// ExtractShortcutTitle +// +// Roots around in the transferable for the appropriate flavor that indicates +// a url and pulls out the title portion of the data. Used mostly for creating +// internet shortcuts on the desktop. The url flavor is of the format: +// +// <url> <linefeed> <page title> +// +nsresult nsDataObj ::ExtractShortcutTitle(nsString& outTitle) { + NS_ASSERTION(mTransferable, "We'd don't have a good transferable"); + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsISupports> genericURL; + if (NS_SUCCEEDED(mTransferable->GetTransferData( + kURLMime, getter_AddRefs(genericURL)))) { + nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL)); + if (urlObject) { + nsAutoString url; + urlObject->GetData(url); + + // find the first linefeed in the data, that's where the url ends. we want + // everything after that linefeed. FindChar() returns -1 if we can't find + int32_t lineIndex = url.FindChar('\n'); + NS_ASSERTION(lineIndex != -1, + "Format for url flavor is <url> <linefeed> <page title>"); + if (lineIndex != -1) { + url.Mid(outTitle, lineIndex + 1, url.Length() - (lineIndex + 1)); + rv = NS_OK; + } + } + } // if found flavor + + return rv; + +} // ExtractShortcutTitle + +// +// BuildPlatformHTML +// +// Munge our HTML data to win32's CF_HTML spec. Basically, put the requisite +// header information on it. This will null terminate |outPlatformHTML|. See +// 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); + + constexpr auto htmlHeaderString = "<html><body>\r\n"_ns; + + constexpr auto fragmentHeaderString = "<!--StartFragment-->"_ns; + + 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, mozilla::fallible); + if (!*outPlatformHTML) return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +HRESULT +nsDataObj ::GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG, + bool aIsUnicode) { + HRESULT res = S_OK; + if (IsFlavourPresent(kURLMime)) { + if (aIsUnicode) + res = ExtractUniformResourceLocatorW(aFE, aSTG); + else + res = ExtractUniformResourceLocatorA(aFE, aSTG); + } else + NS_WARNING("Not yet implemented\n"); + return res; +} + +HRESULT +nsDataObj::ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG) { + HRESULT result = S_OK; + + nsAutoString url; + if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY; + + NS_LossyConvertUTF16toASCII asciiUrl(url); + const int totalLen = asciiUrl.Length() + 1; + HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen); + if (!hGlobalMemory) return E_OUTOFMEMORY; + + char* contents = reinterpret_cast<char*>(GlobalLock(hGlobalMemory)); + if (!contents) { + GlobalFree(hGlobalMemory); + return E_OUTOFMEMORY; + } + + strcpy(contents, asciiUrl.get()); + GlobalUnlock(hGlobalMemory); + aSTG.hGlobal = hGlobalMemory; + aSTG.tymed = TYMED_HGLOBAL; + + return result; +} + +HRESULT +nsDataObj::ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG) { + HRESULT result = S_OK; + + nsAutoString url; + if (NS_FAILED(ExtractShortcutURL(url))) return E_OUTOFMEMORY; + + const int totalLen = (url.Length() + 1) * sizeof(char16_t); + HGLOBAL hGlobalMemory = GlobalAlloc(GMEM_ZEROINIT | GMEM_SHARE, totalLen); + if (!hGlobalMemory) return E_OUTOFMEMORY; + + wchar_t* contents = reinterpret_cast<wchar_t*>(GlobalLock(hGlobalMemory)); + if (!contents) { + GlobalFree(hGlobalMemory); + return E_OUTOFMEMORY; + } + + wcscpy(contents, url.get()); + GlobalUnlock(hGlobalMemory); + aSTG.hGlobal = hGlobalMemory; + aSTG.tymed = TYMED_HGLOBAL; + + return result; +} + +// Gets the filename from the kFilePromiseURLMime flavour +HRESULT nsDataObj::GetDownloadDetails(nsIURI** aSourceURI, + nsAString& aFilename) { + *aSourceURI = nullptr; + + NS_ENSURE_TRUE(mTransferable, E_FAIL); + + // get the URI from the kFilePromiseURLMime flavor + nsCOMPtr<nsISupports> urlPrimitive; + nsresult rv = mTransferable->GetTransferData(kFilePromiseURLMime, + getter_AddRefs(urlPrimitive)); + NS_ENSURE_SUCCESS(rv, E_FAIL); + nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive); + NS_ENSURE_TRUE(srcUrlPrimitive, E_FAIL); + + nsAutoString srcUri; + srcUrlPrimitive->GetData(srcUri); + if (srcUri.IsEmpty()) return E_FAIL; + nsCOMPtr<nsIURI> sourceURI; + NS_NewURI(getter_AddRefs(sourceURI), srcUri); + + nsAutoString srcFileName; + nsCOMPtr<nsISupports> fileNamePrimitive; + Unused << mTransferable->GetTransferData(kFilePromiseDestFilename, + getter_AddRefs(fileNamePrimitive)); + nsCOMPtr<nsISupportsString> srcFileNamePrimitive = + do_QueryInterface(fileNamePrimitive); + if (srcFileNamePrimitive) { + srcFileNamePrimitive->GetData(srcFileName); + } else { + nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI); + if (!sourceURL) return E_FAIL; + + nsAutoCString urlFileName; + sourceURL->GetFileName(urlFileName); + NS_UnescapeURL(urlFileName); + CopyUTF8toUTF16(urlFileName, srcFileName); + } + 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_CopyUnicodeToNative(wideFileName, 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; +} diff --git a/widget/windows/nsDataObj.h b/widget/windows/nsDataObj.h new file mode 100644 index 0000000000..d19a21860d --- /dev/null +++ b/widget/windows/nsDataObj.h @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSDATAOBJ_H_ +#define _NSDATAOBJ_H_ + +#include <oleidl.h> +#include <shldisp.h> + +#include "mozilla/glue/WinUtils.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIURI.h" +#include "nsIStreamListener.h" +#include "nsIChannel.h" +#include "nsCOMArray.h" +#include "nsITimer.h" + +class nsICookieJarSettings; +class nsIThread; +class nsIPrincipal; +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 IDataObjectAsyncCapability { + nsCOMPtr<nsIThread> mIOThread; + + public: // construction, destruction + explicit nsDataObj(nsIURI* uri = nullptr); + + protected: + virtual ~nsDataObj(); + + public: // IUnknown methods - see iunknown.h for documentation + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP QueryInterface(REFIID, void**) override; + STDMETHODIMP_(ULONG) Release() override; + + // support for clipboard + virtual void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE); + void SetTransferable(nsITransferable* aTransferable); + + public: // IDataObject methods - these are general comments. see CfDragDrop + // for overriding behavior + // Store data in pSTM according to the format specified by pFE, if the + // format is supported (supported formats are specified in CfDragDrop:: + // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It + // is the callers responsibility to free pSTM if NOERROR is returned. + STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override; + + // Similar to GetData except that the caller allocates the structure + // referenced by pSTM. + STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) override; + + // Returns S_TRUE if this object supports the format specified by pSTM, + // S_FALSE otherwise. + STDMETHODIMP QueryGetData(LPFORMATETC pFE) override; + + // Set pCanonFE to the canonical format of pFE if one exists and return + // NOERROR, otherwise return DATA_S_SAMEFORMATETC. A canonical format + // implies an identical rendering. + STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC pFE, + LPFORMATETC pCanonFE) final; + + // Set this objects data according to the format specified by pFE and + // the storage medium specified by pSTM and return NOERROR, if the format + // is supported. If release is TRUE this object must release the storage + // associated with pSTM. + STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, + BOOL release) override; + + // Set ppEnum to an IEnumFORMATETC object which will iterate all of the + // data formats that this object supports. direction is either DATADIR_GET + // or DATADIR_SET. + STDMETHODIMP EnumFormatEtc(DWORD direction, LPENUMFORMATETC* ppEnum) final; + + // Set up an advisory connection to this object based on the format specified + // by pFE, flags, and the pAdvise. Set pConn to the established advise + // connection. + STDMETHODIMP DAdvise(LPFORMATETC pFE, DWORD flags, LPADVISESINK pAdvise, + DWORD* pConn) final; + + // Turn off advising of a previous call to DAdvise which set pConn. + STDMETHODIMP DUnadvise(DWORD pConn) final; + + // Set ppEnum to an IEnumSTATDATA object which will iterate over the + // existing objects which have established advisory connections to this + // object. + STDMETHODIMP EnumDAdvise(LPENUMSTATDATA* ppEnum) final; + + // IDataObjectAsyncCapability methods + STDMETHODIMP EndOperation(HRESULT hResult, IBindCtx* pbcReserved, + DWORD dwEffects) final; + STDMETHODIMP GetAsyncMode(BOOL* pfIsOpAsync) final; + STDMETHODIMP InOperation(BOOL* pfInAsyncOp) final; + STDMETHODIMP SetAsyncMode(BOOL fDoOpAsync) final; + STDMETHODIMP StartOperation(IBindCtx* pbcReserved) final; + + private: // other methods + // Gets the filename from the kFilePromiseURLMime flavour + HRESULT GetDownloadDetails(nsIURI** aSourceURI, nsAString& aFilename); + + // help determine the kind of drag + bool IsFlavourPresent(const char* inFlavour); + + protected: + HRESULT GetFile(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetText(const nsACString& aDF, FORMATETC& aFE, STGMEDIUM& aSTG); + + private: + HRESULT GetDib(const nsACString& inFlavor, FORMATETC&, STGMEDIUM& aSTG); + + HRESULT DropImage(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT DropFile(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT DropTempFile(FORMATETC& aFE, STGMEDIUM& aSTG); + + HRESULT GetUniformResourceLocator(FORMATETC& aFE, STGMEDIUM& aSTG, + bool aIsUnicode); + HRESULT ExtractUniformResourceLocatorA(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT ExtractUniformResourceLocatorW(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetFileDescriptor(FORMATETC& aFE, STGMEDIUM& aSTG, bool aIsUnicode); + + protected: + HRESULT GetFileContents(FORMATETC& aFE, STGMEDIUM& aSTG); + + private: + HRESULT GetPreferredDropEffect(FORMATETC& aFE, STGMEDIUM& aSTG); + + // Provide the structures needed for an internet shortcut by the shell + HRESULT GetFileDescriptorInternetShortcutA(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetFileDescriptorInternetShortcutW(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetFileContentsInternetShortcut(FORMATETC& aFE, STGMEDIUM& aSTG); + + // IStream implementation + HRESULT GetFileDescriptor_IStreamA(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetFileDescriptor_IStreamW(FORMATETC& aFE, STGMEDIUM& aSTG); + HRESULT GetFileContents_IStream(FORMATETC& aFE, STGMEDIUM& aSTG); + + nsresult ExtractShortcutURL(nsString& outURL); + nsresult ExtractShortcutTitle(nsString& outTitle); + + // munge our HTML data to win32's CF_HTML spec. Will null terminate + nsresult BuildPlatformHTML(const char* inOurHTML, char** outPlatformHTML); + + // Used for the SourceURL part of CF_HTML + nsCString mSourceURL; + + protected: + BOOL FormatsMatch(const FORMATETC& source, const FORMATETC& target) const; + + ULONG m_cRef; // the reference count + + private: + nsTArray<nsCString> mDataFlavors; + + nsITransferable* mTransferable; // nsDataObj owns and ref counts + // nsITransferable, the nsITransferable does + // know anything about the nsDataObj + + protected: + CEnumFormatEtc* m_enumFE; // Ownership Rules: + // nsDataObj owns and ref counts CEnumFormatEtc, + + private: + nsCOMPtr<nsIFile> mCachedTempFile; + + BOOL mIsAsyncMode; + BOOL mIsInOperation; + /////////////////////////////////////////////////////////////////////////////// + // CStream class implementation + // this class is used in Drag and drop with download sample + // called from IDataObject::GetData + class CStreamBase : public IStream { + // IStream + STDMETHODIMP Clone(IStream** ppStream) final; + STDMETHODIMP Commit(DWORD dwFrags) final; + STDMETHODIMP CopyTo(IStream* pDestStream, ULARGE_INTEGER nBytesToCopy, + ULARGE_INTEGER* nBytesRead, + ULARGE_INTEGER* nBytesWritten) final; + STDMETHODIMP LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, + DWORD dwFlags) final; + STDMETHODIMP Revert(void) final; + STDMETHODIMP Seek(LARGE_INTEGER nMove, DWORD dwOrigin, + ULARGE_INTEGER* nNewPos) final; + STDMETHODIMP SetSize(ULARGE_INTEGER nNewSize) final; + STDMETHODIMP UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, + DWORD dwFlags) final; + STDMETHODIMP Write(const void* pvBuffer, ULONG nBytesToRead, + ULONG* nBytesRead) final; + + protected: + uint32_t mStreamRead; + + CStreamBase(); + virtual ~CStreamBase(); + }; + + class CStream final : public CStreamBase, public nsIStreamListener { + nsCOMPtr<nsIChannel> mChannel; + FallibleTArray<uint8_t> mChannelData; + nsresult mChannelResult; + bool mChannelRead; + + virtual ~CStream(); + nsresult WaitForCompletion(); + + // IUnknown + STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final; + + // IStream + STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead, + ULONG* nBytesRead) final; + STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final; + + public: + CStream(); + nsresult Init(nsIURI* pSourceURI, nsContentPolicyType aContentPolicyType, + nsIPrincipal* aRequestingPrincipal, + nsICookieJarSettings* aCookieJarSettings); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + }; + + HRESULT CreateStream(IStream** outStream); + + // This class must be thread-safe. + class AutoCloseEvent final { + const nsAutoHandle mEvent; + + AutoCloseEvent(const AutoCloseEvent&) = delete; + void operator=(const AutoCloseEvent&) = delete; + ~AutoCloseEvent() = default; + + public: + AutoCloseEvent(); + bool IsInited() const; + void Signal() const; + DWORD Wait(DWORD aMillisec) const; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoCloseEvent) + }; + + // This class must be thread-safe. + class AutoSetEvent final { + const RefPtr<AutoCloseEvent> mEvent; + + AutoSetEvent(const AutoSetEvent&) = delete; + void operator=(const AutoSetEvent&) = delete; + ~AutoSetEvent(); + + public: + explicit AutoSetEvent(mozilla::NotNull<AutoCloseEvent*> aEvent); + void Signal() const; + bool IsWaiting() const; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AutoSetEvent) + }; + + // This class must be thread-safe. + class CMemStream final : public CStreamBase { + static mozilla::glue::Win32SRWLock mLock; + const nsAutoGlobalMem mGlobalMem; + const RefPtr<AutoCloseEvent> mEvent; + const uint32_t mTotalLength; + RefPtr<IUnknown> mMarshaler; + + virtual ~CMemStream(); + void WaitForCompletion(); + + // IStream + STDMETHODIMP Read(void* pvBuffer, ULONG nBytesToRead, + ULONG* nBytesRead) final; + STDMETHODIMP Stat(STATSTG* statstg, DWORD dwFlags) final; + + public: + CMemStream(nsHGLOBAL aGlobalMem, uint32_t mTotalLength, + already_AddRefed<AutoCloseEvent> aEvent); + + // IUnknown + STDMETHOD(QueryInterface)(REFIID refiid, void** ppvResult) final; + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(CMemStream, final) + }; + + private: + // Drag and drop helper data for implementing drag and drop image support + typedef struct { + FORMATETC fe; + STGMEDIUM stgm; + } DATAENTRY, *LPDATAENTRY; + + nsTArray<LPDATAENTRY> mDataEntryList; + nsCOMPtr<nsITimer> mTimer; + + bool LookupArbitraryFormat(FORMATETC* aFormat, LPDATAENTRY* aDataEntry, + BOOL aAddorUpdate); + bool CopyMediumData(STGMEDIUM* aMediumDst, STGMEDIUM* aMediumSrc, + LPFORMATETC aFormat, BOOL aSetData); +}; + +#endif // _NSDATAOBJ_H_ diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp new file mode 100644 index 0000000000..8750563602 --- /dev/null +++ b/widget/windows/nsDataObjCollection.cpp @@ -0,0 +1,370 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <shlobj.h> + +#include "nsDataObjCollection.h" +#include "nsClipboard.h" +#include "IEnumFE.h" + +#include <ole2.h> + +// {25589C3E-1FAC-47b9-BF43-CAEA89B79533} +const IID IID_IDataObjCollection = { + 0x25589c3e, + 0x1fac, + 0x47b9, + {0xbf, 0x43, 0xca, 0xea, 0x89, 0xb7, 0x95, 0x33}}; + +/* + * Class nsDataObjCollection + */ + +nsDataObjCollection::nsDataObjCollection() {} + +nsDataObjCollection::~nsDataObjCollection() { mDataObjects.Clear(); } + +// IUnknown interface methods - see iunknown.h for documentation +STDMETHODIMP nsDataObjCollection::QueryInterface(REFIID riid, void** ppv) { + *ppv = nullptr; + + if ((IID_IUnknown == riid) || (IID_IDataObject == riid)) { + *ppv = static_cast<IDataObject*>(this); + AddRef(); + return NOERROR; + } + + if (IID_IDataObjCollection == riid) { + *ppv = static_cast<nsIDataObjCollection*>(this); + AddRef(); + return NOERROR; + } + // offer to operate asynchronously (required by nsDragService) + if (IID_IDataObjectAsyncCapability == riid) { + *ppv = static_cast<IDataObjectAsyncCapability*>(this); + AddRef(); + return NOERROR; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) nsDataObjCollection::AddRef() { return ++m_cRef; } + +STDMETHODIMP_(ULONG) nsDataObjCollection::Release() { + if (0 != --m_cRef) return m_cRef; + + delete this; + + return 0; +} + +// IDataObject methods +STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) { + static CLIPFORMAT fileDescriptorFlavorA = + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); + static CLIPFORMAT fileDescriptorFlavorW = + ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat(CFSTR_FILECONTENTS); + + switch (pFE->cfFormat) { + case CF_TEXT: + case CF_UNICODETEXT: + return GetText(pFE, pSTM); + case CF_HDROP: + return GetFile(pFE, pSTM); + default: + if (pFE->cfFormat == fileDescriptorFlavorA || + pFE->cfFormat == fileDescriptorFlavorW) { + return GetFileDescriptors(pFE, pSTM); + } + if (pFE->cfFormat == fileFlavor) { + return GetFileContents(pFE, pSTM); + } + } + return GetFirstSupporting(pFE, pSTM); +} + +STDMETHODIMP nsDataObjCollection::GetDataHere(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) { + return E_FAIL; +} + +// Other objects querying to see if we support a particular format +STDMETHODIMP nsDataObjCollection::QueryGetData(LPFORMATETC pFE) { + UINT format = nsClipboard::GetFormat(MULTI_MIME); + + if (format == pFE->cfFormat) { + return S_OK; + } + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + IDataObject* dataObj = mDataObjects.ElementAt(i); + if (S_OK == dataObj->QueryGetData(pFE)) { + return S_OK; + } + } + + return DV_E_FORMATETC; +} + +STDMETHODIMP nsDataObjCollection::SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, + BOOL fRelease) { + // Set arbitrary data formats on the first object in the collection and let + // it handle the heavy lifting + if (mDataObjects.Length() == 0) return E_FAIL; + return mDataObjects.ElementAt(0)->SetData(pFE, pSTM, fRelease); +} + +// Registers a DataFlavor/FE pair +void nsDataObjCollection::AddDataFlavor(const char* aDataFlavor, + LPFORMATETC aFE) { + // Add the FormatEtc to our list if it's not already there. We don't care + // about the internal aDataFlavor because nsDataObj handles that. + IEnumFORMATETC* ifEtc; + FORMATETC fEtc; + ULONG num; + if (S_OK != this->EnumFormatEtc(DATADIR_GET, &ifEtc)) return; + while (S_OK == ifEtc->Next(1, &fEtc, &num)) { + NS_ASSERTION( + 1 == num, + "Bit off more than we can chew in nsDataObjCollection::AddDataFlavor"); + if (FormatsMatch(fEtc, *aFE)) { + ifEtc->Release(); + return; + } + } // If we didn't find a matching format, add this one + ifEtc->Release(); + m_enumFE->AddFormatEtc(aFE); +} + +// We accept ownership of the nsDataObj which we free on destruction +void nsDataObjCollection::AddDataObject(IDataObject* aDataObj) { + nsDataObj* dataObj = reinterpret_cast<nsDataObj*>(aDataObj); + mDataObjects.AppendElement(dataObj); +} + +// Methods for getting data +HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) { + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + // Make enough space for the header and the trailing null + uint32_t buffersize = sizeof(DROPFILES) + sizeof(char16_t); + uint32_t alloclen = 0; + char16_t* realbuffer; + nsAutoString filename; + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the filename + char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) return E_FAIL; + buffer += sizeof(DROPFILES) / sizeof(char16_t); + filename = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the filename into our buffer + alloclen = (filename.Length() + 1) * sizeof(char16_t); + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) return E_FAIL; + realbuffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!realbuffer) return E_FAIL; + realbuffer--; // Overwrite the preceding null + memcpy(realbuffer, filename.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + // We get the last null (on the double null terminator) for free since we used + // the zero memory flag when we allocated. All we need to do is fill the + // DROPFILES structure + DROPFILES* df = (DROPFILES*)GlobalLock(hGlobalMemory); + if (!df) return E_FAIL; + df->pFiles = sizeof(DROPFILES); // Offset to start of file name string + df->fNC = 0; + df->pt.x = 0; + df->pt.y = 0; + df->fWide = TRUE; // utf-16 chars + GlobalUnlock(hGlobalMemory); + // Finally fill out the STGMEDIUM struct + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; +} + +HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) { + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + uint32_t buffersize = 1; + uint32_t alloclen = 0; + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + if (pFE->cfFormat == CF_TEXT) { + nsAutoCString text; + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the text + char* buffer = (char*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) return E_FAIL; + text = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the text into our buffer + alloclen = text.Length(); + hGlobalMemory = + ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) return E_FAIL; + buffer = ((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!buffer) return E_FAIL; + buffer--; // Overwrite the preceding null + memcpy(buffer, text.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; + } + if (pFE->cfFormat == CF_UNICODETEXT) { + buffersize = sizeof(char16_t); + nsAutoString text; + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the text + char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal); + if (buffer == nullptr) return E_FAIL; + text = buffer; + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + // Now put the text into our buffer + alloclen = text.Length() * sizeof(char16_t); + hGlobalMemory = + ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) return E_FAIL; + buffer = (char16_t*)((char*)GlobalLock(hGlobalMemory) + buffersize); + if (!buffer) return E_FAIL; + buffer--; // Overwrite the preceding null + memcpy(buffer, text.get(), alloclen); + GlobalUnlock(hGlobalMemory); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; + } + + return E_FAIL; +} + +HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) { + STGMEDIUM workingmedium; + FORMATETC fe = *pFE; + HGLOBAL hGlobalMemory; + HRESULT hr; + uint32_t buffersize = sizeof(UINT); + uint32_t alloclen = sizeof(FILEDESCRIPTOR); + + hGlobalMemory = GlobalAlloc(GHND, buffersize); + + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + hr = dataObj->GetData(&fe, &workingmedium); + if (hr != S_OK) { + switch (hr) { + case DV_E_FORMATETC: + continue; + default: + return hr; + } + } + // Now we need to pull out the filedescriptor + FILEDESCRIPTOR* buffer = + (FILEDESCRIPTOR*)((char*)GlobalLock(workingmedium.hGlobal) + + sizeof(UINT)); + if (buffer == nullptr) return E_FAIL; + hGlobalMemory = ::GlobalReAlloc(hGlobalMemory, buffersize + alloclen, GHND); + if (hGlobalMemory == nullptr) return E_FAIL; + FILEGROUPDESCRIPTOR* realbuffer = + (FILEGROUPDESCRIPTOR*)GlobalLock(hGlobalMemory); + if (!realbuffer) return E_FAIL; + FILEDESCRIPTOR* copyloc = (FILEDESCRIPTOR*)((char*)realbuffer + buffersize); + memcpy(copyloc, buffer, alloclen); + realbuffer->cItems++; + GlobalUnlock(hGlobalMemory); + GlobalUnlock(workingmedium.hGlobal); + ReleaseStgMedium(&workingmedium); + buffersize += alloclen; + } + pSTM->tymed = TYMED_HGLOBAL; + pSTM->pUnkForRelease = nullptr; // Caller gets to free the data + pSTM->hGlobal = hGlobalMemory; + return S_OK; +} + +HRESULT nsDataObjCollection::GetFileContents(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) { + ULONG num = 0; + ULONG numwanted = (pFE->lindex == -1) ? 0 : pFE->lindex; + FORMATETC fEtc = *pFE; + fEtc.lindex = -1; // We're lying to the data object so it thinks it's alone + + // The key for this data type is to figure out which data object the index + // corresponds to and then just pass it along + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + nsDataObj* dataObj = mDataObjects.ElementAt(i); + if (dataObj->QueryGetData(&fEtc) != S_OK) continue; + if (num == numwanted) return dataObj->GetData(pFE, pSTM); + num++; + } + return DV_E_LINDEX; +} + +HRESULT nsDataObjCollection::GetFirstSupporting(LPFORMATETC pFE, + LPSTGMEDIUM pSTM) { + // There is no way to pass more than one of this, so just find the first data + // object that supports it and pass it along + for (uint32_t i = 0; i < mDataObjects.Length(); ++i) { + if (mDataObjects.ElementAt(i)->QueryGetData(pFE) == S_OK) + return mDataObjects.ElementAt(i)->GetData(pFE, pSTM); + } + return DV_E_FORMATETC; +} diff --git a/widget/windows/nsDataObjCollection.h b/widget/windows/nsDataObjCollection.h new file mode 100644 index 0000000000..02ec7e8916 --- /dev/null +++ b/widget/windows/nsDataObjCollection.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSDATAOBJCOLLECTION_H_ +#define _NSDATAOBJCOLLECTION_H_ + +#include <oleidl.h> + +#include "mozilla/RefPtr.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsDataObj.h" +#include "mozilla/Attributes.h" + +#define MULTI_MIME "Mozilla/IDataObjectCollectionFormat" + +EXTERN_C const IID IID_IDataObjCollection; + +// An interface to make sure we have the right kind of object for D&D +// this way we can filter out collection objects that aren't ours +class nsIDataObjCollection : public IUnknown { + public: +}; + +/* + * This ole registered class is used to facilitate drag-drop of objects which + * can be adapted by an object derived from CfDragDrop. The CfDragDrop is + * associated with instances via SetDragDrop(). + */ + +class nsDataObjCollection final : public nsIDataObjCollection, + public nsDataObj { + public: + nsDataObjCollection(); + + private: + ~nsDataObjCollection() final; + + public: // IUnknown methods - see iunknown.h for documentation + STDMETHODIMP_(ULONG) AddRef() final; + STDMETHODIMP QueryInterface(REFIID, void**) final; + STDMETHODIMP_(ULONG) Release() final; + + private: // DataGet and DataSet helper methods + HRESULT GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + HRESULT GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + HRESULT GetFileDescriptors(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + HRESULT GetFileContents(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + HRESULT GetFirstSupporting(LPFORMATETC pFE, LPSTGMEDIUM pSTM); + + using nsDataObj::GetFile; + using nsDataObj::GetFileContents; + using nsDataObj::GetText; + + // support for clipboard + void AddDataFlavor(const char* aDataFlavor, LPFORMATETC aFE) final; + + public: // from nsPIDataObjCollection + void AddDataObject(IDataObject* aDataObj); + int32_t GetNumDataObjects() { return mDataObjects.Length(); } + nsDataObj* GetDataObjectAt(uint32_t aItem) { + return mDataObjects.SafeElementAt(aItem, RefPtr<nsDataObj>()); + } + + public: + // Store data in pSTM according to the format specified by pFE, if the + // format is supported (supported formats are specified in CfDragDrop:: + // GetFormats) and return NOERROR; otherwise return DATA_E_FORMATETC. It + // is the callers responsibility to free pSTM if NOERROR is returned. + STDMETHODIMP GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final; + + // Similar to GetData except that the caller allocates the structure + // referenced by pSTM. + STDMETHODIMP GetDataHere(LPFORMATETC pFE, LPSTGMEDIUM pSTM) final; + + // Returns S_TRUE if this object supports the format specified by pSTM, + // S_FALSE otherwise. + STDMETHODIMP QueryGetData(LPFORMATETC pFE) final; + + // Set this objects data according to the format specified by pFE and + // the storage medium specified by pSTM and return NOERROR, if the format + // is supported. If release is TRUE this object must release the storage + // associated with pSTM. + STDMETHODIMP SetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM, BOOL release) final; + + private: + nsTArray<RefPtr<nsDataObj> > mDataObjects; +}; + +#endif // diff --git a/widget/windows/nsDeviceContextSpecWin.cpp b/widget/windows/nsDeviceContextSpecWin.cpp new file mode 100644 index 0000000000..7fd71b4db8 --- /dev/null +++ b/widget/windows/nsDeviceContextSpecWin.cpp @@ -0,0 +1,656 @@ +/* -*- 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 "mozilla/Telemetry.h" +#include "nsAnonymousTemporaryFile.h" + +#include <wchar.h> +#include <windef.h> +#include <winspool.h> + +#include "nsIWidget.h" + +#include "nsTArray.h" +#include "nsIPrintSettingsWin.h" + +#include "nsComponentManagerUtils.h" +#include "nsPrinterWin.h" +#include "nsReadableUtils.h" +#include "nsString.h" + +#include "gfxWindowsSurface.h" + +#include "nsIFileStreams.h" +#include "nsWindowsHelpers.h" + +#include "mozilla/gfx/Logging.h" + +#ifdef MOZ_ENABLE_SKIA_PDF +# include "mozilla/gfx/PrintTargetSkPDF.h" +# include "mozilla/gfx/PrintTargetEMF.h" +# include "nsIUUIDGenerator.h" +# include "nsDirectoryServiceDefs.h" +# include "nsPrintfCString.h" +# include "nsThreadUtils.h" +#endif + +extern mozilla::LazyLogModule gPrintingLog; +#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1) + +using namespace mozilla; +using namespace mozilla::gfx; + +#ifdef MOZ_ENABLE_SKIA_PDF +using namespace mozilla::widget; +#endif + +static const wchar_t kDriverName[] = L"WINSPOOL"; + +//---------------------------------------------------------------------------------- +//--------------- +// static members +//---------------------------------------------------------------------------------- +nsDeviceContextSpecWin::nsDeviceContextSpecWin() = default; + +//---------------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec) + +nsDeviceContextSpecWin::~nsDeviceContextSpecWin() { + SetDevMode(nullptr); + + if (mTempFile) { + mTempFile->Remove(/* recursive = */ false); + } + + if (nsCOMPtr<nsIPrintSettingsWin> ps = do_QueryInterface(mPrintSettings)) { + ps->SetDeviceName(u""_ns); + ps->SetDriverName(u""_ns); + ps->SetDevMode(nullptr); + } +} + +static bool GetDefaultPrinterName(nsAString& aDefaultPrinterName) { + DWORD length = 0; + GetDefaultPrinterW(nullptr, &length); + + if (length) { + aDefaultPrinterName.SetLength(length); + if (GetDefaultPrinterW((LPWSTR)aDefaultPrinterName.BeginWriting(), + &length)) { + // `length` includes the terminating null, so we subtract that from our + // string length. + aDefaultPrinterName.SetLength(length - 1); + PR_PL(("DEFAULT PRINTER [%s]\n", + NS_ConvertUTF16toUTF8(aDefaultPrinterName).get())); + return true; + } + } + + aDefaultPrinterName.Truncate(); + PR_PL(("NO DEFAULT PRINTER\n")); + return false; +} + +//---------------------------------------------------------------------------------- +NS_IMETHODIMP nsDeviceContextSpecWin::Init(nsIWidget* aWidget, + nsIPrintSettings* aPrintSettings, + bool aIsPrintPreview) { + mPrintSettings = aPrintSettings; + + // Get the Printer Name to be used and output format. + nsAutoString printerName; + if (mPrintSettings) { + mPrintSettings->GetOutputFormat(&mOutputFormat); + mPrintSettings->GetPrinterName(printerName); + } + + // If there is no name then use the default printer + if (printerName.IsEmpty()) { + GetDefaultPrinterName(printerName); + } + + // Gather telemetry on the print target type. + // + // Unfortunately, if we're not using our own internal save-to-pdf codepaths, + // there isn't a good way to determine whether a print is going to be to a + // physical printer or to a file or some other non-physical output. We do our + // best by checking for what seems to be the most common save-to-PDF virtual + // printers. + // + // We use StringBeginsWith below, since printer names are often followed by a + // version number or other product differentiating string. (True for doPDF, + // novaPDF, PDF-XChange and Soda PDF, for example.) + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, + u"pdf_file"_ns, 1); + } else if (StringBeginsWith(printerName, u"Microsoft Print to PDF"_ns) || + StringBeginsWith(printerName, u"Adobe PDF"_ns) || + StringBeginsWith(printerName, u"Bullzip PDF Printer"_ns) || + StringBeginsWith(printerName, u"CutePDF Writer"_ns) || + StringBeginsWith(printerName, u"doPDF"_ns) || + StringBeginsWith(printerName, u"Foxit Reader PDF Printer"_ns) || + StringBeginsWith(printerName, u"Nitro PDF Creator"_ns) || + StringBeginsWith(printerName, u"novaPDF"_ns) || + StringBeginsWith(printerName, u"PDF-XChange"_ns) || + StringBeginsWith(printerName, u"PDF24 PDF"_ns) || + StringBeginsWith(printerName, u"PDFCreator"_ns) || + StringBeginsWith(printerName, u"PrimoPDF"_ns) || + StringBeginsWith(printerName, u"Soda PDF"_ns) || + StringBeginsWith(printerName, u"Solid PDF Creator"_ns) || + StringBeginsWith(printerName, + u"Universal Document Converter"_ns)) { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, + u"pdf_file"_ns, 1); + } else if (printerName.EqualsLiteral("Microsoft XPS Document Writer")) { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, + u"xps_file"_ns, 1); + } else { + nsAString::const_iterator start, end; + printerName.BeginReading(start); + printerName.EndReading(end); + if (CaseInsensitiveFindInReadable(u"pdf"_ns, start, end)) { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, + u"pdf_unknown"_ns, 1); + } else { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, + u"unknown"_ns, 1); + } + } + + nsresult rv = NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE; + if (aPrintSettings) { +#ifdef MOZ_ENABLE_SKIA_PDF + nsAutoString printViaPdf; + Preferences::GetString("print.print_via_pdf_encoder", printViaPdf); + if (printViaPdf.EqualsLiteral("skia-pdf")) { + mPrintViaSkPDF = true; + } +#endif + + // If we're in the child and printing via the parent or we're printing to + // PDF we only need information from the print settings. + if ((XRE_IsContentProcess() && + Preferences::GetBool("print.print_via_parent")) || + mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + return NS_OK; + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + if (psWin) { + nsAutoString deviceName; + nsAutoString driverName; + psWin->GetDeviceName(deviceName); + psWin->GetDriverName(driverName); + + LPDEVMODEW devMode; + psWin->GetDevMode(&devMode); // creates new memory (makes a copy) + + if (!deviceName.IsEmpty() && !driverName.IsEmpty() && devMode) { + // Scaling is special, it is one of the few + // devMode items that we control in layout + if (devMode->dmFields & DM_SCALE) { + double scale = double(devMode->dmScale) / 100.0f; + if (scale != 1.0) { + aPrintSettings->SetScaling(scale); + devMode->dmScale = 100; + } + } + + SetDeviceName(deviceName); + SetDriverName(driverName); + SetDevMode(devMode); + + return NS_OK; + } else { + PR_PL( + ("***** nsDeviceContextSpecWin::Init - " + "deviceName/driverName/devMode was NULL!\n")); + if (devMode) ::HeapFree(::GetProcessHeap(), 0, devMode); + } + } + } else { + PR_PL(("***** nsDeviceContextSpecWin::Init - aPrintSettingswas NULL!\n")); + } + + if (printerName.IsEmpty()) { + return rv; + } + + return GetDataFromPrinter(printerName, mPrintSettings); +} + +//---------------------------------------------------------- + +already_AddRefed<PrintTarget> nsDeviceContextSpecWin::MakePrintTarget() { + NS_ASSERTION(mDevMode || mOutputFormat == nsIPrintSettings::kOutputFormatPDF, + "DevMode can't be NULL here unless we're printing to PDF."); + +#ifdef MOZ_ENABLE_SKIA_PDF + if (mPrintViaSkPDF) { + double width, height; + mPrintSettings->GetEffectivePageSize(&width, &height); + if (width <= 0 || height <= 0) { + return nullptr; + } + + // convert twips to points + width /= TWIPS_PER_POINT_FLOAT; + height /= TWIPS_PER_POINT_FLOAT; + IntSize size = IntSize::Ceil(width, height); + + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + nsString filename; + mPrintSettings->GetToFileName(filename); + + nsAutoCString printFile(NS_ConvertUTF16toUTF8(filename).get()); + auto skStream = MakeUnique<SkFILEWStream>(printFile.get()); + return PrintTargetSkPDF::CreateOrNull(std::move(skStream), size); + } + + if (mDevMode) { + NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!"); + HDC dc = + ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode); + if (!dc) { + gfxCriticalError(gfxCriticalError::DefaultOptions(false)) + << "Failed to create device context in GetSurfaceForPrinter"; + return nullptr; + } + return PrintTargetEMF::CreateOrNull(dc, size); + } + } +#endif + + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) { + nsString filename; + mPrintSettings->GetToFileName(filename); + + double width, height; + mPrintSettings->GetEffectiveSheetSize(&width, &height); + if (width <= 0 || height <= 0) { + return nullptr; + } + + // convert twips to points + width /= TWIPS_PER_POINT_FLOAT; + height /= TWIPS_PER_POINT_FLOAT; + + nsCOMPtr<nsIFile> file; + nsresult rv; + if (!filename.IsEmpty()) { + file = do_CreateInstance("@mozilla.org/file/local;1"); + rv = file->InitWithPath(filename); + } else { + rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(mTempFile)); + file = mTempFile; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsCOMPtr<nsIFileOutputStream> stream = + do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = stream->Init(file, -1, -1, 0); + if (NS_FAILED(rv)) { + return nullptr; + } + + return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height)); + } + + if (mDevMode) { + NS_WARNING_ASSERTION(!mDriverName.IsEmpty(), "No driver!"); + HDC dc = + ::CreateDCW(mDriverName.get(), mDeviceName.get(), nullptr, mDevMode); + if (!dc) { + gfxCriticalError(gfxCriticalError::DefaultOptions(false)) + << "Failed to create device context in GetSurfaceForPrinter"; + return nullptr; + } + + // The PrintTargetWindows takes over ownership of this DC + return PrintTargetWindows::CreateOrNull(dc); + } + + return nullptr; +} + +float nsDeviceContextSpecWin::GetDPI() { + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF || mPrintViaSkPDF) { + return nsIDeviceContextSpec::GetDPI(); + } + // To match the previous printing code we need to return 144 when printing to + // a Windows surface. + return 144.0f; +} + +float nsDeviceContextSpecWin::GetPrintingScale() { + MOZ_ASSERT(mPrintSettings); + if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF || mPrintViaSkPDF) { + return nsIDeviceContextSpec::GetPrintingScale(); + } + + // The print settings will have the resolution stored from the real device. + // + // FIXME: Shouldn't we use this in GetDPI then instead of hard-coding 144.0? + int32_t resolution; + mPrintSettings->GetResolution(&resolution); + return float(resolution) / GetDPI(); +} + +gfxPoint nsDeviceContextSpecWin::GetPrintingTranslate() { + // The underlying surface on windows is the size of the printable region. When + // the region is smaller than the actual paper size the (0, 0) coordinate + // refers top-left of that unwritable region. To instead have (0, 0) become + // the top-left of the actual paper, translate it's coordinate system by the + // unprintable region's width. + double marginTop, marginLeft; + mPrintSettings->GetUnwriteableMarginTop(&marginTop); + mPrintSettings->GetUnwriteableMarginLeft(&marginLeft); + int32_t resolution; + mPrintSettings->GetResolution(&resolution); + return gfxPoint(-marginLeft * resolution, -marginTop * resolution); +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDeviceName(const nsAString& aDeviceName) { + mDeviceName = aDeviceName; +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDriverName(const nsAString& aDriverName) { + mDriverName = aDriverName; +} + +//---------------------------------------------------------------------------------- +void nsDeviceContextSpecWin::SetDevMode(LPDEVMODEW aDevMode) { + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + } + + mDevMode = aDevMode; +} + +//------------------------------------------------------------------ +void nsDeviceContextSpecWin::GetDevMode(LPDEVMODEW& aDevMode) { + aDevMode = mDevMode; +} + +#define DISPLAY_LAST_ERROR + +//---------------------------------------------------------------------------------- +// Setup the object's data member with the selected printer's data +nsresult nsDeviceContextSpecWin::GetDataFromPrinter(const nsAString& aName, + nsIPrintSettings* aPS) { + nsresult rv = NS_ERROR_FAILURE; + + nsHPRINTER hPrinter = nullptr; + const nsString& flat = PromiseFlatString(aName); + wchar_t* name = + (wchar_t*)flat.get(); // Windows APIs use non-const name argument + + BOOL status = ::OpenPrinterW(name, &hPrinter, nullptr); + if (status) { + nsAutoPrinter autoPrinter(hPrinter); + + LPDEVMODEW pDevMode; + + // Allocate a buffer of the correct size. + LONG needed = + ::DocumentPropertiesW(nullptr, hPrinter, name, nullptr, nullptr, 0); + if (needed < 0) { + PR_PL( + ("**** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't get " + "size of DEVMODE using DocumentPropertiesW(pDeviceName = \"%s\"). " + "GetLastEror() = %08x\n", + NS_ConvertUTF16toUTF8(aName).get(), GetLastError())); + return NS_ERROR_FAILURE; + } + + // Some drivers do not return the correct size for their DEVMODE, so we + // over-allocate to try and compensate. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5) + needed *= 2; + pDevMode = + (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed); + if (!pDevMode) return NS_ERROR_FAILURE; + + // Get the default DevMode for the printer and modify it for our needs. + LONG ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, nullptr, + DM_OUT_BUFFER); + + if (ret == IDOK && aPS) { + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(pDevMode); + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(nullptr, hPrinter, name, pDevMode, pDevMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + + // We need to copy the final DEVMODE settings back to our print settings, + // because they may have been set from invalid prefs. + if (ret == IDOK) { + // We need to get information from the device as well. + nsAutoHDC printerDC(::CreateICW(kDriverName, name, nullptr, pDevMode)); + if (NS_WARN_IF(!printerDC)) { + ::HeapFree(::GetProcessHeap(), 0, pDevMode); + return NS_ERROR_FAILURE; + } + + psWin->CopyFromNative(printerDC, pDevMode); + } + } + + if (ret != IDOK) { + ::HeapFree(::GetProcessHeap(), 0, pDevMode); + PR_PL( + ("***** nsDeviceContextSpecWin::GetDataFromPrinter - " + "DocumentProperties call failed code: %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(nsDependentString(kDriverName)); + + rv = NS_OK; + } else { + rv = NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND; + PR_PL( + ("***** nsDeviceContextSpecWin::GetDataFromPrinter - Couldn't open " + "printer: [%s]\n", + NS_ConvertUTF16toUTF8(aName).get())); + DISPLAY_LAST_ERROR + } + return rv; +} + +//*********************************************************** +// Printer List +//*********************************************************** + +nsPrinterListWin::~nsPrinterListWin() = default; + +// Helper to get the array of PRINTER_INFO_4 records from the OS into a +// caller-supplied byte array; returns the number of records present. +static unsigned GetPrinterInfo4(nsTArray<BYTE>& aBuffer) { + const DWORD kLevel = 4; + DWORD needed = 0; + DWORD count = 0; + const DWORD kFlags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS; + BOOL ok = ::EnumPrintersW(kFlags, + nullptr, // Name + kLevel, // Level + nullptr, // pPrinterEnum + 0, // cbBuf (buffer size) + &needed, // Bytes needed in buffer + &count); + if (needed > 0) { + aBuffer.SetLength(needed); + ok = ::EnumPrintersW(kFlags, nullptr, kLevel, aBuffer.Elements(), + aBuffer.Length(), &needed, &count); + } + if (!ok || !count) { + return 0; + } + return count; +} + +nsTArray<nsPrinterListBase::PrinterInfo> nsPrinterListWin::Printers() const { + PR_PL(("nsPrinterListWin::Printers\n")); + + AutoTArray<BYTE, 1024> buffer; + unsigned count = GetPrinterInfo4(buffer); + + if (!count) { + PR_PL(("[No printers found]\n")); + return {}; + } + + const auto* printers = + reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements()); + nsTArray<PrinterInfo> list; + for (unsigned i = 0; i < count; i++) { + // For LOCAL printers, we check whether OpenPrinter succeeds, and omit + // them from the list if not. This avoids presenting printers that the + // user cannot actually use (e.g. due to Windows permissions). + // For NETWORK printers, this check may block for a long time (waiting for + // network timeout), so we skip it; if the user tries to access a printer + // that isn't available, we'll have to show an error later. + // (We always need to be able to handle an error, anyhow, as the printer + // could get disconnected after we've created the list, for example.) + bool isAvailable = false; + if (printers[i].Attributes & PRINTER_ATTRIBUTE_NETWORK) { + isAvailable = true; + } else if (printers[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) { + HANDLE handle; + if (::OpenPrinterW(printers[i].pPrinterName, &handle, nullptr)) { + ::ClosePrinter(handle); + isAvailable = true; + } + } + if (isAvailable) { + list.AppendElement(PrinterInfo{nsString(printers[i].pPrinterName)}); + PR_PL(("Printer Name: %s\n", + NS_ConvertUTF16toUTF8(printers[i].pPrinterName).get())); + } + } + + if (!count) { + PR_PL(("[No usable printers found]\n")); + return {}; + } + + return list; +} + +Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterByName( + nsString aName) const { + Maybe<PrinterInfo> rv; + + AutoTArray<BYTE, 1024> buffer; + unsigned count = GetPrinterInfo4(buffer); + + const auto* printers = + reinterpret_cast<const _PRINTER_INFO_4W*>(buffer.Elements()); + for (unsigned i = 0; i < count; ++i) { + if (aName.Equals(nsString(printers[i].pPrinterName))) { + rv.emplace(PrinterInfo{aName}); + break; + } + } + + return rv; +} + +Maybe<nsPrinterListBase::PrinterInfo> nsPrinterListWin::PrinterBySystemName( + nsString aName) const { + return PrinterByName(std::move(aName)); +} + +RefPtr<nsIPrinter> nsPrinterListWin::CreatePrinter(PrinterInfo aInfo) const { + return nsPrinterWin::Create(mCommonPaperInfo, std::move(aInfo.mName)); +} + +nsresult nsPrinterListWin::SystemDefaultPrinterName(nsAString& aName) const { + if (GetDefaultPrinterName(aName)) { + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsPrinterListWin::InitPrintSettingsFromPrinter( + const nsAString& aPrinterName, nsIPrintSettings* aPrintSettings) { + NS_ENSURE_ARG_POINTER(aPrintSettings); + + if (aPrinterName.IsEmpty()) { + return NS_OK; + } + + // When printing to PDF on Windows there is no associated printer driver. + int16_t outputFormat; + aPrintSettings->GetOutputFormat(&outputFormat); + if (outputFormat == nsIPrintSettings::kOutputFormatPDF) { + return NS_OK; + } + + RefPtr<nsDeviceContextSpecWin> devSpecWin = new nsDeviceContextSpecWin(); + if (!devSpecWin) return NS_ERROR_OUT_OF_MEMORY; + + // If the settings have already been initialized from prefs then pass these to + // GetDataFromPrinter, so that they are saved to the printer. + bool initializedFromPrefs; + nsresult rv = + aPrintSettings->GetIsInitializedFromPrefs(&initializedFromPrefs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (initializedFromPrefs) { + // If we pass in print settings to GetDataFromPrinter it already copies + // things back to the settings, so we can return here. + return devSpecWin->GetDataFromPrinter(aPrinterName, aPrintSettings); + } + + devSpecWin->GetDataFromPrinter(aPrinterName); + + LPDEVMODEW devmode; + devSpecWin->GetDevMode(devmode); + if (NS_WARN_IF(!devmode)) { + return NS_ERROR_FAILURE; + } + + aPrintSettings->SetPrinterName(aPrinterName); + + // We need to get information from the device as well. + const nsString& flat = PromiseFlatString(aPrinterName); + char16ptr_t printerName = flat.get(); + HDC dc = ::CreateICW(kDriverName, printerName, nullptr, devmode); + if (NS_WARN_IF(!dc)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPrintSettings); + MOZ_ASSERT(psWin); + psWin->CopyFromNative(dc, devmode); + ::DeleteDC(dc); + + return NS_OK; +} diff --git a/widget/windows/nsDeviceContextSpecWin.h b/widget/windows/nsDeviceContextSpecWin.h new file mode 100644 index 0000000000..df3a6a9492 --- /dev/null +++ b/widget/windows/nsDeviceContextSpecWin.h @@ -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/. */ + +#ifndef nsDeviceContextSpecWin_h___ +#define nsDeviceContextSpecWin_h___ + +#include "nsCOMPtr.h" +#include "nsIDeviceContextSpec.h" +#include "nsPrinterListBase.h" +#include "nsIPrintSettings.h" +#include <windows.h> +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +class nsIFile; +class nsIWidget; + +class nsDeviceContextSpecWin : public nsIDeviceContextSpec { + public: + nsDeviceContextSpecWin(); + + NS_DECL_ISUPPORTS + + already_AddRefed<PrintTarget> MakePrintTarget() final; + NS_IMETHOD BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) override { + return NS_OK; + } + 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; + gfxPoint GetPrintingTranslate() final; + + void GetDriverName(nsAString& aDriverName) const { + aDriverName = mDriverName; + } + void GetDeviceName(nsAString& aDeviceName) const { + aDeviceName = mDeviceName; + } + + // The GetDevMode will return a pointer to a DevMode + // whether it is from the Global memory handle or just the DevMode + // To get the DevMode from the Global memory Handle it must lock it + // So this call must be paired with a call to UnlockGlobalHandle + void GetDevMode(LPDEVMODEW& aDevMode); + + // helper functions + nsresult GetDataFromPrinter(const nsAString& aName, + nsIPrintSettings* aPS = nullptr); + + protected: + void SetDeviceName(const nsAString& aDeviceName); + void SetDriverName(const nsAString& aDriverName); + void SetDevMode(LPDEVMODEW aDevMode); + + virtual ~nsDeviceContextSpecWin(); + + nsString mDriverName; + nsString mDeviceName; + LPDEVMODEW mDevMode = nullptr; + + nsCOMPtr<nsIPrintSettings> mPrintSettings; + int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative; + + // A temporary file to create an "anonymous" print target. See bug 1664253, + // this should ideally not be needed. + nsCOMPtr<nsIFile> mTempFile; + + // This variable is independant of nsIPrintSettings::kOutputFormatPDF. + // It controls both whether normal printing is done via PDF using Skia and + // whether print-to-PDF uses Skia. + bool mPrintViaSkPDF = false; +}; + +//------------------------------------------------------------------------- +// Printer List +//------------------------------------------------------------------------- +class nsPrinterListWin final : public nsPrinterListBase { + public: + NS_IMETHOD InitPrintSettingsFromPrinter(const nsAString&, + nsIPrintSettings*) final; + + nsTArray<PrinterInfo> Printers() const final; + RefPtr<nsIPrinter> CreatePrinter(PrinterInfo) const final; + + nsPrinterListWin() = default; + + protected: + nsresult SystemDefaultPrinterName(nsAString&) const final; + + mozilla::Maybe<PrinterInfo> PrinterByName(nsString) const final; + mozilla::Maybe<PrinterInfo> PrinterBySystemName( + nsString aPrinterName) const final; + + private: + ~nsPrinterListWin(); +}; + +#endif diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp new file mode 100644 index 0000000000..39247a033b --- /dev/null +++ b/widget/windows/nsDragService.cpp @@ -0,0 +1,613 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <ole2.h> +#include <oleidl.h> +#include <shlobj.h> +#include <shlwapi.h> + +// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN +#include <shellapi.h> + +#include "mozilla/RefPtr.h" +#include "nsDragService.h" +#include "nsITransferable.h" +#include "nsDataObj.h" + +#include "nsWidgetsCID.h" +#include "nsNativeDragTarget.h" +#include "nsNativeDragSource.h" +#include "nsClipboard.h" +#include "mozilla/dom/Document.h" +#include "nsDataObjCollection.h" + +#include "nsArrayUtils.h" +#include "nsString.h" +#include "nsEscape.h" +#include "nsIScreenManager.h" +#include "nsToolkit.h" +#include "nsCRT.h" +#include "nsDirectoryServiceDefs.h" +#include "nsUnicharUtils.h" +#include "nsRect.h" +#include "nsMathUtils.h" +#include "WinUtils.h" +#include "KeyboardLayout.h" +#include "gfxContext.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Tools.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +//------------------------------------------------------------------------- +// +// DragService constructor +// +//------------------------------------------------------------------------- +nsDragService::nsDragService() + : mDataObject(nullptr), mSentLocalDropEvent(false) {} + +//------------------------------------------------------------------------- +// +// DragService destructor +// +//------------------------------------------------------------------------- +nsDragService::~nsDragService() { NS_IF_RELEASE(mDataObject); } + +bool nsDragService::CreateDragImage(nsINode* aDOMNode, + const Maybe<CSSIntRegion>& aRegion, + SHDRAGIMAGE* psdi) { + if (!psdi) return false; + + memset(psdi, 0, sizeof(SHDRAGIMAGE)); + if (!aDOMNode) return false; + + // Prepare the drag image + LayoutDeviceIntRect dragRect; + RefPtr<SourceSurface> surface; + nsPresContext* pc; + DrawDrag(aDOMNode, aRegion, mScreenPosition, &dragRect, &surface, &pc); + if (!surface) return false; + + uint32_t bmWidth = dragRect.Width(), bmHeight = dragRect.Height(); + + if (bmWidth == 0 || bmHeight == 0) return false; + + psdi->crColorKey = CLR_NONE; + + RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface( + IntSize(bmWidth, bmHeight), SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, false); + + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + return false; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, + dataSurface->GetFormat()); + if (!dt) { + dataSurface->Unmap(); + return false; + } + + dt->DrawSurface( + surface, + Rect(0, 0, dataSurface->GetSize().width, dataSurface->GetSize().height), + Rect(0, 0, surface->GetSize().width, surface->GetSize().height), + DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + dt->Flush(); + + BITMAPV5HEADER bmih; + memset((void*)&bmih, 0, sizeof(BITMAPV5HEADER)); + bmih.bV5Size = sizeof(BITMAPV5HEADER); + bmih.bV5Width = bmWidth; + bmih.bV5Height = -(int32_t)bmHeight; // flip vertical + bmih.bV5Planes = 1; + bmih.bV5BitCount = 32; + bmih.bV5Compression = BI_BITFIELDS; + bmih.bV5RedMask = 0x00FF0000; + bmih.bV5GreenMask = 0x0000FF00; + bmih.bV5BlueMask = 0x000000FF; + bmih.bV5AlphaMask = 0xFF000000; + + HDC hdcSrc = CreateCompatibleDC(nullptr); + void* lpBits = nullptr; + if (hdcSrc) { + psdi->hbmpDragImage = + ::CreateDIBSection(hdcSrc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, + (void**)&lpBits, nullptr, 0); + if (psdi->hbmpDragImage && lpBits) { + CopySurfaceDataToPackedArray(map.mData, static_cast<uint8_t*>(lpBits), + dataSurface->GetSize(), map.mStride, + BytesPerPixel(dataSurface->GetFormat())); + } + + psdi->sizeDragImage.cx = bmWidth; + psdi->sizeDragImage.cy = bmHeight; + + 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, const Maybe<CSSIntRegion>& aRegion, + uint32_t aActionType) { + // Try and get source URI of the items that are being dragged + nsIURI* uri = nullptr; + + RefPtr<dom::Document> doc(mSourceDocument); + if (doc) { + uri = doc->GetDocumentURI(); + } + + uint32_t numItemsToDrag = 0; + nsresult rv = anArrayTransferables->GetLength(&numItemsToDrag); + if (!numItemsToDrag) return NS_ERROR_FAILURE; + + // The clipboard class contains some static utility methods that we + // can use to create an IDataObject from the transferable + + // if we're dragging more than one item, we need to create a + // "collection" object to fake out the OS. This collection contains + // one |IDataObject| for each transferable. If there is just the one + // (most cases), only pass around the native |IDataObject|. + RefPtr<IDataObject> itemToDrag; + if (numItemsToDrag > 1) { + nsDataObjCollection* dataObjCollection = new nsDataObjCollection(); + if (!dataObjCollection) return NS_ERROR_OUT_OF_MEMORY; + itemToDrag = dataObjCollection; + for (uint32_t i = 0; i < numItemsToDrag; ++i) { + nsCOMPtr<nsITransferable> trans = + do_QueryElementAt(anArrayTransferables, i); + if (trans) { + RefPtr<IDataObject> dataObj; + rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj), + uri); + NS_ENSURE_SUCCESS(rv, rv); + // Add the flavors to the collection object too + rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection); + NS_ENSURE_SUCCESS(rv, rv); + + dataObjCollection->AddDataObject(dataObj); + } + } + } // if dragging multiple items + else { + nsCOMPtr<nsITransferable> trans = + do_QueryElementAt(anArrayTransferables, 0); + if (trans) { + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(itemToDrag), uri); + NS_ENSURE_SUCCESS(rv, rv); + } + } // else dragging a single object + + // Create a drag image if support is available + IDragSourceHelper* pdsh; + if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr, + CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, + (void**)&pdsh))) { + SHDRAGIMAGE sdi; + if (CreateDragImage(mSourceNode, aRegion, &sdi)) { + if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag))) + DeleteObject(sdi.hbmpDragImage); + } + pdsh->Release(); + } + + // Kick off the native drag session + return StartInvokingDragSession(itemToDrag, aActionType); +} + +static 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; +} + +//------------------------------------------------------------------------- +nsresult nsDragService::StartInvokingDragSession(IDataObject* aDataObj, + uint32_t aActionType) { + // To do the drag we need to create an object that + // implements the IDataObject interface (for OLE) + RefPtr<nsNativeDragSource> nativeDragSrc = + new nsNativeDragSource(mDataTransfer); + + // Now figure out what the native drag effect should be + DWORD winDropRes; + DWORD effects = DROPEFFECT_SCROLL; + if (aActionType & DRAGDROP_ACTION_COPY) { + effects |= DROPEFFECT_COPY; + } + if (aActionType & DRAGDROP_ACTION_MOVE) { + effects |= DROPEFFECT_MOVE; + } + if (aActionType & DRAGDROP_ACTION_LINK) { + effects |= DROPEFFECT_LINK; + } + + // XXX not sure why we bother to cache this, it can change during + // the drag + mDragAction = aActionType; + mSentLocalDropEvent = false; + + // Start dragging + StartDragSession(); + OpenDragPopup(); + + RefPtr<IDataObjectAsyncCapability> pAsyncOp; + // Offer to do an async drag + if (SUCCEEDED(aDataObj->QueryInterface(IID_IDataObjectAsyncCapability, + getter_AddRefs(pAsyncOp)))) { + pAsyncOp->SetAsyncMode(VARIANT_TRUE); + } else { + MOZ_ASSERT_UNREACHABLE("When did our data object stop being async"); + } + + // Call the native D&D method + HRESULT res = ::DoDragDrop(aDataObj, nativeDragSrc, effects, &winDropRes); + + // In cases where the drop operation completed outside the application, + // update the source node's DataTransfer dropEffect value so it is up to date. + if (!mSentLocalDropEvent) { + uint32_t dropResult; + // Order is important, since multiple flags can be returned. + if (winDropRes & DROPEFFECT_COPY) + dropResult = DRAGDROP_ACTION_COPY; + else if (winDropRes & DROPEFFECT_LINK) + dropResult = DRAGDROP_ACTION_LINK; + else if (winDropRes & DROPEFFECT_MOVE) + dropResult = DRAGDROP_ACTION_MOVE; + else + dropResult = DRAGDROP_ACTION_NONE; + + if (mDataTransfer) { + if (res == DRAGDROP_S_DROP) // Success + mDataTransfer->SetDropEffectInt(dropResult); + else + mDataTransfer->SetDropEffectInt(DRAGDROP_ACTION_NONE); + } + } + + mUserCancelled = nativeDragSrc->UserCancelled(); + + // We're done dragging, get the cursor position and end the drag + // Use GetMessagePos to get the position of the mouse at the last message + // seen by the event loop. (Bug 489729) + // Note that we must convert this from device pixels back to Windows logical + // pixels (bug 818927). + DWORD pos = ::GetMessagePos(); + 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))); + ModifierKeyState modifierKeyState; + EndDragSession(true, modifierKeyState.GetModifiers()); + + mDoingDrag = false; + + return DRAGDROP_S_DROP == res ? NS_OK : NS_ERROR_FAILURE; +} + +//------------------------------------------------------------------------- +// Make Sure we have the right kind of object +nsDataObjCollection* nsDragService::GetDataObjCollection( + IDataObject* aDataObj) { + nsDataObjCollection* dataObjCol = nullptr; + if (aDataObj) { + nsIDataObjCollection* dataObj; + if (aDataObj->QueryInterface(IID_IDataObjCollection, (void**)&dataObj) == + S_OK) { + dataObjCol = static_cast<nsDataObjCollection*>(aDataObj); + dataObj->Release(); + } + } + + return dataObjCol; +} + +//------------------------------------------------------------------------- +NS_IMETHODIMP +nsDragService::GetNumDropItems(uint32_t* aNumItems) { + if (!mDataObject) { + *aNumItems = 0; + return NS_OK; + } + + if (IsCollectionObject(mDataObject)) { + nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject); + if (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, uint32_t aKeyModifiers) { + // Bug 100180: If we've got mouse events captured, make sure we release it - + // that way, if we happen to call EndDragSession before diving into a nested + // event loop, we can still respond to mouse events. + if (::GetCapture()) { + ::ReleaseCapture(); + } + + nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers); + NS_IF_RELEASE(mDataObject); + + return NS_OK; +} + +NS_IMETHODIMP +nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, + int32_t aImageY) { + if (!mDataObject) { + return NS_OK; + } + + nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY); + + IDragSourceHelper* pdsh; + if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr, + CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, + (void**)&pdsh))) { + SHDRAGIMAGE sdi; + if (CreateDragImage(mSourceNode, Nothing(), &sdi)) { + nsNativeDragTarget::DragImageChanged(); + if (FAILED(pdsh->InitializeFromBitmap(&sdi, mDataObject))) + DeleteObject(sdi.hbmpDragImage); + } + pdsh->Release(); + } + + return NS_OK; +} diff --git a/widget/windows/nsDragService.h b/widget/windows/nsDragService.h new file mode 100644 index 0000000000..5a86a74f76 --- /dev/null +++ b/widget/windows/nsDragService.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDragService_h__ +#define nsDragService_h__ + +#include "nsBaseDragService.h" +#include <windows.h> +#include <shlobj.h> + +struct IDataObject; +class nsDataObjCollection; + +/** + * Native Win32 DragService wrapper + */ + +class nsDragService : public nsBaseDragService { + public: + nsDragService(); + virtual ~nsDragService(); + + // nsBaseDragService + MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl( + nsIArray* anArrayTransferables, + const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion, + uint32_t aActionType); + + // nsIDragSession + NS_IMETHOD GetData(nsITransferable* aTransferable, uint32_t anItem) override; + NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override; + NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor, + bool* _retval) override; + MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag, + uint32_t aKeyModifiers) override; + NS_IMETHOD UpdateDragImage(nsINode* aImage, int32_t aImageX, + int32_t aImageY) override; + + // native impl. + NS_IMETHOD SetIDataObject(IDataObject* aDataObj); + MOZ_CAN_RUN_SCRIPT nsresult StartInvokingDragSession(IDataObject* aDataObj, + uint32_t aActionType); + + // A drop occurred within the application vs. outside of it. + void SetDroppedLocal(); + + IDataObject* GetDataObject() { return mDataObject; } + + protected: + nsDataObjCollection* GetDataObjCollection(IDataObject* aDataObj); + + // determine if we have a single data object or one of our private + // collections + bool IsCollectionObject(IDataObject* inDataObj); + + // Create a bitmap for drag operations + bool CreateDragImage(nsINode* aDOMNode, + const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion, + SHDRAGIMAGE* psdi); + + IDataObject* mDataObject; + bool mSentLocalDropEvent; +}; + +#endif // nsDragService_h__ diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp new file mode 100644 index 0000000000..9d6e4d79c4 --- /dev/null +++ b/widget/windows/nsFilePicker.cpp @@ -0,0 +1,622 @@ +/* -*- 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/BackgroundHangMonitor.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsReadableUtils.h" +#include "nsNetUtil.h" +#include "nsWindow.h" +#include "nsEnumeratorUtils.h" +#include "nsCRT.h" +#include "nsString.h" +#include "nsToolkit.h" +#include "WinUtils.h" +#include "nsPIDOMWindow.h" +#include "GeckoProfiler.h" + +using mozilla::IsWin8OrLater; +using mozilla::MakeUnique; +using mozilla::UniquePtr; +using mozilla::mscom::EnsureMTA; + +using namespace mozilla::widget; + +UniquePtr<char16_t[], nsFilePicker::FreeDeleter> + nsFilePicker::sLastUsedUnicodeDirectory; + +#define MAX_EXTENSION_LENGTH 10 +#define FILE_BUFFER_SIZE 4096 + +typedef DWORD FILEOPENDIALOGOPTIONS; + +/////////////////////////////////////////////////////////////////////////////// +// Helper classes + +// 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; +}; + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker + +nsFilePicker::nsFilePicker() : mSelectedType(1) {} + +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); +} + +/* + * Folder picker invocation + */ + +/* + * Show a folder picker. + * + * @param aInitialDir The initial directory, the last used directory will be + * used if left blank. + * @return true if a file was selected successfully. + */ +bool nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) { + 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_SERVER, IID_IFileOpenDialog, + getter_AddRefs(dialog)))) { + return false; + } + + // options + FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS; + HRESULT hr = dialog->SetOptions(fos); + if (FAILED(hr)) { + return false; + } + + // initial strings + hr = dialog->SetTitle(mTitle.get()); + if (FAILED(hr)) { + return false; + } + + if (!mOkButtonLabel.IsEmpty()) { + hr = dialog->SetOkButtonLabel(mOkButtonLabel.get()); + if (FAILED(hr)) { + return false; + } + } + + if (!aInitialDir.IsEmpty()) { + RefPtr<IShellItem> folder; + if (SUCCEEDED(SHCreateItemFromParsingName(aInitialDir.get(), nullptr, + IID_IShellItem, + getter_AddRefs(folder)))) { + hr = dialog->SetFolder(folder); + if (FAILED(hr)) { + return false; + } + } + } + + AutoDestroyTmpWindow adtw((HWND)( + mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) + : nullptr)); + + // display + mozilla::BackgroundHangMonitor().NotifyWait(); + RefPtr<IShellItem> item; + if (FAILED(dialog->Show(adtw.get())) || + FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) { + return false; + } + + // results + + // If the user chose a Win7 Library, resolve to the library's + // default save folder. + RefPtr<IShellItem> folderPath; + RefPtr<IShellLibrary> shellLib; + if (FAILED(CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLibrary, getter_AddRefs(shellLib)))) { + return false; + } + + 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 + */ + +/* + * Show a file picker. + * + * @param aInitialDir The initial directory, the last used directory will be + * used if left blank. + * @return true if a file was selected successfully. + */ +bool nsFilePicker::ShowFilePicker(const nsString& aInitialDir) { + AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", 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_SERVER, IID_IFileOpenDialog, + getter_AddRefs(dialog)))) { + return false; + } + } else { + if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, + CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, + getter_AddRefs(dialog)))) { + return false; + } + } + + // options + + FILEOPENDIALOGOPTIONS fos = 0; + fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM; + + // Handle add to recent docs settings + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { + fos |= FOS_DONTADDTORECENT; + } + + // mode specific + switch (mMode) { + case modeOpen: + fos |= FOS_FILEMUSTEXIST; + break; + + case modeOpenMultiple: + fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT; + break; + + case modeSave: + fos |= FOS_NOREADONLYRETURN; + // Don't follow shortcuts when saving a shortcut, this can be used + // to trick users (bug 271732) + if (IsDefaultPathLink()) fos |= FOS_NODEREFERENCELINKS; + break; + } + + HRESULT hr = dialog->SetOptions(fos); + if (FAILED(hr)) { + return false; + } + + // initial strings + + // title + hr = dialog->SetTitle(mTitle.get()); + if (FAILED(hr)) { + return false; + } + + // default filename + if (!mDefaultFilename.IsEmpty()) { + hr = dialog->SetFileName(mDefaultFilename.get()); + if (FAILED(hr)) { + return false; + } + } + + // default extension to append to new files + if (!mDefaultExtension.IsEmpty()) { + hr = dialog->SetDefaultExtension(mDefaultExtension.get()); + if (FAILED(hr)) { + return false; + } + } else if (IsDefaultPathHtml()) { + hr = dialog->SetDefaultExtension(L"html"); + if (FAILED(hr)) { + return false; + } + } + + // initial location + if (!aInitialDir.IsEmpty()) { + RefPtr<IShellItem> folder; + if (SUCCEEDED(SHCreateItemFromParsingName(aInitialDir.get(), nullptr, + IID_IShellItem, + getter_AddRefs(folder)))) { + hr = dialog->SetFolder(folder); + if (FAILED(hr)) { + return false; + } + } + } + + // filter types and the default index + if (!mComFilterList.IsEmpty()) { + hr = dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get()); + if (FAILED(hr)) { + return false; + } + + hr = dialog->SetFileTypeIndex(mSelectedType); + if (FAILED(hr)) { + return false; + } + } + + // display + + { + AutoDestroyTmpWindow adtw((HWND)( + mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) + : nullptr)); + AutoWidgetPickerState awps(mParentWidget); + + mozilla::BackgroundHangMonitor().NotifyWait(); + if (FAILED(dialog->Show(adtw.get()))) { + return false; + } + } + + // 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; + if (NS_SUCCEEDED(NS_NewLocalFile(str, false, getter_AddRefs(file)))) { + mFiles.AppendObject(file); + } + } + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIFilePicker impl. + +nsresult nsFilePicker::ShowW(int16_t* aReturnVal) { + NS_ENSURE_ARG_POINTER(aReturnVal); + + *aReturnVal = returnCancel; + + nsAutoString initialDir; + if (mDisplayDirectory) mDisplayDirectory->GetPath(initialDir); + + // If no display directory, re-use the last one. + if (initialDir.IsEmpty()) { + // Allocate copy of last used dir. + initialDir = sLastUsedUnicodeDirectory.get(); + } + + // Clear previous file selections + 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; + + bool result = false; + if (mMode == modeGetFolder) { + result = ShowFolderPicker(initialDir); + } else { + result = ShowFilePicker(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; + nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)); + + bool flag = false; + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(file->Exists(&flag)) && flag) { + retValue = returnReplace; + } + } + + *aReturnVal = retValue; + return NS_OK; +} + +nsresult 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; + nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + file.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsFilePicker::GetFileURL(nsIURI** aFileURL) { + *aFileURL = nullptr; + nsCOMPtr<nsIFile> file; + nsresult rv = GetFile(getter_AddRefs(file)); + if (!file) return rv; + + return NS_NewFileURI(aFileURL, file); +} + +NS_IMETHODIMP +nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) { + NS_ENSURE_ARG_POINTER(aFiles); + return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile)); +} + +// Get the file + path +NS_IMETHODIMP +nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) { + mDefaultFilePath = aString; + + // First, make sure the file name is not too long. + int32_t nameLength; + int32_t nameIndex = mDefaultFilePath.RFind("\\"); + 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); +} + +NS_IMETHODIMP +nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) { + mComFilterList.Append(aTitle, aFilter); + return NS_OK; +} + +void nsFilePicker::RememberLastUsedDirectory() { + if (IsPrivacyModeEnabled()) { + // Don't remember the directory if private browsing was in effect + return; + } + + nsCOMPtr<nsIFile> file; + if (NS_FAILED(NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)))) { + NS_WARNING("RememberLastUsedDirectory failed to init file path."); + return; + } + + nsCOMPtr<nsIFile> dir; + nsAutoString newDir; + if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) || + !(mDisplayDirectory = dir) || + NS_FAILED(mDisplayDirectory->GetPath(newDir)) || newDir.IsEmpty()) { + NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); + return; + } + + sLastUsedUnicodeDirectory.reset(ToNewUnicode(newDir)); +} + +bool nsFilePicker::IsPrivacyModeEnabled() { + return mLoadContext && mLoadContext->UsePrivateBrowsing(); +} + +bool nsFilePicker::IsDefaultPathLink() { + NS_ConvertUTF16toUTF8 ext(mDefaultFilePath); + ext.Trim(" .", false, true); // watch out for trailing space and dots + ToLowerCase(ext); + if (StringEndsWith(ext, ".lnk"_ns) || StringEndsWith(ext, ".pif"_ns) || + StringEndsWith(ext, ".url"_ns)) + 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..56fcc3895b --- /dev/null +++ b/widget/windows/nsFilePicker.h @@ -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/. */ + +#ifndef nsFilePicker_h__ +#define nsFilePicker_h__ + +#include <windows.h> + +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "nsCOMArray.h" +#include "nsBaseFilePicker.h" +#include "nsString.h" +#include "nsdefs.h" +#include <commdlg.h> +#include <shobjidl.h> +#undef LogSeverity // SetupAPI.h #defines this as DWORD + +class nsILoadContext; + +class nsBaseWinFilePicker : public nsBaseFilePicker { + public: + NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override; + NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override; + NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override; + NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override; + + protected: + nsString mDefaultFilePath; + nsString mDefaultFilename; + nsString mDefaultExtension; +}; + +/** + * Native Windows FileSelector wrapper + */ + +class nsFilePicker : public nsBaseWinFilePicker { + virtual ~nsFilePicker() = default; + + public: + nsFilePicker(); + + NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle, + int16_t aMode) override; + + NS_DECL_ISUPPORTS + + // nsIFilePicker (less what's in nsBaseFilePicker and nsBaseWinFilePicker) + NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override; + NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override; + NS_IMETHOD GetFile(nsIFile** aFile) override; + NS_IMETHOD GetFileURL(nsIURI** aFileURL) override; + NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override; + NS_IMETHOD AppendFilter(const nsAString& aTitle, + const nsAString& aFilter) override; + + protected: + /* method from nsBaseFilePicker */ + virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) override; + nsresult Show(int16_t* aReturnVal) override; + nsresult ShowW(int16_t* aReturnVal); + void GetFilterListArray(nsString& aFilterList); + bool ShowFolderPicker(const nsString& aInitialDir); + bool ShowFilePicker(const nsString& aInitialDir); + void RememberLastUsedDirectory(); + bool IsPrivacyModeEnabled(); + bool IsDefaultPathLink(); + bool IsDefaultPathHtml(); + + nsCOMPtr<nsILoadContext> mLoadContext; + nsCOMPtr<nsIWidget> mParentWidget; + nsString mTitle; + nsCString mFile; + nsString mFilterList; + int16_t mSelectedType; + nsCOMArray<nsIFile> mFiles; + nsString mUnicodeFile; + + struct FreeDeleter { + void operator()(void* aPtr) { ::free(aPtr); } + }; + static mozilla::UniquePtr<char16_t[], FreeDeleter> sLastUsedUnicodeDirectory; + + 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; +}; + +#endif // nsFilePicker_h__ diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp new file mode 100644 index 0000000000..4b7b732727 --- /dev/null +++ b/widget/windows/nsLookAndFeel.cpp @@ -0,0 +1,976 @@ +/* -*- 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 "nsWindowsHelpers.h" +#include "WinUtils.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "gfxFontConstants.h" +#include "gfxWindowsPlatform.h" + +using namespace mozilla; +using namespace mozilla::widget; + +// static +LookAndFeel::OperatingSystemVersion nsLookAndFeel::GetOperatingSystemVersion() { + static OperatingSystemVersion version = OperatingSystemVersion::Unknown; + + if (version != OperatingSystemVersion::Unknown) { + return version; + } + + if (IsWin10OrLater()) { + version = OperatingSystemVersion::Windows10; + } else if (IsWin8OrLater()) { + version = OperatingSystemVersion::Windows8; + } else { + version = OperatingSystemVersion::Windows7; + } + + 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; +} + +static nsresult SystemWantsDarkTheme(int32_t& darkThemeEnabled) { + if (!IsWin10OrLater()) { + darkThemeEnabled = 0; + return NS_OK; + } + + nsresult rv = NS_OK; + nsCOMPtr<nsIWindowsRegKey> personalizeKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = personalizeKey->Open( + nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + nsLiteralString( + u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t lightThemeEnabled; + rv = + personalizeKey->ReadIntValue(u"AppsUseLightTheme"_ns, &lightThemeEnabled); + if (NS_SUCCEEDED(rv)) { + darkThemeEnabled = !lightThemeEnabled; + } + + return rv; +} + +nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) + : nsXPLookAndFeel(), + mUseAccessibilityTheme(0), + mUseDefaultTheme(0), + mNativeThemeId(eWindowsTheme_Generic), + mCaretBlinkTime(-1), + mHasColorMenuHoverText(false), + mHasColorAccent(false), + mHasColorAccentText(false), + mHasColorMediaText(false), + mHasColorCommunicationsText(false), + mInitialized(false) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::TOUCH_ENABLED_DEVICE, + WinUtils::IsTouchDeviceSupportPresent()); + if (aCache) { + DoSetCache(*aCache); + } +} + +nsLookAndFeel::~nsLookAndFeel() {} + +void nsLookAndFeel::NativeInit() { EnsureInit(); } + +/* virtual */ +void nsLookAndFeel::RefreshImpl() { + nsXPLookAndFeel::RefreshImpl(); + + for (auto e = mSystemFontCache.begin(), end = mSystemFontCache.end(); + e != end; ++e) { + e->mCacheValid = false; + } + mCaretBlinkTime = -1; + + mInitialized = false; +} + +nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) { + EnsureInit(); + + nsresult res = NS_OK; + + int idx; + switch (aID) { + case ColorID::WindowBackground: + idx = COLOR_WINDOW; + break; + case ColorID::WindowForeground: + idx = COLOR_WINDOWTEXT; + break; + case ColorID::WidgetBackground: + idx = COLOR_BTNFACE; + break; + case ColorID::WidgetForeground: + idx = COLOR_BTNTEXT; + break; + case ColorID::WidgetSelectBackground: + idx = COLOR_HIGHLIGHT; + break; + case ColorID::WidgetSelectForeground: + idx = COLOR_HIGHLIGHTTEXT; + break; + case ColorID::Widget3DHighlight: + idx = COLOR_BTNHIGHLIGHT; + break; + case ColorID::Widget3DShadow: + idx = COLOR_BTNSHADOW; + break; + case ColorID::TextBackground: + idx = COLOR_WINDOW; + break; + case ColorID::TextForeground: + idx = COLOR_WINDOWTEXT; + break; + case ColorID::TextSelectBackground: + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + idx = COLOR_HIGHLIGHT; + break; + case ColorID::TextSelectForeground: + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + idx = COLOR_HIGHLIGHTTEXT; + break; + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + return NS_OK; + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + return NS_OK; + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + return NS_OK; + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + return NS_OK; + case ColorID::SpellCheckerUnderline: + aColor = NS_RGB(0xff, 0, 0); + return NS_OK; + + // New CSS 2 Color definitions + case ColorID::Activeborder: + idx = COLOR_ACTIVEBORDER; + break; + case ColorID::Activecaption: + idx = COLOR_ACTIVECAPTION; + break; + case ColorID::Appworkspace: + idx = COLOR_APPWORKSPACE; + break; + case ColorID::Background: + idx = COLOR_BACKGROUND; + break; + case ColorID::Buttonface: + case ColorID::MozButtonhoverface: + idx = COLOR_BTNFACE; + break; + case ColorID::Buttonhighlight: + idx = COLOR_BTNHIGHLIGHT; + break; + case ColorID::Buttonshadow: + idx = COLOR_BTNSHADOW; + break; + case ColorID::Buttontext: + case ColorID::MozButtonhovertext: + idx = COLOR_BTNTEXT; + break; + case ColorID::Captiontext: + idx = COLOR_CAPTIONTEXT; + break; + case ColorID::Graytext: + idx = COLOR_GRAYTEXT; + break; + case ColorID::Highlight: + case ColorID::MozHtmlCellhighlight: + case ColorID::MozMenuhover: + idx = COLOR_HIGHLIGHT; + break; + case ColorID::MozMenubarhovertext: + if (!nsUXThemeData::IsAppThemed()) { + idx = nsUXThemeData::AreFlatMenusEnabled() ? COLOR_HIGHLIGHTTEXT + : COLOR_MENUTEXT; + break; + } + // Fall through + case ColorID::MozMenuhovertext: + if (mHasColorMenuHoverText) { + aColor = mColorMenuHoverText; + return NS_OK; + } + // Fall through + case ColorID::Highlighttext: + case ColorID::MozHtmlCellhighlighttext: + idx = COLOR_HIGHLIGHTTEXT; + break; + case ColorID::Inactiveborder: + idx = COLOR_INACTIVEBORDER; + break; + case ColorID::Inactivecaption: + idx = COLOR_INACTIVECAPTION; + break; + case ColorID::Inactivecaptiontext: + idx = COLOR_INACTIVECAPTIONTEXT; + break; + case ColorID::Infobackground: + idx = COLOR_INFOBK; + break; + case ColorID::Infotext: + idx = COLOR_INFOTEXT; + break; + case ColorID::Menu: + idx = COLOR_MENU; + break; + case ColorID::Menutext: + case ColorID::MozMenubartext: + idx = COLOR_MENUTEXT; + break; + case ColorID::Scrollbar: + idx = COLOR_SCROLLBAR; + break; + case ColorID::Threeddarkshadow: + idx = COLOR_3DDKSHADOW; + break; + case ColorID::Threedface: + idx = COLOR_3DFACE; + break; + case ColorID::Threedhighlight: + idx = COLOR_3DHIGHLIGHT; + break; + case ColorID::Threedlightshadow: + idx = COLOR_3DLIGHT; + break; + case ColorID::Threedshadow: + idx = COLOR_3DSHADOW; + break; + case ColorID::Window: + idx = COLOR_WINDOW; + break; + case ColorID::Windowframe: + idx = COLOR_WINDOWFRAME; + break; + case ColorID::Windowtext: + idx = COLOR_WINDOWTEXT; + break; + case ColorID::MozEventreerow: + case ColorID::MozOddtreerow: + case ColorID::Field: + case ColorID::MozCombobox: + idx = COLOR_WINDOW; + break; + case ColorID::Fieldtext: + case ColorID::MozComboboxtext: + idx = COLOR_WINDOWTEXT; + break; + case ColorID::MozDialog: + case ColorID::MozCellhighlight: + idx = COLOR_3DFACE; + break; + case ColorID::MozWinAccentcolor: + if (mHasColorAccent) { + aColor = mColorAccent; + } else { + // Seems to be the default color (hardcoded because of bug 1065998) + aColor = NS_RGB(158, 158, 158); + } + return NS_OK; + case ColorID::MozWinAccentcolortext: + if (mHasColorAccentText) { + aColor = mColorAccentText; + } else { + aColor = NS_RGB(0, 0, 0); + } + return NS_OK; + case ColorID::MozWinMediatext: + if (mHasColorMediaText) { + aColor = mColorMediaText; + return NS_OK; + } + // if we've gotten here just return -moz-dialogtext instead + idx = COLOR_WINDOWTEXT; + break; + case ColorID::MozWinCommunicationstext: + if (mHasColorCommunicationsText) { + aColor = mColorCommunicationsText; + return NS_OK; + } + // if we've gotten here just return -moz-dialogtext instead + idx = COLOR_WINDOWTEXT; + break; + case ColorID::MozDialogtext: + case ColorID::MozCellhighlighttext: + case ColorID::MozColheadertext: + case ColorID::MozColheaderhovertext: + idx = COLOR_WINDOWTEXT; + break; + case ColorID::MozDragtargetzone: + idx = COLOR_HIGHLIGHTTEXT; + break; + case ColorID::MozButtondefault: + idx = COLOR_3DDKSHADOW; + break; + case ColorID::MozNativehyperlinktext: + idx = COLOR_HOTLIGHT; + break; + default: + NS_WARNING("Unknown color for nsLookAndFeel"); + idx = COLOR_WINDOW; + res = NS_ERROR_FAILURE; + break; + } + + aColor = GetColorForSysColorIndex(idx); + + return res; +} + +nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { + nsresult res = NS_OK; + + switch (aID) { + case IntID::ScrollButtonLeftMouseButtonAction: + aResult = 0; + break; + case IntID::ScrollButtonMiddleMouseButtonAction: + case IntID::ScrollButtonRightMouseButtonAction: + aResult = 3; + break; + case IntID::CaretBlinkTime: + // IntID::CaretBlinkTime is often called by updating editable text + // that has focus. So it should be cached to improve performance. + if (mCaretBlinkTime < 0) { + mCaretBlinkTime = static_cast<int32_t>(::GetCaretBlinkTime()); + } + aResult = mCaretBlinkTime; + break; + case IntID::CaretWidth: + aResult = 1; + break; + case IntID::ShowCaretDuringSelection: + aResult = 0; + break; + case IntID::SelectTextfieldsOnKeyFocus: + // Select textfield content when focused by kbd + // used by EventStateManager::sTextfieldSelectModel + aResult = 1; + break; + case IntID::SubmenuDelay: + // This will default to the Windows' default + // (400ms) on error. + aResult = GetSystemParam(SPI_GETMENUSHOWDELAY, 400); + break; + case IntID::TooltipDelay: + aResult = 500; + break; + case IntID::MenusCanOverlapOSBar: + // we want XUL popups to be able to overlap the task bar. + aResult = 1; + break; + case IntID::DragThresholdX: + // The system metric is the number of pixels at which a drag should + // start. Our look and feel metric is the number of pixels you can + // move before starting a drag, so subtract 1. + + aResult = ::GetSystemMetrics(SM_CXDRAG) - 1; + break; + case IntID::DragThresholdY: + aResult = ::GetSystemMetrics(SM_CYDRAG) - 1; + break; + case IntID::UseAccessibilityTheme: + // High contrast is a misnomer under Win32 -- any theme can be used with + // it, e.g. normal contrast with large fonts, low contrast, etc. The high + // contrast flag really means -- use this theme and don't override it. + 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 IntID::ScrollArrowStyle: + aResult = eScrollArrowStyle_Single; + break; + case IntID::ScrollSliderStyle: + aResult = eScrollThumbStyle_Proportional; + break; + case IntID::TreeOpenDelay: + aResult = 1000; + break; + case IntID::TreeCloseDelay: + aResult = 0; + break; + case IntID::TreeLazyScrollDelay: + aResult = 150; + break; + case IntID::TreeScrollDelay: + aResult = 100; + break; + case IntID::TreeScrollLinesMax: + aResult = 3; + break; + case IntID::WindowsClassic: + aResult = !nsUXThemeData::IsAppThemed(); + break; + case IntID::TouchEnabled: + aResult = WinUtils::IsTouchDeviceSupportPresent(); + break; + case IntID::WindowsDefaultTheme: + if (XRE_IsContentProcess()) { + aResult = mUseDefaultTheme; + } else { + aResult = nsUXThemeData::IsDefaultWindowTheme(); + } + break; + case IntID::WindowsThemeIdentifier: + if (XRE_IsContentProcess()) { + aResult = mNativeThemeId; + } else { + aResult = nsUXThemeData::GetNativeThemeId(); + } + break; + + case IntID::OperatingSystemVersionIdentifier: { + aResult = int32_t(GetOperatingSystemVersion()); + break; + } + + case IntID::MacGraphiteTheme: + aResult = 0; + res = NS_ERROR_NOT_IMPLEMENTED; + break; + case IntID::DWMCompositor: + aResult = gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled(); + break; + case IntID::WindowsAccentColorInTitlebar: { + nscolor unused; + if (NS_WARN_IF(NS_FAILED(GetAccentColor(unused)))) { + aResult = 0; + break; + } + + uint32_t colorPrevalence; + nsresult rv = mDwmKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // The ColorPrevalence value is set to 1 when the "Show color on title + // bar" setting in the Color section of Window's Personalization settings + // is turned on. + aResult = (NS_SUCCEEDED(mDwmKey->ReadIntValue(u"ColorPrevalence"_ns, + &colorPrevalence)) && + colorPrevalence == 1) + ? 1 + : 0; + + mDwmKey->Close(); + } break; + case IntID::WindowsGlass: + // Aero Glass is only available prior to Windows 8 when DWM is used. + aResult = (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled() && + !IsWin8OrLater()); + break; + case IntID::AlertNotificationOrigin: + aResult = 0; + { + // Get task bar window handle + HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr); + + if (shellWindow != nullptr) { + // Determine position + APPBARDATA appBarData; + appBarData.hWnd = shellWindow; + appBarData.cbSize = sizeof(appBarData); + if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) { + // Set alert origin as a bit field - see LookAndFeel.h + // 0 represents bottom right, sliding vertically. + switch (appBarData.uEdge) { + case ABE_LEFT: + aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT; + break; + case ABE_RIGHT: + aResult = NS_ALERT_HORIZONTAL; + break; + case ABE_TOP: + aResult = NS_ALERT_TOP; + // 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 IntID::IMERawInputUnderlineStyle: + case IntID::IMEConvertedTextUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_DASHED; + break; + case IntID::IMESelectedRawTextUnderlineStyle: + case IntID::IMESelectedConvertedTextUnderline: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE; + break; + case IntID::SpellCheckerUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY; + break; + case IntID::ScrollbarButtonAutoRepeatBehavior: + aResult = 0; + break; + case IntID::SwipeAnimationEnabled: + aResult = 0; + break; + case IntID::UseOverlayScrollbars: + aResult = false; + break; + case IntID::AllowOverlayScrollbarsOverlap: + aResult = 0; + break; + case IntID::ScrollbarDisplayOnMouseMove: + aResult = 1; + break; + case IntID::ScrollbarFadeBeginDelay: + aResult = 2500; + break; + case IntID::ScrollbarFadeDuration: + aResult = 350; + break; + case IntID::ContextMenuOffsetVertical: + case IntID::ContextMenuOffsetHorizontal: + aResult = 2; + break; + case IntID::SystemUsesDarkTheme: + res = SystemWantsDarkTheme(aResult); + break; + case IntID::PrefersReducedMotion: { + BOOL enableAnimation = TRUE; + ::SystemParametersInfoW(SPI_GETCLIENTAREAANIMATION, 0, &enableAnimation, + 0); + aResult = enableAnimation ? 0 : 1; + break; + } + case IntID::PrimaryPointerCapabilities: { + PointerCapabilities caps = + widget::WinUtils::GetPrimaryPointerCapabilities(); + aResult = static_cast<int32_t>(caps); + break; + } + case IntID::AllPointerCapabilities: { + PointerCapabilities caps = widget::WinUtils::GetAllPointerCapabilities(); + aResult = static_cast<int32_t>(caps); + break; + } + default: + aResult = 0; + res = NS_ERROR_FAILURE; + } + return res; +} + +nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) { + nsresult res = NS_OK; + + switch (aID) { + case FloatID::IMEUnderlineRelativeSize: + aResult = 1.0f; + break; + case FloatID::SpellCheckerUnderlineRelativeSize: + aResult = 1.0f; + break; + default: + aResult = -1.0; + res = NS_ERROR_FAILURE; + } + return res; +} + +LookAndFeelFont nsLookAndFeel::GetLookAndFeelFontInternal( + const LOGFONTW& aLogFont, bool aUseShellDlg) { + LookAndFeelFont result{}; + + result.haveFont() = false; + + // Get scaling factor from physical to logical pixels + double pixelScale = 1.0 / WinUtils::SystemScaleFactor(); + + // The lfHeight is in pixels, and it needs to be adjusted for the + // device it will be displayed on. + // Screens and Printers will differ in DPI + // + // So this accounts for the difference in the DeviceContexts + // The pixelScale will typically be 1.0 for the screen + // (though larger for hi-dpi screens where the Windows resolution + // scale factor is 125% or 150% or even more), and could be + // any value when going to a printer, for example pixelScale is + // 6.25 when going to a 600dpi printer. + float pixelHeight = -aLogFont.lfHeight; + if (pixelHeight < 0) { + nsAutoFont hFont(::CreateFontIndirectW(&aLogFont)); + if (!hFont) { + return result; + } + + nsAutoHDC dc(::GetDC(nullptr)); + HGDIOBJ hObject = ::SelectObject(dc, hFont); + TEXTMETRIC tm; + ::GetTextMetrics(dc, &tm); + ::SelectObject(dc, hObject); + + pixelHeight = tm.tmAscent; + } + + pixelHeight *= pixelScale; + + // we have problem on Simplified Chinese system because the system + // report the default font size is 8 points. but if we use 8, the text + // display very ugly. force it to be at 9 points (12 pixels) on that + // system (cp936), but leave other sizes alone. + if (pixelHeight < 12 && ::GetACP() == 936) { + pixelHeight = 12; + } + + result.haveFont() = true; + + if (aUseShellDlg) { + result.name() = u"MS Shell Dlg 2"_ns; + } else { + result.name() = aLogFont.lfFaceName; + } + + result.size() = pixelHeight; + result.italic() = !!aLogFont.lfItalic; + // FIXME: Other weights? + result.weight() = ((aLogFont.lfWeight == FW_BOLD) ? FontWeight::Bold() + : FontWeight::Normal()) + .ToFloat(); + + return result; +} + +LookAndFeelFont nsLookAndFeel::GetLookAndFeelFont(LookAndFeel::FontID anID) { + if (XRE_IsContentProcess()) { + return mFontCache[size_t(anID)]; + } + + LookAndFeelFont result{}; + + result.haveFont() = false; + + // FontID::Icon is handled differently than the others + if (anID == LookAndFeel::FontID::Icon) { + LOGFONTW logFont; + if (::SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(logFont), + (PVOID)&logFont, 0)) { + result = GetLookAndFeelFontInternal(logFont, false); + } + return result; + } + + NONCLIENTMETRICSW ncm; + ncm.cbSize = sizeof(NONCLIENTMETRICSW); + if (!::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), + (PVOID)&ncm, 0)) { + return result; + } + + switch (anID) { + case LookAndFeel::FontID::Menu: + case LookAndFeel::FontID::PullDownMenu: + result = GetLookAndFeelFontInternal(ncm.lfMenuFont, false); + break; + case LookAndFeel::FontID::Caption: + result = GetLookAndFeelFontInternal(ncm.lfCaptionFont, false); + break; + case LookAndFeel::FontID::SmallCaption: + result = GetLookAndFeelFontInternal(ncm.lfSmCaptionFont, false); + break; + case LookAndFeel::FontID::StatusBar: + case LookAndFeel::FontID::Tooltips: + result = GetLookAndFeelFontInternal(ncm.lfStatusFont, false); + break; + case LookAndFeel::FontID::Widget: + case LookAndFeel::FontID::Dialog: + case LookAndFeel::FontID::Button: + case LookAndFeel::FontID::Field: + case LookAndFeel::FontID::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. + result = GetLookAndFeelFontInternal(ncm.lfMessageFont, true); + break; + default: + result = GetLookAndFeelFontInternal(ncm.lfMessageFont, false); + break; + } + + return result; +} + +bool nsLookAndFeel::GetSysFont(LookAndFeel::FontID anID, nsString& aFontName, + gfxFontStyle& aFontStyle) { + LookAndFeelFont font = GetLookAndFeelFont(anID); + + if (!font.haveFont()) { + return false; + } + + aFontName = std::move(font.name()); + + aFontStyle.size = font.size(); + + // FIXME: What about oblique? + aFontStyle.style = + font.italic() ? FontSlantStyle::Italic() : FontSlantStyle::Normal(); + + aFontStyle.weight = FontWeight(font.weight()); + + // FIXME: Set aFontStyle->stretch correctly! + aFontStyle.stretch = FontStretch::Normal(); + + aFontStyle.systemFont = true; + + return true; +} + +bool nsLookAndFeel::NativeGetFont(FontID anID, nsString& aFontName, + gfxFontStyle& aFontStyle) { + CachedSystemFont& cacheSlot = mSystemFontCache[size_t(anID)]; + + bool status; + if (cacheSlot.mCacheValid) { + status = cacheSlot.mHaveFont; + if (status) { + aFontName = cacheSlot.mFontName; + aFontStyle = cacheSlot.mFontStyle; + } + } else { + status = GetSysFont(anID, aFontName, aFontStyle); + + cacheSlot.mCacheValid = true; + cacheSlot.mHaveFont = status; + if (status) { + cacheSlot.mFontName = aFontName; + cacheSlot.mFontStyle = aFontStyle; + } + } + return status; +} + +/* virtual */ +char16_t nsLookAndFeel::GetPasswordCharacterImpl() { +#define UNICODE_BLACK_CIRCLE_CHAR 0x25cf + return UNICODE_BLACK_CIRCLE_CHAR; +} + +LookAndFeelCache nsLookAndFeel::GetCacheImpl() { + MOZ_ASSERT(XRE_IsParentProcess()); + + LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl(); + + LookAndFeelInt lafInt; + lafInt.id() = IntID::UseAccessibilityTheme; + lafInt.value() = GetInt(IntID::UseAccessibilityTheme); + cache.mInts().AppendElement(lafInt); + + lafInt.id() = IntID::WindowsDefaultTheme; + lafInt.value() = GetInt(IntID::WindowsDefaultTheme); + cache.mInts().AppendElement(lafInt); + + lafInt.id() = IntID::WindowsThemeIdentifier; + lafInt.value() = GetInt(IntID::WindowsThemeIdentifier); + cache.mInts().AppendElement(lafInt); + + for (size_t i = size_t(LookAndFeel::FontID::MINIMUM); + i <= size_t(LookAndFeel::FontID::MAXIMUM); ++i) { + cache.mFonts().AppendElement(GetLookAndFeelFont(LookAndFeel::FontID(i))); + } + + return cache; +} + +void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) { + DoSetCache(aCache); +} + +void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) { + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_RELEASE_ASSERT(aCache.mFonts().Length() == mFontCache.length()); + + for (auto entry : aCache.mInts()) { + switch (entry.id()) { + case IntID::UseAccessibilityTheme: + mUseAccessibilityTheme = entry.value(); + break; + case IntID::WindowsDefaultTheme: + mUseDefaultTheme = entry.value(); + break; + case IntID::WindowsThemeIdentifier: + mNativeThemeId = entry.value(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache"); + break; + } + } + + size_t i = mFontCache.minIndex(); + for (const auto& font : aCache.mFonts()) { + mFontCache[i] = font; + ++i; + } +} + +/* static */ +nsresult nsLookAndFeel::GetAccentColor(nscolor& aColor) { + nsresult rv; + + if (!mDwmKey) { + mDwmKey = do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = mDwmKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"SOFTWARE\\Microsoft\\Windows\\DWM"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t accentColor; + if (NS_SUCCEEDED(mDwmKey->ReadIntValue(u"AccentColor"_ns, &accentColor))) { + // The order of the color components in the DWORD stored in the registry + // happens to be the same order as we store the components in nscolor + // so we can just assign directly here. + aColor = accentColor; + rv = NS_OK; + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + mDwmKey->Close(); + + return rv; +} + +/* static */ +nsresult nsLookAndFeel::GetAccentColorText(nscolor& aColor) { + nscolor accentColor; + nsresult rv = GetAccentColor(accentColor); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We want the color that we return for text that will be drawn over + // a background that has the accent color to have good contrast with + // the accent color. Windows itself uses either white or black text + // depending on how light or dark the accent color is. We do the same + // here based on the luminance of the accent color with a threshhold + // value. This algorithm should match what Windows does. It comes from: + // + // https://docs.microsoft.com/en-us/windows/uwp/style/color + + float luminance = (NS_GET_R(accentColor) * 2 + NS_GET_G(accentColor) * 5 + + NS_GET_B(accentColor)) / + 8; + + aColor = (luminance <= 128) ? NS_RGB(255, 255, 255) : NS_RGB(0, 0, 0); + + return NS_OK; +} + +nscolor nsLookAndFeel::GetColorForSysColorIndex(int index) { + MOZ_ASSERT(index >= SYS_COLOR_MIN && index <= SYS_COLOR_MAX); + return mSysColorTable[index - SYS_COLOR_MIN]; +} + +void nsLookAndFeel::EnsureInit() { + if (mInitialized) { + return; + } + mInitialized = true; + + nsresult res; + + res = GetAccentColor(mColorAccent); + mHasColorAccent = NS_SUCCEEDED(res); + + res = GetAccentColorText(mColorAccentText); + mHasColorAccentText = NS_SUCCEEDED(res); + + if (nsUXThemeData::IsAppThemed()) { + res = ::GetColorFromTheme(eUXMenu, MENU_POPUPITEM, MPI_HOT, TMT_TEXTCOLOR, + mColorMenuHoverText); + mHasColorMenuHoverText = NS_SUCCEEDED(res); + + res = ::GetColorFromTheme(eUXMediaToolbar, TP_BUTTON, TS_NORMAL, + TMT_TEXTCOLOR, mColorMediaText); + mHasColorMediaText = NS_SUCCEEDED(res); + + res = ::GetColorFromTheme(eUXCommunicationsToolbar, TP_BUTTON, TS_NORMAL, + TMT_TEXTCOLOR, mColorCommunicationsText); + mHasColorCommunicationsText = NS_SUCCEEDED(res); + } + + // Fill out the sys color table. + for (int i = SYS_COLOR_MIN; i <= SYS_COLOR_MAX; ++i) { + DWORD color = ::GetSysColor(i); + mSysColorTable[i - SYS_COLOR_MIN] = COLOREF_2_NSRGB(color); + } + + RecordTelemetry(); +} diff --git a/widget/windows/nsLookAndFeel.h b/widget/windows/nsLookAndFeel.h new file mode 100644 index 0000000000..ded464fa05 --- /dev/null +++ b/widget/windows/nsLookAndFeel.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsLookAndFeel +#define __nsLookAndFeel + +#include <windows.h> + +#include "nsXPLookAndFeel.h" +#include "gfxFont.h" +#include "mozilla/RangedArray.h" +#include "nsIWindowsRegKey.h" + +/* + * Gesture System Metrics + */ +#ifndef SM_DIGITIZER +# define SM_DIGITIZER 94 +# define TABLET_CONFIG_NONE 0x00000000 +# define NID_INTEGRATED_TOUCH 0x00000001 +# define NID_EXTERNAL_TOUCH 0x00000002 +# define NID_INTEGRATED_PEN 0x00000004 +# define NID_EXTERNAL_PEN 0x00000008 +# define NID_MULTI_INPUT 0x00000040 +# define NID_READY 0x00000080 +#endif + +/* + * Tablet mode detection + */ +#ifndef SM_SYSTEMDOCKED +# define SM_CONVERTIBLESLATEMODE 0x00002003 +# define SM_SYSTEMDOCKED 0x00002004 +#endif + +/* + * Color constant inclusive bounds for GetSysColor + */ +#define SYS_COLOR_MIN 0 +#define SYS_COLOR_MAX 30 +#define SYS_COLOR_COUNT (SYS_COLOR_MAX - SYS_COLOR_MIN + 1) + +class nsLookAndFeel final : public nsXPLookAndFeel { + static OperatingSystemVersion GetOperatingSystemVersion(); + + public: + explicit nsLookAndFeel(const LookAndFeelCache* aCache); + virtual ~nsLookAndFeel(); + + void NativeInit() final; + void RefreshImpl() override; + nsresult NativeGetInt(IntID aID, int32_t& aResult) override; + nsresult NativeGetFloat(FloatID aID, float& aResult) override; + nsresult NativeGetColor(ColorID aID, nscolor& aResult) override; + bool NativeGetFont(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle) override; + char16_t GetPasswordCharacterImpl() override; + + LookAndFeelCache GetCacheImpl() override; + void SetCacheImpl(const LookAndFeelCache& aCache) override; + + private: + void DoSetCache(const LookAndFeelCache& aCache); + + /** + * Fetches the Windows accent color from the Windows settings if + * the accent color is set to apply to the title bar, otherwise + * returns an error code. + */ + nsresult GetAccentColor(nscolor& aColor); + + /** + * If the Windows accent color from the Windows settings is set + * to apply to the title bar, this computes the color that should + * be used for text that is to be written over a background that has + * the accent color. Otherwise, (if the accent color should not + * apply to the title bar) this returns an error code. + */ + nsresult GetAccentColorText(nscolor& aColor); + + nscolor GetColorForSysColorIndex(int index); + + LookAndFeelFont GetLookAndFeelFontInternal(const LOGFONTW& aLogFont, + bool aUseShellDlg); + + LookAndFeelFont GetLookAndFeelFont(LookAndFeel::FontID anID); + + bool GetSysFont(LookAndFeel::FontID anID, nsString& aFontName, + gfxFontStyle& aFontStyle); + + // Content process cached values that get shipped over from the browser + // process. + int32_t mUseAccessibilityTheme; + int32_t mUseDefaultTheme; // is the current theme a known default? + int32_t mNativeThemeId; // see LookAndFeel enum 'WindowsTheme' + int32_t mCaretBlinkTime; + + // Cached colors and flags indicating success in their retrieval. + nscolor mColorMenuHoverText; + bool mHasColorMenuHoverText; + nscolor mColorAccent; + bool mHasColorAccent; + nscolor mColorAccentText; + bool mHasColorAccentText; + nscolor mColorMediaText; + bool mHasColorMediaText; + nscolor mColorCommunicationsText; + bool mHasColorCommunicationsText; + + nscolor mSysColorTable[SYS_COLOR_COUNT]; + + bool mInitialized; + + void EnsureInit(); + + struct CachedSystemFont { + CachedSystemFont() : mCacheValid(false) {} + + bool mCacheValid; + bool mHaveFont; + nsString mFontName; + gfxFontStyle mFontStyle; + }; + + mozilla::RangedArray<CachedSystemFont, size_t(FontID::MINIMUM), + size_t(FontID::MAXIMUM) + 1 - size_t(FontID::MINIMUM)> + mSystemFontCache; + + mozilla::RangedArray<LookAndFeelFont, size_t(FontID::MINIMUM), + size_t(FontID::MAXIMUM) + 1 - size_t(FontID::MINIMUM)> + mFontCache; + + nsCOMPtr<nsIWindowsRegKey> mDwmKey; +}; + +#endif diff --git a/widget/windows/nsNativeBasicThemeWin.cpp b/widget/windows/nsNativeBasicThemeWin.cpp new file mode 100644 index 0000000000..bb4054d8d6 --- /dev/null +++ b/widget/windows/nsNativeBasicThemeWin.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "nsNativeBasicThemeWin.h" + +#include "LookAndFeel.h" +#include "ScrollbarUtil.h" + +nsITheme::Transparency nsNativeBasicThemeWin::GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) { + if (auto transparency = + ScrollbarUtil::GetScrollbarPartTransparency(aFrame, aAppearance)) { + return *transparency; + } + return nsNativeBasicTheme::GetWidgetTransparency(aFrame, aAppearance); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeCheckboxColors( + const EventStates& aState, StyleAppearance aAppearance) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeCheckboxColors(aState, aAppearance); + } + + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED); + bool isIndeterminate = aAppearance == StyleAppearance::Checkbox && + aState.HasState(NS_EVENT_STATE_INDETERMINATE); + + sRGBColor backgroundColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground)); + sRGBColor borderColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext)); + if (isDisabled && (isChecked || isIndeterminate)) { + backgroundColor = borderColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext)); + } else if (isDisabled) { + borderColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext)); + } else if (isChecked || isIndeterminate) { + backgroundColor = borderColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight)); + } + + return std::make_pair(backgroundColor, borderColor); +} + +sRGBColor nsNativeBasicThemeWin::ComputeCheckmarkColor( + const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeCheckmarkColor(aState); + } + + return sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground)); +} + +std::pair<sRGBColor, sRGBColor> +nsNativeBasicThemeWin::ComputeRadioCheckmarkColors(const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeRadioCheckmarkColors(aState); + } + + auto [unusedColor, checkColor] = + ComputeCheckboxColors(aState, StyleAppearance::Radio); + (void)unusedColor; + sRGBColor backgroundColor = ComputeCheckmarkColor(aState); + + return std::make_pair(backgroundColor, checkColor); +} + +sRGBColor nsNativeBasicThemeWin::ComputeBorderColor(const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeBorderColor(aState); + } + + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + + if (isDisabled) { + return sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext)); + } + return sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext)); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeButtonColors( + const EventStates& aState, nsIFrame* aFrame) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeButtonColors(aState, aFrame); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Buttonface)), + ComputeBorderColor(aState)); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeTextfieldColors( + const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeTextfieldColors(aState); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextBackground)), + ComputeBorderColor(aState)); +} + +std::pair<sRGBColor, sRGBColor> +nsNativeBasicThemeWin::ComputeRangeProgressColors(const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeRangeProgressColors(aState); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Highlight)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Buttontext))); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeRangeTrackColors( + const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeRangeTrackColors(aState); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextBackground)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Buttontext))); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeRangeThumbColors( + const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeRangeThumbColors(aState); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Highlight)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Highlight))); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeProgressColors() { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeProgressColors(); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Highlight)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Buttontext))); +} + +std::pair<sRGBColor, sRGBColor> +nsNativeBasicThemeWin::ComputeProgressTrackColors() { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeProgressTrackColors(); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextBackground)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Buttontext))); +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeMeterchunkColors( + const EventStates& aMeterState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeMeterchunkColors(aMeterState); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::Highlight)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextForeground))); +} + +std::pair<sRGBColor, sRGBColor> +nsNativeBasicThemeWin::ComputeMeterTrackColors() { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeMeterTrackColors(); + } + + return std::make_pair(sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextBackground)), + sRGBColor::FromABGR(LookAndFeel::GetColor( + LookAndFeel::ColorID::TextForeground))); +} + +sRGBColor nsNativeBasicThemeWin::ComputeMenulistArrowButtonColor( + const EventStates& aState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeMenulistArrowButtonColor(aState); + } + + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + + if (isDisabled) { + return sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Graytext)); + } + return sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground)); +} + +std::array<sRGBColor, 3> nsNativeBasicThemeWin::ComputeFocusRectColors() { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return nsNativeBasicTheme::ComputeFocusRectColors(); + } + + return {sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight)), + sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Buttontext)), + sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground))}; +} + +std::pair<sRGBColor, sRGBColor> nsNativeBasicThemeWin::ComputeScrollbarColors( + nsIFrame* aFrame, const ComputedStyle& aStyle, + const EventStates& aDocumentState, bool aIsRoot) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame); + sRGBColor color = sRGBColor::FromABGR(trackColor); + return std::make_pair(color, color); + } + + sRGBColor color = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground)); + return std::make_pair(color, color); +} + +sRGBColor nsNativeBasicThemeWin::ComputeScrollbarThumbColor( + nsIFrame* aFrame, const ComputedStyle& aStyle, + const EventStates& aElementState, const EventStates& aDocumentState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + return gfx::sRGBColor::FromABGR( + ScrollbarUtil::GetScrollbarThumbColor(aFrame, aElementState)); + } + + bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE); + bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER); + const nsStyleUI* ui = aStyle.StyleUI(); + nscolor color; + + if (ui->mScrollbarColor.IsColors()) { + color = ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle); + } else if (isActive || isHovered) { + color = LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight); + } else { + color = LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground); + } + + return gfx::sRGBColor::FromABGR(color); +} + +std::array<sRGBColor, 3> nsNativeBasicThemeWin::ComputeScrollbarButtonColors( + nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle, + const EventStates& aElementState, const EventStates& aDocumentState) { + if (!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0)) { + nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame); + nscolor buttonColor = + ScrollbarUtil::GetScrollbarButtonColor(trackColor, aElementState); + nscolor arrowColor = ScrollbarUtil::GetScrollbarArrowColor(buttonColor); + return {sRGBColor::FromABGR(buttonColor), sRGBColor::FromABGR(arrowColor), + sRGBColor::FromABGR(buttonColor)}; + } + + bool isActive = aElementState.HasState(NS_EVENT_STATE_ACTIVE); + bool isHovered = aElementState.HasState(NS_EVENT_STATE_HOVER); + + sRGBColor buttonColor; + sRGBColor arrowColor; + if (isActive || isHovered) { + buttonColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Highlight)); + arrowColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::Buttonface)); + } else { + buttonColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextBackground)); + arrowColor = sRGBColor::FromABGR( + LookAndFeel::GetColor(LookAndFeel::ColorID::TextForeground)); + } + + return {buttonColor, arrowColor, buttonColor}; +} + +already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() { + static StaticRefPtr<nsITheme> gInstance; + if (MOZ_UNLIKELY(!gInstance)) { + gInstance = new nsNativeBasicThemeWin(); + ClearOnShutdown(&gInstance); + } + return do_AddRef(gInstance); +} diff --git a/widget/windows/nsNativeBasicThemeWin.h b/widget/windows/nsNativeBasicThemeWin.h new file mode 100644 index 0000000000..25eaeefb3b --- /dev/null +++ b/widget/windows/nsNativeBasicThemeWin.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNativeBasicThemeWin_h +#define nsNativeBasicThemeWin_h + +#include "nsNativeBasicTheme.h" + +class nsNativeBasicThemeWin : public nsNativeBasicTheme { + public: + nsNativeBasicThemeWin() = default; + + Transparency GetWidgetTransparency(nsIFrame* aFrame, + StyleAppearance aAppearance) override; + + protected: + virtual ~nsNativeBasicThemeWin() = default; + + std::pair<sRGBColor, sRGBColor> ComputeCheckboxColors( + const EventStates& aState, StyleAppearance aAppearance) override; + sRGBColor ComputeCheckmarkColor(const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeRadioCheckmarkColors( + const EventStates& aState) override; + sRGBColor ComputeBorderColor(const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeButtonColors( + const EventStates& aState, nsIFrame* aFrame = nullptr) override; + std::pair<sRGBColor, sRGBColor> ComputeTextfieldColors( + const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeRangeProgressColors( + const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeRangeTrackColors( + const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeRangeThumbColors( + const EventStates& aState) override; + std::pair<sRGBColor, sRGBColor> ComputeProgressColors() override; + std::pair<sRGBColor, sRGBColor> ComputeProgressTrackColors() override; + std::pair<sRGBColor, sRGBColor> ComputeMeterchunkColors( + const EventStates& aMeterState) override; + std::pair<sRGBColor, sRGBColor> ComputeMeterTrackColors() override; + sRGBColor ComputeMenulistArrowButtonColor(const EventStates& aState) override; + std::array<sRGBColor, 3> ComputeFocusRectColors() override; + std::pair<sRGBColor, sRGBColor> ComputeScrollbarColors( + nsIFrame* aFrame, const ComputedStyle& aStyle, + const EventStates& aDocumentState, bool aIsRoot) override; + sRGBColor ComputeScrollbarThumbColor( + nsIFrame* aFrame, const ComputedStyle& aStyle, + const EventStates& aElementState, + const EventStates& aDocumentState) override; + std::array<sRGBColor, 3> ComputeScrollbarButtonColors( + nsIFrame* aFrame, StyleAppearance aAppearance, + const ComputedStyle& aStyle, const EventStates& aElementState, + const EventStates& aDocumentState) override; +}; + +#endif diff --git a/widget/windows/nsNativeDragSource.cpp b/widget/windows/nsNativeDragSource.cpp new file mode 100644 index 0000000000..aed8e31d5a --- /dev/null +++ b/widget/windows/nsNativeDragSource.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeDragSource.h" +#include <stdio.h> +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsToolkit.h" +#include "nsWidgetsCID.h" +#include "nsIDragService.h" +#include "mozilla/dom/DataTransfer.h" + +/* + * class nsNativeDragSource + */ +nsNativeDragSource::nsNativeDragSource( + mozilla::dom::DataTransfer* aDataTransfer) + : m_cRef(0), m_hCursor(nullptr), mUserCancelled(false) { + mDataTransfer = aDataTransfer; +} + +nsNativeDragSource::~nsNativeDragSource() {} + +STDMETHODIMP +nsNativeDragSource::QueryInterface(REFIID riid, void** ppv) { + *ppv = nullptr; + + if (IID_IUnknown == riid || IID_IDropSource == riid) *ppv = this; + + if (nullptr != *ppv) { + ((LPUNKNOWN)*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) +nsNativeDragSource::AddRef(void) { + ++m_cRef; + NS_LOG_ADDREF(this, m_cRef, "nsNativeDragSource", sizeof(*this)); + return m_cRef; +} + +STDMETHODIMP_(ULONG) +nsNativeDragSource::Release(void) { + --m_cRef; + NS_LOG_RELEASE(this, m_cRef, "nsNativeDragSource"); + if (0 != m_cRef) return m_cRef; + + delete this; + return 0; +} + +STDMETHODIMP +nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState) { + 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..a8e37b90a8 --- /dev/null +++ b/widget/windows/nsNativeDragSource.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsNativeDragSource_h_ +#define _nsNativeDragSource_h_ + +#include "nscore.h" +#include <ole2.h> +#include <oleidl.h> +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace dom { +class DataTransfer; +} // namespace dom +} // namespace mozilla + +// class nsIDragSource; + +/* + * nsNativeDragSource implements the IDropSource interface and gets + * most of its behavior from the associated adapter (m_dragDrop). + */ +class nsNativeDragSource final : public IDropSource { + public: + // construct an nsNativeDragSource referencing adapter + // nsNativeDragSource(nsIDragSource * adapter); + explicit nsNativeDragSource(mozilla::dom::DataTransfer* aDataTransfer); + ~nsNativeDragSource(); + + // IUnknown methods - see iunknown.h for documentation + + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IDropSource methods - see idropsrc.h for documentation + + // Return DRAGDROP_S_USEDEFAULTCURSORS if this object lets OLE provide + // default cursors, otherwise return NOERROR. This method gets called in + // response to changes that the target makes to dEffect (DragEnter, + // DragOver). + STDMETHODIMP GiveFeedback(DWORD dEffect); + + // This method gets called if there is any change in the mouse or key + // state. Return DRAGDROP_S_CANCEL to stop the drag, DRAGDROP_S_DROP + // to execute the drop, otherwise NOERROR. + STDMETHODIMP QueryContinueDrag(BOOL fESC, DWORD grfKeyState); + + bool UserCancelled() { return mUserCancelled; } + + protected: + // Reference count + ULONG m_cRef; + + // Data object, hold information about cursor state + RefPtr<mozilla::dom::DataTransfer> mDataTransfer; + + // Custom drag cursor + HCURSOR m_hCursor; + + // true if the user cancelled the drag by pressing escape + bool mUserCancelled; +}; + +#endif // _nsNativeDragSource_h_ diff --git a/widget/windows/nsNativeDragTarget.cpp b/widget/windows/nsNativeDragTarget.cpp new file mode 100644 index 0000000000..cdb53dfde3 --- /dev/null +++ b/widget/windows/nsNativeDragTarget.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsIDragService.h" +#include "nsWidgetsCID.h" +#include "nsNativeDragTarget.h" +#include "nsDragService.h" +#include "nsINode.h" +#include "nsCOMPtr.h" + +#include "nsIWidget.h" +#include "nsWindow.h" +#include "nsClipboard.h" +#include "KeyboardLayout.h" + +#include "mozilla/MouseEvents.h" + +using namespace mozilla; +using namespace mozilla::widget; + +/* Define Interface IDs */ +static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID); + +// This is cached for Leave notification +static POINTL gDragLastPoint; + +bool nsNativeDragTarget::gDragImageChanged = false; + +/* + * class nsNativeDragTarget + */ +nsNativeDragTarget::nsNativeDragTarget(nsIWidget* aWidget) + : m_cRef(0), + mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK), + mEffectsPreferred(DROPEFFECT_NONE), + mTookOwnRef(false), + mWidget(aWidget), + mDropTargetHelper(nullptr) { + 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) { + WidgetDragEvent event(true, aEventMessage, mWidget); + + nsWindow* win = static_cast<nsWindow*>(mWidget); + win->InitEvent(event); + POINT cpos; + + cpos.x = aPT.x; + cpos.y = aPT.y; + + if (mHWnd != nullptr) { + ::ScreenToClient(mHWnd, &cpos); + event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y); + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + + event.mInputSource = + static_cast<nsBaseDragService*>(mDragService)->GetInputSource(); + + mWidget->DispatchInputEvent(&event); +} + +void nsNativeDragTarget::ProcessDrag(EventMessage aEventMessage, + DWORD grfKeyState, POINTL ptl, + DWORD* pdwEffect) { + // Before dispatching the event make sure we have the correct drop action set + uint32_t geckoAction; + GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction); + + // Set the current action into the Gecko specific type + nsCOMPtr<nsIDragSession> currSession; + mDragService->GetCurrentSession(getter_AddRefs(currSession)); + if (!currSession) { + return; + } + + currSession->SetDragAction(geckoAction); + + // Dispatch the event into Gecko + DispatchDragDropEvent(aEventMessage, ptl); + + // If TakeChildProcessDragAction returns something other than + // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent + // to the child process and this event is also being sent to the child + // process. In this case, use the last event's action instead. + nsDragService* dragService = static_cast<nsDragService*>(mDragService); + currSession->GetDragAction(&geckoAction); + + int32_t childDragAction = dragService->TakeChildProcessDragAction(); + if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) { + geckoAction = childDragAction; + } + + if (nsIDragService::DRAGDROP_ACTION_LINK & geckoAction) { + *pdwEffect = DROPEFFECT_LINK; + } else if (nsIDragService::DRAGDROP_ACTION_COPY & geckoAction) { + *pdwEffect = DROPEFFECT_COPY; + } else if (nsIDragService::DRAGDROP_ACTION_MOVE & geckoAction) { + *pdwEffect = DROPEFFECT_MOVE; + } else { + *pdwEffect = DROPEFFECT_NONE; + } + + if (aEventMessage != eDrop) { + // Get the cached drag effect from the drag service, the data member should + // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on + // drags. + bool canDrop; + currSession->GetCanDrop(&canDrop); + if (!canDrop) { + *pdwEffect = DROPEFFECT_NONE; + } + } + + // Clear the cached value + currSession->SetCanDrop(false); +} + +// IDropTarget methods +STDMETHODIMP +nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState, + POINTL ptl, DWORD* pdwEffect) { + if (!mDragService) { + return E_FAIL; + } + + mEffectsAllowed = *pdwEffect; + AddLinkSupportIfCanBeGenerated(pIDataSource); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + // We get a lot of crashes (often uncaught by our handler) later on during + // DragOver calls, see bug 1465513. It looks like this might be because + // we're not cleaning up previous drags fully and now released resources get + // used. Calling IDropTargetHelper::DragLeave before DragEnter seems to fix + // this for at least one reproduction of this crash. + GetDropTargetHelper()->DragLeave(); + POINT pt = {ptl.x, ptl.y}; + GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect); + } + + // save a ref to this, in case the window is destroyed underneath us + NS_ASSERTION(!mTookOwnRef, "own ref already taken!"); + this->AddRef(); + mTookOwnRef = true; + + // tell the drag service about this drag (it may have come from an + // outside app). + mDragService->StartDragSession(); + + void* tempOutData = nullptr; + uint32_t tempDataLen = 0; + nsresult loadResult = nsClipboard::GetNativeDataOffClipboard( + pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), + nullptr, &tempOutData, &tempDataLen); + if (NS_SUCCEEDED(loadResult) && tempOutData) { + mEffectsPreferred = *((DWORD*)tempOutData); + free(tempOutData); + } else { + // We have no preference if we can't obtain it + mEffectsPreferred = DROPEFFECT_NONE; + } + + // Set the native data object into drag service + // + // This cast is ok because in the constructor we created a + // the actual implementation we wanted, so we know this is + // a nsDragService. It should be a private interface, though. + nsDragService* winDragService = static_cast<nsDragService*>(mDragService); + winDragService->SetIDataObject(pIDataSource); + + // Now process the native drag state and then dispatch the event + ProcessDrag(eDragEnter, grfKeyState, ptl, pdwEffect); + + return S_OK; +} + +void nsNativeDragTarget::AddLinkSupportIfCanBeGenerated( + LPDATAOBJECT aIDataSource) { + // If we don't have a link effect, but we can generate one, fix the + // drop effect to include it. + if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) { + if (S_OK == ::OleQueryLinkFromData(aIDataSource)) { + mEffectsAllowed |= DROPEFFECT_LINK; + } + } +} + +STDMETHODIMP +nsNativeDragTarget::DragOver(DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect) { + if (!mDragService) { + return E_FAIL; + } + + bool dragImageChanged = gDragImageChanged; + gDragImageChanged = false; + + // If a LINK effect could be generated previously from a DragEnter(), + // then we should include it as an allowed effect. + mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK); + + nsCOMPtr<nsIDragSession> currentDragSession; + mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); + if (!currentDragSession) { + return S_OK; // Drag was canceled. + } + + // without the AddRef() |this| can get destroyed in an event handler + this->AddRef(); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + if (dragImageChanged) { + // See comment in nsNativeDragTarget::DragEnter. + GetDropTargetHelper()->DragLeave(); + // The drop helper only updates the image during DragEnter, so emulate + // a DragEnter if the image was changed. + POINT pt = {ptl.x, ptl.y}; + nsDragService* dragService = static_cast<nsDragService*>(mDragService); + GetDropTargetHelper()->DragEnter(mHWnd, dragService->GetDataObject(), &pt, + *pdwEffect); + } + POINT pt = {ptl.x, ptl.y}; + GetDropTargetHelper()->DragOver(&pt, *pdwEffect); + } + + ModifierKeyState modifierKeyState; + nsCOMPtr<nsIDragService> dragService = mDragService; + dragService->FireDragEventAtSource(eDrag, modifierKeyState.GetModifiers()); + // Now process the native drag state and then dispatch the event + ProcessDrag(eDragOver, grfKeyState, ptl, pdwEffect); + + this->Release(); + + return S_OK; +} + +STDMETHODIMP +nsNativeDragTarget::DragLeave() { + if (!mDragService) { + return E_FAIL; + } + + // Drag and drop image helper + if (GetDropTargetHelper()) { + GetDropTargetHelper()->DragLeave(); + } + + // dispatch the event into Gecko + DispatchDragDropEvent(eDragExit, gDragLastPoint); + + nsCOMPtr<nsIDragSession> currentDragSession; + mDragService->GetCurrentSession(getter_AddRefs(currentDragSession)); + + if (currentDragSession) { + nsCOMPtr<nsINode> sourceNode; + currentDragSession->GetSourceNode(getter_AddRefs(sourceNode)); + + if (!sourceNode) { + // We're leaving a window while doing a drag that was + // initiated in a different app. End the drag session, since + // we're done with it for now (until the user drags back into + // mozilla). + ModifierKeyState modifierKeyState; + nsCOMPtr<nsIDragService> dragService = mDragService; + dragService->EndDragSession(false, modifierKeyState.GetModifiers()); + } + } + + // release the ref that was taken in DragEnter + NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); + if (mTookOwnRef) { + this->Release(); + mTookOwnRef = false; + } + + return S_OK; +} + +void nsNativeDragTarget::DragCancel() { + // Cancel the drag session if we did DragEnter. + if (mTookOwnRef) { + if (GetDropTargetHelper()) { + GetDropTargetHelper()->DragLeave(); + } + if (mDragService) { + ModifierKeyState modifierKeyState; + nsCOMPtr<nsIDragService> dragService = mDragService; + dragService->EndDragSession(false, modifierKeyState.GetModifiers()); + } + this->Release(); // matching the AddRef in DragEnter + mTookOwnRef = false; + } +} + +STDMETHODIMP +nsNativeDragTarget::Drop(LPDATAOBJECT pData, DWORD grfKeyState, POINTL aPT, + LPDWORD pdwEffect) { + if (!mDragService) { + return E_FAIL; + } + + mEffectsAllowed = *pdwEffect; + AddLinkSupportIfCanBeGenerated(pData); + + // Drag and drop image helper + if (GetDropTargetHelper()) { + POINT pt = {aPT.x, aPT.y}; + GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect); + } + + // Set the native data object into the drag service + // + // This cast is ok because in the constructor we created a + // the actual implementation we wanted, so we know this is + // a nsDragService (but it should still be a private interface) + nsDragService* winDragService = static_cast<nsDragService*>(mDragService); + winDragService->SetIDataObject(pData); + + // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects. + // We use strong refs to prevent it from destroying these: + RefPtr<nsNativeDragTarget> kungFuDeathGrip = this; + nsCOMPtr<nsIDragService> serv = mDragService; + + // Now process the native drag state and then dispatch the event + ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect); + + nsCOMPtr<nsIDragSession> currentDragSession; + serv->GetCurrentSession(getter_AddRefs(currentDragSession)); + if (!currentDragSession) { + return S_OK; // DragCancel() was called. + } + + // Let the win drag service know whether this session experienced + // a drop event within the application. Drop will not oocur if the + // drop landed outside the app. (used in tab tear off, bug 455884) + winDragService->SetDroppedLocal(); + + // tell the drag service we're done with the session + // Use GetMessagePos to get the position of the mouse at the last message + // seen by the event loop. (Bug 489729) + DWORD pos = ::GetMessagePos(); + POINT cpos; + cpos.x = GET_X_LPARAM(pos); + cpos.y = GET_Y_LPARAM(pos); + winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y)); + ModifierKeyState modifierKeyState; + serv->EndDragSession(true, modifierKeyState.GetModifiers()); + + // release the ref that was taken in DragEnter + NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!"); + if (mTookOwnRef) { + this->Release(); + mTookOwnRef = false; + } + + return S_OK; +} + +/** + * By lazy loading mDropTargetHelper we save 50-70ms of startup time + * which is ~5% of startup time. + */ +IDropTargetHelper* nsNativeDragTarget::GetDropTargetHelper() { + if (!mDropTargetHelper) { + CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER, + IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper); + } + + return mDropTargetHelper; +} diff --git a/widget/windows/nsNativeDragTarget.h b/widget/windows/nsNativeDragTarget.h new file mode 100644 index 0000000000..5c3896dd0a --- /dev/null +++ b/widget/windows/nsNativeDragTarget.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsNativeDragTarget_h_ +#define _nsNativeDragTarget_h_ + +#include "nsCOMPtr.h" +#include <ole2.h> +#include <shlobj.h> + +#ifndef IDropTargetHelper +# include <shobjidl.h> // Vista drag image interfaces +# undef LogSeverity // SetupAPI.h #defines this as DWORD +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" + +class nsIDragService; +class nsIWidget; + +/* + * nsNativeDragTarget implements the IDropTarget interface and gets most of its + * behavior from the associated adapter (m_dragDrop). + */ + +class nsNativeDragTarget final : public IDropTarget { + public: + explicit nsNativeDragTarget(nsIWidget* aWidget); + ~nsNativeDragTarget(); + + // IUnknown members - see iunknown.h for documentation + STDMETHODIMP QueryInterface(REFIID, void**); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IDataTarget members + + // Set pEffect based on whether this object can support a drop based on + // the data available from pSource, the key and mouse states specified + // in grfKeyState, and the coordinates specified by point. This is + // called by OLE when a drag enters this object's window (as registered + // by Initialize). + STDMETHODIMP DragEnter(LPDATAOBJECT pSource, DWORD grfKeyState, POINTL point, + DWORD* pEffect); + + // Similar to DragEnter except it is called frequently while the drag + // is over this object's window. + MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragOver(DWORD grfKeyState, + POINTL point, + DWORD* pEffect); + + // Release the drag-drop source and put internal state back to the point + // before the call to DragEnter. This is called when the drag leaves + // without a drop occurring. + MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP DragLeave(); + + // If point is within our region of interest and pSource's data supports + // one of our formats, get the data and set pEffect according to + // grfKeyState (DROPEFFECT_MOVE if the control key was not pressed, + // DROPEFFECT_COPY if the control key was pressed). Otherwise return + // E_FAIL. + MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP Drop(LPDATAOBJECT pSource, + DWORD grfKeyState, POINTL point, + DWORD* pEffect); + /** + * Cancel the current drag session, if any. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY void DragCancel(); + + static void DragImageChanged() { gDragImageChanged = true; } + + protected: + void GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect, + uint32_t* aGeckoAction); + void ProcessDrag(mozilla::EventMessage aEventMessage, DWORD grfKeyState, + POINTL pt, DWORD* pdwEffect); + void DispatchDragDropEvent(mozilla::EventMessage aEventMessage, + const POINTL& aPT); + void AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource); + + // Native Stuff + ULONG m_cRef; // reference count + HWND mHWnd; + DWORD mEffectsAllowed; + DWORD mEffectsPreferred; + bool mTookOwnRef; + + // Gecko Stuff + nsIWidget* mWidget; + nsIDragService* mDragService; + // Drag target helper + IDropTargetHelper* GetDropTargetHelper(); + + private: + // Drag target helper + IDropTargetHelper* mDropTargetHelper; + + static bool gDragImageChanged; +}; + +#endif // _nsNativeDragTarget_h_ diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp new file mode 100644 index 0000000000..b6240d9ac7 --- /dev/null +++ b/widget/windows/nsNativeThemeWin.cpp @@ -0,0 +1,4013 @@ +/* -*- 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/ClearOnShutdown.h" +#include "mozilla/EventStates.h" +#include "mozilla/Logging.h" +#include "mozilla/RelativeLuminanceUtils.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/gfx/Types.h" // for Color::FromABGR +#include "nsNativeBasicTheme.h" +#include "nsColor.h" +#include "nsDeviceContext.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsTransform2D.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIFrame.h" +#include "nsLayoutUtils.h" +#include "nsNameSpaceManager.h" +#include "nsLookAndFeel.h" +#include "nsMenuFrame.h" +#include "nsGkAtoms.h" +#include <malloc.h> +#include "nsWindow.h" +#include "nsComboboxControlFrame.h" +#include "prinrval.h" +#include "ScrollbarUtil.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 namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme) + +nsNativeThemeWin::nsNativeThemeWin() + : mProgressDeterminateTimeStamp(TimeStamp::Now()), + mProgressIndeterminateTimeStamp(TimeStamp::Now()), + mBorderCacheValid(), + mMinimumWidgetSizeCacheValid(), + mGutterSizeCacheValid(false) { + // 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; + } + // All headless windows are considered active so they are painted. + if (gfxPlatform::IsHeadless()) { + return mozilla::widget::themeconst::FS_ACTIVE; + } + // 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 + // Needs the system scale for consistency with Windows Theme API. + double scaleFactor = WinUtils::SystemScaleFactor(); + int iconDevicePixels = NSToIntRound(16 * scaleFactor); + SIZE iconSize = {iconDevicePixels, iconDevicePixels}; + // Not really sure what margins should be used here, but this seems to work in + // practice... + MARGINS margins = {0}; + GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL, + TMT_CONTENTMARGINS, nullptr, &margins); + iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth; + iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight; + + int width = std::max( + itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx); + int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy)); + + SIZE ret; + ret.cx = width; + ret.cy = height; + return ret; +} + +SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) { + if (mGutterSizeCacheValid) { + return mGutterSizeCache; + } + + mGutterSizeCache = GetGutterSize(theme, nullptr); + mGutterSizeCacheValid = true; + + return mGutterSizeCache; +} + +/* 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 + * + * '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 + */ + +enum CaptionDesktopTheme { + CAPTION_CLASSIC = 0, + CAPTION_BASIC, +}; + +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 (!nsUXThemeData::IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].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 (!nsUXThemeData::IsAppThemed()) + offset = buttonData[CAPTION_CLASSIC].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 kProgressHorizontalOverlaySize = 120; +// The height of the overlay used to animate the vertical progress bar (Vista +// and later). +static const int32_t kProgressVerticalOverlaySize = 45; +// The height of the overlay used for the vertical indeterminate progress bar +// (Vista and later). +static const int32_t kProgressVerticalIndeterminateOverlaySize = 60; +// The width of the overlay used to animate the indeterminate progress bar +// (Windows Classic). +static const int32_t kProgressClassicOverlaySize = 40; + +/* + * GetProgressOverlayStyle - returns the proper overlay part for themed + * progress bars based on os and orientation. + */ +static int32_t GetProgressOverlayStyle(bool aIsVertical) { + return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY; +} + +/* + * GetProgressOverlaySize - returns the minimum width or height for themed + * progress bar overlays. This includes the width of indeterminate chunks + * and vista pulse overlays. + */ +static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) { + if (aIsVertical) { + return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize + : kProgressVerticalOverlaySize; + } + return kProgressHorizontalOverlaySize; +} + +/* + * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based + * on a comparison of the current value and maximum. + */ +static bool IsProgressMeterFilled(nsIFrame* aFrame) { + NS_ENSURE_TRUE(aFrame, false); + nsIFrame* parentFrame = aFrame->GetParent(); + NS_ENSURE_TRUE(parentFrame, false); + return nsNativeTheme::GetProgressValue(parentFrame) == + nsNativeTheme::GetProgressMaxValue(parentFrame); +} + +/* + * CalculateProgressOverlayRect - returns the padded overlay animation rect + * used in rendering progress bars. Resulting rects are used in rendering + * vista+ pulse overlays and indeterminate progress meters. Graphics should + * be rendered at the origin. + */ +RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame, + RECT* aWidgetRect, + bool aIsVertical, + bool aIsIndeterminate, + bool aIsClassic) { + NS_ASSERTION(aFrame, "bad frame pointer"); + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + + int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top + : aWidgetRect->right - aWidgetRect->left; + + // Recycle a set of progress pulse timers - these timers control the position + // of all progress overlays and indeterminate chunks that get rendered. + double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan + : kProgressDeterminateTimeSpan; + TimeDuration period; + if (!aIsIndeterminate) { + if (TimeStamp::Now() > + (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) { + mProgressDeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressDeterminateTimeStamp; + } else { + if (TimeStamp::Now() > + (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) { + mProgressIndeterminateTimeStamp = TimeStamp::Now(); + } + period = TimeStamp::Now() - mProgressIndeterminateTimeStamp; + } + + double percent = period / TimeDuration::FromSeconds(span); + + if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent; + + RECT overlayRect = *aWidgetRect; + int32_t overlaySize; + if (!aIsClassic) { + overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate); + } else { + overlaySize = kProgressClassicOverlaySize; + } + + // Calculate a bounds that is larger than the meters frame such that the + // overlay starts and ends completely off the edge of the frame: + // [overlay][frame][overlay] + // This also yields a nice delay on rotation. Use overlaySize as the minimum + // size for [overlay] based on the graphics dims. If [frame] is larger, use + // the frame size instead. + int trackWidth = frameSize > overlaySize ? frameSize : overlaySize; + if (!aIsVertical) { + int xPos = aWidgetRect->left - trackWidth; + xPos += (int)ceil(((double)(trackWidth * 2) * percent)); + overlayRect.left = xPos; + overlayRect.right = xPos + overlaySize; + } else { + int yPos = aWidgetRect->bottom + trackWidth; + yPos -= (int)ceil(((double)(trackWidth * 2) * percent)); + overlayRect.bottom = yPos; + overlayRect.top = yPos - overlaySize; + } + return overlayRect; +} + +/* + * DrawProgressMeter - render an appropriate progress meter based on progress + * meter style, orientation, and os. Note, this does not render the underlying + * progress track. + * + * @param aFrame the widget frame + * @param aAppearance type of widget + * @param aTheme progress theme handle + * @param aHdc hdc returned by gfxWindowsNativeDrawing + * @param aPart the PP_X progress part + * @param aState the theme state + * @param aWidgetRect bounding rect for the widget + * @param aClipRect dirty rect that needs drawing. + * @param aAppUnits app units per device pixel + */ +void nsNativeThemeWin::DrawThemedProgressMeter( + nsIFrame* aFrame, StyleAppearance aAppearance, HANDLE aTheme, HDC aHdc, + int aPart, int aState, RECT* aWidgetRect, RECT* aClipRect) { + if (!aFrame || !aTheme || !aHdc) return; + + NS_ASSERTION(aWidgetRect, "bad rect pointer"); + NS_ASSERTION(aClipRect, "bad clip rect pointer"); + + RECT adjWidgetRect, adjClipRect; + adjWidgetRect = *aWidgetRect; + adjClipRect = *aClipRect; + + nsIFrame* parentFrame = aFrame->GetParent(); + if (!parentFrame) { + // We have no parent to work with, just bail. + NS_WARNING("No parent frame for progress rendering. Can't paint."); + return; + } + + EventStates eventStates = GetContentState(parentFrame, aAppearance); + bool vertical = IsVerticalProgress(parentFrame); + bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates); + bool animate = indeterminate; + + // Vista and up progress meter is fill style, rendered here. We render + // the pulse overlay in the follow up section below. + DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect, + &adjClipRect); + if (!IsProgressMeterFilled(aFrame)) { + animate = true; + } + + if (animate) { + // Indeterminate rendering + int32_t overlayPart = GetProgressOverlayStyle(vertical); + RECT overlayRect = CalculateProgressOverlayRect( + aFrame, &adjWidgetRect, vertical, indeterminate, false); + DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect, + &adjClipRect); + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) { + NS_WARNING("unable to animate progress widget!"); + } + } +} + +LayoutDeviceIntMargin nsNativeThemeWin::GetCachedWidgetBorder( + HTHEME aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance, + int32_t aPart, int32_t aState) { + int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart; + int32_t cacheBitIndex = cacheIndex / 8; + uint8_t cacheBit = 1u << (cacheIndex % 8); + + if (mBorderCacheValid[cacheBitIndex] & cacheBit) { + return mBorderCache[cacheIndex]; + } + + // Get our info. + RECT outerRect; // Create a fake outer rect. + outerRect.top = outerRect.left = 100; + outerRect.right = outerRect.bottom = 200; + RECT contentRect(outerRect); + HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState, + &outerRect, &contentRect); + + if (FAILED(res)) { + return LayoutDeviceIntMargin(); + } + + // Now compute the delta in each direction and place it in our + // nsIntMargin struct. + LayoutDeviceIntMargin result; + result.top = contentRect.top - outerRect.top; + result.bottom = outerRect.bottom - contentRect.bottom; + result.left = contentRect.left - outerRect.left; + result.right = outerRect.right - contentRect.right; + + mBorderCacheValid[cacheBitIndex] |= cacheBit; + mBorderCache[cacheIndex] = result; + + return result; +} + +nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize( + nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass, + StyleAppearance aAppearance, int32_t aPart, int32_t aState, + THEMESIZE aSizeReq, mozilla::LayoutDeviceIntSize* aResult) { + int32_t cachePart = aPart; + + if (aAppearance == StyleAppearance::Button && aSizeReq == TS_MIN) { + // In practice, StyleAppearance::Button is the only widget type which has an + // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just + // stuff that extra bit into the aPart part of the cache, since BP_Count is + // well below THEME_PART_DISTINCT_VALUE_COUNT anyway. + cachePart = BP_Count; + } + + MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT); + int32_t cacheIndex = + aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart; + int32_t cacheBitIndex = cacheIndex / 8; + uint8_t cacheBit = 1u << (cacheIndex % 8); + + if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) { + *aResult = mMinimumWidgetSizeCache[cacheIndex]; + return NS_OK; + } + + HDC hdc = ::GetDC(NULL); + if (!hdc) { + return NS_ERROR_FAILURE; + } + + SIZE sz; + GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz); + aResult->width = sz.cx; + aResult->height = sz.cy; + + switch (aAppearance) { + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + aResult->width++; + aResult->height = aResult->height / 2 + 1; + break; + + case StyleAppearance::Menuseparator: { + SIZE gutterSize(GetGutterSize(aTheme, hdc)); + aResult->width += gutterSize.cx; + break; + } + + case StyleAppearance::Menuarrow: + // Use the width of the arrow glyph as padding. See the drawing + // code for details. + aResult->width *= 2; + break; + + default: + break; + } + + ::ReleaseDC(nullptr, hdc); + + mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit; + mMinimumWidgetSizeCache[cacheIndex] = *aResult; + + return NS_OK; +} + +mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass( + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::Button: + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + case StyleAppearance::Groupbox: + return Some(eUXButton); + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::FocusOutline: + return Some(eUXEdit); + case StyleAppearance::Tooltip: + return Some(eUXTooltip); + case StyleAppearance::Toolbox: + return Some(eUXRebar); + case StyleAppearance::MozWinMediaToolbox: + return Some(eUXMediaRebar); + case StyleAppearance::MozWinCommunicationsToolbox: + return Some(eUXCommunicationsRebar); + case StyleAppearance::MozWinBrowsertabbarToolbox: + return Some(eUXBrowserTabBarRebar); + case StyleAppearance::Toolbar: + case StyleAppearance::Toolbarbutton: + case StyleAppearance::Separator: + return Some(eUXToolbar); + case StyleAppearance::ProgressBar: + case StyleAppearance::Progresschunk: + return Some(eUXProgress); + case StyleAppearance::Tab: + case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + return Some(eUXTab); + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::Scrollcorner: + return Some(eUXScrollbar); + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + return Some(eUXTrackbar); + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + return Some(eUXSpin); + case StyleAppearance::Statusbar: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::Resizer: + return Some(eUXStatus); + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistArrowButton: + return Some(eUXCombobox); + case StyleAppearance::Treeheadercell: + case StyleAppearance::Treeheadersortarrow: + return Some(eUXHeader); + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::Treetwistyopen: + case StyleAppearance::Treeitem: + return Some(eUXListview); + case StyleAppearance::Menubar: + case StyleAppearance::Menupopup: + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + case StyleAppearance::Menuseparator: + case StyleAppearance::Menuarrow: + case StyleAppearance::Menuimage: + case StyleAppearance::Menuitemtext: + return Some(eUXMenu); + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + case StyleAppearance::MozWindowFrameBottom: + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + case StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + case StyleAppearance::MozWinGlass: + case StyleAppearance::MozWinBorderlessGlass: + return Some(eUXWindowFrame); + default: + return Nothing(); + } +} + +HANDLE +nsNativeThemeWin::GetTheme(StyleAppearance aAppearance) { + mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance); + if (themeClass.isNothing()) { + return nullptr; + } + return nsUXThemeData::GetTheme(themeClass.value()); +} + +int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame, + StyleAppearance aAppearance, + bool wantFocused) { + EventStates eventState = GetContentState(aFrame, aAppearance); + 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) { + if (eventState.HasState(NS_EVENT_STATE_FOCUSRING)) { + return TS_FOCUSED; + } + // On Windows, focused buttons are always drawn as such by the native + // theme, that's why we check NS_EVENT_STATE_FOCUS instead of + // NS_EVENT_STATE_FOCUSRING. + if (aAppearance == StyleAppearance::Button && + eventState.HasState(NS_EVENT_STATE_FOCUS)) { + return TS_FOCUSED; + } + } + + return TS_NORMAL; +} + +bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame, + StyleAppearance aAppearance) { + nsIContent* content = aFrame->GetContent(); + if (content->IsXULElement() && + content->NodeInfo()->Equals(nsGkAtoms::richlistitem)) + return CheckBooleanAttr(aFrame, nsGkAtoms::selected); + + return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive); +} + +/** + * aPart is filled in with the UXTheme part code. On return, values > 0 + * are the actual UXTheme part code; -1 means the widget will be drawn by + * us; 0 means that we should use part code 0, which isn't a real part code + * but elicits some kind of default behaviour from UXTheme when drawing + * (but isThemeBackgroundPartiallyTransparent may not work). + */ +nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, + StyleAppearance aAppearance, + int32_t& aPart, + int32_t& aState) { + switch (aAppearance) { + case StyleAppearance::Button: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aAppearance); + 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, aAppearance, true); + + // Check for default dialog buttons. These buttons should always look + // focused. + if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED; + return NS_OK; + } + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: { + bool isCheckbox = (aAppearance == StyleAppearance::Checkbox); + aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO; + + enum InputState { UNCHECKED = 0, CHECKED, INDETERMINATE }; + InputState inputState = UNCHECKED; + + if (!aFrame) { + aState = TS_NORMAL; + } else { + if (GetCheckedOrSelected(aFrame, !isCheckbox)) { + inputState = CHECKED; + } + if (isCheckbox && GetIndeterminate(aFrame)) { + inputState = INDETERMINATE; + } + + EventStates eventState = GetContentState(aFrame, aAppearance); + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + } else { + aState = StandardGetState(aFrame, aAppearance, false); + } + } + + // 4 unchecked states, 4 checked states, 4 indeterminate states. + aState += inputState * 4; + return NS_OK; + } + case StyleAppearance::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 StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: { + EventStates eventState = GetContentState(aFrame, aAppearance); + + /* Note: the NOSCROLL type has a rounded corner in each corner. The more + * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom + * edges rendered as straight horizontal lines with sharp corners to + * accommodate a scrollbar. However, the scrollbar gets rendered on top + * of this for us, so we don't care, and can just use NOSCROLL here. + */ + aPart = TFP_EDITBORDER_NOSCROLL; + + if (!aFrame) { + aState = TFS_EDITBORDER_NORMAL; + } else if (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_FOCUSRING)) + aState = TFS_EDITBORDER_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TFS_EDITBORDER_HOVER; + else + aState = TFS_EDITBORDER_NORMAL; + } + + return NS_OK; + } + case StyleAppearance::FocusOutline: { + // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT + aPart = TFP_TEXTFIELD; // TFP_EDITBORDER_NOSCROLL; + aState = TS_FOCUSED; // TFS_EDITBORDER_FOCUSED; + return NS_OK; + } + case StyleAppearance::Tooltip: { + aPart = TTP_STANDARD; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::ProgressBar: { + bool vertical = IsVerticalProgress(aFrame); + aPart = vertical ? PP_BARVERT : PP_BAR; + aState = PBBS_NORMAL; + return NS_OK; + } + case StyleAppearance::Progresschunk: { + nsIFrame* parentFrame = aFrame->GetParent(); + if (IsVerticalProgress(parentFrame)) { + aPart = PP_FILLVERT; + } else { + aPart = PP_FILL; + } + + aState = PBBVS_NORMAL; + return NS_OK; + } + case StyleAppearance::Toolbarbutton: { + aPart = BP_BUTTON; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aAppearance); + 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 StyleAppearance::Separator: { + aPart = TP_SEPARATOR; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { + aPart = SP_BUTTON; + aState = (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) * 4; + EventStates eventState = GetContentState(aFrame, aAppearance); + if (!aFrame) + aState += TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState += TS_DISABLED; + else { + nsIFrame* parent = aFrame->GetParent(); + EventStates parentState = GetContentState( + parent, parent->StyleDisplay()->EffectiveAppearance()); + 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 (parentState.HasState(NS_EVENT_STATE_HOVER)) + aState = + (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) + + SP_BUTTON_IMPLICIT_HOVER_BASE; + else + aState += TS_NORMAL; + } + return NS_OK; + } + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: { + aPart = (aAppearance == StyleAppearance::ScrollbarHorizontal) + ? SP_TRACKSTARTHOR + : SP_TRACKSTARTVERT; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: { + aPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) + ? SP_THUMBHOR + : SP_THUMBVERT; + EventStates eventState = GetContentState(aFrame, aAppearance); + 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 StyleAppearance::Range: { + if (IsRangeHorizontal(aFrame)) { + aPart = TKP_TRACK; + aState = TRS_NORMAL; + } else { + aPart = TKP_TRACKVERT; + aState = TRVS_NORMAL; + } + return NS_OK; + } + case StyleAppearance::RangeThumb: { + if (IsRangeHorizontal(aFrame)) { + aPart = TKP_THUMBBOTTOM; + } else { + aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT; + } + EventStates eventState = GetContentState(aFrame, aAppearance); + 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_FOCUSRING)) + aState = TKP_FOCUSED; + else if (eventState.HasState(NS_EVENT_STATE_HOVER)) + aState = TS_HOVER; + else + aState = TS_NORMAL; + } + return NS_OK; + } + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: { + aPart = (aAppearance == StyleAppearance::SpinnerUpbutton) ? SPNP_UP + : SPNP_DOWN; + EventStates eventState = GetContentState(aFrame, aAppearance); + if (!aFrame) + aState = TS_NORMAL; + else if (IsDisabled(aFrame, eventState)) + aState = TS_DISABLED; + else + aState = StandardGetState(aFrame, aAppearance, false); + return NS_OK; + } + case StyleAppearance::Toolbox: + case StyleAppearance::MozWinMediaToolbox: + case StyleAppearance::MozWinCommunicationsToolbox: + case StyleAppearance::MozWinBrowsertabbarToolbox: + case StyleAppearance::Statusbar: + case StyleAppearance::Scrollcorner: { + aState = 0; + aPart = RP_BACKGROUND; + return NS_OK; + } + case StyleAppearance::Toolbar: { + // Use -1 to indicate we don't wish to have the theme background drawn + // for this item. We will pass any nessessary information via aState, + // and will render the item using separate code. + aPart = -1; + aState = 0; + if (aFrame) { + nsIContent* content = aFrame->GetContent(); + nsIContent* parent = content->GetParent(); + // XXXzeniko hiding the first toolbar will result in an unwanted margin + if (parent && parent->GetFirstChild() == content) { + aState = 1; + } + } + return NS_OK; + } + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::Resizer: { + switch (aAppearance) { + case StyleAppearance::Statusbarpanel: + aPart = 1; + break; + case StyleAppearance::Resizerpanel: + aPart = 2; + break; + case StyleAppearance::Resizer: + aPart = 3; + break; + default: + MOZ_ASSERT_UNREACHABLE("Oops, we're missing a case"); + aPart = 1; // just something valid + } + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::Treeview: + case StyleAppearance::Listbox: { + aPart = TREEVIEW_BODY; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::Tabpanels: { + aPart = TABP_PANELS; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::Tabpanel: { + aPart = TABP_PANEL; + aState = TS_NORMAL; + return NS_OK; + } + case StyleAppearance::Tab: { + aPart = TABP_TAB; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + EventStates eventState = GetContentState(aFrame, aAppearance); + 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, aAppearance, true); + + return NS_OK; + } + case StyleAppearance::Treeheadersortarrow: { + // XXX Probably will never work due to a bug in the Luna theme. + aPart = 4; + aState = 1; + return NS_OK; + } + case StyleAppearance::Treeheadercell: { + aPart = 1; + if (!aFrame) { + aState = TS_NORMAL; + return NS_OK; + } + + aState = StandardGetState(aFrame, aAppearance, true); + + return NS_OK; + } + case StyleAppearance::MenulistButton: + case StyleAppearance::Menulist: { + nsIContent* content = aFrame->GetContent(); + bool useDropBorder = content && content->IsHTMLElement(); + EventStates eventState = GetContentState(aFrame, aAppearance); + + /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML + * content or for editable menulists; this gives us the thin outline, + * instead of the gradient-filled background */ + if (useDropBorder) + aPart = CBP_DROPBORDER; + else + aPart = CBP_DROPFRAME; + + if (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_FOCUSRING) || + 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 StyleAppearance::MozMenulistArrowButton: { + bool isHTML = IsHTMLContent(aFrame); + nsIFrame* parentFrame = aFrame->GetParent(); + bool isMenulist = !isHTML && parentFrame->IsMenuFrame(); + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the + // parent. + if (isHTML || isMenulist) aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aAppearance); + aPart = CBP_DROPMARKER_VISTA; + + // 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, + StyleAppearance::Menulist)) + aPart = CBP_DROPMARKER; + + if (IsDisabled(aFrame, eventState)) { + aState = TS_DISABLED; + return NS_OK; + } + + if (isHTML) { + nsComboboxControlFrame* ccf = do_QueryFrame(aFrame); + isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup()); + } else + isOpen = IsOpenButton(aFrame); + + if (isHTML) { + 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 { + /* 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 StyleAppearance::Menupopup: { + aPart = MENU_POPUPBACKGROUND; + aState = MB_ACTIVE; + return NS_OK; + } + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: { + bool isTopLevel = false; + bool isOpen = false; + bool isHover = false; + nsMenuFrame* menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aAppearance); + + isTopLevel = IsTopLevelMenu(aFrame); + + if (menuFrame) isOpen = menuFrame->IsOpen(); + + isHover = IsMenuActive(aFrame, aAppearance); + + 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 StyleAppearance::Menuseparator: + aPart = MENU_POPUPSEPARATOR; + aState = 0; + return NS_OK; + case StyleAppearance::Menuarrow: { + aPart = MENU_POPUPSUBMENU; + EventStates eventState = GetContentState(aFrame, aAppearance); + aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL; + return NS_OK; + } + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: { + EventStates eventState = GetContentState(aFrame, aAppearance); + + aPart = MENU_POPUPCHECK; + aState = MC_CHECKMARKNORMAL; + + // Radio states are offset by 2 + if (aAppearance == StyleAppearance::Menuradio) aState += 2; + + // the disabled states are offset by 1 + if (IsDisabled(aFrame, eventState)) aState += 1; + + return NS_OK; + } + case StyleAppearance::Menuitemtext: + case StyleAppearance::Menuimage: + aPart = -1; + aState = 0; + return NS_OK; + + case StyleAppearance::MozWindowTitlebar: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowTitlebarMaximized: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameLeft: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameRight: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameBottom: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowButtonClose: + aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON; + aState = GetWindowFrameButtonState(aFrame, + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonMinimize: + aPart = mozilla::widget::themeconst::WP_MINBUTTON; + aState = GetWindowFrameButtonState(aFrame, + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonMaximize: + aPart = mozilla::widget::themeconst::WP_MAXBUTTON; + aState = GetWindowFrameButtonState(aFrame, + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonRestore: + aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON; + aState = GetWindowFrameButtonState(aFrame, + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + case StyleAppearance::MozWinGlass: + case StyleAppearance::MozWinBorderlessGlass: + aPart = -1; + aState = 0; + return NS_OK; + default: + aPart = 0; + aState = 0; + return NS_ERROR_FAILURE; + } +} + +static bool AssumeThemePartAndStateAreTransparent(int32_t aPart, + int32_t aState) { + if (!(IsWin8Point1OrLater() && nsUXThemeData::IsHighContrastOn()) && + aPart == MENU_POPUPITEM && aState == MBI_NORMAL) { + return true; + } + return false; +} + +// When running with per-monitor DPI (on Win8.1+), and rendering on a display +// with a different DPI setting from the system's default scaling, we need to +// apply scaling to native-themed elements as the Windows theme APIs assume +// the system default resolution. +static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) { + if (WinUtils::IsPerMonitorDPIAware() || + StaticPrefs::layout_css_devPixelsPerPx() > 0.0) { + nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget(); + if (rootWidget) { + double systemScale = WinUtils::SystemScaleFactor(); + return rootWidget->GetDefaultScale().scale / systemScale; + } + } + return 1.0; +} + +NS_IMETHODIMP +nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& aDirtyRect) { + if (IsWidgetScrollbarPart(aAppearance)) { + if (MayDrawCustomScrollbarPart(aContext, aFrame, aAppearance, aRect, + aDirtyRect)) { + return NS_OK; + } + } + + HANDLE theme = GetTheme(aAppearance); + if (!theme) + return ClassicDrawWidgetBackground(aContext, aFrame, aAppearance, aRect, + aDirtyRect); + + // ^^ without the right sdk, assume xp theming and fall through. + if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + switch (aAppearance) { + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + case StyleAppearance::MozWindowFrameBottom: + // Nothing to draw, these areas are glass. Minimum dimensions + // should be set, so xul content should be layed out correctly. + return NS_OK; + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + // 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; + case StyleAppearance::MozWinGlass: + case StyleAppearance::MozWinBorderlessGlass: + // Nothing to draw, this is the glass background. + return NS_OK; + case StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + // We handle these through nsIWidget::UpdateThemeGeometries + return NS_OK; + default: + break; + } + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state); + if (NS_FAILED(rv)) return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + RefPtr<gfxContext> ctx = aContext; + gfxContextMatrixAutoSaveRestore save(ctx); + + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + ctx->SetMatrix(ctx->CurrentMatrix().PreScale(themeScale, themeScale)); + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + RECT clipRect; + gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()), + dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(), + aDirtyRect.Height()); + + tr.Scale(1.0 / (p2a * themeScale)); + dr.Scale(1.0 / (p2a * themeScale)); + + gfxWindowsNativeDrawing nativeDrawing( + ctx, dr, GetWidgetNativeDrawingFlags(aAppearance)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + nativeDrawing.TransformToNativeRect(dr, clipRect); + +#if 0 + { + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22, + m._31, m._32)); + MOZ_LOG(gWindowsLog, LogLevel::Error, + (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n", + tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height, + offset.x, offset.y)); + } +#endif + + if (aAppearance == StyleAppearance::MozWindowTitlebar) { + // 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 (aAppearance == StyleAppearance::MozWindowTitlebarMaximized) { + // 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 (aAppearance == StyleAppearance::Tab) { + // For left edge and right edge tabs, we need to adjust the widget + // rects and clip rects so that the edges don't get drawn. + bool isLeft = IsLeftToSelectedTab(aFrame); + bool isRight = !isLeft && IsRightToSelectedTab(aFrame); + + if (isLeft || isRight) { + // HACK ALERT: There appears to be no way to really obtain this value, so + // we're forced to just use the default value for Luna (which also happens + // to be correct for all the other skins I've tried). + int32_t edgeSize = 2; + + // Armed with the size of the edge, we now need to either shift to the + // left or to the right. The clip rect won't include this extra area, so + // we know that we're effectively shifting the edge out of view (such that + // it won't be painted). + if (isLeft) + // The right edge should not be drawn. Extend our rect by the edge + // size. + widgetRect.right += edgeSize; + else + // The left edge should not be drawn. Move the widget rect's left coord + // back. + widgetRect.left -= edgeSize; + } + } else if (aAppearance == StyleAppearance::MozWindowButtonMinimize) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize || + aAppearance == StyleAppearance::MozWindowButtonRestore) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } else if (aAppearance == StyleAppearance::MozWindowButtonClose) { + 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 (aAppearance == StyleAppearance::Range) { + RECT contentRect; + GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect, + &contentRect); + + SIZE siz; + GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz); + + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (IsRangeHorizontal(aFrame)) { + contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2; + contentRect.bottom = contentRect.top + siz.cy; + } else { + if (!IsFrameRTL(aFrame)) { + contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.right = contentRect.left + siz.cx; + } else { + contentRect.right -= + (contentRect.right - contentRect.left - siz.cx) / 2; + contentRect.left = contentRect.right - siz.cx; + } + } + + DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect); + } else if (aAppearance == StyleAppearance::Menucheckbox || + aAppearance == StyleAppearance::Menuradio) { + bool isChecked = false; + isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked); + + if (isChecked) { + int bgState = MCB_NORMAL; + EventStates eventState = GetContentState(aFrame, aAppearance); + + // 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 (aAppearance == StyleAppearance::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 (aAppearance == StyleAppearance::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 (aAppearance == StyleAppearance::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 StyleAppearance::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 (aAppearance == StyleAppearance::Resizer || + aAppearance == StyleAppearance::MozMenulistArrowButton) { + DrawThemeBGRTLAware(theme, hdc, part, state, &widgetRect, &clipRect, + IsFrameRTL(aFrame)); + } else if (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::Textfield || + aAppearance == StyleAppearance::Textarea) { + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + + if (state == TFS_EDITBORDER_DISABLED) { + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1)); + } + } else if (aAppearance == StyleAppearance::ProgressBar) { + // DrawThemeBackground renders each corner with a solid white pixel. + // Restore these pixels to the underlying color. Tracks are rendered + // using alpha recovery, so this makes the corners transparent. + COLORREF color; + color = GetPixel(hdc, widgetRect.left, widgetRect.top); + DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); + SetPixel(hdc, widgetRect.left, widgetRect.top, color); + SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color); + SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color); + SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color); + } else if (aAppearance == StyleAppearance::Progresschunk) { + DrawThemedProgressMeter(aFrame, aAppearance, theme, hdc, part, state, + &widgetRect, &clipRect); + } else if (aAppearance == StyleAppearance::FocusOutline) { + // Inflate 'widgetRect' with the focus outline size. + LayoutDeviceIntMargin border = GetWidgetBorder( + aFrame->PresContext()->DeviceContext(), aFrame, aAppearance); + 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 elements + // XXX it'd be nice to draw these outside of the frame + if (aAppearance == StyleAppearance::Range) { + EventStates contentState = GetContentState(aFrame, aAppearance); + + if (contentState.HasState(NS_EVENT_STATE_FOCUSRING)) { + POINT vpOrg; + HPEN hPen = nullptr; + + uint8_t id = SaveDC(hdc); + + ::SelectClipRgn(hdc, nullptr); + ::GetViewportOrgEx(hdc, &vpOrg); + ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top, + nullptr); + ::SetTextColor(hdc, 0); + ::DrawFocusRect(hdc, &widgetRect); + ::RestoreDC(hdc, id); + if (hPen) { + ::DeleteObject(hPen); + } + } + } else if (aAppearance == StyleAppearance::Toolbar && state == 0) { + // Draw toolbar separator lines above all toolbars except the first one. + // The lines are part of the Rebar theme, which is loaded for + // StyleAppearance::Toolbox. + theme = GetTheme(StyleAppearance::Toolbox); + if (!theme) return NS_ERROR_FAILURE; + + widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT; + DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP, + nullptr); + } else if (aAppearance == StyleAppearance::ScrollbarthumbHorizontal || + aAppearance == StyleAppearance::ScrollbarthumbVertical) { + // Draw the decorative gripper for the scrollbar thumb button, if it fits + + SIZE gripSize; + MARGINS thumbMgns; + int gripPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) + ? 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(LayoutDeviceIntMargin* aMargin, nsIFrame* aFrame) { + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aMargin->top = NSToIntRound(aMargin->top * themeScale); + aMargin->left = NSToIntRound(aMargin->left * themeScale); + aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale); + aMargin->right = NSToIntRound(aMargin->right * themeScale); + } +} + +static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) { + double themeScale = GetThemeDpiScaleFactor(aFrame); + if (themeScale != 1.0) { + aSize->width = NSToIntRound(aSize->width * themeScale); + aSize->height = NSToIntRound(aSize->height * themeScale); + } +} + +LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) { + LayoutDeviceIntMargin result; + mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance); + HTHEME theme = NULL; + if (!themeClass.isNothing()) { + theme = nsUXThemeData::GetTheme(themeClass.value()); + } + if (!theme) { + result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance); + ScaleForFrameDPI(&result, aFrame); + return result; + } + + if (!WidgetIsContainer(aAppearance) || + aAppearance == StyleAppearance::Toolbox || + aAppearance == StyleAppearance::MozWinMediaToolbox || + aAppearance == StyleAppearance::MozWinCommunicationsToolbox || + aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox || + aAppearance == StyleAppearance::Statusbar || + aAppearance == StyleAppearance::Resizer || + aAppearance == StyleAppearance::Tabpanel || + aAppearance == StyleAppearance::ScrollbarHorizontal || + aAppearance == StyleAppearance::ScrollbarVertical || + aAppearance == StyleAppearance::Scrollcorner || + aAppearance == StyleAppearance::Menuitem || + aAppearance == StyleAppearance::Checkmenuitem || + aAppearance == StyleAppearance::Radiomenuitem || + aAppearance == StyleAppearance::Menupopup || + aAppearance == StyleAppearance::Menuimage || + aAppearance == StyleAppearance::Menuitemtext || + aAppearance == StyleAppearance::Separator || + aAppearance == StyleAppearance::MozWindowTitlebar || + aAppearance == StyleAppearance::MozWindowTitlebarMaximized || + aAppearance == StyleAppearance::MozWinGlass || + aAppearance == StyleAppearance::MozWinBorderlessGlass) + return result; // Don't worry about it. + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state); + if (NS_FAILED(rv)) return result; + + if (aAppearance == StyleAppearance::Toolbar) { + // make space for the separator line above all toolbars but the first + if (state == 0) result.top = TB_SEPARATOR_HEIGHT; + return result; + } + + result = GetCachedWidgetBorder(theme, themeClass.value(), aAppearance, part, + state); + + // Remove the edges for tabs that are before or after the selected tab, + if (aAppearance == StyleAppearance::Tab) { + if (IsLeftToSelectedTab(aFrame)) + // Remove the right edge, since we won't be drawing it. + result.right = 0; + else if (IsRightToSelectedTab(aFrame)) + // Remove the left edge, since we won't be drawing it. + result.left = 0; + } + + if (aFrame && (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::Textfield || + aAppearance == StyleAppearance::Textarea)) { + nsIContent* content = aFrame->GetContent(); + if (content && content->IsHTMLElement()) { + // We need to pad textfields by 1 pixel, since the caret will draw + // flush against the edge by default if we don't. + result.top++; + result.left++; + result.bottom++; + result.right++; + } + } + + ScaleForFrameDPI(&result, aFrame); + return result; +} + +bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) { + switch (aAppearance) { + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + aResult->SizeTo(0, 0, 0, 0); + return true; + default: + break; + } + + bool ok = true; + + if (aAppearance == StyleAppearance::MozWindowButtonBox || + aAppearance == StyleAppearance::MozWindowButtonBoxMaximized) { + aResult->SizeTo(0, 0, 0, 0); + + // aero glass doesn't display custom buttons + if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) return true; + + // button padding for standard windows + if (aAppearance == StyleAppearance::MozWindowButtonBox) { + aResult->top = GetSystemMetrics(SM_CXFRAME); + } + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + // Content padding + if (aAppearance == StyleAppearance::MozWindowTitlebar || + aAppearance == StyleAppearance::MozWindowTitlebarMaximized) { + 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 (aAppearance == StyleAppearance::MozWindowTitlebarMaximized) { + nsIWidget* rootWidget = nullptr; + if (WinUtils::HasSystemMetricsForDpi()) { + rootWidget = aFrame->PresContext()->GetRootWidget(); + } + if (rootWidget) { + double dpi = rootWidget->GetDPI(); + aResult->top = WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) + + WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi); + } else { + aResult->top = + GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + } + } + return ok; + } + + HANDLE theme = GetTheme(aAppearance); + if (!theme) { + ok = ClassicGetWidgetPadding(aContext, aFrame, aAppearance, aResult); + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + if (aAppearance == StyleAppearance::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 (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::Textfield || + aAppearance == StyleAppearance::Textarea || + aAppearance == StyleAppearance::MenulistButton || + aAppearance == StyleAppearance::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 (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::Textfield || + aAppearance == StyleAppearance::Textarea) { + aResult->top = aResult->bottom = 2; + aResult->left = aResult->right = 2; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } else if (IsHTMLContent(aFrame) && + (aAppearance == StyleAppearance::Menulist || + aAppearance == StyleAppearance::MenulistButton)) { + /* For content menulist controls, we need an extra pixel so that we have + * room to draw our focus rectangle stuff. Otherwise, the focus rect might + * overlap the control's border. + */ + aResult->top = aResult->bottom = 1; + aResult->left = aResult->right = 1; + ScaleForFrameDPI(aResult, aFrame); + return ok; + } + + int32_t right, left, top, bottom; + right = left = top = bottom = 0; + switch (aAppearance) { + case StyleAppearance::Menuimage: + right = 8; + left = 3; + break; + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + right = 8; + left = 0; + break; + case StyleAppearance::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 StyleAppearance::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, + StyleAppearance aAppearance, + 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. + * + * TODO(jwatt): Figure out what do to about + * StyleAppearance::MozMenulistArrowButton too. + */ +#if 0 + /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right, + * and bottom so that they overlap the dropdown's border like they're + * supposed to. + */ + if (aAppearance == StyleAppearance::MenulistButton && + IsHTMLContent(aFrame) && + !IsWidgetStyled(aFrame->GetParent()->PresContext(), + aFrame->GetParent(), + StyleAppearance::Menulist)) + { + int32_t p2a = aContext->AppUnitsPerDevPixel(); + /* Note: no overflow on the left */ + nsMargin m(p2a, p2a, p2a, 0); + aOverflowRect->Inflate (m); + return true; + } +#endif + + if (aAppearance == StyleAppearance::FocusOutline) { + LayoutDeviceIntMargin border = + GetWidgetBorder(aContext, aFrame, aAppearance); + 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, + StyleAppearance aAppearance, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) { + aResult->width = aResult->height = 0; + *aIsOverridable = true; + nsresult rv = NS_OK; + + mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aAppearance); + HTHEME theme = NULL; + if (!themeClass.isNothing()) { + theme = nsUXThemeData::GetTheme(themeClass.value()); + } + if (!theme) { + rv = ClassicGetMinimumWidgetSize(aFrame, aAppearance, aResult, + aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + switch (aAppearance) { + case StyleAppearance::Groupbox: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Toolbox: + case StyleAppearance::MozWinMediaToolbox: + case StyleAppearance::MozWinCommunicationsToolbox: + case StyleAppearance::MozWinBrowsertabbarToolbox: + case StyleAppearance::Toolbar: + case StyleAppearance::Statusbar: + case StyleAppearance::Progresschunk: + case StyleAppearance::Tabpanels: + case StyleAppearance::Tabpanel: + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::Menuitemtext: + case StyleAppearance::MozWinGlass: + case StyleAppearance::MozWinBorderlessGlass: + return NS_OK; // Don't worry about it. + default: + break; + } + + if (aAppearance == StyleAppearance::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 (aAppearance) { + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::MozMenulistArrowButton: { + rv = ClassicGetMinimumWidgetSize(aFrame, aAppearance, aResult, + aIsOverridable); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + if (!IsTopLevelMenu(aFrame)) { + SIZE gutterSize(GetCachedGutterSize(theme)); + aResult->width = gutterSize.cx; + aResult->height = gutterSize.cy; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + + case StyleAppearance::Menuimage: + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: { + SIZE boxSize(GetCachedGutterSize(theme)); + aResult->width = boxSize.cx + 2; + aResult->height = boxSize.cy; + *aIsOverridable = false; + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + case StyleAppearance::Menuitemtext: + return NS_OK; + + case StyleAppearance::ProgressBar: + // Best-fit size for progress meters is too large for most + // themes. We want these widgets to be able to really shrink + // down, so use the min-size request value (of 0). + sizeReq = TS_MIN; + break; + + case StyleAppearance::Resizer: + *aIsOverridable = false; + break; + + case StyleAppearance::RangeThumb: { + *aIsOverridable = false; + if (IsRangeHorizontal(aFrame)) { + aResult->width = 12; + aResult->height = 20; + } else { + aResult->width = 20; + aResult->height = 12; + } + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + + case StyleAppearance::Scrollcorner: { + if (nsLookAndFeel::GetInt(nsLookAndFeel::IntID::UseOverlayScrollbars) != + 0) { + aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL), + ::GetSystemMetrics(SM_CYVSCROLL)); + ScaleForFrameDPI(aResult, aFrame); + return rv; + } + break; + } + + case StyleAppearance::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 StyleAppearance::Button: + // We should let HTML buttons shrink to their min size. + // FIXME bug 403934: We should probably really separate + // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can + // use the one they want. + if (aFrame->GetContent()->IsHTMLElement()) { + sizeReq = TS_MIN; + } + break; + + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + // 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::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cx; + aResult->height = + nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cy; + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + *aIsOverridable = false; + return rv; + + case StyleAppearance::MozWindowButtonMinimize: + aResult->width = + nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cx; + aResult->height = + nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cy; + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + *aIsOverridable = false; + return rv; + + case StyleAppearance::MozWindowButtonClose: + aResult->width = + nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cx; + aResult->height = + nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cy; + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + *aIsOverridable = false; + return rv; + + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: + 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 StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + if (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + aResult->width = nsUXThemeData::GetCommandButtonBoxMetrics().cx; + aResult->height = nsUXThemeData::GetCommandButtonBoxMetrics().cy - + GetSystemMetrics(SM_CYFRAME) - + GetSystemMetrics(SM_CXPADDEDBORDER); + if (aAppearance == StyleAppearance::MozWindowButtonBoxMaximized) { + aResult->width += 1; + aResult->height -= 2; + } + *aIsOverridable = false; + return rv; + } + break; + + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + case StyleAppearance::MozWindowFrameBottom: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = GetSystemMetrics(SM_CYFRAME); + *aIsOverridable = false; + return rv; + + default: + break; + } + + int32_t part, state; + rv = GetThemePartAndState(aFrame, aAppearance, part, state); + if (NS_FAILED(rv)) return rv; + + rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(), + aAppearance, part, state, sizeReq, aResult); + + ScaleForFrameDPI(aResult, aFrame); + return rv; +} + +NS_IMETHODIMP +nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, + StyleAppearance aAppearance, + nsAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) { + // Some widget types just never change state. + if (aAppearance == StyleAppearance::Toolbox || + aAppearance == StyleAppearance::MozWinMediaToolbox || + aAppearance == StyleAppearance::MozWinCommunicationsToolbox || + aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox || + aAppearance == StyleAppearance::Toolbar || + aAppearance == StyleAppearance::Statusbar || + aAppearance == StyleAppearance::Statusbarpanel || + aAppearance == StyleAppearance::Resizerpanel || + aAppearance == StyleAppearance::Progresschunk || + aAppearance == StyleAppearance::ProgressBar || + aAppearance == StyleAppearance::Tooltip || + aAppearance == StyleAppearance::Tabpanels || + aAppearance == StyleAppearance::Tabpanel || + aAppearance == StyleAppearance::Separator || + aAppearance == StyleAppearance::MozWinGlass || + aAppearance == StyleAppearance::MozWinBorderlessGlass) { + *aShouldRepaint = false; + return NS_OK; + } + + if (aAppearance == StyleAppearance::MozWindowTitlebar || + aAppearance == StyleAppearance::MozWindowTitlebarMaximized || + aAppearance == StyleAppearance::MozWindowFrameLeft || + aAppearance == StyleAppearance::MozWindowFrameRight || + aAppearance == StyleAppearance::MozWindowFrameBottom || + aAppearance == StyleAppearance::MozWindowButtonClose || + aAppearance == StyleAppearance::MozWindowButtonMinimize || + aAppearance == StyleAppearance::MozWindowButtonMaximize || + aAppearance == StyleAppearance::MozWindowButtonRestore) { + *aShouldRepaint = true; + return NS_OK; + } + + // We need to repaint the dropdown arrow in vista HTML combobox controls when + // the control is closed to get rid of the hover effect. + if ((aAppearance == StyleAppearance::Menulist || + aAppearance == StyleAppearance::MenulistButton || + aAppearance == StyleAppearance::MozMenulistArrowButton) && + nsNativeTheme::IsHTMLContent(aFrame)) { + *aShouldRepaint = true; + return NS_OK; + } + + // XXXdwh Not sure what can really be done here. Can at least guess for + // specific widgets that they're highly unlikely to have certain states. + // For example, a toolbar doesn't care about any states. + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked || + aAttribute == nsGkAtoms::selected || + aAttribute == nsGkAtoms::visuallyselected || + aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open || + aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused) + *aShouldRepaint = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeWin::ThemeChanged() { + nsUXThemeData::Invalidate(); + memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid)); + memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid)); + mGutterSizeCacheValid = false; + return NS_OK; +} + +bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aAppearance) { + // XXXdwh We can go even further and call the API to ask if support exists for + // specific widgets. + + if (aAppearance == StyleAppearance::FocusOutline) { + return true; + } + + HANDLE theme = nullptr; + if (aAppearance == StyleAppearance::CheckboxContainer) + theme = GetTheme(StyleAppearance::Checkbox); + else if (aAppearance == StyleAppearance::RadioContainer) + theme = GetTheme(StyleAppearance::Radio); + else + theme = GetTheme(aAppearance); + + if (theme && aAppearance == StyleAppearance::Resizer) return true; + + if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aAppearance))) + // turn off theming for some HTML widgets styled by the page + return (!IsWidgetStyled(aPresContext, aFrame, aAppearance)); + + return false; +} + +bool nsNativeThemeWin::WidgetIsContainer(StyleAppearance aAppearance) { + // XXXdwh At some point flesh all of this out. + if (aAppearance == StyleAppearance::MozMenulistArrowButton || + aAppearance == StyleAppearance::Radio || + aAppearance == StyleAppearance::Checkbox) + return false; + return true; +} + +bool nsNativeThemeWin::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::Textarea: + case StyleAppearance::Textfield: + case StyleAppearance::NumberInput: + return true; + default: + return false; + } +} + +bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; } + +bool nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus( + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + case StyleAppearance::MozWindowFrameBottom: + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + return true; + default: + return false; + } +} + +nsITheme::ThemeGeometryType nsNativeThemeWin::ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + return eThemeGeometryTypeWindowButtons; + default: + return eThemeGeometryTypeUnknown; + } +} + +nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) { + if (auto transparency = + ScrollbarUtil::GetScrollbarPartTransparency(aFrame, aAppearance)) { + return *transparency; + } + + switch (aAppearance) { + case StyleAppearance::MozWinGlass: + case StyleAppearance::MozWinBorderlessGlass: + case StyleAppearance::ProgressBar: + case StyleAppearance::Progresschunk: + case StyleAppearance::Range: + return eTransparent; + default: + break; + } + + HANDLE theme = GetTheme(aAppearance); + // For the classic theme we don't really have a way of knowing + if (!theme) { + // menu backgrounds and tooltips which can't be themed are opaque + if (aAppearance == StyleAppearance::Menupopup || + aAppearance == StyleAppearance::Tooltip) { + return eOpaque; + } + return eUnknownTransparency; + } + + int32_t part, state; + nsresult rv = GetThemePartAndState(aFrame, aAppearance, part, state); + // Fail conservatively + NS_ENSURE_SUCCESS(rv, eUnknownTransparency); + + if (part <= 0) { + // Not a real part code, so IsThemeBackgroundPartiallyTransparent may + // not work, so don't call it. + return eUnknownTransparency; + } + + if (IsThemeBackgroundPartiallyTransparent(theme, part, state)) + return eTransparent; + return eOpaque; +} + +/* Windows 9x/NT/2000/Classic XP Theme Support */ + +bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::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->IsScrollFrame(); + } + case StyleAppearance::Menubar: + case StyleAppearance::Menupopup: + // Classic non-flat menus are handled almost entirely through CSS. + if (!nsUXThemeData::AreFlatMenusEnabled()) return false; + case StyleAppearance::Button: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::Groupbox: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarNonDisappearing: + case StyleAppearance::Scrollcorner: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::Tooltip: + case StyleAppearance::Statusbar: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::ProgressBar: + case StyleAppearance::Progresschunk: + case StyleAppearance::Tab: + case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + case StyleAppearance::Menuarrow: + case StyleAppearance::Menuseparator: + case StyleAppearance::Menuitemtext: + case StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + case StyleAppearance::MozWindowFrameBottom: + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + case StyleAppearance::MozWindowButtonBox: + case StyleAppearance::MozWindowButtonBoxMaximized: + return true; + default: + return false; + } +} + +LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) { + LayoutDeviceIntMargin result; + switch (aAppearance) { + case StyleAppearance::Groupbox: + case StyleAppearance::Button: + result.top = result.left = result.bottom = result.right = 2; + break; + case StyleAppearance::Statusbar: + result.bottom = result.left = result.right = 0; + result.top = 2; + break; + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::Tab: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::FocusOutline: + result.top = result.left = result.bottom = result.right = 2; + break; + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: { + result.top = 1; + result.left = 1; + result.bottom = 1; + result.right = aFrame->GetNextSibling() ? 3 : 1; + break; + } + case StyleAppearance::Tooltip: + result.top = result.left = result.bottom = result.right = 1; + break; + case StyleAppearance::ProgressBar: + result.top = result.left = result.bottom = result.right = 1; + break; + case StyleAppearance::Menubar: + result.top = result.left = result.bottom = result.right = 0; + break; + case StyleAppearance::Menupopup: + result.top = result.left = result.bottom = result.right = 3; + break; + default: + result.top = result.bottom = result.left = result.right = 0; + break; + } + return result; +} + +bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) { + switch (aAppearance) { + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: { + int32_t part, state; + bool focused; + + if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aAppearance, part, + state, focused))) + return false; + + if (part == 1) { // top-level menu + if (nsUXThemeData::AreFlatMenusEnabled() || !(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 StyleAppearance::ProgressBar: + (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right = + 1; + return true; + default: + return false; + } +} + +nsresult nsNativeThemeWin::ClassicGetMinimumWidgetSize( + nsIFrame* aFrame, StyleAppearance aAppearance, LayoutDeviceIntSize* aResult, + bool* aIsOverridable) { + (*aResult).width = (*aResult).height = 0; + *aIsOverridable = true; + switch (aAppearance) { + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + (*aResult).width = (*aResult).height = 13; + break; + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + case StyleAppearance::Menuarrow: + (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK); + (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK); + break; + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = 8; // No good metrics available for this + *aIsOverridable = false; + break; + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + // For scrollbar-width:thin, we don't display the buttons. + if (!ScrollbarUtil::IsScrollbarWidthThin(aFrame)) { + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL); + } + *aIsOverridable = false; + break; + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + // For scrollbar-width:thin, we don't display the buttons. + if (!ScrollbarUtil::IsScrollbarWidthThin(aFrame)) { + (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL); + (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL); + } + *aIsOverridable = false; + break; + case StyleAppearance::ScrollbarVertical: + // 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 StyleAppearance::ScrollbarNonDisappearing: { + aResult->SizeTo(::GetSystemMetrics(SM_CXVSCROLL), + ::GetSystemMetrics(SM_CYHSCROLL)); + break; + } + case StyleAppearance::RangeThumb: { + if (IsRangeHorizontal(aFrame)) { + (*aResult).width = 12; + (*aResult).height = 20; + } else { + (*aResult).width = 20; + (*aResult).height = 12; + } + *aIsOverridable = false; + break; + } + case StyleAppearance::MozMenulistArrowButton: + (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL); + break; + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::Button: + case StyleAppearance::Groupbox: + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::Statusbar: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::Progresschunk: + case StyleAppearance::Tooltip: + case StyleAppearance::ProgressBar: + case StyleAppearance::Tab: + case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + // no minimum widget size + break; + case StyleAppearance::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 StyleAppearance::ScrollbarthumbVertical: + (*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(aAppearance)) { + (*aResult).height >>= 1; + } + // If scrollbar-width is thin, divide the thickness by two to make + // it look more compact. + if (ScrollbarUtil::IsScrollbarWidthThin(aFrame)) { + aResult->width >>= 1; + } + *aIsOverridable = false; + break; + case StyleAppearance::ScrollbarthumbHorizontal: + (*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(aAppearance)) { + (*aResult).width >>= 1; + } + // If scrollbar-width is thin, divide the thickness by two to make + // it look more compact. + if (ScrollbarUtil::IsScrollbarWidthThin(aFrame)) { + aResult->height >>= 1; + } + *aIsOverridable = false; + break; + case StyleAppearance::ScrollbarHorizontal: + (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1; + break; + case StyleAppearance::Menuseparator: { + aResult->width = 0; + aResult->height = 10; + break; + } + + case StyleAppearance::MozWindowTitlebarMaximized: + case StyleAppearance::MozWindowTitlebar: + aResult->height = GetSystemMetrics(SM_CYCAPTION); + aResult->height += GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + case StyleAppearance::MozWindowFrameLeft: + case StyleAppearance::MozWindowFrameRight: + aResult->width = GetSystemMetrics(SM_CXFRAME); + aResult->height = 0; + break; + + case StyleAppearance::MozWindowFrameBottom: + aResult->height = GetSystemMetrics(SM_CYFRAME); + aResult->width = 0; + break; + + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: + 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 (aAppearance == StyleAppearance::MozWindowButtonMinimize) { + AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE); + } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize || + aAppearance == StyleAppearance::MozWindowButtonRestore) { + AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE); + } else if (aAppearance == StyleAppearance::MozWindowButtonClose) { + AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE); + } + break; + + default: + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult nsNativeThemeWin::ClassicGetThemePartAndState( + nsIFrame* aFrame, StyleAppearance aAppearance, int32_t& aPart, + int32_t& aState, bool& aFocused) { + aFocused = false; + switch (aAppearance) { + case StyleAppearance::Button: { + EventStates contentState; + + aPart = DFC_BUTTON; + aState = DFCS_BUTTONPUSH; + aFocused = false; + + contentState = GetContentState(aFrame, aAppearance); + 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 nsStyleUI* uiData = aFrame->StyleUI(); + // The down state is flat if the button is focusable + if (uiData->mUserFocus == StyleUserFocus::Normal) { + if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT; + + aFocused = true; + } + } + // On Windows, focused buttons are always drawn as such by the native + // theme, that's why we check NS_EVENT_STATE_FOCUS instead of + // NS_EVENT_STATE_FOCUSRING. + if (contentState.HasState(NS_EVENT_STATE_FOCUS) || + (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) { + aFocused = true; + } + } + + return NS_OK; + } + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: { + EventStates contentState; + aFocused = false; + + aPart = DFC_BUTTON; + aState = 0; + nsIContent* content = aFrame->GetContent(); + bool isCheckbox = (aAppearance == StyleAppearance::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, aAppearance); + if (!content->IsXULElement() && + contentState.HasState(NS_EVENT_STATE_FOCUSRING)) { + 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 StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: { + bool isTopLevel = false; + bool isOpen = false; + nsMenuFrame* menuFrame = do_QueryFrame(aFrame); + EventStates eventState = GetContentState(aFrame, aAppearance); + + // 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, aAppearance)) aState |= DFCS_HOT; + + return NS_OK; + } + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + case StyleAppearance::Menuarrow: { + aState = 0; + EventStates eventState = GetContentState(aFrame, aAppearance); + + if (IsDisabled(aFrame, eventState)) aState |= DFCS_INACTIVE; + if (IsMenuActive(aFrame, aAppearance)) aState |= DFCS_HOT; + + if (aAppearance == StyleAppearance::Menucheckbox || + aAppearance == StyleAppearance::Menuradio) { + if (IsCheckedButton(aFrame)) aState |= DFCS_CHECKED; + } else if (IsFrameRTL(aFrame)) { + aState |= DFCS_RTL; + } + return NS_OK; + } + case StyleAppearance::Listbox: + case StyleAppearance::Treeview: + case StyleAppearance::NumberInput: + case StyleAppearance::FocusOutline: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::Scrollcorner: + case StyleAppearance::Statusbar: + case StyleAppearance::Statusbarpanel: + case StyleAppearance::Resizerpanel: + case StyleAppearance::Progresschunk: + case StyleAppearance::Tooltip: + case StyleAppearance::ProgressBar: + case StyleAppearance::Tab: + case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + case StyleAppearance::Menubar: + case StyleAppearance::Menupopup: + case StyleAppearance::Groupbox: + // these don't use DrawFrameControl + return NS_OK; + case StyleAppearance::MozMenulistArrowButton: { + aPart = DFC_SCROLL; + aState = DFCS_SCROLLCOMBOBOX; + + nsIFrame* parentFrame = aFrame->GetParent(); + bool isHTML = IsHTMLContent(aFrame); + bool isMenulist = !isHTML && parentFrame->IsMenuFrame(); + bool isOpen = false; + + // HTML select and XUL menulist dropdown buttons get state from the + // parent. + if (isHTML || isMenulist) aFrame = parentFrame; + + EventStates eventState = GetContentState(aFrame, aAppearance); + + if (IsDisabled(aFrame, eventState)) { + aState |= DFCS_INACTIVE; + return NS_OK; + } + + if (isHTML) { + nsComboboxControlFrame* 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 StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { + EventStates contentState = GetContentState(aFrame, aAppearance); + + aPart = DFC_SCROLL; + switch (aAppearance) { + case StyleAppearance::ScrollbarbuttonUp: + aState = DFCS_SCROLLUP; + break; + case StyleAppearance::ScrollbarbuttonDown: + aState = DFCS_SCROLLDOWN; + break; + case StyleAppearance::ScrollbarbuttonLeft: + aState = DFCS_SCROLLLEFT; + break; + case StyleAppearance::ScrollbarbuttonRight: + aState = DFCS_SCROLLRIGHT; + break; + default: + 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 StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: { + EventStates contentState = GetContentState(aFrame, aAppearance); + + aPart = DFC_SCROLL; + switch (aAppearance) { + case StyleAppearance::SpinnerUpbutton: + aState = DFCS_SCROLLUP; + break; + case StyleAppearance::SpinnerDownbutton: + aState = DFCS_SCROLLDOWN; + break; + default: + 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 StyleAppearance::Resizer: + aPart = DFC_SCROLL; + aState = + (IsFrameRTL(aFrame)) ? DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP; + return NS_OK; + case StyleAppearance::Menuseparator: + aPart = 0; + aState = 0; + return NS_OK; + case StyleAppearance::MozWindowTitlebar: + aPart = mozilla::widget::themeconst::WP_CAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowTitlebarMaximized: + aPart = mozilla::widget::themeconst::WP_MAXCAPTION; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameLeft: + aPart = mozilla::widget::themeconst::WP_FRAMELEFT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameRight: + aPart = mozilla::widget::themeconst::WP_FRAMERIGHT; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowFrameBottom: + aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM; + aState = GetTopLevelWindowActiveState(aFrame); + return NS_OK; + case StyleAppearance::MozWindowButtonClose: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONCLOSE | GetClassicWindowFrameButtonState( + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonMinimize: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMIN | GetClassicWindowFrameButtonState( + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonMaximize: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONMAX | GetClassicWindowFrameButtonState( + GetContentState(aFrame, aAppearance)); + return NS_OK; + case StyleAppearance::MozWindowButtonRestore: + aPart = DFC_CAPTION; + aState = DFCS_CAPTIONRESTORE | GetClassicWindowFrameButtonState( + GetContentState(aFrame, aAppearance)); + return NS_OK; + default: + return NS_ERROR_FAILURE; + } +} + +// Draw classic Windows tab +// (no system API for this, but DrawEdge can draw all the parts of a tab) +static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected, + bool aDrawLeft, bool aDrawRight) { + int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag; + RECT topRect, sideRect, bottomRect, lightRect, shadeRect; + int32_t selectedOffset, lOffset, rOffset; + + selectedOffset = aSelected ? 1 : 0; + lOffset = aDrawLeft ? 2 : 0; + rOffset = aDrawRight ? 2 : 0; + + // Get info for tab orientation/position (Left, Top, Right, Bottom) + switch (aPosition) { + case BF_LEFT: + leftFlag = BF_TOP; + topFlag = BF_LEFT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset); + ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset, + R.bottom); + ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3); + ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1); + break; + case BF_TOP: + leftFlag = BF_LEFT; + topFlag = BF_TOP; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPRIGHT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT; + + ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top + 2, R.right, + R.bottom - 1 + selectedOffset); + ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom); + ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3); + ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2); + break; + case BF_RIGHT: + leftFlag = BF_TOP; + topFlag = BF_RIGHT; + rightFlag = BF_BOTTOM; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset); + ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2, + R.bottom); + ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom); + ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2); + ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1); + break; + case BF_BOTTOM: + leftFlag = BF_LEFT; + topFlag = BF_BOTTOM; + rightFlag = BF_RIGHT; + lightFlag = BF_DIAGONAL_ENDTOPLEFT; + shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT; + + ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom); + ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right, + R.bottom - 2); + ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2); + ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1); + ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1); + break; + default: + MOZ_CRASH(); + } + + // Background + ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1)); + + // Tab "Top" + ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Bottom" + if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag); + + // Tab "Sides" + if (!aDrawLeft) leftFlag = 0; + if (!aDrawRight) rightFlag = 0; + ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag); + + // Tab Diagonal Corners + if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag); + + if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag); +} + +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( + gfxContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance, + const nsRect& aRect, const nsRect& aDirtyRect) { + int32_t part, state; + bool focused; + nsresult rv; + rv = ClassicGetThemePartAndState(aFrame, aAppearance, part, state, focused); + if (NS_FAILED(rv)) return rv; + + if (AssumeThemePartAndStateAreTransparent(part, state)) { + return NS_OK; + } + + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + RECT widgetRect; + gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()), + dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(), + aDirtyRect.Height()); + + tr.Scale(1.0 / p2a); + dr.Scale(1.0 / p2a); + + RefPtr<gfxContext> ctx = aContext; + + gfxWindowsNativeDrawing nativeDrawing( + ctx, dr, GetWidgetNativeDrawingFlags(aAppearance)); + +RENDER_AGAIN: + + HDC hdc = nativeDrawing.BeginNativeDrawing(); + if (!hdc) return NS_ERROR_FAILURE; + + nativeDrawing.TransformToNativeRect(tr, widgetRect); + + rv = NS_OK; + switch (aAppearance) { + // Draw button + case StyleAppearance::Button: { + if (focused) { + // draw dark button focus border first + HBRUSH brush; + brush = ::GetSysColorBrush(COLOR_3DDKSHADOW); + if (brush) ::FrameRect(hdc, &widgetRect, brush); + InflateRect(&widgetRect, -1, -1); + } + // fall-through... + } + // Draw controls supported by DrawFrameControl + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::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 StyleAppearance::NumberInput: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + EventStates eventState = GetContentState(aFrame, aAppearance); + + // 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 StyleAppearance::Treeview: { + // Draw inset edge + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + + // Fill in window color background + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1)); + + break; + } + // Draw ToolTip background + case StyleAppearance::Tooltip: + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME)); + InflateRect(&widgetRect, -1, -1); + ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK)); + + break; + case StyleAppearance::Groupbox: + ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1)); + break; + // Draw 3D face background controls + case StyleAppearance::ProgressBar: + // Draw 3D border + ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE); + InflateRect(&widgetRect, -1, -1); + // fall through + case StyleAppearance::Tabpanel: + case StyleAppearance::Statusbar: + case StyleAppearance::Resizerpanel: { + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1)); + + break; + } + // Draw 3D inset statusbar panel + case StyleAppearance::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 StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); + + break; + case StyleAppearance::RangeThumb: { + EventStates eventState = GetContentState(aFrame, aAppearance); + + ::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 StyleAppearance::ScrollbarVertical: + case StyleAppearance::ScrollbarHorizontal: { + // 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; + } + case StyleAppearance::Scrollcorner: { + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1)); + } + // Draw scale track background + case StyleAppearance::Range: { + const int32_t trackWidth = 4; + // When rounding is necessary, we round the position of the track + // away from the chevron of the thumb to make it look better. + if (IsRangeHorizontal(aFrame)) { + widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2; + widgetRect.bottom = widgetRect.top + trackWidth; + } else { + if (!IsFrameRTL(aFrame)) { + widgetRect.left += + (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.right = widgetRect.left + trackWidth; + } else { + widgetRect.right -= + (widgetRect.right - widgetRect.left - trackWidth) / 2; + widgetRect.left = widgetRect.right - trackWidth; + } + } + + ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH)); + + break; + } + case StyleAppearance::Progresschunk: { + nsIFrame* stateFrame = aFrame->GetParent(); + EventStates eventStates = GetContentState(stateFrame, aAppearance); + + bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates); + bool vertical = IsVerticalProgress(stateFrame); + + nsIContent* content = aFrame->GetContent(); + if (!indeterminate || !content) { + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1)); + break; + } + + RECT overlayRect = CalculateProgressOverlayRect( + aFrame, &widgetRect, vertical, indeterminate, true); + + ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1)); + + if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { + NS_WARNING("unable to animate progress widget!"); + } + break; + } + + // Draw Tab + case StyleAppearance::Tab: { + DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP, + IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame), + !IsLeftToSelectedTab(aFrame)); + + break; + } + case StyleAppearance::Tabpanels: + ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, + BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM); + + break; + case StyleAppearance::Menubar: + break; + case StyleAppearance::Menupopup: + NS_ASSERTION(nsUXThemeData::AreFlatMenusEnabled(), + "Classic menus are styled entirely through CSS"); + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_MENU + 1)); + ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW)); + break; + case StyleAppearance::Menuitem: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + // part == 0 for normal items + // part == 1 for top-level menu items + if (nsUXThemeData::AreFlatMenusEnabled()) { + // 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 StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + if (!(state & DFCS_CHECKED)) break; // nothin' to do + case StyleAppearance::Menuarrow: { + uint32_t color = COLOR_MENUTEXT; + if ((state & DFCS_INACTIVE)) + color = COLOR_GRAYTEXT; + else if ((state & DFCS_HOT)) + color = COLOR_HIGHLIGHTTEXT; + + if (aAppearance == StyleAppearance::Menucheckbox) + DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color); + else if (aAppearance == StyleAppearance::Menuradio) + DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color); + else if (aAppearance == StyleAppearance::Menuarrow) + DrawMenuImage(hdc, widgetRect, + (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW, + color); + break; + } + case StyleAppearance::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 StyleAppearance::MozWindowTitlebar: + case StyleAppearance::MozWindowTitlebarMaximized: { + 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 (aAppearance == StyleAppearance::MozWindowTitlebar) { + // frame things up with a top raised border. + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP); + } + break; + } + + case StyleAppearance::MozWindowFrameLeft: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT); + break; + + case StyleAppearance::MozWindowFrameRight: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT); + break; + + case StyleAppearance::MozWindowFrameBottom: + DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM); + break; + + case StyleAppearance::MozWindowButtonClose: + case StyleAppearance::MozWindowButtonMinimize: + case StyleAppearance::MozWindowButtonMaximize: + case StyleAppearance::MozWindowButtonRestore: { + if (aAppearance == StyleAppearance::MozWindowButtonMinimize) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE); + } else if (aAppearance == StyleAppearance::MozWindowButtonMaximize || + aAppearance == StyleAppearance::MozWindowButtonRestore) { + OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE); + } else if (aAppearance == StyleAppearance::MozWindowButtonClose) { + 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( + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::Button: + case StyleAppearance::NumberInput: + case StyleAppearance::FocusOutline: + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + + // 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 StyleAppearance::MozMenulistArrowButton: + // these are definitely no; they're all graphics that don't get scaled up + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + case StyleAppearance::Groupbox: + case StyleAppearance::Checkmenuitem: + case StyleAppearance::Radiomenuitem: + case StyleAppearance::Menucheckbox: + case StyleAppearance::Menuradio: + case StyleAppearance::Menuarrow: + return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + + // need to check these others + default: + return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA | + gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE | + gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM; + } +} + +// This tries to draw a Windows 10 style scrollbar with given colors. +bool nsNativeThemeWin::MayDrawCustomScrollbarPart(gfxContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& aClipRect) { + ComputedStyle* style = ScrollbarUtil::GetCustomScrollbarStyle(aFrame); + if (!style) { + return false; + } + + EventStates eventStates = GetContentState(aFrame, aAppearance); + + gfxContextAutoSaveRestore autoSave(aContext); + RefPtr<gfxContext> ctx = aContext; + DrawTarget* dt = ctx->GetDrawTarget(); + gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxRect rect = ThebesRect(NSRectToSnappedRect(aRect, p2a, *dt)); + gfxRect clipRect = ThebesRect(NSRectToSnappedRect(aClipRect, p2a, *dt)); + ctx->Clip(clipRect); + + nscolor trackColor = ScrollbarUtil::GetScrollbarTrackColor(aFrame); + + switch (aAppearance) { + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: { + ctx->SetColor(sRGBColor::FromABGR(trackColor)); + ctx->Rectangle(rect); + ctx->Fill(); + return true; + } + default: + break; + } + + // Scrollbar thumb and button are two CSS pixels thinner than the track. + gfxRect bgRect = rect; + gfxFloat dev2css = round(AppUnitsPerCSSPixel() / p2a); + switch (aAppearance) { + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + bgRect.Deflate(dev2css, 0); + break; + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + bgRect.Deflate(0, dev2css); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown widget type"); + } + + switch (aAppearance) { + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarthumbHorizontal: { + nscolor faceColor = + ScrollbarUtil::GetScrollbarThumbColor(aFrame, eventStates); + ctx->SetColor(sRGBColor::FromABGR(faceColor)); + ctx->Rectangle(bgRect); + ctx->Fill(); + break; + } + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: { + nscolor buttonColor = + ScrollbarUtil::GetScrollbarButtonColor(trackColor, eventStates); + ctx->SetColor(sRGBColor::FromABGR(buttonColor)); + ctx->Rectangle(bgRect); + ctx->Fill(); + + // We use the path of scrollbar up arrow on Windows 10 which is + // in a 17x17 area. + const gfxFloat kSize = 17.0; + // Setup the transform matrix. + gfxFloat width = rect.Width(); + gfxFloat height = rect.Height(); + gfxFloat size = std::min(width, height); + gfxFloat left = (width - size) / 2.0 + rect.x; + gfxFloat top = (height - size) / 2.0 + rect.y; + gfxFloat scale = size / kSize; + gfxFloat rad = 0.0; + if (aAppearance == StyleAppearance::ScrollbarbuttonRight) { + rad = M_PI / 2; + } else if (aAppearance == StyleAppearance::ScrollbarbuttonDown) { + rad = M_PI; + } else if (aAppearance == StyleAppearance::ScrollbarbuttonLeft) { + rad = -M_PI / 2; + } + gfx::Matrix mat = ctx->CurrentMatrix(); + mat.PreTranslate(left, top); + mat.PreScale(scale, scale); + if (rad != 0.0) { + const gfxFloat kOffset = kSize / 2.0; + mat.PreTranslate(kOffset, kOffset); + mat.PreRotate(rad); + mat.PreTranslate(-kOffset, -kOffset); + } + ctx->SetMatrix(mat); + // The arrow should not have antialias applied. + ctx->SetAntialiasMode(gfx::AntialiasMode::NONE); + // Set the arrow path. + ctx->NewPath(); + ctx->MoveTo(gfxPoint(5.0, 9.0)); + ctx->LineTo(gfxPoint(8.5, 6.0)); + ctx->LineTo(gfxPoint(12.0, 9.0)); + ctx->LineTo(gfxPoint(12.0, 12.0)); + ctx->LineTo(gfxPoint(8.5, 9.0)); + ctx->LineTo(gfxPoint(5.0, 12.0)); + ctx->ClosePath(); + // And paint the arrow. + nscolor arrowColor = ScrollbarUtil::GetScrollbarArrowColor(buttonColor); + ctx->SetColor(sRGBColor::FromABGR(arrowColor)); + ctx->Fill(); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown widget type"); + } + return true; +} + +/////////////////////////////////////////// +// Creation Routine +/////////////////////////////////////////// + +already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() { + static nsCOMPtr<nsITheme> inst; + + if (!inst) { + inst = new nsNativeThemeWin(); + ClearOnShutdown(&inst); + } + + return do_AddRef(inst); +} diff --git a/widget/windows/nsNativeThemeWin.h b/widget/windows/nsNativeThemeWin.h new file mode 100644 index 0000000000..0b41d6a2fa --- /dev/null +++ b/widget/windows/nsNativeThemeWin.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 nsNativeThemeWin_h +#define nsNativeThemeWin_h + +#include "nsITheme.h" +#include "nsCOMPtr.h" +#include "nsAtom.h" +#include "nsNativeTheme.h" +#include "nsStyleConsts.h" +#include "nsUXThemeConstants.h" +#include "nsUXThemeData.h" +#include "gfxTypes.h" +#include <windows.h> +#include "mozilla/Maybe.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(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& aDirtyRect) override; + + [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance) override; + + bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) override; + + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + nsRect* aOverflowRect) override; + + NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable) override; + + virtual Transparency GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) override; + + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance, + nsAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) override; + + NS_IMETHOD ThemeChanged() override; + + bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, + StyleAppearance aAppearance) override; + + bool WidgetIsContainer(StyleAppearance aAppearance) override; + + bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override; + + bool ThemeWantsButtonInnerFocusRing(StyleAppearance) override { return true; } + + bool ThemeNeedsComboboxDropmarker() override; + + virtual bool WidgetAppearanceDependsOnWindowFocus( + StyleAppearance aAppearance) override; + + enum { eThemeGeometryTypeWindowButtons = eThemeGeometryTypeUnknown + 1 }; + virtual ThemeGeometryType ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aAppearance) override; + + nsNativeThemeWin(); + + protected: + mozilla::Maybe<nsUXThemeClass> GetThemeClass(StyleAppearance aAppearance); + HANDLE GetTheme(StyleAppearance aAppearance); + nsresult GetThemePartAndState(nsIFrame* aFrame, StyleAppearance aAppearance, + int32_t& aPart, int32_t& aState); + nsresult ClassicGetThemePartAndState(nsIFrame* aFrame, + StyleAppearance aAppearance, + int32_t& aPart, int32_t& aState, + bool& aFocused); + nsresult ClassicDrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& aClipRect); + [[nodiscard]] LayoutDeviceIntMargin ClassicGetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance); + bool ClassicGetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult); + nsresult ClassicGetMinimumWidgetSize(nsIFrame* aFrame, + StyleAppearance aAppearance, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable); + bool ClassicThemeSupportsWidget(nsIFrame* aFrame, + StyleAppearance aAppearance); + void DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore, int32_t back, + HBRUSH defaultBack); + bool MayDrawCustomScrollbarPart(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, const nsRect& aClipRect); + uint32_t GetWidgetNativeDrawingFlags(StyleAppearance aAppearance); + int32_t StandardGetState(nsIFrame* aFrame, StyleAppearance aAppearance, + bool wantFocused); + bool IsMenuActive(nsIFrame* aFrame, StyleAppearance aAppearance); + RECT CalculateProgressOverlayRect(nsIFrame* aFrame, RECT* aWidgetRect, + bool aIsVertical, bool aIsIndeterminate, + bool aIsClassic); + void DrawThemedProgressMeter(nsIFrame* aFrame, StyleAppearance aAppearance, + HANDLE aTheme, HDC aHdc, int aPart, int aState, + RECT* aWidgetRect, RECT* aClipRect); + + [[nodiscard]] LayoutDeviceIntMargin GetCachedWidgetBorder( + HANDLE aTheme, nsUXThemeClass aThemeClass, StyleAppearance aAppearance, + int32_t aPart, int32_t aState); + + nsresult GetCachedMinimumWidgetSize(nsIFrame* aFrame, HANDLE aTheme, + nsUXThemeClass aThemeClass, + StyleAppearance aAppearance, + int32_t aPart, int32_t aState, + THEMESIZE aSizeReq, + mozilla::LayoutDeviceIntSize* aResult); + + SIZE GetCachedGutterSize(HANDLE theme); + + private: + TimeStamp mProgressDeterminateTimeStamp; + TimeStamp mProgressIndeterminateTimeStamp; + + // eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT is about 800 at the time of + // writing this, and nsIntMargin is 16 bytes wide, which makes this cache (1/8 + // + 16) * 800 bytes, or about ~12KB. We could probably reduce this cache to + // 3KB by caching on the aAppearance value instead, but there would be some + // uncacheable values, since we derive some theme parts from other arguments. + uint8_t + mBorderCacheValid[(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / + 8]; + LayoutDeviceIntMargin + mBorderCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT]; + + // See the above not for mBorderCache and friends. However + // mozilla::LayoutDeviceIntSize is half the size of nsIntMargin, making the + // cache roughly half as large. In total the caches should come to about 18KB. + uint8_t mMinimumWidgetSizeCacheValid + [(eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT + 7) / 8]; + mozilla::LayoutDeviceIntSize + mMinimumWidgetSizeCache[eUXNumClasses * THEME_PART_DISTINCT_VALUE_COUNT]; + + bool mGutterSizeCacheValid; + SIZE mGutterSizeCache; +}; + +#endif diff --git a/widget/windows/nsPrintDialogUtil.cpp b/widget/windows/nsPrintDialogUtil.cpp new file mode 100644 index 0000000000..f624018b48 --- /dev/null +++ b/widget/windows/nsPrintDialogUtil.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* ------------------------------------------------------------------- +To Build This: + + You need to add this to the the makefile.win in mozilla/dom/base: + + .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \ + + + And this to the makefile.win in mozilla/content/build: + +WIN_LIBS= \ + winspool.lib \ + comctl32.lib \ + comdlg32.lib + +---------------------------------------------------------------------- */ + +#include "plstr.h" +#include <windows.h> +#include <tchar.h> + +#include <unknwn.h> +#include <commdlg.h> + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/ScopeExit.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsWin.h" +#include "nsIPrinterList.h" + +#include "nsRect.h" + +#include "nsCRT.h" +#include "prenv.h" /* for PR_GetEnv */ + +#include <windows.h> +#include <winspool.h> + +// For Localization + +// For NS_CopyUnicodeToNative +#include "nsNativeCharsetUtils.h" + +// This is for extending the dialog +#include <dlgs.h> + +#include "nsWindowsHelpers.h" +#include "WinUtils.h" + +//----------------------------------------------- +// Global Data +//----------------------------------------------- + +static HWND gParentWnd = nullptr; + +//---------------------------------------------------------------------------------- +// Returns a Global Moveable Memory Handle to a DevMode +// from the Printer by the name of aPrintName +// +// NOTE: +// This function assumes that aPrintName has already been converted from +// unicode +// +static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit( + const nsString& aPrintName, nsIPrintSettings* aPS) { + nsHPRINTER hPrinter = nullptr; + // const cast kludge for silly Win32 api's + LPWSTR printName = + const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get())); + BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr); + if (!status) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Make sure hPrinter is closed on all paths + nsAutoPrinter autoPrinter(hPrinter); + + // Get the buffer size + LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr, + nullptr, 0); + if (needed < 0) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Some drivers do not return the correct size for their DEVMODE, so we + // over-allocate to try and compensate. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5) + needed *= 2; + nsAutoDevMode newDevMode( + (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed)); + if (!newDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed); + nsAutoGlobalMem globalDevMode(hDevMode); + if (!hDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode, + nullptr, DM_OUT_BUFFER); + if (ret != IDOK) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Lock memory and copy contents from DEVMODE (current printer) + // to Global Memory DEVMODE + LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode); + if (!devMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + memcpy(devMode, newDevMode.get(), needed); + // Initialize values from the PrintSettings + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(devMode); + + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + ::GlobalUnlock(hDevMode); + return nsReturnRef<nsHGLOBAL>(); + } + + ::GlobalUnlock(hDevMode); + + return globalDevMode.out(); +} + +//------------------------------------------------------------------ +// helper +static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) { + aPrinterName.Truncate(); + nsCOMPtr<nsIPrinterList> printerList = + do_GetService("@mozilla.org/gfx/printerlist;1"); + if (printerList) { + printerList->GetSystemDefaultPrinterName(aPrinterName); + } +} + +//------------------------------------------------------------------ +// Displays the native Print Dialog +static nsresult ShowNativePrintDialog(HWND aHWnd, + nsIPrintSettings* aPrintSettings) { + // NS_ENSURE_ARG_POINTER(aHWnd); + NS_ENSURE_ARG_POINTER(aPrintSettings); + + // Get the Print Name to be used + nsString printerName; + aPrintSettings->GetPrinterName(printerName); + + // If there is no name then use the default printer + if (printerName.IsEmpty()) { + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + HANDLE hPrinter = nullptr; + if (!::OpenPrinterW(const_cast<wchar_t*>( + static_cast<const wchar_t*>(printerName.get())), + &hPrinter, nullptr)) { + // If the last used printer is not found, we should use default printer. + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + ::ClosePrinter(hPrinter); + } + } + + // Now create a DEVNAMES struct so the the dialog is initialized correctly. + + uint32_t len = printerName.Length(); + nsHGLOBAL hDevNames = + ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES)); + nsAutoGlobalMem autoDevNames(hDevNames); + if (!hDevNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames); + if (!pDevNames) { + return NS_ERROR_FAILURE; + } + pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t); + pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t); + pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len; + pDevNames->wDefault = 0; + + memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t)); + ::GlobalUnlock(hDevNames); + + // Create a Moveable Memory Object that holds a new DevMode + // from the Printer Name + // The PRINTDLG.hDevMode requires that it be a moveable memory object + // NOTE: autoDevMode is automatically freed when any error occurred + nsAutoGlobalMem autoDevMode( + CreateGlobalDevModeAndInit(printerName, aPrintSettings)); + + // Prepare to Display the Print Dialog + // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85) + // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw + PRINTDLGEXW prntdlg; + memset(&prntdlg, 0, sizeof(prntdlg)); + + prntdlg.lStructSize = sizeof(prntdlg); + prntdlg.hwndOwner = aHWnd; + prntdlg.hDevMode = autoDevMode.get(); + prntdlg.hDevNames = hDevNames; + prntdlg.hDC = nullptr; + prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE | + PD_COLLATE | PD_NOCURRENTPAGE; + + // If there is a current selection then enable the "Selection" radio button + if (!aPrintSettings->GetIsPrintSelectionRBEnabled()) { + prntdlg.Flags |= PD_NOSELECTION; + } + + // 10 seems like a reasonable max number of ranges to support by default if + // the user doesn't choose a greater thing in the UI. + constexpr size_t kMinSupportedRanges = 10; + + AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges; + // Set up the page ranges. + { + AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges; + aPrintSettings->GetPageRanges(pageRanges); + // If there is a specified page range then enable the "Custom" radio button + if (!pageRanges.IsEmpty()) { + prntdlg.Flags |= PD_PAGENUMS; + } + + const size_t specifiedRanges = pageRanges.Length() / 2; + const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges); + + prntdlg.nMaxPageRanges = maxRanges; + prntdlg.nPageRanges = specifiedRanges; + + winPageRanges.SetCapacity(maxRanges); + for (size_t i = 0; i < pageRanges.Length(); i += 2) { + PRINTPAGERANGE* range = winPageRanges.AppendElement(); + range->nFromPage = pageRanges[i]; + range->nToPage = pageRanges[i + 1]; + } + prntdlg.lpPageRanges = winPageRanges.Elements(); + + prntdlg.nMinPage = 1; + // TODO(emilio): Could probably get the right page number here from the + // new print UI. + prntdlg.nMaxPage = 0xFFFF; + } + + // NOTE(emilio): This can always be 1 because we use the DEVMODE copies + // feature (see PD_USEDEVMODECOPIESANDCOLLATE). + prntdlg.nCopies = 1; + + prntdlg.hInstance = nullptr; + prntdlg.lpPrintTemplateName = nullptr; + + prntdlg.lpCallback = nullptr; + prntdlg.nPropertyPages = 0; + prntdlg.lphPropertyPages = nullptr; + + prntdlg.nStartPage = START_PAGE_GENERAL; + prntdlg.dwResultAction = 0; + + HRESULT result; + { + mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness; + mozilla::BackgroundHangMonitor().NotifyWait(); + result = ::PrintDlgExW(&prntdlg); + } + + auto cancelOnExit = mozilla::MakeScopeExit([&] { + ::SetFocus(aHWnd); + aPrintSettings->SetIsCancelled(true); + }); + + if (NS_WARN_IF(!SUCCEEDED(result))) { +#ifdef DEBUG + printf_stderr("PrintDlgExW failed with %x\n", result); +#endif + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) { + return NS_ERROR_ABORT; + } + // check to make sure we don't have any nullptr pointers + NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT); + NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT); + // Lock the deviceNames and check for nullptr + DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames); + NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT); + + char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]); + char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]); + + // Check to see if the "Print To File" control is checked + // then take the name from devNames and set it in the PrintSettings + // + // NOTE: + // As per Microsoft SDK documentation the returned value offset from + // devnames->wOutputOffset is either "FILE:" or nullptr + // if the "Print To File" checkbox is checked it MUST be "FILE:" + // We assert as an extra safety check. + if (prntdlg.Flags & PD_PRINTTOFILE) { + char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]); + NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`"); + aPrintSettings->SetToFileName(nsDependentString(fileName)); + aPrintSettings->SetPrintToFile(true); + } else { + // clear "print to file" info + aPrintSettings->SetPrintToFile(false); + aPrintSettings->SetToFileName(u""_ns); + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + MOZ_RELEASE_ASSERT(psWin); + + // Setup local Data members + psWin->SetDeviceName(nsDependentString(device)); + psWin->SetDriverName(nsDependentString(driver)); + + // Fill the print options with the info from the dialog + aPrintSettings->SetPrinterName(nsDependentString(device)); + aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION); + + AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges; + if (prntdlg.Flags & PD_PAGENUMS) { + pageRanges.SetCapacity(prntdlg.nPageRanges * 2); + for (const auto& range : Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) { + pageRanges.AppendElement(range.nFromPage); + pageRanges.AppendElement(range.nToPage); + } + } + aPrintSettings->SetPageRanges(pageRanges); + + // Unlock DeviceNames + ::GlobalUnlock(prntdlg.hDevNames); + + // Transfer the settings from the native data to the PrintSettings + LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode); + if (!devMode || !prntdlg.hDC) { + return NS_ERROR_FAILURE; + } + psWin->SetDevMode(devMode); // copies DevMode + psWin->CopyFromNative(prntdlg.hDC, devMode); + ::GlobalUnlock(prntdlg.hDevMode); + ::DeleteDC(prntdlg.hDC); + + cancelOnExit.release(); + return NS_OK; +} + +//---------------------------------------------------------------------------------- +//-- Show Print Dialog +//---------------------------------------------------------------------------------- +nsresult NativeShowPrintDialog(HWND aHWnd, nsIPrintSettings* aPrintSettings) { + nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings); + if (aHWnd) { + ::DestroyWindow(aHWnd); + } + + return rv; +} diff --git a/widget/windows/nsPrintDialogUtil.h b/widget/windows/nsPrintDialogUtil.h new file mode 100644 index 0000000000..3599118880 --- /dev/null +++ b/widget/windows/nsPrintDialogUtil.h @@ -0,0 +1,10 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsFlyOwnDialog_h___ +#define nsFlyOwnDialog_h___ + +nsresult NativeShowPrintDialog(HWND aHWnd, nsIPrintSettings* aPrintSettings); + +#endif /* nsFlyOwnDialog_h___ */ diff --git a/widget/windows/nsPrintDialogWin.cpp b/widget/windows/nsPrintDialogWin.cpp new file mode 100644 index 0000000000..40e4b0356f --- /dev/null +++ b/widget/windows/nsPrintDialogWin.cpp @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintDialogWin.h" + +#include "nsArray.h" +#include "nsCOMPtr.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserChild.h" +#include "nsIDialogParamBlock.h" +#include "nsIDocShell.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPrintSettings.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWidget.h" +#include "nsPrintDialogUtil.h" +#include "nsIPrintSettings.h" +#include "nsIWebBrowserChrome.h" +#include "nsPIDOMWindow.h" +#include "nsQueryObject.h" + +static const char* kPageSetupDialogURL = + "chrome://global/content/printPageSetup.xhtml"; + +using namespace mozilla; +using namespace mozilla::widget; + +/** + * ParamBlock + */ + +class ParamBlock { + public: + ParamBlock() { mBlock = 0; } + ~ParamBlock() { NS_IF_RELEASE(mBlock); } + nsresult Init() { + return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock); + } + nsIDialogParamBlock* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + return mBlock; + } + operator nsIDialogParamBlock* const() { return mBlock; } + + private: + nsIDialogParamBlock* mBlock; +}; + +NS_IMPL_ISUPPORTS(nsPrintDialogServiceWin, nsIPrintDialogService) + +nsPrintDialogServiceWin::nsPrintDialogServiceWin() {} + +nsPrintDialogServiceWin::~nsPrintDialogServiceWin() {} + +NS_IMETHODIMP +nsPrintDialogServiceWin::Init() { + nsresult rv; + mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + return rv; +} + +NS_IMETHODIMP +nsPrintDialogServiceWin::Show(nsPIDOMWindowOuter* aParent, + nsIPrintSettings* aSettings) { + NS_ENSURE_ARG(aParent); + HWND hWnd = GetHWNDForDOMWindow(aParent); + NS_ASSERTION(hWnd, "Couldn't get native window for PRint Dialog!"); + + return NativeShowPrintDialog(hWnd, aSettings); +} + +NS_IMETHODIMP +nsPrintDialogServiceWin::ShowPageSetup(nsPIDOMWindowOuter* aParent, + nsIPrintSettings* aNSSettings) { + NS_ENSURE_ARG(aParent); + NS_ENSURE_ARG(aNSSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) return rv; + + block->SetInt(0, 0); + rv = DoDialog(aParent, block, aNSSettings, kPageSetupDialogURL); + + // if aWebBrowserPrint is not null then we are printing + // so we want to pass back NS_ERROR_ABORT on cancel + if (NS_SUCCEEDED(rv)) { + int32_t status; + block->GetInt(0, &status); + return status == 0 ? NS_ERROR_ABORT : NS_OK; + } + + // We don't call nsPrintSettingsService::SavePrintSettingsToPrefs here since + // it's called for us in printPageSetup.js. Maybe we should move that call + // here for consistency with the other platforms though? + + return rv; +} + +nsresult nsPrintDialogServiceWin::DoDialog(mozIDOMWindowProxy* aParent, + nsIDialogParamBlock* aParamBlock, + nsIPrintSettings* aPS, + const char* aChromeURL) { + NS_ENSURE_ARG(aParamBlock); + NS_ENSURE_ARG(aPS); + NS_ENSURE_ARG(aChromeURL); + + if (!mWatcher) return NS_ERROR_FAILURE; + + // get a parent, if at all possible + // (though we'd rather this didn't fail, it's OK if it does. so there's + // no failure or null check.) + // retain ownership for method lifetime + nsCOMPtr<mozIDOMWindowProxy> activeParent; + if (!aParent) { + mWatcher->GetActiveWindow(getter_AddRefs(activeParent)); + aParent = activeParent; + } + + // create a nsIMutableArray of the parameters + // being passed to the window + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS)); + NS_ASSERTION(psSupports, "PrintSettings must be a supports"); + array->AppendElement(psSupports); + + nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock)); + NS_ASSERTION(blkSupps, "IOBlk must be a supports"); + array->AppendElement(blkSupps); + + nsCOMPtr<mozIDOMWindowProxy> dialog; + nsresult rv = mWatcher->OpenWindow( + aParent, nsDependentCString(aChromeURL), "_blank"_ns, + "centerscreen,chrome,modal,titlebar"_ns, array, getter_AddRefs(dialog)); + + return rv; +} + +HWND nsPrintDialogServiceWin::GetHWNDForDOMWindow(mozIDOMWindowProxy* aWindow) { + nsCOMPtr<nsIWebBrowserChrome> chrome; + + // We might be embedded so check this path first + if (mWatcher) { + nsCOMPtr<mozIDOMWindowProxy> fosterParent; + // it will be a dependent window. try to find a foster parent. + if (!aWindow) { + mWatcher->GetActiveWindow(getter_AddRefs(fosterParent)); + aWindow = fosterParent; + } + mWatcher->GetChromeForWindow(aWindow, getter_AddRefs(chrome)); + } + + if (chrome) { + nsCOMPtr<nsIEmbeddingSiteWindow> site(do_QueryInterface(chrome)); + if (site) { + HWND w; + site->GetSiteWindow(reinterpret_cast<void**>(&w)); + return w; + } + } + + // Now we might be the Browser so check this path + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = + window->GetWebBrowserChrome(); + if (!webBrowserChrome) return nullptr; + + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(webBrowserChrome)); + if (!baseWin) return nullptr; + + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (!widget) return nullptr; + + return (HWND)widget->GetNativeData(NS_NATIVE_TMP_WINDOW); +} diff --git a/widget/windows/nsPrintDialogWin.h b/widget/windows/nsPrintDialogWin.h new file mode 100644 index 0000000000..d4b77817ea --- /dev/null +++ b/widget/windows/nsPrintDialogWin.h @@ -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/. */ + +#ifndef nsPrintDialog_h__ +#define nsPrintDialog_h__ + +#include "nsIPrintDialogService.h" + +#include "nsCOMPtr.h" +#include "nsIWindowWatcher.h" + +class nsIPrintSettings; +class nsIDialogParamBlock; + +class nsPrintDialogServiceWin : public nsIPrintDialogService { + virtual ~nsPrintDialogServiceWin(); + + public: + nsPrintDialogServiceWin(); + + NS_DECL_ISUPPORTS + + NS_IMETHOD Init() override; + NS_IMETHOD Show(nsPIDOMWindowOuter* aParent, + nsIPrintSettings* aSettings) override; + NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter* aParent, + nsIPrintSettings* aSettings) override; + + private: + nsresult DoDialog(mozIDOMWindowProxy* aParent, + nsIDialogParamBlock* aParamBlock, nsIPrintSettings* aPS, + const char* aChromeURL); + + HWND GetHWNDForDOMWindow(mozIDOMWindowProxy* aWindow); + + nsCOMPtr<nsIWindowWatcher> mWatcher; +}; + +#endif diff --git a/widget/windows/nsPrintSettingsServiceWin.cpp b/widget/windows/nsPrintSettingsServiceWin.cpp new file mode 100644 index 0000000000..ab0f95abba --- /dev/null +++ b/widget/windows/nsPrintSettingsServiceWin.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintSettingsServiceWin.h" + +#include "nsCOMPtr.h" +#include "nsPrintSettingsWin.h" +#include "nsPrintDialogUtil.h" + +#include "nsGfxCIID.h" +#include "nsIServiceManager.h" +#include "nsWindowsHelpers.h" +#include "ipc/IPCMessageUtils.h" + +const char kPrinterListContractID[] = "@mozilla.org/gfx/printerlist;1"; + +using namespace mozilla::embedding; + +NS_IMETHODIMP +nsPrintSettingsServiceWin::SerializeToPrintData(nsIPrintSettings* aSettings, + PrintData* data) { + nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aSettings); + if (!psWin) { + return NS_ERROR_FAILURE; + } + + nsAutoString deviceName; + nsAutoString driverName; + + psWin->GetDeviceName(deviceName); + psWin->GetDriverName(driverName); + + data->deviceName().Assign(deviceName); + data->driverName().Assign(driverName); + + // When creating the print dialog on Windows, we only need to send certain + // print settings information from the parent to the child not vice versa. + if (XRE_IsParentProcess()) { + // A DEVMODE can actually be of arbitrary size. If it turns out that it'll + // make our IPC message larger than the limit, then we'll error out. + LPDEVMODEW devModeRaw; + psWin->GetDevMode(&devModeRaw); // This actually allocates a copy of the + // the nsIPrintSettingsWin DEVMODE, so + // we're now responsible for deallocating + // it. We'll use an nsAutoDevMode helper + // to do this. + if (devModeRaw) { + nsAutoDevMode devMode(devModeRaw); + devModeRaw = nullptr; + + size_t devModeTotalSize = devMode->dmSize + devMode->dmDriverExtra; + size_t msgTotalSize = sizeof(PrintData) + devModeTotalSize; + + if (msgTotalSize > IPC::Channel::kMaximumMessageSize / 2) { + return NS_ERROR_FAILURE; + } + + // Instead of reaching in and manually reading each member, we'll just + // copy the bits over. + const char* devModeData = reinterpret_cast<const char*>(devMode.get()); + nsTArray<uint8_t> arrayBuf; + arrayBuf.AppendElements(devModeData, devModeTotalSize); + data->devModeData() = std::move(arrayBuf); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintSettingsServiceWin::DeserializeToPrintSettings( + const PrintData& data, nsIPrintSettings* settings) { + nsresult rv = + nsPrintSettingsService::DeserializeToPrintSettings(data, settings); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(settings); + if (!settings) { + return NS_ERROR_FAILURE; + } + + if (XRE_IsContentProcess()) { + psWin->SetDeviceName(data.deviceName()); + psWin->SetDriverName(data.driverName()); + + if (data.devModeData().IsEmpty()) { + psWin->SetDevMode(nullptr); + } else { + // Check minimum length of DEVMODE data. + auto devModeDataLength = data.devModeData().Length(); + if (devModeDataLength < sizeof(DEVMODEW)) { + NS_WARNING("DEVMODE data is too short."); + return NS_ERROR_FAILURE; + } + + DEVMODEW* devMode = reinterpret_cast<DEVMODEW*>( + const_cast<uint8_t*>(data.devModeData().Elements())); + + // Check actual length of DEVMODE data. + if ((devMode->dmSize + devMode->dmDriverExtra) != devModeDataLength) { + NS_WARNING("DEVMODE length is incorrect."); + return NS_ERROR_FAILURE; + } + + psWin->SetDevMode(devMode); // Copies + } + } + + return NS_OK; +} + +nsresult nsPrintSettingsServiceWin::_CreatePrintSettings( + nsIPrintSettings** _retval) { + *_retval = nullptr; + nsPrintSettingsWin* printSettings = + new nsPrintSettingsWin(); // does not initially ref count + NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY); + + NS_ADDREF(*_retval = printSettings); // ref count + + return NS_OK; +} diff --git a/widget/windows/nsPrintSettingsServiceWin.h b/widget/windows/nsPrintSettingsServiceWin.h new file mode 100644 index 0000000000..88a5b22b51 --- /dev/null +++ b/widget/windows/nsPrintSettingsServiceWin.h @@ -0,0 +1,30 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPrintSettingsServiceWin_h +#define nsPrintSettingsServiceWin_h + +#include "mozilla/embedding/PPrinting.h" +#include "nsPrintSettingsService.h" + +class nsIPrintSettings; + +class nsPrintSettingsServiceWin final : public nsPrintSettingsService { + public: + nsPrintSettingsServiceWin() {} + + NS_IMETHODIMP SerializeToPrintData( + nsIPrintSettings* aSettings, + mozilla::embedding::PrintData* data) override; + + NS_IMETHODIMP DeserializeToPrintSettings( + const mozilla::embedding::PrintData& data, + nsIPrintSettings* settings) override; + + nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override; +}; + +#endif // nsPrintSettingsServiceWin_h diff --git a/widget/windows/nsPrintSettingsWin.cpp b/widget/windows/nsPrintSettingsWin.cpp new file mode 100644 index 0000000000..3978d87256 --- /dev/null +++ b/widget/windows/nsPrintSettingsWin.cpp @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsPrintSettingsWin.h" + +#include "mozilla/ArrayUtils.h" +#include "nsCRT.h" +#include "nsDeviceContextSpecWin.h" +#include "nsPrintSettingsImpl.h" +#include "WinUtils.h" + +using namespace mozilla; + +// Using paper sizes from wingdi.h and the units given there, plus a little +// extra research for the ones it doesn't give. Looks like the list hasn't +// changed since Windows 2000, so should be fairly stable now. +const short kPaperSizeUnits[] = { + nsIPrintSettings::kPaperSizeMillimeters, // Not Used default to mm as + // DEVMODE uses tenths of mm, just + // in case + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTERSMALL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEDGER + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_STATEMENT + nsIPrintSettings::kPaperSizeInches, // DMPAPER_EXECUTIVE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4SMALL + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FOLIO + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_QUARTO + nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X14 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_11X17 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_NOTE + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_9 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_10 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_12 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_14 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_CSHEET + nsIPrintSettings::kPaperSizeInches, // DMPAPER_DSHEET + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ESHEET + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_DL + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_C65 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_B6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_ITALY + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_MONARCH + nsIPrintSettings::kPaperSizeInches, // DMPAPER_ENV_PERSONAL + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_US + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_STD_GERMAN + nsIPrintSettings::kPaperSizeInches, // DMPAPER_FANFOLD_LGL_GERMAN + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ISO_B4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD + nsIPrintSettings::kPaperSizeInches, // DMPAPER_9X11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_10X11 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_15X11 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_ENV_INVITE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_48 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_RESERVED_49 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LEGAL_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_TABLOID_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_A4_EXTRA + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_TRANSVERSE + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_EXTRA_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B_PLUS + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_PLUS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_EXTRA + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_EXTRA_TRANSVERSE + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4 + nsIPrintSettings::kPaperSizeInches, // DMPAPER_LETTER_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A5_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B4_JIS_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B5_JIS_ROTATED + nsIPrintSettings:: + kPaperSizeMillimeters, // DMPAPER_JAPANESE_POSTCARD_ROTATED + nsIPrintSettings:: + kPaperSizeMillimeters, // DMPAPER_DBL_JAPANESE_POSTCARD_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_A6_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU2_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_KAKU3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_CHOU4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_B6_JIS_ROTATED + nsIPrintSettings::kPaperSizeInches, // DMPAPER_12X11 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_JENV_YOU4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10 + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P16K_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32K_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_P32KBIG_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_1_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_2_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_3_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_4_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_5_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_6_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_7_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_8_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_9_ROTATED + nsIPrintSettings::kPaperSizeMillimeters, // DMPAPER_PENV_10_ROTATED +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsWin, nsPrintSettings, + nsIPrintSettingsWin) + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::nsPrintSettingsWin() + : nsPrintSettings(), + mDeviceName(nullptr), + mDriverName(nullptr), + mDevMode(nullptr) {} + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::nsPrintSettingsWin(const nsPrintSettingsWin& aPS) + : mDevMode(nullptr) { + *this = aPS; +} + +void nsPrintSettingsWin::InitWithInitializer( + const PrintSettingsInitializer& aSettings) { + nsPrintSettings::InitWithInitializer(aSettings); + + if (aSettings.mDevmodeWStorage.Length() < sizeof(DEVMODEW)) { + return; + } + + // SetDevMode copies the DEVMODE. + SetDevMode(const_cast<DEVMODEW*>(reinterpret_cast<const DEVMODEW*>( + aSettings.mDevmodeWStorage.Elements()))); + + if (mDevMode->dmFields & DM_ORIENTATION) { + const bool areSheetsOfPaperPortraitMode = + (mDevMode->dmOrientation == DMORIENT_PORTRAIT); + + // If our Windows print settings say that we're producing portrait-mode + // sheets of paper, then our page format must also be portrait-mode; unless + // we've got a pages-per-sheet value with orthogonal pages/sheets, in which + // case it's reversed. + const bool arePagesPortraitMode = + (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages()); + + SetOrientation(arePagesPortraitMode ? kPortraitOrientation + : kLandscapeOrientation); + } + + if (mDevMode->dmFields & DM_COPIES) { + SetNumCopies(mDevMode->dmCopies); + } + + if (mDevMode->dmFields & DM_SCALE) { + // Since we do the scaling, grab the DEVMODE value and reset it back to 100. + double scale = double(mDevMode->dmScale) / 100.0f; + if (mScaling == 1.0 || scale != 1.0) { + SetScaling(scale); + } + mDevMode->dmScale = 100; + } + + if (mDevMode->dmFields & DM_PAPERSIZE) { + nsString paperIdString; + paperIdString.AppendInt(mDevMode->dmPaperSize); + SetPaperId(paperIdString); + if (mDevMode->dmPaperSize > 0 && + mDevMode->dmPaperSize < int32_t(ArrayLength(kPaperSizeUnits))) { + SetPaperSizeUnit(kPaperSizeUnits[mDevMode->dmPaperSize]); + } + } + + if (mDevMode->dmFields & DM_COLOR) { + SetPrintInColor(mDevMode->dmColor == DMCOLOR_COLOR); + } + + if (mDevMode->dmFields & DM_DUPLEX) { + switch (mDevMode->dmDuplex) { + default: + MOZ_ASSERT_UNREACHABLE("bad value for dmDuplex field"); + [[fallthrough]]; + case DMDUP_SIMPLEX: + SetDuplex(kSimplex); + break; + case DMDUP_VERTICAL: + SetDuplex(kDuplexHorizontal); + break; + case DMDUP_HORIZONTAL: + SetDuplex(kDuplexVertical); + break; + } + } + + // Set the paper sizes to match the unit. + double pointsToSizeUnit = + mPaperSizeUnit == kPaperSizeInches ? 1.0 / 72.0 : 25.4 / 72.0; + SetPaperWidth(aSettings.mPaperInfo.mSize.width * pointsToSizeUnit); + SetPaperHeight(aSettings.mPaperInfo.mSize.height * pointsToSizeUnit); +} + +already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings( + const PrintSettingsInitializer& aSettings) { + auto settings = MakeRefPtr<nsPrintSettingsWin>(); + settings->InitWithInitializer(aSettings); + return settings.forget(); +} + +/** --------------------------------------------------- + * See documentation in nsPrintSettingsWin.h + * @update + */ +nsPrintSettingsWin::~nsPrintSettingsWin() { + if (mDevMode) ::HeapFree(::GetProcessHeap(), 0, mDevMode); +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDeviceName(const nsAString& aDeviceName) { + mDeviceName = aDeviceName; + return NS_OK; +} +NS_IMETHODIMP nsPrintSettingsWin::GetDeviceName(nsAString& aDeviceName) { + aDeviceName = mDeviceName; + return NS_OK; +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDriverName(const nsAString& aDriverName) { + mDriverName = aDriverName; + return NS_OK; +} +NS_IMETHODIMP nsPrintSettingsWin::GetDriverName(nsAString& aDriverName) { + aDriverName = mDriverName; + return NS_OK; +} + +void nsPrintSettingsWin::CopyDevMode(DEVMODEW* aInDevMode, + DEVMODEW*& aOutDevMode) { + aOutDevMode = nullptr; + size_t size = aInDevMode->dmSize + aInDevMode->dmDriverExtra; + aOutDevMode = + (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, size); + if (aOutDevMode) { + memcpy(aOutDevMode, aInDevMode, size); + } +} + +NS_IMETHODIMP nsPrintSettingsWin::GetDevMode(DEVMODEW** aDevMode) { + NS_ENSURE_ARG_POINTER(aDevMode); + + if (mDevMode) { + CopyDevMode(mDevMode, *aDevMode); + } else { + *aDevMode = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintSettingsWin::SetDevMode(DEVMODEW* aDevMode) { + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + mDevMode = nullptr; + } + + if (aDevMode) { + CopyDevMode(aDevMode, mDevMode); + } + return NS_OK; +} + +void nsPrintSettingsWin::InitUnwriteableMargin(HDC aHdc) { + mozilla::gfx::MarginDouble margin = + mozilla::widget::WinUtils::GetUnwriteableMarginsForDeviceInInches(aHdc); + + mUnwriteableMargin.SizeTo(NS_INCHES_TO_INT_TWIPS(margin.top), + NS_INCHES_TO_INT_TWIPS(margin.right), + NS_INCHES_TO_INT_TWIPS(margin.bottom), + NS_INCHES_TO_INT_TWIPS(margin.left)); +} + +void nsPrintSettingsWin::CopyFromNative(HDC aHdc, DEVMODEW* aDevMode) { + MOZ_ASSERT(aHdc); + MOZ_ASSERT(aDevMode); + + mIsInitedFromPrinter = true; + if (aDevMode->dmFields & DM_ORIENTATION) { + const bool areSheetsOfPaperPortraitMode = + (aDevMode->dmOrientation == DMORIENT_PORTRAIT); + + // If our Windows print settings say that we're producing portrait-mode + // sheets of paper, then our page format must also be portrait-mode; unless + // we've got a pages-per-sheet value with orthogonal pages/sheets, in which + // case it's reversed. + const bool arePagesPortraitMode = + (areSheetsOfPaperPortraitMode != HasOrthogonalSheetsAndPages()); + + // Record the orientation of the pages (determined above) in mOrientation: + mOrientation = int32_t(arePagesPortraitMode ? kPortraitOrientation + : kLandscapeOrientation); + } + + if (aDevMode->dmFields & DM_COPIES) { + mNumCopies = aDevMode->dmCopies; + } + + if (aDevMode->dmFields & DM_DUPLEX) { + switch (aDevMode->dmDuplex) { + default: + MOZ_ASSERT_UNREACHABLE("bad value for dmDuplex field"); + [[fallthrough]]; + case DMDUP_SIMPLEX: + mDuplex = kSimplex; + break; + case DMDUP_VERTICAL: + mDuplex = kDuplexHorizontal; + break; + case DMDUP_HORIZONTAL: + mDuplex = kDuplexVertical; + break; + } + } + + // Since we do the scaling, grab their value and reset back to 100. + if (aDevMode->dmFields & DM_SCALE) { + double scale = double(aDevMode->dmScale) / 100.0f; + if (mScaling == 1.0 || scale != 1.0) { + mScaling = scale; + } + aDevMode->dmScale = 100; + } + + if (aDevMode->dmFields & DM_PAPERSIZE) { + mPaperId.Truncate(0); + mPaperId.AppendInt(aDevMode->dmPaperSize); + // If not a paper size we know about, the unit will be the last one saved. + if (aDevMode->dmPaperSize > 0 && + aDevMode->dmPaperSize < int32_t(ArrayLength(kPaperSizeUnits))) { + mPaperSizeUnit = kPaperSizeUnits[aDevMode->dmPaperSize]; + } + } + + if (aDevMode->dmFields & DM_COLOR) { + mPrintInColor = aDevMode->dmColor == DMCOLOR_COLOR; + } + + InitUnwriteableMargin(aHdc); + + int pixelsPerInchY = ::GetDeviceCaps(aHdc, LOGPIXELSY); + int physicalHeight = ::GetDeviceCaps(aHdc, PHYSICALHEIGHT); + double physicalHeightInch = double(physicalHeight) / pixelsPerInchY; + int pixelsPerInchX = ::GetDeviceCaps(aHdc, LOGPIXELSX); + int physicalWidth = ::GetDeviceCaps(aHdc, PHYSICALWIDTH); + double physicalWidthInch = double(physicalWidth) / pixelsPerInchX; + + // 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 { + // We need the paper height to be set, because it is used in the child for + // layout. If it is not set in the DEVMODE, get it from the device context. + // We want the normalized (in portrait orientation) paper height, so account + // for orientation. + double paperHeightInch = mOrientation == kPortraitOrientation + ? physicalHeightInch + : physicalWidthInch; + mPaperHeight = mPaperSizeUnit == kPaperSizeInches + ? paperHeightInch + : paperHeightInch * MM_PER_INCH_FLOAT; + } + + if (aDevMode->dmFields & DM_PAPERWIDTH) { + mPaperWidth = aDevMode->dmPaperWidth / sizeUnitToTenthsOfAmm; + } else { + // We need the paper width to be set, because it is used in the child for + // layout. If it is not set in the DEVMODE, get it from the device context. + // We want the normalized (in portrait orientation) paper width, so account + // for orientation. + double paperWidthInch = mOrientation == kPortraitOrientation + ? physicalWidthInch + : physicalHeightInch; + mPaperWidth = mPaperSizeUnit == kPaperSizeInches + ? paperWidthInch + : paperWidthInch * MM_PER_INCH_FLOAT; + } + + // Using LOGPIXELSY to match existing code for print scaling calculations. + mResolution = pixelsPerInchY; +} + +void nsPrintSettingsWin::CopyToNative(DEVMODEW* aDevMode) { + MOZ_ASSERT(aDevMode); + + if (!mPaperId.IsEmpty()) { + aDevMode->dmPaperSize = _wtoi((const wchar_t*)mPaperId.BeginReading()); + aDevMode->dmFields |= DM_PAPERSIZE; + } else { + aDevMode->dmPaperSize = 0; + aDevMode->dmFields &= ~DM_PAPERSIZE; + } + + aDevMode->dmFields |= DM_COLOR; + aDevMode->dmColor = mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME; + + // The length and width in DEVMODE are always in tenths of a millimeter. + double 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 = GetSheetOrientation() == kPortraitOrientation + ? DMORIENT_PORTRAIT + : DMORIENT_LANDSCAPE; + aDevMode->dmFields |= DM_ORIENTATION; + + // Setup Number of Copies + aDevMode->dmCopies = mNumCopies; + aDevMode->dmFields |= DM_COPIES; + + // Setup Simplex/Duplex mode + switch (mDuplex) { + case kSimplex: + aDevMode->dmDuplex = DMDUP_SIMPLEX; + aDevMode->dmFields |= DM_DUPLEX; + break; + case kDuplexHorizontal: + aDevMode->dmDuplex = DMDUP_VERTICAL; + aDevMode->dmFields |= DM_DUPLEX; + break; + case kDuplexVertical: + aDevMode->dmDuplex = DMDUP_HORIZONTAL; + aDevMode->dmFields |= DM_DUPLEX; + break; + default: + MOZ_ASSERT_UNREACHABLE("bad value for duplex option"); + break; + } +} + +//------------------------------------------- +nsresult nsPrintSettingsWin::_Clone(nsIPrintSettings** _retval) { + RefPtr<nsPrintSettingsWin> printSettings = new nsPrintSettingsWin(*this); + printSettings.forget(_retval); + return NS_OK; +} + +//------------------------------------------- +nsPrintSettingsWin& nsPrintSettingsWin::operator=( + const nsPrintSettingsWin& rhs) { + if (this == &rhs) { + return *this; + } + + ((nsPrintSettings&)*this) = rhs; + + // Use free because we used the native malloc to create the memory + if (mDevMode) { + ::HeapFree(::GetProcessHeap(), 0, mDevMode); + } + + mDeviceName = rhs.mDeviceName; + mDriverName = rhs.mDriverName; + + if (rhs.mDevMode) { + CopyDevMode(rhs.mDevMode, mDevMode); + } else { + mDevMode = nullptr; + } + + return *this; +} + +//------------------------------------------- +nsresult nsPrintSettingsWin::_Assign(nsIPrintSettings* aPS) { + nsPrintSettingsWin* psWin = static_cast<nsPrintSettingsWin*>(aPS); + *this = *psWin; + return NS_OK; +} diff --git a/widget/windows/nsPrintSettingsWin.h b/widget/windows/nsPrintSettingsWin.h new file mode 100644 index 0000000000..a193eac1e0 --- /dev/null +++ b/widget/windows/nsPrintSettingsWin.h @@ -0,0 +1,53 @@ +/* -*- 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); + + void InitWithInitializer(const PrintSettingsInitializer& aSettings) final; + + /** + * Makes a new copy + */ + virtual nsresult _Clone(nsIPrintSettings** _retval); + + /** + * Assigns values + */ + virtual nsresult _Assign(nsIPrintSettings* aPS); + + /** + * Assignment + */ + nsPrintSettingsWin& operator=(const nsPrintSettingsWin& rhs); + + protected: + void CopyDevMode(DEVMODEW* aInDevMode, DEVMODEW*& aOutDevMode); + void InitUnwriteableMargin(HDC aHdc); + + nsString mDeviceName; + nsString mDriverName; + LPDEVMODEW mDevMode; +}; + +#endif /* nsPrintSettingsWin_h__ */ diff --git a/widget/windows/nsPrinterWin.cpp b/widget/windows/nsPrinterWin.cpp new file mode 100644 index 0000000000..2af06de4e4 --- /dev/null +++ b/widget/windows/nsPrinterWin.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrinterWin.h" + +#include <algorithm> +#include <windows.h> +#include <winspool.h> + +#include "mozilla/Array.h" +#include "nsPaper.h" +#include "nsPrintSettingsImpl.h" +#include "nsWindowsHelpers.h" +#include "WinUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +static const double kPointsPerTenthMM = 72.0 / 254.0; +static const double kPointsPerInch = 72.0; + +nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray* aArray, + const nsAString& aName) + : nsPrinterBase(aArray), + mName(aName), + mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {} + +// static +already_AddRefed<nsPrinterWin> nsPrinterWin::Create( + const CommonPaperInfoArray* aArray, const nsAString& aName) { + return do_AddRef(new nsPrinterWin(aArray, aName)); +} + +template <class T> +static nsTArray<T> GetDeviceCapabilityArray(const LPWSTR aPrinterName, + WORD aCapabilityID, + const DEVMODEW* aDevmodeW, + int& aCount) { + MOZ_ASSERT(aCount >= 0, "Possibly passed aCount from previous error case."); + + nsTArray<T> caps; + + // We only want to access printer drivers in the parent process. + if (!XRE_IsParentProcess()) { + return caps; + } + + // Both the call to get the size and the call to actually populate the array + // are relatively expensive, so as sometimes the lengths of the arrays that we + // retrieve depend on each other we allow a count to be passed in to save the + // first call. As we allocate double the count anyway this should allay any + // safety worries. + if (!aCount) { + // Passing nullptr as the port here seems to work. Given that we would have + // to OpenPrinter with just the name anyway to get the port that makes + // sense. Also, the printer set-up seems to stop you from having two + // printers with the same name. Note: this (and the call below) are blocking + // calls, which could be slow. + aCount = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID, + nullptr, aDevmodeW); + if (aCount <= 0) { + return caps; + } + } + + // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the + // buffer being overflowed, so we over-allocate for safety. + caps.SetLength(aCount * 2); + int count = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID, + reinterpret_cast<LPWSTR>(caps.Elements()), + aDevmodeW); + if (count <= 0) { + caps.Clear(); + return caps; + } + + // We know from bug 1673708 that sometimes the final array returned is smaller + // than the required array count. Assert here to see if this is reproduced on + // test servers. + MOZ_ASSERT(count == aCount, "Different array count returned than expected."); + + // Note that TruncateLength will crash if count > caps.Length(). + caps.TruncateLength(count); + return caps; +} + +NS_IMETHODIMP +nsPrinterWin::GetName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +nsPrinterWin::GetSystemName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +bool nsPrinterWin::SupportsDuplex() const { + // Some printer drivers have threading issues around using their default + // DEVMODE, so we use a copy of our cached one. + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return false; + } + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_DUPLEX, nullptr, + devmode) == 1; +} + +bool nsPrinterWin::SupportsColor() const { + // Some printer drivers have threading issues around using their default + // DEVMODE, so we use a copy of our cached one. + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return false; + } + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLORDEVICE, nullptr, + devmode) == 1; +} + +bool nsPrinterWin::SupportsMonochrome() const { + if (!SupportsColor()) { + return true; + } + + nsHPRINTER hPrinter = nullptr; + if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) { + return false; + } + nsAutoPrinter autoPrinter(hPrinter); + + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return false; + } + + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + devmode->dmFields |= DM_COLOR; + devmode->dmColor = DMCOLOR_MONOCHROME; + // Try to modify the devmode settings and see if the setting sticks. + // + // This has been the only reliable way to detect it that we've found. + LONG ret = + ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), devmode, + devmode, DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + return false; + } + return !(devmode->dmFields & DM_COLOR) || + devmode->dmColor == DMCOLOR_MONOCHROME; +} + +bool nsPrinterWin::SupportsCollation() const { + // Some printer drivers have threading issues around using their default + // DEVMODE, so we use a copy of our cached one. + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return false; + } + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLLATE, nullptr, + devmode) == 1; +} + +nsPrinterBase::PrinterInfo nsPrinterWin::CreatePrinterInfo() const { + return PrinterInfo{PaperList(), DefaultSettings()}; +} + +mozilla::gfx::MarginDouble nsPrinterWin::GetMarginsForPaper( + nsString aPaperId) const { + gfx::MarginDouble margin; + + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return margin; + } + + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + devmode->dmFields = DM_PAPERSIZE; + devmode->dmPaperSize = _wtoi((const wchar_t*)aPaperId.BeginReading()); + nsAutoHDC printerDc(::CreateICW(nullptr, mName.get(), nullptr, devmode)); + MOZ_ASSERT(printerDc, "CreateICW failed"); + if (!printerDc) { + return margin; + } + margin = WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc); + margin.top *= kPointsPerInch; + margin.right *= kPointsPerInch; + margin.bottom *= kPointsPerInch; + margin.left *= kPointsPerInch; + + return margin; +} + +nsTArray<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const { + nsTArray<uint8_t> devmodeStorageW; + + auto devmodeStorageWLock = mDefaultDevmodeWStorage.Lock(); + if (devmodeStorageWLock->IsEmpty()) { + nsHPRINTER hPrinter = nullptr; + // OpenPrinter could fail if, for example, the printer has been removed + // or otherwise become inaccessible since it was selected. + if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) { + return devmodeStorageW; + } + nsAutoPrinter autoPrinter(hPrinter); + // Allocate devmode storage of the correct size. + LONG bytesNeeded = ::DocumentPropertiesW(nullptr, autoPrinter.get(), + mName.get(), nullptr, nullptr, 0); + // Note that we must cast the sizeof() to a signed type so that comparison + // with the signed, potentially-negative bytesNeeded will work! + MOZ_ASSERT(bytesNeeded >= LONG(sizeof(DEVMODEW)), + "DocumentPropertiesW failed to get valid size"); + if (bytesNeeded < LONG(sizeof(DEVMODEW))) { + return devmodeStorageW; + } + + // Allocate extra space in case of bad drivers that return a too-small + // result from DocumentProperties. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5) + devmodeStorageWLock->SetLength(bytesNeeded * 2); + auto* devmode = + reinterpret_cast<DEVMODEW*>(devmodeStorageWLock->Elements()); + LONG ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), + devmode, nullptr, DM_OUT_BUFFER); + MOZ_ASSERT(ret == IDOK, "DocumentPropertiesW failed"); + if (ret != IDOK) { + return devmodeStorageW; + } + } + + devmodeStorageW.Assign(devmodeStorageWLock.ref()); + return devmodeStorageW; +} + +nsTArray<mozilla::PaperInfo> nsPrinterWin::PaperList() const { + // Some printer drivers have threading issues around using their default + // DEVMODE, so we use a copy of our cached one. + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return {}; + } + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + // Paper IDs are returned as WORDs. + int requiredArrayCount = 0; + auto paperIds = GetDeviceCapabilityArray<WORD>(mName.get(), DC_PAPERS, + devmode, requiredArrayCount); + if (!paperIds.Length()) { + return {}; + } + + // Paper names are returned in 64 long character buffers. + auto paperNames = GetDeviceCapabilityArray<Array<wchar_t, 64>>( + mName.get(), DC_PAPERNAMES, devmode, requiredArrayCount); + // Check that we have the same number of names as IDs. + if (paperNames.Length() != paperIds.Length()) { + return {}; + } + + // Paper sizes are returned as POINT structs with a tenth of a millimeter as + // the unit. + auto paperSizes = GetDeviceCapabilityArray<POINT>( + mName.get(), DC_PAPERSIZE, devmode, requiredArrayCount); + // Check that we have the same number of sizes as IDs. + if (paperSizes.Length() != paperIds.Length()) { + return {}; + } + + nsTArray<mozilla::PaperInfo> paperList; + paperList.SetCapacity(paperNames.Length()); + for (size_t i = 0; i < paperNames.Length(); ++i) { + // Paper names are null terminated unless they are 64 characters long. + auto firstNull = + std::find(paperNames[i].cbegin(), paperNames[i].cend(), L'\0'); + auto nameLength = firstNull - paperNames[i].cbegin(); + double width = paperSizes[i].x * kPointsPerTenthMM; + double height = paperSizes[i].y * kPointsPerTenthMM; + + // Skip if no name or invalid size. + if (!nameLength || width <= 0 || height <= 0) { + continue; + } + + // Windows paper IDs are 16-bit integers; we stringify them to store in the + // PaperInfo.mId field. + nsString paperIdString; + paperIdString.AppendInt(paperIds[i]); + + // We don't resolve the margins eagerly because they're really expensive (on + // the order of seconds for some drivers). + nsDependentSubstring name(paperNames[i].cbegin(), nameLength); + paperList.AppendElement(mozilla::PaperInfo(paperIdString, nsString(name), + {width, height}, Nothing())); + } + + return paperList; +} + +PrintSettingsInitializer nsPrinterWin::DefaultSettings() const { + // Initialize to something reasonable, in case we fail to get usable data + // from the devmode below. + nsString paperIdString(u"1"); // DMPAPER_LETTER + bool color = true; + + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return {}; + } + + const auto* devmode = + reinterpret_cast<const DEVMODEW*>(devmodeWStorage.Elements()); + + // XXX If DM_PAPERSIZE is not set, or is not a known value, should we + // be returning a "Custom" name of some kind? + if (devmode->dmFields & DM_PAPERSIZE) { + paperIdString.Truncate(0); + paperIdString.AppendInt(devmode->dmPaperSize); + } + + if (devmode->dmFields & DM_COLOR) { + // See comment for PrintSettingsInitializer.mPrintInColor + color = devmode->dmColor != DMCOLOR_MONOCHROME; + } + + nsAutoHDC printerDc(::CreateICW(nullptr, mName.get(), nullptr, devmode)); + MOZ_ASSERT(printerDc, "CreateICW failed"); + if (!printerDc) { + return {}; + } + + int pixelsPerInchY = ::GetDeviceCaps(printerDc, LOGPIXELSY); + int physicalHeight = ::GetDeviceCaps(printerDc, PHYSICALHEIGHT); + double heightInInches = double(physicalHeight) / pixelsPerInchY; + int pixelsPerInchX = ::GetDeviceCaps(printerDc, LOGPIXELSX); + int physicalWidth = ::GetDeviceCaps(printerDc, PHYSICALWIDTH); + double widthInches = double(physicalWidth) / pixelsPerInchX; + if (devmode->dmFields & DM_ORIENTATION && + devmode->dmOrientation == DMORIENT_LANDSCAPE) { + std::swap(widthInches, heightInInches); + } + SizeDouble paperSize(widthInches * kPointsPerInch, + heightInInches * kPointsPerInch); + + gfx::MarginDouble margin = + WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc); + margin.top *= kPointsPerInch; + margin.right *= kPointsPerInch; + margin.bottom *= kPointsPerInch; + margin.left *= kPointsPerInch; + + // Using Y to match existing code for print scaling calculations. + int resolution = pixelsPerInchY; + + // We don't actually need the paper name in the settings because the + // paperIdString is used to look up the paper details in the front end. + nsString paperName; + return PrintSettingsInitializer{ + mName, PaperInfo(paperIdString, paperName, paperSize, Some(margin)), + color, resolution, std::move(devmodeWStorage)}; +} diff --git a/widget/windows/nsPrinterWin.h b/widget/windows/nsPrinterWin.h new file mode 100644 index 0000000000..7e84ab66f9 --- /dev/null +++ b/widget/windows/nsPrinterWin.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPrinterWin_h_ +#define nsPrinterWin_h_ + +#include "nsPrinterBase.h" +#include "mozilla/DataMutex.h" +#include "nsTArrayForwardDeclare.h" + +class nsPrinterWin final : public nsPrinterBase { + public: + NS_IMETHOD GetName(nsAString& aName) override; + NS_IMETHOD GetSystemName(nsAString& aName) override; + bool SupportsDuplex() const final; + bool SupportsColor() const final; + bool SupportsMonochrome() const final; + bool SupportsCollation() const final; + PrinterInfo CreatePrinterInfo() const final; + MarginDouble GetMarginsForPaper(nsString aPaperId) const final; + + nsPrinterWin() = delete; + static already_AddRefed<nsPrinterWin> Create( + const mozilla::CommonPaperInfoArray* aPaperInfoArray, + const nsAString& aName); + + private: + nsPrinterWin(const mozilla::CommonPaperInfoArray* aPaperInfoArray, + const nsAString& aName); + ~nsPrinterWin() = default; + + nsTArray<uint8_t> CopyDefaultDevmodeW() const; + nsTArray<mozilla::PaperInfo> PaperList() const; + PrintSettingsInitializer DefaultSettings() const; + + const nsString mName; + mutable mozilla::DataMutex<nsTArray<uint8_t>> mDefaultDevmodeWStorage; +}; + +#endif // nsPrinterWin_h_ diff --git a/widget/windows/nsSharePicker.cpp b/widget/windows/nsSharePicker.cpp new file mode 100644 index 0000000000..9cdc792718 --- /dev/null +++ b/widget/windows/nsSharePicker.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSharePicker.h" + +#include "nsString.h" +#include "nsThreadUtils.h" +#include "WindowsUIUtils.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Unused.h" + +using mozilla::dom::Promise; + +/////////////////////////////////////////////////////////////////////////////// +// nsISharePicker + +NS_IMPL_ISUPPORTS(nsSharePicker, nsISharePicker) + +namespace { +inline NS_ConvertUTF8toUTF16 NS_ConvertUTF8toUTF16_MaybeVoid( + const nsACString& aStr) { + auto str = NS_ConvertUTF8toUTF16(aStr); + str.SetIsVoid(aStr.IsVoid()); + return str; +} +inline nsIGlobalObject* GetGlobalObject() { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} +} // namespace + +NS_IMETHODIMP +nsSharePicker::Init(mozIDOMWindowProxy* aOpenerWindow) { + if (mInited) { + return NS_ERROR_FAILURE; + } + mOpenerWindow = aOpenerWindow; + mInited = true; + return NS_OK; +} + +NS_IMETHODIMP +nsSharePicker::GetOpenerWindow(mozIDOMWindowProxy** aOpenerWindow) { + *aOpenerWindow = mOpenerWindow; + return NS_OK; +} + +NS_IMETHODIMP +nsSharePicker::Share(const nsACString& aTitle, const nsACString& aText, + nsIURI* aUrl, Promise** aPromise) { + mozilla::ErrorResult result; + RefPtr<Promise> promise = Promise::Create(GetGlobalObject(), result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + nsAutoCString urlString; + if (aUrl) { + nsresult rv = aUrl->GetSpec(urlString); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + mozilla::Unused << rv; + } else { + urlString.SetIsVoid(true); + } + + auto mozPromise = + WindowsUIUtils::Share(NS_ConvertUTF8toUTF16_MaybeVoid(aTitle), + NS_ConvertUTF8toUTF16_MaybeVoid(aText), + NS_ConvertUTF8toUTF16_MaybeVoid(urlString)); + mozPromise->Then( + mozilla::GetCurrentSerialEventTarget(), __func__, + [promise]() { promise->MaybeResolveWithUndefined(); }, + [promise]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); }); + + promise.forget(aPromise); + + return NS_OK; +} diff --git a/widget/windows/nsSharePicker.h b/widget/windows/nsSharePicker.h new file mode 100644 index 0000000000..d9ba9d2a6d --- /dev/null +++ b/widget/windows/nsSharePicker.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSharePicker_h__ +#define nsSharePicker_h__ + +#include "nsCOMPtr.h" +#include "nsISharePicker.h" +#include "nsPIDOMWindow.h" +#include "nsThreadUtils.h" + +class nsSharePicker : public nsISharePicker { + virtual ~nsSharePicker() = default; + + public: + nsSharePicker() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSISHAREPICKER + + private: + bool mInited = false; + mozIDOMWindowProxy* mOpenerWindow; +}; + +#endif // nsSharePicker_h__ diff --git a/widget/windows/nsSound.cpp b/widget/windows/nsSound.cpp new file mode 100644 index 0000000000..c187174705 --- /dev/null +++ b/widget/windows/nsSound.cpp @@ -0,0 +1,330 @@ +/* -*- 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 "HeadlessSound.h" +#include "nsSound.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsIObserverService.h" + +#include "mozilla/Logging.h" +#include "prtime.h" + +#include "nsNativeCharsetUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "gfxPlatform.h" + +using mozilla::LogLevel; + +static mozilla::LazyLogModule gWin32SoundLog("nsSound"); + +// Hackaround for bug 1644240 +// When we call PlaySound for the first time in the process, winmm.dll creates +// a new thread and starts a message loop in winmm!mciwindow. After that, +// every call of PlaySound communicates with that thread via Window messages. +// It seems that Warsaw application hooks USER32!GetMessageA, and there is +// a timing window where they free their trampoline region without reverting +// the hook on USER32!GetMessageA, resulting in crash when winmm!mciwindow +// receives a message because it tries to jump to a freed buffer. +// Based on the crash reports, it happened on all versions of Windows x64, and +// the possible condition was wslbdhm64.dll was loaded but wslbscrwh64.dll was +// unloaded. Therefore we suppress playing a sound under such a condition. +static bool ShouldSuppressPlaySound() { +#if defined(_M_AMD64) + if (::GetModuleHandle(L"wslbdhm64.dll") && + !::GetModuleHandle(L"wslbscrwh64.dll")) { + return true; + } +#endif // defined(_M_AMD64) + return false; +} + +class nsSoundPlayer : public mozilla::Runnable { + public: + explicit nsSoundPlayer(const nsAString& aSoundName) + : mozilla::Runnable("nsSoundPlayer"), + mSoundName(aSoundName), + mSoundData(nullptr) {} + + nsSoundPlayer(const uint8_t* aData, size_t aSize) + : mozilla::Runnable("nsSoundPlayer"), mSoundName(u""_ns) { + MOZ_ASSERT(aSize > 0, "Size should not be zero"); + MOZ_ASSERT(aData, "Data shoud not be null"); + + // We will disptach nsSoundPlayer to playerthread, so keep a data copy + mSoundData = new uint8_t[aSize]; + memcpy(mSoundData, aData, aSize); + } + + NS_DECL_NSIRUNNABLE + + protected: + ~nsSoundPlayer(); + + nsString mSoundName; + uint8_t* mSoundData; +}; + +NS_IMETHODIMP +nsSoundPlayer::Run() { + if (ShouldSuppressPlaySound()) { + return NS_OK; + } + + MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData, + "Sound name or sound data should be specified"); + DWORD flags = SND_NODEFAULT | SND_ASYNC; + + if (mSoundData) { + flags |= SND_MEMORY; + ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags); + } else { + flags |= SND_ALIAS; + ::PlaySoundW(mSoundName.get(), nullptr, flags); + } + return NS_OK; +} + +nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; } + +mozilla::StaticRefPtr<nsISound> nsSound::sInstance; + +/* static */ +already_AddRefed<nsISound> nsSound::GetInstance() { + if (!sInstance) { + if (gfxPlatform::IsHeadless()) { + sInstance = new mozilla::widget::HeadlessSound(); + } else { + RefPtr<nsSound> sound = new nsSound(); + nsresult rv = sound->CreatePlayerThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + sInstance = sound.forget(); + } + ClearOnShutdown(&sInstance); + } + + RefPtr<nsISound> service = sInstance; + return service.forget(); +} + +#ifndef SND_PURGE +// Not available on Windows CE, and according to MSDN +// doesn't do anything on recent windows either. +# define SND_PURGE 0 +#endif + +NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver, nsIObserver) + +nsSound::nsSound() : mInited(false) {} + +nsSound::~nsSound() {} + +void nsSound::PurgeLastSound() { + // Halt any currently playing sound. + if (mSoundPlayer) { + if (mPlayerThread) { + mPlayerThread->Dispatch( + NS_NewRunnableFunction("nsSound::PurgeLastSound", + [player = std::move(mSoundPlayer)]() { + // Capture move mSoundPlayer to lambda then + // PlaySoundW(nullptr, nullptr, SND_PURGE) + // will be called before freeing the + // nsSoundPlayer. + if (ShouldSuppressPlaySound()) { + return; + } + ::PlaySoundW(nullptr, nullptr, SND_PURGE); + }), + NS_DISPATCH_NORMAL); + } + } +} + +NS_IMETHODIMP nsSound::Beep() { + ::MessageBeep(0); + + return NS_OK; +} + +NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader* aLoader, + nsISupports* context, nsresult aStatus, + uint32_t dataLen, const uint8_t* data) { + MOZ_ASSERT(mPlayerThread, "player thread should not be null "); + // print a load error on bad status + if (NS_FAILED(aStatus)) { +#ifdef DEBUG + if (aLoader) { + nsCOMPtr<nsIRequest> request; + nsCOMPtr<nsIChannel> channel; + aLoader->GetRequest(getter_AddRefs(request)); + if (request) channel = do_QueryInterface(request); + if (channel) { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + nsAutoCString uriSpec; + uri->GetSpec(uriSpec); + MOZ_LOG(gWin32SoundLog, LogLevel::Info, + ("Failed to load %s\n", uriSpec.get())); + } + } + } +#endif + return aStatus; + } + + PurgeLastSound(); + + if (data && dataLen > 0) { + MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null"); + mSoundPlayer = new nsSoundPlayer(data, dataLen); + MOZ_ASSERT(mSoundPlayer, "Could not create player"); + + nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsSound::Play(nsIURL* aURL) { + nsresult rv; + +#ifdef DEBUG_SOUND + char* url; + aURL->GetSpec(&url); + MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url)); +#endif + + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader( + getter_AddRefs(loader), aURL, + this, // aObserver + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + return rv; +} + +nsresult nsSound::CreatePlayerThread() { + if (mPlayerThread) { + return NS_OK; + } + if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound", + getter_AddRefs(mPlayerThread))))) { + return NS_ERROR_FAILURE; + } + + // Add an observer for shutdown event to release the thread at that time + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) { + return NS_ERROR_FAILURE; + } + + observerService->AddObserver(this, "xpcom-shutdown-threads", false); + return NS_OK; +} + +NS_IMETHODIMP +nsSound::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-shutdown-threads")) { + PurgeLastSound(); + + if (mPlayerThread) { + mPlayerThread->Shutdown(); + mPlayerThread = nullptr; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsSound::Init() { + if (mInited) { + return NS_OK; + } + + MOZ_ASSERT(mPlayerThread, "player thread should not be null "); + // This call halts a sound if it was still playing. + // We have to use the sound library for something to make sure + // it is initialized. + // If we wait until the first sound is played, there will + // be a time lag as the library gets loaded. + // This should be done in player thread otherwise it will block main thread + // at the first time loading sound library. + mPlayerThread->Dispatch( + NS_NewRunnableFunction("nsSound::Init", + []() { + if (ShouldSuppressPlaySound()) { + return; + } + ::PlaySoundW(nullptr, nullptr, SND_PURGE); + }), + NS_DISPATCH_NORMAL); + + mInited = true; + + return NS_OK; +} + +NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) { + MOZ_ASSERT(mPlayerThread, "player thread should not be null "); + PurgeLastSound(); + + const wchar_t* sound = nullptr; + switch (aEventId) { + case EVENT_NEW_MAIL_RECEIVED: + sound = L"MailBeep"; + break; + case EVENT_ALERT_DIALOG_OPEN: + sound = L"SystemExclamation"; + break; + case EVENT_CONFIRM_DIALOG_OPEN: + sound = L"SystemQuestion"; + break; + case EVENT_MENU_EXECUTE: + sound = L"MenuCommand"; + break; + case EVENT_MENU_POPUP: + sound = L"MenuPopup"; + break; + case EVENT_EDITOR_MAX_LEN: + sound = L".Default"; + break; + default: + // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and + // NS_SYSSOUND_SELECT_DIALOG. + return NS_OK; + } + NS_ASSERTION(sound, "sound is null"); + MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null"); + mSoundPlayer = new nsSoundPlayer(nsDependentString(sound)); + MOZ_ASSERT(mSoundPlayer, "Could not create player"); + nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} diff --git a/widget/windows/nsSound.h b/widget/windows/nsSound.h new file mode 100644 index 0000000000..d600b0873a --- /dev/null +++ b/widget/windows/nsSound.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsSound_h__ +#define __nsSound_h__ + +#include "nsISound.h" +#include "nsIObserver.h" +#include "nsIStreamLoader.h" +#include "nsCOMPtr.h" +#include "mozilla/StaticPtr.h" + +class nsIThread; +class nsIRunnable; + +class nsSound : public nsISound, + public nsIStreamLoaderObserver, + public nsIObserver + +{ + public: + nsSound(); + static already_AddRefed<nsISound> GetInstance(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISOUND + NS_DECL_NSISTREAMLOADEROBSERVER + NS_DECL_NSIOBSERVER + + private: + virtual ~nsSound(); + void PurgeLastSound(); + + private: + nsresult CreatePlayerThread(); + + nsCOMPtr<nsIThread> mPlayerThread; + nsCOMPtr<nsIRunnable> mSoundPlayer; + bool mInited; + + static mozilla::StaticRefPtr<nsISound> sInstance; +}; + +#endif /* __nsSound_h__ */ diff --git a/widget/windows/nsToolkit.cpp b/widget/windows/nsToolkit.cpp new file mode 100644 index 0000000000..6eea9c958f --- /dev/null +++ b/widget/windows/nsToolkit.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsToolkit.h" +#include "nsAppShell.h" +#include "nsWindow.h" +#include "nsWidgetsCID.h" +#include "prmon.h" +#include "prtime.h" +#include "nsComponentManagerUtils.h" +#include <objbase.h> +#include "WinUtils.h" + +#include "nsUXThemeData.h" + +// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN +#include <unknwn.h> + +using namespace mozilla::widget; + +nsToolkit* nsToolkit::gToolkit = nullptr; +HINSTANCE nsToolkit::mDllInstance = 0; + +//------------------------------------------------------------------------- +// +// constructor +// +//------------------------------------------------------------------------- +nsToolkit::nsToolkit() { + MOZ_COUNT_CTOR(nsToolkit); + +#if defined(MOZ_STATIC_COMPONENT_LIBS) + nsToolkit::Startup(GetModuleHandle(nullptr)); +#endif +} + +//------------------------------------------------------------------------- +// +// destructor +// +//------------------------------------------------------------------------- +nsToolkit::~nsToolkit() { MOZ_COUNT_DTOR(nsToolkit); } + +void nsToolkit::Startup(HMODULE hModule) { + nsToolkit::mDllInstance = hModule; + WinUtils::Initialize(); +} + +void nsToolkit::Shutdown() { + delete gToolkit; + gToolkit = nullptr; +} + +//------------------------------------------------------------------------- +// +// Return the nsToolkit for the current thread. If a toolkit does not +// yet exist, then one will be created... +// +//------------------------------------------------------------------------- +// static +nsToolkit* nsToolkit::GetToolkit() { + if (!gToolkit) { + gToolkit = new nsToolkit(); + } + + return gToolkit; +} diff --git a/widget/windows/nsToolkit.h b/widget/windows/nsToolkit.h new file mode 100644 index 0000000000..4be7bdb80a --- /dev/null +++ b/widget/windows/nsToolkit.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsToolkit_h__ +#define nsToolkit_h__ + +#include "nsdefs.h" + +#include "nsCOMPtr.h" +#include <windows.h> + +// Avoid including windowsx.h to prevent macro pollution +#ifndef GET_X_LPARAM +# define GET_X_LPARAM(pt) (short(LOWORD(pt))) +#endif +#ifndef GET_Y_LPARAM +# define GET_Y_LPARAM(pt) (short(HIWORD(pt))) +#endif + +/** + * Wrapper around the thread running the message pump. + * The toolkit abstraction is necessary because the message pump must + * execute within the same thread that created the widget under Win32. + */ + +class nsToolkit { + public: + nsToolkit(); + + private: + ~nsToolkit(); + + public: + static nsToolkit* GetToolkit(); + + static HINSTANCE mDllInstance; + + static void Startup(HMODULE hModule); + static void Shutdown(); + + protected: + static nsToolkit* gToolkit; +}; + +#endif // TOOLKIT_H diff --git a/widget/windows/nsUXThemeConstants.h b/widget/windows/nsUXThemeConstants.h new file mode 100644 index 0000000000..e47ca8f108 --- /dev/null +++ b/widget/windows/nsUXThemeConstants.h @@ -0,0 +1,255 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUXThemeConstants_h +#define nsUXThemeConstants_h + +/* + * The following constants are used to determine how a widget is drawn using + * Windows' Theme API. For more information on theme parts and states see + * http://msdn.microsoft.com/en-us/library/bb773210(VS.85).aspx + */ + +#include <vssym32.h> +#include <vsstyle.h> + +#define THEME_COLOR 204 +#define THEME_FONT 210 + +// Generic state constants +#define TS_NORMAL 1 +#define TS_HOVER 2 +#define TS_ACTIVE 3 +#define TS_DISABLED 4 +#define TS_FOCUSED 5 + +// These constants are reversed for the trackbar (scale) thumb +#define TKP_FOCUSED 4 +#define TKP_DISABLED 5 + +// Toolbarbutton constants +#define TB_CHECKED 5 +#define TB_HOVER_CHECKED 6 + +// Button constants +#define BP_BUTTON 1 +#define BP_RADIO 2 +#define BP_CHECKBOX 3 +#define BP_GROUPBOX 4 +#define BP_Count 5 + +// Textfield constants +/* This is the EP_EDITTEXT part */ +#define TFP_TEXTFIELD 1 +#define TFP_EDITBORDER_NOSCROLL 6 +#define TFS_READONLY 6 + +/* These are the state constants for the EDITBORDER parts */ +#define TFS_EDITBORDER_NORMAL 1 +#define TFS_EDITBORDER_HOVER 2 +#define TFS_EDITBORDER_FOCUSED 3 +#define TFS_EDITBORDER_DISABLED 4 + +// Treeview/listbox constants +#define TREEVIEW_BODY 1 + +// Scrollbar constants +#define SP_BUTTON 1 +#define SP_THUMBHOR 2 +#define SP_THUMBVERT 3 +#define SP_TRACKSTARTHOR 4 +#define SP_TRACKENDHOR 5 +#define SP_TRACKSTARTVERT 6 +#define SP_TRACKENDVERT 7 +#define SP_GRIPPERHOR 8 +#define SP_GRIPPERVERT 9 + +// Implicit hover state. +// BASE + 0 = UP, + 1 = DOWN, etc. +#define SP_BUTTON_IMPLICIT_HOVER_BASE 17 + +// Scale constants +#define TKP_TRACK 1 +#define TKP_TRACKVERT 2 +#define TKP_THUMB 3 +#define TKP_THUMBBOTTOM 4 +#define TKP_THUMBTOP 5 +#define TKP_THUMBVERT 6 +#define TKP_THUMBLEFT 7 +#define TKP_THUMBRIGHT 8 + +// Track state contstants +#define TRS_NORMAL 1 + +// Track vertical state constants +#define TRVS_NORMAL 1 + +// Spin constants +#define SPNP_UP 1 +#define SPNP_DOWN 2 + +// Tab constants +#define TABP_TAB 4 +#define TABP_TAB_SELECTED 5 +#define TABP_PANELS 9 +#define TABP_PANEL 10 + +// Tooltip constants +#define TTP_STANDARD 1 + +// Dropdown constants +#define CBP_DROPMARKER 1 +#define CBP_DROPBORDER 4 +/* This is actually the 'READONLY' style */ +#define CBP_DROPFRAME 5 +#define CBP_DROPMARKER_VISTA 6 + +// Menu Constants +#define MENU_BARBACKGROUND 7 +#define MENU_BARITEM 8 +#define MENU_POPUPBACKGROUND 9 +#define MENU_POPUPBORDERS 10 +#define MENU_POPUPCHECK 11 +#define MENU_POPUPCHECKBACKGROUND 12 +#define MENU_POPUPGUTTER 13 +#define MENU_POPUPITEM 14 +#define MENU_POPUPSEPARATOR 15 +#define MENU_POPUPSUBMENU 16 +#define MENU_SYSTEMCLOSE 17 +#define MENU_SYSTEMMAXIMIZE 18 +#define MENU_SYSTEMMINIMIZE 19 +#define MENU_SYSTEMRESTORE 20 + +#define MB_ACTIVE 1 +#define MB_INACTIVE 2 + +#define MS_NORMAL 1 +#define MS_SELECTED 2 +#define MS_DEMOTED 3 + +#define MBI_NORMAL 1 +#define MBI_HOT 2 +#define MBI_PUSHED 3 +#define MBI_DISABLED 4 +#define MBI_DISABLEDHOT 5 +#define MBI_DISABLEDPUSHED 6 + +#define MC_CHECKMARKNORMAL 1 +#define MC_CHECKMARKDISABLED 2 +#define MC_BULLETNORMAL 3 +#define MC_BULLETDISABLED 4 + +#define MCB_DISABLED 1 +#define MCB_NORMAL 2 +#define MCB_BITMAP 3 + +#define MPI_NORMAL 1 +#define MPI_HOT 2 +#define MPI_DISABLED 3 +#define MPI_DISABLEDHOT 4 + +#define MSM_NORMAL 1 +#define MSM_DISABLED 2 + +// Rebar constants +#define RP_BAND 3 +#define RP_BACKGROUND 6 + +// Constants only found in new (98+, 2K+, XP+, etc.) Windows. +#ifdef DFCS_HOT +# undef DFCS_HOT +#endif +#define DFCS_HOT 0x00001000 + +#ifdef COLOR_MENUHILIGHT +# undef COLOR_MENUHILIGHT +#endif +#define COLOR_MENUHILIGHT 29 + +#ifdef SPI_GETFLATMENU +# undef SPI_GETFLATMENU +#endif +#define SPI_GETFLATMENU 0x1022 +#ifndef SPI_GETMENUSHOWDELAY +# define SPI_GETMENUSHOWDELAY 106 +#endif // SPI_GETMENUSHOWDELAY +#ifndef WS_EX_LAYOUTRTL +# define WS_EX_LAYOUTRTL 0x00400000L // Right to left mirroring +#endif + +// Our extra constants for passing a little bit more info to the renderer. +#define DFCS_RTL 0x00010000 + +// Toolbar separator dimension which can't be gotten from Windows +#define TB_SEPARATOR_HEIGHT 2 + +namespace mozilla { +namespace widget { +namespace themeconst { + +// Pulled from sdk/include/vsstyle.h +enum { + WP_CAPTION = 1, + WP_SMALLCAPTION = 2, + WP_MINCAPTION = 3, + WP_SMALLMINCAPTION = 4, + WP_MAXCAPTION = 5, + WP_SMALLMAXCAPTION = 6, + WP_FRAMELEFT = 7, + WP_FRAMERIGHT = 8, + WP_FRAMEBOTTOM = 9, + WP_SMALLFRAMELEFT = 10, + WP_SMALLFRAMERIGHT = 11, + WP_SMALLFRAMEBOTTOM = 12, + WP_SYSBUTTON = 13, + WP_MDISYSBUTTON = 14, + WP_MINBUTTON = 15, + WP_MDIMINBUTTON = 16, + WP_MAXBUTTON = 17, + WP_CLOSEBUTTON = 18, + WP_SMALLCLOSEBUTTON = 19, + WP_MDICLOSEBUTTON = 20, + WP_RESTOREBUTTON = 21, + WP_MDIRESTOREBUTTON = 22, + WP_HELPBUTTON = 23, + WP_MDIHELPBUTTON = 24, + WP_HORZSCROLL = 25, + WP_HORZTHUMB = 26, + WP_VERTSCROLL = 27, + WP_VERTTHUMB = 28, + WP_DIALOG = 29, + WP_CAPTIONSIZINGTEMPLATE = 30, + WP_SMALLCAPTIONSIZINGTEMPLATE = 31, + WP_FRAMELEFTSIZINGTEMPLATE = 32, + WP_SMALLFRAMELEFTSIZINGTEMPLATE = 33, + WP_FRAMERIGHTSIZINGTEMPLATE = 34, + WP_SMALLFRAMERIGHTSIZINGTEMPLATE = 35, + WP_FRAMEBOTTOMSIZINGTEMPLATE = 36, + WP_SMALLFRAMEBOTTOMSIZINGTEMPLATE = 37, + WP_FRAME = 38, + WP_Count +}; + +enum 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 */ +}; + +} // namespace themeconst +} // namespace widget +} // namespace mozilla + +// If any theme part ends up having a value higher than WP_Count, this will +// need to change. +#define THEME_PART_DISTINCT_VALUE_COUNT mozilla::widget::themeconst::WP_Count + +#endif diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp new file mode 100644 index 0000000000..b0e34deb6c --- /dev/null +++ b/widget/windows/nsUXThemeData.cpp @@ -0,0 +1,410 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/WindowsVersion.h" + +#include "nsUXThemeData.h" +#include "nsDebug.h" +#include "nsToolkit.h" +#include "nsUXThemeConstants.h" +#include "WinContentSystemParameters.h" + +using namespace mozilla; +using namespace mozilla::widget; + +nsUXThemeData::ThemeHandle nsUXThemeData::sThemes[eUXNumClasses]; + +const int NUM_COMMAND_BUTTONS = 3; +SIZE nsUXThemeData::sCommandButtonMetrics[NUM_COMMAND_BUTTONS]; +bool nsUXThemeData::sCommandButtonMetricsInitialized = false; +SIZE nsUXThemeData::sCommandButtonBoxMetrics; +bool nsUXThemeData::sCommandButtonBoxMetricsInitialized = false; + +bool nsUXThemeData::sTitlebarInfoPopulatedAero = false; +bool nsUXThemeData::sTitlebarInfoPopulatedThemed = false; + +nsUXThemeData::ThemeHandle::~ThemeHandle() { Close(); } + +void nsUXThemeData::ThemeHandle::OpenOnce(HWND aWindow, LPCWSTR aClassList) { + if (mHandle.isSome()) { + return; + } + + mHandle = Some(OpenThemeData(aWindow, aClassList)); +} + +void nsUXThemeData::ThemeHandle::Close() { + if (mHandle.isNothing() || !mHandle.value()) { + return; + } + + CloseThemeData(mHandle.value()); + mHandle = Nothing(); +} + +nsUXThemeData::ThemeHandle::operator HANDLE() { + return mHandle.isSome() ? mHandle.value() : nullptr; +} + +void nsUXThemeData::Invalidate() { + for (auto& theme : sThemes) { + theme.Close(); + } +} + +HANDLE +nsUXThemeData::GetTheme(nsUXThemeClass cls) { + NS_ASSERTION(cls < eUXNumClasses, "Invalid theme class!"); + sThemes[cls].OpenOnce(nullptr, GetClassName(cls)); + return sThemes[cls]; +} + +const wchar_t* nsUXThemeData::GetClassName(nsUXThemeClass cls) { + switch (cls) { + case eUXButton: + return L"Button"; + case eUXEdit: + return L"Edit"; + case 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: + MOZ_ASSERT_UNREACHABLE("unknown uxtheme class"); + return L""; + } +} + +// static +void nsUXThemeData::EnsureCommandButtonMetrics() { + if (sCommandButtonMetricsInitialized) { + return; + } + sCommandButtonMetricsInitialized = true; + + // This code should never need to be evaluated for our UI since if we need + // these metrics for our UI we should make sure that we obtain the correct + // metrics when nsWindow::Create() is called. The generic metrics that we + // fetch here will likley not match the current theme, but we provide these + // values in case arbitrary content is styled with the '-moz-appearance' + // value '-moz-window-button-close' etc. + // + // ISSUE: We'd prefer to use MOZ_ASSERT_UNREACHABLE here, but since content + // (and at least one of our crashtests) can use '-moz-window-button-close' + // we need to use NS_WARNING instead. + NS_WARNING("Making expensive and likely unnecessary GetSystemMetrics calls"); + + sCommandButtonMetrics[0].cx = GetSystemMetrics(SM_CXSIZE); + sCommandButtonMetrics[0].cy = GetSystemMetrics(SM_CYSIZE); + sCommandButtonMetrics[1].cx = sCommandButtonMetrics[2].cx = + sCommandButtonMetrics[0].cx; + sCommandButtonMetrics[1].cy = sCommandButtonMetrics[2].cy = + sCommandButtonMetrics[0].cy; + + // Trigger a refresh on the next layout. + sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed = false; +} + +// static +void nsUXThemeData::EnsureCommandButtonBoxMetrics() { + if (sCommandButtonBoxMetricsInitialized) { + return; + } + sCommandButtonBoxMetricsInitialized = true; + + EnsureCommandButtonMetrics(); + + sCommandButtonBoxMetrics.cx = sCommandButtonMetrics[0].cx + + sCommandButtonMetrics[1].cx + + sCommandButtonMetrics[2].cx; + sCommandButtonBoxMetrics.cy = sCommandButtonMetrics[0].cy + + sCommandButtonMetrics[1].cy + + sCommandButtonMetrics[2].cy; + + // Trigger a refresh on the next layout. + sTitlebarInfoPopulatedAero = sTitlebarInfoPopulatedThemed = false; +} + +// static +void nsUXThemeData::UpdateTitlebarInfo(HWND aWnd) { + if (!aWnd) return; + + if (!sTitlebarInfoPopulatedAero && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + RECT captionButtons; + if (SUCCEEDED(DwmGetWindowAttribute(aWnd, DWMWA_CAPTION_BUTTON_BOUNDS, + &captionButtons, + sizeof(captionButtons)))) { + sCommandButtonBoxMetrics.cx = + captionButtons.right - captionButtons.left - 3; + sCommandButtonBoxMetrics.cy = + (captionButtons.bottom - captionButtons.top) - 1; + sCommandButtonBoxMetricsInitialized = true; + MOZ_ASSERT( + sCommandButtonBoxMetrics.cx > 0 && sCommandButtonBoxMetrics.cy > 0, + "We must not cache bad command button box dimensions"); + 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 && + !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled())) { + 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 + sCommandButtonMetrics[0].cx = info.rgrect[2].right - info.rgrect[2].left; + sCommandButtonMetrics[0].cy = info.rgrect[2].bottom - info.rgrect[2].top; + // maximize/restore + sCommandButtonMetrics[1].cx = info.rgrect[3].right - info.rgrect[3].left; + sCommandButtonMetrics[1].cy = info.rgrect[3].bottom - info.rgrect[3].top; + // close + sCommandButtonMetrics[2].cx = info.rgrect[5].right - info.rgrect[5].left; + sCommandButtonMetrics[2].cy = info.rgrect[5].bottom - info.rgrect[5].top; + sCommandButtonMetricsInitialized = true; + +#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(sCommandButtonMetrics[i].cx > 0); + MOZ_ASSERT(sCommandButtonMetrics[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 +void nsUXThemeData::UpdateNativeThemeInfo() { + // Trigger a refresh of themed button metrics if needed + sTitlebarInfoPopulatedThemed = false; + + 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 (!nsUXThemeData::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; + } +} + +// static +bool nsUXThemeData::AreFlatMenusEnabled() { + if (XRE_IsContentProcess()) { + return WinContentSystemParameters::GetSingleton()->AreFlatMenusEnabled(); + } + + BOOL useFlat = FALSE; + return !!::SystemParametersInfo(SPI_GETFLATMENU, 0, &useFlat, 0) ? useFlat + : false; +} + +// static +bool nsUXThemeData::IsAppThemed() { + if (XRE_IsContentProcess()) { + return WinContentSystemParameters::GetSingleton()->IsAppThemed(); + } + return !!::IsAppThemed(); +} diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h new file mode 100644 index 0000000000..604dae6089 --- /dev/null +++ b/widget/windows/nsUXThemeData.h @@ -0,0 +1,133 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef __UXThemeData_h__ +#define __UXThemeData_h__ +#include <windows.h> +#include <uxtheme.h> + +#include "nscore.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Maybe.h" +#include "WinUtils.h" + +#include "nsWindowDefs.h" + +enum nsUXThemeClass { + eUXButton = 0, + eUXEdit, + 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 +}; +enum CmdButtonIdx { + CMDBUTTONIDX_MINIMIZE = 0, + CMDBUTTONIDX_RESTORE, + CMDBUTTONIDX_CLOSE, + CMDBUTTONIDX_BUTTONBOX +}; + +class nsUXThemeData { + // This class makes sure we don't attempt to open a theme if the previous + // loading attempt has failed because OpenThemeData is a heavy task and + // it's less likely that the API returns a different result. + class ThemeHandle final { + mozilla::Maybe<HANDLE> mHandle; + + public: + ThemeHandle() = default; + ~ThemeHandle(); + + // Disallow copy and move + ThemeHandle(const ThemeHandle&) = delete; + ThemeHandle(ThemeHandle&&) = delete; + ThemeHandle& operator=(const ThemeHandle&) = delete; + ThemeHandle& operator=(ThemeHandle&&) = delete; + + operator HANDLE(); + void OpenOnce(HWND aWindow, LPCWSTR aClassList); + void Close(); + }; + + static ThemeHandle sThemes[eUXNumClasses]; + + // We initialize sCommandButtonBoxMetrics separately as a performance + // optimization to avoid fetching dummy values for sCommandButtonMetrics + // when we don't need those. + static SIZE sCommandButtonMetrics[3]; + static bool sCommandButtonMetricsInitialized; + static SIZE sCommandButtonBoxMetrics; + static bool sCommandButtonBoxMetricsInitialized; + + static const wchar_t* GetClassName(nsUXThemeClass); + static void EnsureCommandButtonMetrics(); + static void EnsureCommandButtonBoxMetrics(); + + public: + static bool sTitlebarInfoPopulatedAero; + static bool sTitlebarInfoPopulatedThemed; + static mozilla::LookAndFeel::WindowsTheme sThemeId; + static bool sIsDefaultWindowsTheme; + static bool sIsHighContrastOn; + + static void Invalidate(); + static HANDLE GetTheme(nsUXThemeClass cls); + static HMODULE GetThemeDLL(); + + // nsWindow calls this to update desktop settings info + static void UpdateTitlebarInfo(HWND aWnd); + + static SIZE GetCommandButtonMetrics(CmdButtonIdx aMetric) { + EnsureCommandButtonMetrics(); + return sCommandButtonMetrics[aMetric]; + } + static SIZE GetCommandButtonBoxMetrics() { + EnsureCommandButtonBoxMetrics(); + return sCommandButtonBoxMetrics; + } + static void UpdateNativeThemeInfo(); + static mozilla::LookAndFeel::WindowsTheme GetNativeThemeId(); + static bool IsDefaultWindowTheme(); + static bool IsHighContrastOn(); + + static bool AreFlatMenusEnabled(); + static bool IsAppThemed(); +}; +#endif // __UXThemeData_h__ diff --git a/widget/windows/nsUserIdleServiceWin.cpp b/widget/windows/nsUserIdleServiceWin.cpp new file mode 100644 index 0000000000..1176b8a43a --- /dev/null +++ b/widget/windows/nsUserIdleServiceWin.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsUserIdleServiceWin.h" +#include <windows.h> + +bool nsUserIdleServiceWin::PollIdleTime(uint32_t* aIdleTime) { + LASTINPUTINFO inputInfo; + inputInfo.cbSize = sizeof(inputInfo); + if (!::GetLastInputInfo(&inputInfo)) return false; + + *aIdleTime = + SAFE_COMPARE_EVEN_WITH_WRAPPING(GetTickCount(), inputInfo.dwTime); + + return true; +} + +bool nsUserIdleServiceWin::UsePollMode() { return true; } diff --git a/widget/windows/nsUserIdleServiceWin.h b/widget/windows/nsUserIdleServiceWin.h new file mode 100644 index 0000000000..352f1e0a3e --- /dev/null +++ b/widget/windows/nsUserIdleServiceWin.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUserIdleServiceWin_h__ +#define nsUserIdleServiceWin_h__ + +#include "nsUserIdleService.h" + +/* NOTE: Compare of GetTickCount() could overflow. This corrects for + * overflow situations. + ***/ +#ifndef SAFE_COMPARE_EVEN_WITH_WRAPPING +# define SAFE_COMPARE_EVEN_WITH_WRAPPING(A, B) \ + (((int)((long)A - (long)B) & 0xFFFFFFFF)) +#endif + +class nsUserIdleServiceWin : public nsUserIdleService { + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceWin, nsUserIdleService) + + bool PollIdleTime(uint32_t* aIdleTime) override; + + static already_AddRefed<nsUserIdleServiceWin> GetInstance() { + RefPtr<nsUserIdleServiceWin> idleService = + nsUserIdleService::GetInstance().downcast<nsUserIdleServiceWin>(); + if (!idleService) { + idleService = new nsUserIdleServiceWin(); + } + + return idleService.forget(); + } + + protected: + nsUserIdleServiceWin() {} + virtual ~nsUserIdleServiceWin() {} + bool UsePollMode() override; +}; + +#endif // nsUserIdleServiceWin_h__ diff --git a/widget/windows/nsWidgetFactory.cpp b/widget/windows/nsWidgetFactory.cpp new file mode 100644 index 0000000000..47c7d021ed --- /dev/null +++ b/widget/windows/nsWidgetFactory.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWidgetFactory.h" + +#include "mozilla/Components.h" +#include "nsISupports.h" +#include "nsdefs.h" +#include "nsWidgetsCID.h" +#include "nsAppShell.h" +#include "nsAppShellSingleton.h" +#include "mozilla/WidgetUtils.h" +#include "mozilla/widget/ScreenManager.h" +#include "nsLookAndFeel.h" +#include "WinMouseScrollHandler.h" +#include "KeyboardLayout.h" +#include "nsToolkit.h" + +// Modules that switch out based on the environment +#include "nsXULAppAPI.h" +// Desktop +#include "nsFilePicker.h" // needs to be included before other shobjidl.h includes +#include "nsColorPicker.h" +// Content processes +#include "nsFilePickerProxy.h" + +// Clipboard +#include "nsClipboardHelper.h" +#include "nsClipboard.h" +#include "HeadlessClipboard.h" + +#include "WindowsUIUtils.h" + +using namespace mozilla; +using namespace mozilla::widget; + +NS_IMPL_COMPONENT_FACTORY(nsIClipboard) { + nsCOMPtr<nsIClipboard> inst; + if (gfxPlatform::IsHeadless()) { + inst = new HeadlessClipboard(); + } else { + inst = new nsClipboard(); + } + return inst.forget().downcast<nsISupports>(); +} + +nsresult nsWidgetWindowsModuleCtor() { return nsAppShellInit(); } + +void nsWidgetWindowsModuleDtor() { + // Shutdown all XP level widget classes. + WidgetUtils::Shutdown(); + + KeyboardLayout::Shutdown(); + MouseScrollHandler::Shutdown(); + nsLookAndFeel::Shutdown(); + nsToolkit::Shutdown(); + nsAppShellShutdown(); +} diff --git a/widget/windows/nsWidgetFactory.h b/widget/windows/nsWidgetFactory.h new file mode 100644 index 0000000000..797d0a60bb --- /dev/null +++ b/widget/windows/nsWidgetFactory.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef widget_windows_nsWidgetFactory_h +#define widget_windows_nsWidgetFactory_h + +#include "nscore.h" +#include "nsID.h" + +class nsISupports; + +nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid, + void** result); + +nsresult nsWidgetWindowsModuleCtor(); +void nsWidgetWindowsModuleDtor(); + +#endif // defined widget_windows_nsWidgetFactory_h diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp new file mode 100644 index 0000000000..8fd00b3ff0 --- /dev/null +++ b/widget/windows/nsWinGesture.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWinGesture - Touch input handling for tablet displays. + */ + +#include "nscore.h" +#include "nsWinGesture.h" +#include "nsUXThemeData.h" +#include "mozilla/Logging.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/dom/SimpleGestureEventBinding.h" +#include "mozilla/dom/WheelEventBinding.h" + +#include <cmath> + +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +static bool gEnableSingleFingerPanEvents = false; + +nsWinGesture::nsWinGesture() + : mPanActive(false), + mFeedbackActive(false), + mXAxisFeedback(false), + mYAxisFeedback(false), + mPanInertiaActive(false) { + (void)InitLibrary(); + mPixelScrollOverflow = 0; +} + +/* Load and shutdown */ + +bool nsWinGesture::InitLibrary() { + // Check to see if we want single finger gesture input. Only do this once + // for the app so we don't have to look it up on every window create. + gEnableSingleFingerPanEvents = + Preferences::GetBool("gestures.enable_single_finger_input", false); + + return true; +} + +#define GCOUNT 5 + +bool nsWinGesture::SetWinGestureSupport( + HWND hWnd, WidgetGestureNotifyEvent::PanDirection aDirection) { + GESTURECONFIG config[GCOUNT]; + + memset(&config, 0, sizeof(config)); + + config[0].dwID = GID_ZOOM; + config[0].dwWant = GC_ZOOM; + config[0].dwBlock = 0; + + config[1].dwID = GID_ROTATE; + config[1].dwWant = GC_ROTATE; + config[1].dwBlock = 0; + + config[2].dwID = GID_PAN; + config[2].dwWant = GC_PAN | GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER; + config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | + GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + + if (gEnableSingleFingerPanEvents) { + if (aDirection == WidgetGestureNotifyEvent::ePanVertical || + aDirection == WidgetGestureNotifyEvent::ePanBoth) { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + } + + if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal || + aDirection == WidgetGestureNotifyEvent::ePanBoth) { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + } + } + + config[3].dwWant = GC_TWOFINGERTAP; + config[3].dwID = GID_TWOFINGERTAP; + config[3].dwBlock = 0; + + config[4].dwWant = GC_PRESSANDTAP; + config[4].dwID = GID_PRESSANDTAP; + config[4].dwBlock = 0; + + return SetGestureConfig(hWnd, 0, GCOUNT, (PGESTURECONFIG)&config, + sizeof(GESTURECONFIG)); +} + +/* Helpers */ + +bool nsWinGesture::IsPanEvent(LPARAM lParam) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + if (gi.dwID == GID_PAN) return true; + + return false; +} + +/* Gesture event processing */ + +bool nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, + LPARAM lParam, + WidgetSimpleGestureEvent& evt) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + // The coordinates of this event + nsPointWin coord; + coord = gi.ptsLocation; + coord.ScreenToClient(hWnd); + + evt.mRefPoint = LayoutDeviceIntPoint(coord.x, coord.y); + + // Multiple gesture can occur at the same time so gesture state + // info can't be shared. + switch (gi.dwID) { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + case GID_ZOOM: { + if (gi.dwFlags & GF_BEGIN) { + // Send a zoom start event + + // The low 32 bits are the distance in pixels. + mZoomIntermediate = (float)gi.ullArguments; + + evt.mMessage = eMagnifyGestureStart; + evt.mDelta = 0.0; + } else if (gi.dwFlags & GF_END) { + // Send a zoom end event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGesture; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } else { + // Send a zoom intermediate event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGestureUpdate; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } + } break; + + case GID_ROTATE: { + // Send a rotate start event + double radians = 0.0; + + // On GF_BEGIN, ullArguments contains the absolute rotation at the + // start of the gesture. In later events it contains the offset from + // the start angle. + if (gi.ullArguments != 0) + radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments); + + double degrees = -1 * radians * (180 / M_PI); + + if (gi.dwFlags & GF_BEGIN) { + // At some point we should pass the initial angle in + // along with delta. It's useful. + degrees = mRotateIntermediate = 0.0; + } + + evt.mDirection = 0; + evt.mDelta = degrees - mRotateIntermediate; + mRotateIntermediate = degrees; + + if (evt.mDelta > 0) { + evt.mDirection = + dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE; + } else if (evt.mDelta < 0) { + evt.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE; + } + + if (gi.dwFlags & GF_BEGIN) { + evt.mMessage = eRotateGestureStart; + } else if (gi.dwFlags & GF_END) { + evt.mMessage = eRotateGesture; + } else { + evt.mMessage = eRotateGestureUpdate; + } + } break; + + case GID_TWOFINGERTAP: + // Normally maps to "restore" from whatever you may have recently changed. + // A simple double click. + evt.mMessage = eTapGesture; + evt.mClickCount = 1; + break; + + case GID_PRESSANDTAP: + // Two finger right click. Defaults to right click if it falls through. + evt.mMessage = ePressTapGesture; + evt.mClickCount = 1; + break; + } + + return true; +} + +bool nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + // The coordinates of this event + nsPointWin coord; + coord = mPanRefPoint = gi.ptsLocation; + // We want screen coordinates in our local offsets as client coordinates will + // change when feedback is taking place. Gui events though require client + // coordinates. + mPanRefPoint.ScreenToClient(hWnd); + + switch (gi.dwID) { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + // Setup pixel scroll events for both axis + case GID_PAN: { + if (gi.dwFlags & GF_BEGIN) { + mPanIntermediate = coord; + mPixelScrollDelta = 0; + mPanActive = true; + mPanInertiaActive = false; + } else { +#ifdef DBG_jimm + int32_t deltaX = mPanIntermediate.x - coord.x; + int32_t deltaY = mPanIntermediate.y - coord.y; + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x, + coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback)); +#endif + + mPixelScrollDelta.x = mPanIntermediate.x - coord.x; + mPixelScrollDelta.y = mPanIntermediate.y - coord.y; + mPanIntermediate = coord; + + if (gi.dwFlags & GF_INERTIA) mPanInertiaActive = true; + + if (gi.dwFlags & GF_END) { + mPanActive = false; + mPanInertiaActive = false; + PanFeedbackFinalize(hWnd, true); + } + } + } break; + } + return true; +} + +inline bool TestTransition(int32_t a, int32_t b) { + // If a is zero, overflow is zero, implying the cursor has moved back to the + // start position. If b is zero, cached overscroll is zero, implying feedback + // just begun. + if (a == 0 || b == 0) return true; + // Test for different signs. + return (a < 0) == (b < 0); +} + +void nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, + bool& endFeedback) { + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mXAxisFeedback = true; + return; + } + + if (mXAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x; + + // Detect a reverse transition past the starting drag point. This tells us + // the user has panned all the way back so we can stop providing feedback + // for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || + newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.x = newOverflow; + endFeedback = false; + } +} + +void nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, + bool& endFeedback) { + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mYAxisFeedback = true; + return; + } + + if (mYAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y; + + // Detect a reverse transition past the starting drag point. This tells us + // the user has panned all the way back so we can stop providing feedback + // for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || + newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.y = newOverflow; + endFeedback = false; + } +} + +void nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) { + if (!mFeedbackActive) return; + + if (endFeedback) { + mFeedbackActive = false; + mXAxisFeedback = false; + mYAxisFeedback = false; + mPixelScrollOverflow = 0; + EndPanningFeedback(hWnd, TRUE); + return; + } + + UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, + mPanInertiaActive); +} + +bool nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent) { + aWheelEvent.mDeltaX = aWheelEvent.mDeltaY = aWheelEvent.mDeltaZ = 0.0; + aWheelEvent.mLineOrPageDeltaX = aWheelEvent.mLineOrPageDeltaY = 0; + + aWheelEvent.mRefPoint = LayoutDeviceIntPoint(mPanRefPoint.x, mPanRefPoint.y); + aWheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PIXEL; + aWheelEvent.mScrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY; + aWheelEvent.mIsNoLineOrPageDelta = true; + + aWheelEvent.mOverflowDeltaX = 0.0; + aWheelEvent.mOverflowDeltaY = 0.0; + + // Don't scroll the view if we are currently at a bounds, or, if we are + // panning back from a max feedback position. This keeps the original drag + // point constant. + if (!mXAxisFeedback) { + aWheelEvent.mDeltaX = mPixelScrollDelta.x; + } + if (!mYAxisFeedback) { + aWheelEvent.mDeltaY = mPixelScrollDelta.y; + } + + return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0); +} diff --git a/widget/windows/nsWinGesture.h b/widget/windows/nsWinGesture.h new file mode 100644 index 0000000000..040b460da9 --- /dev/null +++ b/widget/windows/nsWinGesture.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WinGesture_h__ +#define WinGesture_h__ + +/* + * nsWinGesture - Touch input handling for tablet displays. + */ + +#include "nsdefs.h" +#include <winuser.h> +#include <tpcshrd.h> +#include "nsPoint.h" +#include "mozilla/EventForwards.h" +#include "mozilla/TouchEvents.h" + +// WM_TABLET_QUERYSYSTEMGESTURESTATUS return values +#define TABLET_ROTATE_GESTURE_ENABLE 0x02000000 + +class nsPointWin : public nsIntPoint { + public: + nsPointWin& operator=(const POINTS& aPoint) { + x = aPoint.x; + y = aPoint.y; + return *this; + } + nsPointWin& operator=(const POINT& aPoint) { + x = aPoint.x; + y = aPoint.y; + return *this; + } + nsPointWin& operator=(int val) { + x = y = val; + return *this; + } + void ScreenToClient(HWND hWnd) { + POINT tmp; + tmp.x = x; + tmp.y = y; + ::ScreenToClient(hWnd, &tmp); + *this = tmp; + } +}; + +class nsWinGesture { + public: + nsWinGesture(); + + public: + bool SetWinGestureSupport( + HWND hWnd, mozilla::WidgetGestureNotifyEvent::PanDirection aDirection); + bool ShutdownWinGestureSupport(); + + // Simple gesture process + bool ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam, + mozilla::WidgetSimpleGestureEvent& evt); + + // Pan processing + bool IsPanEvent(LPARAM lParam); + bool ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam); + bool PanDeltaToPixelScroll(mozilla::WidgetWheelEvent& aWheelEvent); + void UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback); + void UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback); + void PanFeedbackFinalize(HWND hWnd, bool endFeedback); + + private: + // Delay load info + bool InitLibrary(); + + // Pan and feedback state + nsPointWin mPanIntermediate; + nsPointWin mPanRefPoint; + nsPointWin mPixelScrollDelta; + bool mPanActive; + bool mFeedbackActive; + bool mXAxisFeedback; + bool mYAxisFeedback; + bool mPanInertiaActive; + nsPointWin mPixelScrollOverflow; + + // Zoom state + double mZoomIntermediate; + + // Rotate state + double mRotateIntermediate; +}; + +#endif /* WinGesture_h__ */ diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp new file mode 100644 index 0000000000..814ead00d2 --- /dev/null +++ b/widget/windows/nsWindow.cpp @@ -0,0 +1,8663 @@ +/* -*- 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 "mozilla/AppShutdown.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/PreXULSkeletonUI.h" +#include "mozilla/Logging.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/TimeStamp.h" + +#include "mozilla/ipc/MessageChannel.h" +#include <algorithm> +#include <limits> + +#include "nsWindow.h" +#include "nsAppRunner.h" + +#include <shellapi.h> +#include <windows.h> +#include <wtsapi32.h> +#include <process.h> +#include <commctrl.h> +#include <dbt.h> +#include <unknwn.h> +#include <psapi.h> +#include <rpc.h> + +#include "mozilla/Logging.h" +#include "prtime.h" +#include "prenv.h" + +#include "mozilla/WidgetTraceEvent.h" +#include "nsISupportsPrimitives.h" +#include "nsIKeyEventInPluginCallback.h" +#include "nsITheme.h" +#include "nsIObserverService.h" +#include "nsIScreenManager.h" +#include "imgIContainer.h" +#include "nsIFile.h" +#include "nsIRollupListener.h" +#include "nsIClipboard.h" +#include "WinMouseScrollHandler.h" +#include "nsFontMetrics.h" +#include "nsIFontEnumerator.h" +#include "nsFont.h" +#include "nsRect.h" +#include "nsThreadUtils.h" +#include "nsNativeCharsetUtils.h" +#include "nsGkAtoms.h" +#include "nsCRT.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsWidgetsCID.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsString.h" +#include "mozilla/Services.h" +#include "nsNativeThemeWin.h" +#include "nsWindowsDllInterceptor.h" +#include "nsLayoutUtils.h" +#include "nsView.h" +#include "nsWindowGfx.h" +#include "gfxWindowsPlatform.h" +#include "gfxDWriteFonts.h" +#include "Layers.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" +#include "SystemTimeConverter.h" +#include "WinTaskbar.h" +#include "WidgetUtils.h" +#include "WinContentSystemParameters.h" +#include "nsIWidgetListener.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent +#include "mozilla/TextEventDispatcherListener.h" +#include "mozilla/widget/nsAutoRollup.h" +#include "mozilla/widget/WinNativeEventData.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsStyleConsts.h" +#include "nsBidiKeyboard.h" +#include "nsStyleConsts.h" +#include "gfxConfig.h" +#include "InProcessWinCompositorWidget.h" +#include "InputDeviceUtils.h" +#include "ScreenHelperWin.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_layout.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/PresShell.h" +# include "mozilla/a11y/DocAccessible.h" +# include "mozilla/a11y/LazyInstantiator.h" +# include "mozilla/a11y/Platform.h" +# if !defined(WINABLEAPI) +# include <winable.h> +# endif // !defined(WINABLEAPI) +#endif // defined(ACCESSIBILITY) + +#include "nsIWinTaskbar.h" +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +#include "nsIWindowsUIUtils.h" + +#include "nsWindowDefs.h" + +#include "nsCrashOnException.h" + +#include "nsIContent.h" + +#include "mozilla/BackgroundHangMonitor.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/APZInputBridge.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/KnowsCompositor.h" +#include "mozilla/layers/ScrollInputMethods.h" +#include "InputData.h" + +#include "mozilla/Telemetry.h" +#include "mozilla/plugins/PluginProcessParent.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/layers/IAPZCTreeManager.h" + +#include "DirectManipulationOwner.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::plugins; + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Variables + ** + ** nsWindow Class static initializations and global variables. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: nsWindow statics + * + **************************************************************/ + +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; + +bool nsWindow::sHaveInitializedPrefs = false; +bool nsWindow::sIsRestoringSession = false; + +TriStateBool nsWindow::sHasBogusPopupsDropShadowOnMultiMonitor = TRI_UNKNOWN; + +static SystemTimeConverter<DWORD>& TimeConverter() { + static SystemTimeConverter<DWORD> timeConverterSingleton; + return timeConverterSingleton; +} + +namespace mozilla { + +class CurrentWindowsTimeGetter { + public: + explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {} + + DWORD GetCurrentTime() const { return ::GetTickCount(); } + + void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) { + DWORD currentTime = GetCurrentTime(); + if (sBackwardsSkewStamp && currentTime == sLastPostTime) { + // There's already one inflight with this timestamp. Don't + // send a duplicate. + return; + } + sBackwardsSkewStamp = Some(aNow); + sLastPostTime = currentTime; + static_assert(sizeof(WPARAM) >= sizeof(DWORD), + "Can't fit a DWORD in a WPARAM"); + ::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0); + } + + static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime, + TimeStamp* aOutSkewStamp) { + if (aPostTime != sLastPostTime) { + // The SKEWFIX message is stale; we've sent a new one since then. + // Ignore this one. + return false; + } + MOZ_ASSERT(sBackwardsSkewStamp); + *aOutSkewStamp = sBackwardsSkewStamp.value(); + sBackwardsSkewStamp = Nothing(); + return true; + } + + private: + static Maybe<TimeStamp> sBackwardsSkewStamp; + static DWORD sLastPostTime; + HWND mWnd; +}; + +Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp; +DWORD CurrentWindowsTimeGetter::sLastPostTime = 0; + +} // namespace mozilla + +/************************************************************** + * + * SECTION: globals variables + * + **************************************************************/ + +static const char* sScreenManagerContractID = + "@mozilla.org/gfx/screenmanager;1"; + +extern mozilla::LazyLogModule gWindowsLog; + +// 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; + +// We should never really try to accelerate windows bigger than this. In some +// cases this might lead to no D3D9 acceleration where we could have had it +// but D3D9 does not reliably report when it supports bigger windows. 8192 +// is as safe as we can get, we know at least D3D10 hardware always supports +// this, other hardware we expect to report correctly in D3D9. +#define MAX_ACCELERATED_DIMENSION 8192 + +// On window open (as well as after), Windows has an unfortunate habit of +// sending rather a lot of WM_NCHITTEST messages. Because we have to do point +// to DOM target conversions for these, we cache responses for a given +// coordinate this many milliseconds: +#define HITTEST_CACHE_LIFETIME_MS 50 + +#if defined(ACCESSIBILITY) + +namespace mozilla { + +/** + * Windows touchscreen code works by setting a global WH_GETMESSAGE hook and + * injecting tiptsf.dll. The touchscreen process then posts registered messages + * to our main thread. The tiptsf hook picks up those registered messages and + * uses them as commands, some of which call into UIA, which then calls into + * MSAA, which then sends WM_GETOBJECT to us. + * + * We can get ahead of this by installing our own thread-local WH_GETMESSAGE + * hook. Since thread-local hooks are called ahead of global hooks, we will + * see these registered messages before tiptsf does. At this point we can then + * raise a flag that blocks a11y before invoking CallNextHookEx which will then + * invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the + * flag by calling TIPMessageHandler::IsA11yBlocked(). + * + * For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook + * function that also calls into UIA. + */ +class TIPMessageHandler { + public: + ~TIPMessageHandler() { + if (mHook) { + ::UnhookWindowsHookEx(mHook); + } + } + + static void Initialize() { + if (!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 = sProcessCaretEventsStub.Set( + sTipTsfInterceptor, "ProcessCaretEvents", &ProcessCaretEventsHook); + MOZ_ASSERT(ok); + } + + if (!sSendMessageTimeoutWStub) { + sUser32Intercept.Init("user32.dll"); + DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set( + sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook); + MOZ_ASSERT(hooked); + } + } + + class MOZ_RAII A11yInstantiationBlocker { + public: + A11yInstantiationBlocker() { + if (!TIPMessageHandler::sInstance) { + return; + } + ++TIPMessageHandler::sInstance->mA11yBlockCount; + } + + ~A11yInstantiationBlocker() { + if (!TIPMessageHandler::sInstance) { + return; + } + MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0); + --TIPMessageHandler::sInstance->mA11yBlockCount; + } + }; + + friend class A11yInstantiationBlocker; + + static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) { + if (aCode < 0 || !sInstance) { + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + + MSG* msg = reinterpret_cast<MSG*>(aLParam); + UINT& msgCode = msg->message; + + for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) { + if (msgCode == sInstance->mMessages[i]) { + A11yInstantiationBlocker block; + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + } + + return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam); + } + + static 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 || + static_cast<DWORD>(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 WindowsDllInterceptor::FuncHookType<WINEVENTPROC> + sProcessCaretEventsStub; + static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)> + sSendMessageTimeoutWStub; + static StaticAutoPtr<TIPMessageHandler> sInstance; + + HHOOK mHook; + UINT mMessages[7]; + uint32_t mA11yBlockCount; +}; + +WindowsDllInterceptor TIPMessageHandler::sTipTsfInterceptor; +WindowsDllInterceptor::FuncHookType<WINEVENTPROC> + TIPMessageHandler::sProcessCaretEventsStub; +WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)> + TIPMessageHandler::sSendMessageTimeoutWStub; +StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance; + +} // namespace mozilla + +#endif // defined(ACCESSIBILITY) + +/************************************************************** + ************************************************************** + ** + ** BLOCK: nsIWidget impl. + ** + ** nsIWidget interface implementation, broken down into + ** sections. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: nsWindow construction and destruction + * + **************************************************************/ + +nsWindow::nsWindow(bool aIsChildWindow) + : nsWindowBase(), + mResizeState(NOT_RESIZING), + mIsChildWindow(aIsChildWindow) { + mIconSmall = nullptr; + mIconBig = nullptr; + mWnd = nullptr; + mLastKillFocusWindow = nullptr; + mTransitionWnd = nullptr; + mPaintDC = nullptr; + mPrevWndProc = nullptr; + mNativeDragTarget = nullptr; + mDeviceNotifyHandle = nullptr; + mInDtor = false; + mIsVisible = false; + mIsTopWidgetWindow = false; + mDisplayPanFeedback = false; + mTouchWindow = false; + mFutureMarginsToUse = false; + mCustomNonClient = false; + mHideChrome = false; + mFullscreenMode = false; + mMousePresent = false; + mMouseInDraggableArea = false; + mDestroyCalled = false; + mIsEarlyBlankWindow = false; + mIsShowingPreXULSkeletonUI = false; + mResizable = 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; + 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 + mAspectRatio = 0.0; // not yet set, will be calculated on first use + + mTaskbarPreview = nullptr; + + mCompositorWidgetDelegate = 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) + mozilla::TIPMessageHandler::Initialize(); +#endif // defined(ACCESSIBILITY) + if (SUCCEEDED(::OleInitialize(nullptr))) { + sIsOleInitialized = TRUE; + } + NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n"); + MouseScrollHandler::Initialize(); + // Init theme data + nsUXThemeData::UpdateNativeThemeInfo(); + RedirectedKeyDownMessageManager::Forget(); + if (mPointerEvents.ShouldEnableInkCollector()) { + InkCollector::sInkCollector = new InkCollector(); + } + } // !sInstanceCount + + mIdleService = nullptr; + + mSizeConstraintsScale = GetDefaultScale().scale; + mMaxTextureSize = -1; // Will be calculated when layer manager is created. + + mRequestFxrOutputPending = false; + + 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) { + if (InkCollector::sInkCollector) { + InkCollector::sInkCollector->Shutdown(); + InkCollector::sInkCollector = nullptr; + } + IMEHandler::Terminate(); + NS_IF_RELEASE(sCursorImgContainer); + if (sIsOleInitialized) { + ::OleFlushClipboard(); + ::OleUninitialize(); + sIsOleInitialized = FALSE; + } + } + + NS_IF_RELEASE(mNativeDragTarget); +} + +/************************************************************** + * + * SECTION: nsIWidget::Create, nsIWidget::Destroy + * + * Creating and destroying windows for this widget. + * + **************************************************************/ + +// Allow Derived classes to modify the height that is passed +// when the window is created or resized. +int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; } + +static bool ShouldCacheTitleBarInfo(nsWindowType aWindowType, + nsBorderStyle aBorderStyle) { + return (aWindowType == eWindowType_toplevel) && + (aBorderStyle == eBorderStyle_default || + aBorderStyle == eBorderStyle_all) && + (!nsUXThemeData::sTitlebarInfoPopulatedThemed || + !nsUXThemeData::sTitlebarInfoPopulatedAero); +} + +void nsWindow::SendAnAPZEvent(InputData& aEvent) { + LRESULT popupHandlingResult; + if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) { + // We need to consume the event after using it to roll up the popup(s). + return; + } + + APZEventResult result; + if (mAPZC) { + result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent); + } + if (result.mStatus == nsEventStatus_eConsumeNoDefault) { + return; + } + + MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT || + aEvent.mInputType == PINCHGESTURE_INPUT); + + if (aEvent.mInputType == PANGESTURE_INPUT) { + PanGestureInput& panInput = aEvent.AsPanGestureInput(); + WidgetWheelEvent event = panInput.ToWidgetEvent(this); + ProcessUntransformedAPZEvent(&event, result); + + return; + } + + PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput(); + WidgetWheelEvent event = pinchInput.ToWidgetEvent(this); + ProcessUntransformedAPZEvent(&event, result); +} + +void nsWindow::RecreateDirectManipulationIfNeeded() { + DestroyDirectManipulation(); + + if (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_popup) { + return; + } + + if (!(StaticPrefs::apz_allow_zooming() || + StaticPrefs::apz_windows_use_direct_manipulation()) || + StaticPrefs::apz_windows_force_disable_direct_manipulation()) { + return; + } + + if (!IsWin10OrLater()) { + // Chrome source said the Windows Direct Manipulation implementation had + // important bugs until Windows 10 (although IE on Windows 8.1 seems to use + // Direct Manipulation). + return; + } + + mDmOwner = MakeUnique<DirectManipulationOwner>(this); + + LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(), + GetHeight(mBounds.Height())); + mDmOwner->Init(bounds); +} + +void nsWindow::ResizeDirectManipulationViewport() { + if (mDmOwner) { + LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(), + GetHeight(mBounds.Height())); + mDmOwner->ResizeViewport(bounds); + } +} + +void nsWindow::DestroyDirectManipulation() { + if (mDmOwner) { + mDmOwner->Destroy(); + mDmOwner.reset(); + } +} + +// Create the proper widget +nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) { + nsWidgetInitData defaultInitData; + if (!aInitData) aInitData = &defaultInitData; + + 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; + mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed; + mAlwaysOnTop = aInitData->mAlwaysOnTop; + mResizable = aInitData->mResizable; + + DWORD style = WindowStyle(); + DWORD extendedStyle = WindowExStyle(); + + // When window is PiP window on Windows7, WS_EX_COMPOSITED is set to suppress + // flickering during resizing with hardware acceleration. + bool isPIPWindow = aInitData && aInitData->mPIPWindow; + if (isPIPWindow && !IsWin8OrLater() && + gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) && + WidgetTypeSupportsAcceleration()) { + extendedStyle |= WS_EX_COMPOSITED; + } + + if (mWindowType == eWindowType_popup) { + if (!aParent) { + parent = nullptr; + } + + if (!IsWin8OrLater() && HasBogusPopupsDropShadowOnMultiMonitor() && + ShouldUseOffMainThreadCompositing()) { + 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; + } + + if (aInitData->mWindowType == eWindowType_toplevel && !aParent) { + mWnd = ConsumePreXULSkeletonUIHandle(); + if (mWnd) { + MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle, + "The skeleton UI window style should match the expected " + "style for the first window created"); + MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx, + "The skeleton UI window extended style should match the " + "expected extended style for the first window created"); + mIsShowingPreXULSkeletonUI = true; + + // If we successfully consumed the pre-XUL skeleton UI, just update + // our internal state to match what is currently being displayed. + mIsVisible = true; + mSizeMode = WasPreXULSkeletonUIMaximized() ? nsSizeMode_Maximized + : nsSizeMode_Normal; + + // These match the margins set in browser-tabsintitlebar.js with + // default prefs on Windows. Bug 1673092 tracks lining this up with + // that more correctly instead of hard-coding it. + LayoutDeviceIntMargin margins(0, 2, 2, 2); + SetNonClientMargins(margins); + + // Reset the WNDPROC for this window and its whole class, as we had + // to use our own WNDPROC when creating the the skeleton UI window. + ::SetWindowLongPtrW(mWnd, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>( + WinUtils::NonClientDpiScalingDefWindowProcW)); + ::SetClassLongPtrW(mWnd, GCLP_WNDPROC, + reinterpret_cast<LONG_PTR>( + WinUtils::NonClientDpiScalingDefWindowProcW)); + } + } + + if (!mWnd) { + mWnd = + ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(), + aRect.Y(), aRect.Width(), GetHeight(aRect.Height()), + parent, nullptr, nsToolkit::mDllInstance, nullptr); + } + + if (!mWnd) { + NS_WARNING("nsWindow CreateWindowEx failed."); + return NS_ERROR_FAILURE; + } + + mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd); + + // If mDefaultScale is set before mWnd has been set, it will have the scale of + // the primary monitor, rather than the monitor that the window is actually + // on. For non-popup windows this gets corrected by the WM_DPICHANGED message + // which resets mDefaultScale, but for popup windows we don't reset + // mDefaultScale on that message. In order to ensure that popup windows + // spawned on a non-primary monitor end up with the correct scale, we reset + // mDefaultScale here so that it gets recomputed using the correct monitor now + // that we have a mWnd. + mDefaultScale = -1.0; + + if (mIsRTL) { + DWORD dwAttribute = TRUE; + DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute, + sizeof dwAttribute); + } + + if (mOpeningAnimationSuppressed) { + SuppressAnimation(true); + } + + if (mAlwaysOnTop) { + ::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + + 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 = (WNDPROC)::SetWindowLongPtrW( + 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); + + // Do some initialization work, but only if (a) it hasn't already been done, + // and (b) this is the hidden window (which is conveniently created before + // any visible windows but after the profile has been initialized). + if (!sHaveInitializedPrefs && mWindowType == eWindowType_invisible) { + sSwitchKeyboardLayout = + Preferences::GetBool("intl.keyboard.per_window_layout", false); + sHaveInitializedPrefs = true; + } + + // 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); + } + + static bool a11yPrimed = false; + if (!a11yPrimed && mWindowType == eWindowType_toplevel) { + a11yPrimed = true; + if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) { + ::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0); + } + } + + RecreateDirectManipulationIfNeeded(); + + 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); + + DestroyDirectManipulation(); + + /** + * On windows the LayerManagerOGL destructor wants the widget to be around for + * cleanup. It also would like to have the HWND intact, so we nullptr it here. + */ + DestroyLayerManager(); + + /* We should clear our cached resources now and not wait for the GC to + * delete the nsWindow. */ + ClearCachedResources(); + + InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle); + mDeviceNotifyHandle = nullptr; + + // The DestroyWindow function destroys the specified window. The function + // sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it + // and remove the keyboard focus from it. The function also destroys the + // window's menu, flushes the thread message queue, destroys timers, removes + // clipboard ownership, and breaks the clipboard viewer chain (if the window + // is at the top of the viewer chain). + // + // If the specified window is a parent or owner window, DestroyWindow + // automatically destroys the associated child or owned windows when it + // destroys the parent or owner window. The function first destroys child or + // owned windows, and then it destroys the parent or owner window. + VERIFY(::DestroyWindow(mWnd)); + + // Our windows can be subclassed which may prevent us receiving WM_DESTROY. If + // OnDestroy() didn't get called, call it now. + if (false == mOnDestroyCalled) { + MSGResult msgResult; + mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult); + OnDestroy(); + } +} + +/************************************************************** + * + * SECTION: Window class utilities + * + * Utilities for calculating the proper window class name for + * Create window. + * + **************************************************************/ + +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; + } + } + } + + if (mIsChildWindow) { + style |= WS_CLIPCHILDREN; + if (!(style & WS_POPUP)) { + style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive. + } + } + + VERIFY_WINDOW_STYLE(style); + return style; +} + +// Return nsWindow extended styles +DWORD nsWindow::WindowExStyle() { + 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"); + } + + mPrevWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtrW( + 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)) { + SetWindowLongPtrW(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 +void nsWindow::SetParent(nsIWidget* aNewParent) { + nsCOMPtr<nsIWidget> kungFuDeathGrip(this); + nsIWidget* parent = GetParent(); + if (parent) { + parent->RemoveChild(this); + } + + mParent = aNewParent; + + if (aNewParent) { + ReparentNativeWidget(aNewParent); + aNewParent->AddChild(this); + return; + } + if (mWnd) { + // If we have no parent, SetParent should return the desktop. + VERIFY(::SetParent(mWnd, nullptr)); + RecreateDirectManipulationIfNeeded(); + } +} + +void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) { + MOZ_ASSERT(aNewParent, "null widget"); + + mParent = aNewParent; + if (mWindowType == 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); + RecreateDirectManipulationIfNeeded(); + } +} + +nsIWidget* nsWindow::GetParent(void) { + if (mIsTopWidgetWindow) { + return nullptr; + } + if (mInDtor || mOnDestroyCalled) { + return nullptr; + } + return mParent; +} + +static int32_t RoundDown(double aDouble) { + return aDouble > 0 ? static_cast<int32_t>(floor(aDouble)) + : static_cast<int32_t>(ceil(aDouble)); +} + +float nsWindow::GetDPI() { + float dpi = 96.0f; + nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); + if (screen) { + screen->GetDpi(&dpi); + } + return dpi; +} + +double nsWindow::GetDefaultScaleInternal() { + if (mDefaultScale <= 0.0) { + mDefaultScale = WinUtils::LogToPhysFactor(mWnd); + } + return mDefaultScale; +} + +int32_t nsWindow::LogToPhys(double aValue) { + return WinUtils::LogToPhys( + ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue); +} + +nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) { + return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner)); +} + +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) { + reinterpret_cast<nsTArray<nsWindow*>*>(aParam)->AppendElement(wnd); + } + return TRUE; +} + +BOOL CALLBACK nsWindow::EnumAllThreadWindowProc(HWND aWnd, LPARAM aParam) { + nsWindow* wnd = WinUtils::GetNSWindowPtr(aWnd); + if (wnd) { + reinterpret_cast<nsTArray<nsWindow*>*>(aParam)->AppendElement(wnd); + } + EnumChildWindows(aWnd, EnumAllChildWindProc, aParam); + return TRUE; +} + +/* static*/ +nsTArray<nsWindow*> nsWindow::EnumAllWindows() { + nsTArray<nsWindow*> windows; + EnumThreadWindows(GetCurrentThreadId(), EnumAllThreadWindowProc, + reinterpret_cast<LPARAM>(&windows)); + return windows; +} + +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. + * + **************************************************************/ + +void nsWindow::Show(bool bState) { + if (bState && mIsShowingPreXULSkeletonUI) { + // The first time we decide to actually show the window is when we decide + // that we've taken over the window from the skeleton UI, and we should + // no longer treat resizes / moves specially. + mIsShowingPreXULSkeletonUI = false; + // Initialize the UI state - this would normally happen below, but since + // we're actually already showing, we won't hit it in the normal way. + ::SendMessageW(mWnd, WM_CHANGEUISTATE, + MAKEWPARAM(UIS_INITIALIZE, UISF_HIDEFOCUS | UISF_HIDEACCEL), + 0); + } + + 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 && + !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + 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; + + // Set the cursor before showing the window to avoid the default wait + // cursor. + SetCursor(eCursor_standard, nullptr, 0, 0); + + 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() && !mAlwaysOnTop) { + ::ShowWindow(mWnd, SW_SHOWNORMAL); + } else { + ::ShowWindow(mWnd, SW_SHOWNOACTIVATE); + // Don't flicker the window if we're restoring session + if (!sIsRestoringSession) { + Unused << GetAttention(2); + } + } + break; + } + } else { + DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW; + if (wasVisible) flags |= SWP_NOZORDER; + if (mAlwaysOnTop) flags |= SWP_NOACTIVATE; + + 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 + + if (mOpeningAnimationSuppressed) { + SuppressAnimation(false); + } +} + +/************************************************************** + * + * 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. +// XXX this is apparently still needed in Windows 7 and later +void nsWindow::ClearThemeRegion() { + if (!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 (!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; + ::RegisterTouchWindow(mWnd, TWF_WANTPALM); + ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0); +} + +BOOL CALLBACK nsWindow::RegisterTouchForDescendants(HWND aWnd, LPARAM aMsg) { + nsWindow* win = WinUtils::GetNSWindowPtr(aWnd); + if (win) { + ::RegisterTouchWindow(aWnd, TWF_WANTPALM); + } + return TRUE; +} + +void nsWindow::LockAspectRatio(bool aShouldLock) { + if (aShouldLock) { + mAspectRatio = (float)mBounds.Height() / (float)mBounds.Width(); + } else { + mAspectRatio = 0.0; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::SetWindowMouseTransparent + * + * Sets whether the window should ignore mouse events. + * + **************************************************************/ +void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) { + if (!mWnd) { + return; + } + + LONG_PTR oldStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE); + LONG_PTR newStyle = aIsTransparent ? (oldStyle | WS_EX_TRANSPARENT) + : (oldStyle & ~WS_EX_TRANSPARENT); + ::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, newStyle); + mMouseTransparent = aIsTransparent; +} + +/************************************************************** + * + * 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 && mResizable) { + c.mMinSize.width = + std::max(int32_t(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width); + c.mMinSize.height = + std::max(int32_t(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height); + } + + if (mMaxTextureSize > 0) { + // We can't make ThebesLayers bigger than this anyway.. no point it letting + // a window grow bigger as we won't be able to draw content there in + // general. + c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize); + c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize); + } + + mSizeConstraintsScale = GetDefaultScale().scale; + + nsBaseWidget::SetSizeConstraints(c); +} + +const SizeConstraints nsWindow::GetSizeConstraints() { + double scale = GetDefaultScale().scale; + if (mSizeConstraintsScale == scale || mSizeConstraintsScale == 0.0) { + return mSizeConstraints; + } + scale /= mSizeConstraintsScale; + SizeConstraints c = mSizeConstraints; + if (c.mMinSize.width != NS_MAXSIZE) { + c.mMinSize.width = NSToIntRound(c.mMinSize.width * scale); + } + if (c.mMinSize.height != NS_MAXSIZE) { + c.mMinSize.height = NSToIntRound(c.mMinSize.height * scale); + } + if (c.mMaxSize.width != NS_MAXSIZE) { + c.mMaxSize.width = NSToIntRound(c.mMaxSize.width * scale); + } + if (c.mMaxSize.height != NS_MAXSIZE) { + c.mMaxSize.height = NSToIntRound(c.mMaxSize.height * scale); + } + return c; +} + +// Move this component +void nsWindow::Move(double aX, double aY) { + if (mWindowType == 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.IsEqualXY(x, y)) { + // Nothing to do, since it is already positioned correctly. + return; + } + + mBounds.MoveTo(x, y); + + if (mWnd) { +#ifdef DEBUG + // complain if a window is moved offscreen (legal, but potentially + // worrisome) + if (mIsTopWidgetWindow) { // only a problem for top-level windows + // Make sure this window is actually on the screen before we move it + // XXX: Needs multiple monitor support + HDC dc = ::GetDC(mWnd); + if (dc) { + if (::GetDeviceCaps(dc, TECHNOLOGY) == DT_RASDISPLAY) { + RECT workArea; + ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0); + // no annoying assertions. just mention the issue. + if (x < 0 || x >= workArea.right || y < 0 || y >= workArea.bottom) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("window moved to offscreen position\n")); + } + } + ::ReleaseDC(mWnd, dc); + } + } +#endif + + // Normally, when the skeleton UI is disabled, we resize+move the window + // before showing it in order to ensure that it restores to the correct + // position when the user un-maximizes it. However, when we are using the + // skeleton UI, this results in the skeleton UI window being moved around + // undesirably before being locked back into the maximized position. To + // avoid this, we simply set the placement to restore to via + // SetWindowPlacement. It's a little bit more of a dance, though, since we + // need to convert the workspace coords that SetWindowPlacement uses to the + // screen space coordinates we normally use with SetWindowPos. + if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) { + WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)}; + VERIFY(::GetWindowPlacement(mWnd, &pl)); + + HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL); + if (NS_WARN_IF(!monitor)) { + return; + } + MONITORINFO mi = {sizeof(MONITORINFO)}; + VERIFY(::GetMonitorInfo(monitor, &mi)); + + int32_t deltaX = + x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left; + int32_t deltaY = + y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top; + pl.rcNormalPosition.left += deltaX; + pl.rcNormalPosition.right += deltaX; + pl.rcNormalPosition.top += deltaY; + pl.rcNormalPosition.bottom += deltaY; + VERIFY(::SetWindowPlacement(mWnd, &pl)); + } else { + 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 && 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(); + + ResizeDirectManipulationViewport(); + } + NotifyRollupGeometryChange(); +} + +// Resize this component +void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) { + // for top-level windows only, convert coordinates from desktop pixels + // (the "parent" coordinate space) to the window's device pixel space + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(aWidth * scale); + int32_t height = NSToIntRound(aHeight * scale); + + NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize"); + NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize"); + + ConstrainSize(&width, &height); + + // Avoid unnecessary resizing calls + if (mBounds.IsEqualSize(width, height)) { + if (aRepaint) { + Invalidate(); + } + return; + } + + // Set cached value for lightweight and printing + bool wasLocking = mAspectRatio != 0.0; + mBounds.SizeTo(width, height); + if (wasLocking) { + LockAspectRatio(true); // This causes us to refresh the mAspectRatio value + } + + if (mWnd) { + // Refer to the comment above a similar check in nsWindow::Move + if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) { + WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)}; + VERIFY(::GetWindowPlacement(mWnd, &pl)); + pl.rcNormalPosition.right = pl.rcNormalPosition.left + width; + pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height); + mResizeState = RESIZING; + VERIFY(::SetWindowPlacement(mWnd, &pl)); + mResizeState = NOT_RESIZING; + } else { + UINT flags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE; + + if (!aRepaint) { + flags |= SWP_NOREDRAW; + } + + ClearThemeRegion(); + double oldScale = mDefaultScale; + mResizeState = RESIZING; + VERIFY( + ::SetWindowPos(mWnd, nullptr, 0, 0, width, GetHeight(height), flags)); + + mResizeState = NOT_RESIZING; + if (WinUtils::LogToPhysFactor(mWnd) != oldScale) { + ChangedDPI(); + } + SetThemeRegion(); + + ResizeDirectManipulationViewport(); + } + } + + if (aRepaint) Invalidate(); + + NotifyRollupGeometryChange(); +} + +// Resize this component +void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) { + // for top-level windows only, convert coordinates from desktop pixels + // (the "parent" coordinate space) to the window's device pixel space + double scale = + BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t x = NSToIntRound(aX * scale); + int32_t y = NSToIntRound(aY * scale); + int32_t width = NSToIntRound(aWidth * scale); + int32_t height = NSToIntRound(aHeight * scale); + + NS_ASSERTION((width >= 0), "Negative width passed to nsWindow::Resize"); + NS_ASSERTION((height >= 0), "Negative height passed to nsWindow::Resize"); + + ConstrainSize(&width, &height); + + // Avoid unnecessary resizing calls + if (mBounds.IsEqualRect(x, y, width, height)) { + if (aRepaint) { + Invalidate(); + } + return; + } + + // Set cached value for lightweight and printing + mBounds.SetRect(x, y, width, height); + + if (mWnd) { + // Refer to the comment above a similar check in nsWindow::Move + if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) { + WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)}; + VERIFY(::GetWindowPlacement(mWnd, &pl)); + + HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL); + if (NS_WARN_IF(!monitor)) { + return; + } + MONITORINFO mi = {sizeof(MONITORINFO)}; + VERIFY(::GetMonitorInfo(monitor, &mi)); + + int32_t deltaX = + x + mi.rcWork.left - mi.rcMonitor.left - pl.rcNormalPosition.left; + int32_t deltaY = + y + mi.rcWork.top - mi.rcMonitor.top - pl.rcNormalPosition.top; + pl.rcNormalPosition.left += deltaX; + pl.rcNormalPosition.right = pl.rcNormalPosition.left + width; + pl.rcNormalPosition.top += deltaY; + pl.rcNormalPosition.bottom = pl.rcNormalPosition.top + GetHeight(height); + VERIFY(::SetWindowPlacement(mWnd, &pl)); + } else { + UINT flags = SWP_NOZORDER | SWP_NOACTIVATE; + if (!aRepaint) { + flags |= SWP_NOREDRAW; + } + + ClearThemeRegion(); + + double oldScale = mDefaultScale; + mResizeState = RESIZING; + VERIFY( + ::SetWindowPos(mWnd, nullptr, x, y, width, GetHeight(height), flags)); + mResizeState = NOT_RESIZING; + if (WinUtils::LogToPhysFactor(mWnd) != oldScale) { + ChangedDPI(); + } + + if (mTransitionWnd) { + // If we have a fullscreen transition window, we need to make + // it topmost again, otherwise the taskbar may be raised by + // the system unexpectedly when we leave fullscreen state. + ::SetWindowPos(mTransitionWnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + SetThemeRegion(); + + ResizeDirectManipulationViewport(); + } + } + + if (aRepaint) Invalidate(); + + NotifyRollupGeometryChange(); +} + +mozilla::Maybe<bool> nsWindow::IsResizingNativeWidget() { + if (mResizeState == RESIZING) { + return Some(true); + } + return Some(false); +} + +nsresult 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()->mButton != MouseButton::ePrimary) { + // 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; + + // If we are still displaying a maximized pre-XUL skeleton UI, ignore the + // noise of sizemode changes. Once we have "shown" the window for the first + // time (called nsWindow::Show(true), even though the window is already + // technically displayed), we will again accept sizemode changes. + if (mIsShowingPreXULSkeletonUI && WasPreXULSkeletonUIMaximized()) { + return; + } + + // 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: + mode = SW_MINIMIZE; + 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); + } +} + +RefPtr<IVirtualDesktopManager> GetVirtualDesktopManager() { +#ifdef __MINGW32__ + return nullptr; +#else + if (!IsWin10OrLater()) { + return nullptr; + } + + RefPtr<IServiceProvider> serviceProvider; + HRESULT hr = ::CoCreateInstance( + CLSID_ImmersiveShell, NULL, CLSCTX_LOCAL_SERVER, + __uuidof(IServiceProvider), getter_AddRefs(serviceProvider)); + if (FAILED(hr)) { + return nullptr; + } + + RefPtr<IVirtualDesktopManager> desktopManager; + serviceProvider->QueryService(__uuidof(IVirtualDesktopManager), + desktopManager.StartAssignment()); + return desktopManager; +#endif +} + +void nsWindow::GetWorkspaceID(nsAString& workspaceID) { + RefPtr<IVirtualDesktopManager> desktopManager = GetVirtualDesktopManager(); + if (!desktopManager) { + return; + } + + GUID desktop; + HRESULT hr = desktopManager->GetWindowDesktopId(mWnd, &desktop); + if (FAILED(hr)) { + return; + } + + RPC_WSTR workspaceIDStr = nullptr; + if (UuidToStringW(&desktop, &workspaceIDStr) == RPC_S_OK) { + workspaceID.Assign((wchar_t*)workspaceIDStr); + RpcStringFreeW(&workspaceIDStr); + } +} + +void nsWindow::MoveToWorkspace(const nsAString& workspaceID) { + RefPtr<IVirtualDesktopManager> desktopManager = GetVirtualDesktopManager(); + if (!desktopManager) { + return; + } + + GUID desktop; + const nsString& flat = PromiseFlatString(workspaceID); + RPC_WSTR workspaceIDStr = reinterpret_cast<RPC_WSTR>((wchar_t*)flat.get()); + if (UuidFromStringW(workspaceIDStr, &desktop) == RPC_S_OK) { + desktopManager->MoveWindowToDesktop(mWnd, desktop); + } +} + +void nsWindow::SuppressAnimation(bool aSuppress) { + DWORD dwAttribute = aSuppress ? TRUE : FALSE; + DwmSetWindowAttribute(mWnd, DWMWA_TRANSITIONS_FORCEDISABLED, &dwAttribute, + sizeof dwAttribute); +} + +// Constrain a potential move to fit onscreen +// Position (aX, aY) is specified in Windows screen (logical) pixels, +// except when using per-monitor DPI, in which case it's device pixels. +void nsWindow::ConstrainPosition(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 +void nsWindow::Enable(bool bState) { + if (mWnd) { + ::EnableWindow(mWnd, bState); + } +} + +// Return the current enable state +bool nsWindow::IsEnabled() const { + return !mWnd || (::IsWindowEnabled(mWnd) && + ::IsWindowEnabled(::GetAncestor(mWnd, GA_ROOT))); +} + +/************************************************************** + * + * SECTION: nsIWidget::SetFocus + * + * Give the focus to this widget. + * + **************************************************************/ + +void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) { + if (mWnd) { +#ifdef WINSTATE_DEBUG_OUTPUT + if (mWnd == WinUtils::GetTopLevelHWND(mWnd)) { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** SetFocus: [ top] raise=%d\n", aRaise == Raise::Yes)); + } else { + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** SetFocus: [child] raise=%d\n", aRaise == Raise::Yes)); + } +#endif + // Uniconify, if necessary + HWND toplevelWnd = WinUtils::GetTopLevelHWND(mWnd); + if (aRaise == Raise::Yes && ::IsIconic(toplevelWnd)) { + ::ShowWindow(toplevelWnd, SW_RESTORE); + } + ::SetFocus(mWnd); + } +} + +/************************************************************** + * + * SECTION: Bounds + * + * GetBounds, GetClientBounds, GetScreenBounds, + * GetRestoredBounds, GetClientOffset + * 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.SizeTo(r.right - r.left, r.bottom - r.top); + + // popup window bounds' are in screen coordinates, not relative to parent + // window + if (mWindowType == eWindowType_popup) { + rect.MoveTo(r.left, r.top); + return rect; + } + + // chrome on parent: + // ___ 5,5 (chrome start) + // | ____ 10,10 (client start) + // | | ____ 20,20 (child start) + // | | | + // 20,20 - 5,5 = 15,15 (??) + // minus GetClientOffset: + // 15,15 - 5,5 = 10,10 + // + // no chrome on parent: + // ______ 10,10 (win start) + // | ____ 20,20 (child start) + // | | + // 20,20 - 10,10 = 10,10 + // + // walking the chain: + // ___ 5,5 (chrome start) + // | ___ 10,10 (client start) + // | | ___ 20,20 (child start) + // | | | __ 30,30 (child start) + // | | | | + // 30,30 - 20,20 = 10,10 (offset from second child to first) + // 20,20 - 5,5 = 15,15 + 10,10 = 25,25 (??) + // minus GetClientOffset: + // 25,25 - 5,5 = 20,20 (offset from second child to parent client) + + // convert coordinates if parent exists + HWND parent = ::GetParent(mWnd); + if (parent) { + RECT pr; + VERIFY(::GetWindowRect(parent, &pr)); + r.left -= pr.left; + r.top -= pr.top; + // adjust for chrome + nsWindow* pWidget = static_cast<nsWindow*>(GetParent()); + if (pWidget && pWidget->IsTopLevelWidget()) { + LayoutDeviceIntPoint clientOffset = pWidget->GetClientOffset(); + r.left -= clientOffset.x; + r.top -= clientOffset.y; + } + } + rect.MoveTo(r.left, r.top); + 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.SizeTo(r.right - r.left, r.bottom - r.top); + return rect; +} + +// Like GetBounds, but don't offset by the parent +LayoutDeviceIntRect nsWindow::GetScreenBounds() { + if (!mWnd) { + return mBounds; + } + + RECT r; + VERIFY(::GetWindowRect(mWnd, &r)); + + return LayoutDeviceIntRect(r.left, r.top, r.right - r.left, r.bottom - r.top); +} + +nsresult nsWindow::GetRestoredBounds(LayoutDeviceIntRect& aRect) { + if (SizeMode() == nsSizeMode_Normal) { + aRect = GetScreenBounds(); + return NS_OK; + } + if (!mWnd) { + return NS_ERROR_FAILURE; + } + + WINDOWPLACEMENT pl = {sizeof(WINDOWPLACEMENT)}; + VERIFY(::GetWindowPlacement(mWnd, &pl)); + const RECT& r = pl.rcNormalPosition; + + HMONITOR monitor = ::MonitorFromWindow(mWnd, MONITOR_DEFAULTTONULL); + if (!monitor) { + return NS_ERROR_FAILURE; + } + MONITORINFO mi = {sizeof(MONITORINFO)}; + VERIFY(::GetMonitorInfo(monitor, &mi)); + + aRect.SetRect(r.left, r.top, r.right - r.left, r.bottom - r.top); + aRect.MoveBy(mi.rcWork.left - mi.rcMonitor.left, + mi.rcWork.top - mi.rcMonitor.top); + return NS_OK; +} + +// Return the x,y offset of the client area from the origin of the window. If +// the window is borderless returns (0,0). +LayoutDeviceIntPoint nsWindow::GetClientOffset() { + if (!mWnd) { + return LayoutDeviceIntPoint(0, 0); + } + + RECT r1; + GetWindowRect(mWnd, &r1); + LayoutDeviceIntPoint pt = WidgetToScreenOffset(); + return LayoutDeviceIntPoint(pt.x - 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); + OnResize(WinUtils::ToIntRect(clientRc).Size()); + + // Invalidate and update + Invalidate(); +} + +// Internally track the caption status via a window property. Required +// due to our internal handling of WM_NCACTIVATE when custom client +// margins are set. +static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty"; +typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); +static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr> + sGetWindowInfoPtrStub; + +BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) { + if (!sGetWindowInfoPtrStub) { + NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!"); + return FALSE; + } + int windowStatus = + reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty)); + // No property set, return the default data. + if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi); + // Call GetWindowInfo and update dwWindowStatus with our + // internally tracked value. + BOOL result = sGetWindowInfoPtrStub(hWnd, pwi); + if (result && pwi) + pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION); + return result; +} + +void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) { + if (!mWnd) return; + + sUser32Intercept.Init("user32.dll"); + sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo", + &GetWindowInfoHook); + if (!sGetWindowInfoPtrStub) { + return; + } + + // Update our internally tracked caption status + SetPropW(mWnd, kManageWindowInfoProperty, + reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1)); +} + +/** + * 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)); + + float dpi = GetDPI(); + + // mCaptionHeight is the default size of the NC area at + // the top of the window. If the window has a caption, + // the size is calculated as the sum of: + // SM_CYFRAME - The thickness of the sizing border + // around a resizable window + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows + // SM_CYCAPTION - The height of the caption area + // + // If the window does not have a caption, mCaptionHeight will be equal to + // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)` + mCaptionHeight = + WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) + + (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + + WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) + : 0); + + // mHorResizeMargin is the size of the default NC areas on the + // left and right sides of our window. It is calculated as + // the sum of: + // SM_CXFRAME - The thickness of the sizing border + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows + // + // If the window does not have a caption, mHorResizeMargin will be equal to + // `WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi)` + mHorResizeMargin = + WinUtils::GetSystemMetricsForDpi(SM_CXFRAME, dpi) + + (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) + : 0); + + // mVertResizeMargin is the size of the default NC area at the + // bottom of the window. It is calculated as the sum of: + // SM_CYFRAME - The thickness of the sizing border + // SM_CXPADDEDBORDER - The amount of border padding + // for captioned windows. + // + // If the window does not have a caption, mVertResizeMargin will be equal to + // `WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi)` + mVertResizeMargin = + WinUtils::GetSystemMetricsForDpi(SM_CYFRAME, dpi) + + (hasCaption ? WinUtils::GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) + : 0); + + if (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 = gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled(); + + // 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; +} + +nsresult 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 +static HCURSOR CursorFor(nsCursor aCursor) { + switch (aCursor) { + case eCursor_select: + return ::LoadCursor(nullptr, IDC_IBEAM); + case eCursor_wait: + return ::LoadCursor(nullptr, IDC_WAIT); + case eCursor_hyperlink: + return ::LoadCursor(nullptr, IDC_HAND); + case eCursor_standard: + case eCursor_context_menu: // XXX See bug 258960. + return ::LoadCursor(nullptr, IDC_ARROW); + + case eCursor_n_resize: + case eCursor_s_resize: + return ::LoadCursor(nullptr, IDC_SIZENS); + + case eCursor_w_resize: + case eCursor_e_resize: + return ::LoadCursor(nullptr, IDC_SIZEWE); + + case eCursor_nw_resize: + case eCursor_se_resize: + return ::LoadCursor(nullptr, IDC_SIZENWSE); + + case eCursor_ne_resize: + case eCursor_sw_resize: + return ::LoadCursor(nullptr, IDC_SIZENESW); + + case eCursor_crosshair: + return ::LoadCursor(nullptr, IDC_CROSS); + + case eCursor_move: + return ::LoadCursor(nullptr, IDC_SIZEALL); + + case eCursor_help: + return ::LoadCursor(nullptr, IDC_HELP); + + case eCursor_copy: // CSS3 + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_COPY)); + + case eCursor_alias: + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ALIAS)); + + case eCursor_cell: + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_CELL)); + case eCursor_grab: + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_GRAB)); + + case eCursor_grabbing: + return ::LoadCursor(nsToolkit::mDllInstance, + MAKEINTRESOURCE(IDC_GRABBING)); + + case eCursor_spinning: + return ::LoadCursor(nullptr, IDC_APPSTARTING); + + case eCursor_zoom_in: + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_ZOOMIN)); + + case eCursor_zoom_out: + return ::LoadCursor(nsToolkit::mDllInstance, + MAKEINTRESOURCE(IDC_ZOOMOUT)); + + case eCursor_not_allowed: + case eCursor_no_drop: + return ::LoadCursor(nullptr, IDC_NO); + + case eCursor_col_resize: + return ::LoadCursor(nsToolkit::mDllInstance, + MAKEINTRESOURCE(IDC_COLRESIZE)); + + case eCursor_row_resize: + return ::LoadCursor(nsToolkit::mDllInstance, + MAKEINTRESOURCE(IDC_ROWRESIZE)); + + case eCursor_vertical_text: + return ::LoadCursor(nsToolkit::mDllInstance, + MAKEINTRESOURCE(IDC_VERTICALTEXT)); + + case eCursor_all_scroll: + // XXX not 100% appropriate perhaps + return ::LoadCursor(nullptr, IDC_SIZEALL); + + case eCursor_nesw_resize: + return ::LoadCursor(nullptr, IDC_SIZENESW); + + case eCursor_nwse_resize: + return ::LoadCursor(nullptr, IDC_SIZENWSE); + + case eCursor_ns_resize: + return ::LoadCursor(nullptr, IDC_SIZENS); + + case eCursor_ew_resize: + return ::LoadCursor(nullptr, IDC_SIZEWE); + + case eCursor_none: + return ::LoadCursor(nsToolkit::mDllInstance, MAKEINTRESOURCE(IDC_NONE)); + + default: + NS_ERROR("Invalid cursor type"); + return nullptr; + } +} + +static HCURSOR CursorForImage(imgIContainer* aImageContainer, + CSSIntPoint aHotspot, + CSSToLayoutDeviceScale aScale) { + if (!aImageContainer) { + return nullptr; + } + + int32_t width = 0; + int32_t height = 0; + + if (NS_FAILED(aImageContainer->GetWidth(&width)) || + NS_FAILED(aImageContainer->GetHeight(&height))) { + return nullptr; + } + + // 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 nullptr; + } + + LayoutDeviceIntSize size = RoundedToInt(CSSIntSize(width, height) * aScale); + LayoutDeviceIntPoint hotspot = RoundedToInt(aHotspot * aScale); + HCURSOR cursor; + nsresult rv = + nsWindowGfx::CreateIcon(aImageContainer, true, hotspot, size, &cursor); + if (NS_FAILED(rv)) { + return nullptr; + } + + return cursor; +} + +// Setting the actual cursor +void nsWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aImageCursor, + uint32_t aHotspotX, uint32_t aHotspotY) { + if (aImageCursor && sCursorImgContainer == aImageCursor && sHCursor) { + ::SetCursor(sHCursor); + return; + } + + HCURSOR cursor = CursorForImage( + aImageCursor, CSSIntPoint(aHotspotX, aHotspotY), GetDefaultScale()); + if (cursor) { + mCursor = eCursorInvalid; + ::SetCursor(cursor); + + NS_IF_RELEASE(sCursorImgContainer); + sCursorImgContainer = aImageCursor; + NS_ADDREF(sCursorImgContainer); + + if (sHCursor) { + ::DestroyIcon(sHCursor); + } + sHCursor = cursor; + return; + } + + cursor = CursorFor(aDefaultCursor); + if (!cursor) { + return; + } + + mCursor = aDefaultCursor; + HCURSOR oldCursor = ::SetCursor(cursor); + + if (sHCursor == oldCursor) { + NS_IF_RELEASE(sCursorImgContainer); + if (sHCursor) { + ::DestroyIcon(sHCursor); + } + sHCursor = nullptr; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::Get/SetTransparencyMode + * + * Manage the transparency mode of the window containing this + * widget. Only works for popup and dialog windows when the + * Desktop Window Manager compositor is not enabled. + * + **************************************************************/ + +#ifdef MOZ_XUL +nsTransparencyMode nsWindow::GetTransparencyMode() { + return GetTopLevelWindow(true)->GetWindowTranslucencyInner(); +} + +void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) { + nsWindow* window = GetTopLevelWindow(true); + MOZ_ASSERT(window); + + if (!window || window->DestroyCalled()) { + return; + } + + if (nsWindowType::eWindowType_toplevel == window->mWindowType && + mTransparencyMode != aMode && + !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + NS_WARNING("Cannot set transparency mode on top-level windows."); + return; + } + + window->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.MoveToY(std::max<uint32_t>( + largest.Y(), nsUXThemeData::GetCommandButtonBoxMetrics().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 (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + DwmExtendFrameIntoClientArea(mWnd, &margins); + DwmSetWindowAttribute(mWnd, DWMWA_NCRENDERING_POLICY, &policy, + sizeof policy); + } +} +#endif + +/************************************************************** + * + * SECTION: nsIWidget::HideWindowChrome + * + * Show or hide window chrome. + * + **************************************************************/ + +void nsWindow::HideWindowChrome(bool aShouldHide) { + HWND hwnd = WinUtils::GetTopLevelHWND(mWnd, true); + if (!WinUtils::GetNSWindowPtr(hwnd)) { + NS_WARNING("Trying to hide window decorations in an embedded context"); + return; + } + + if (mHideChrome == aShouldHide) return; + + DWORD_PTR style, exStyle; + mHideChrome = aShouldHide; + if (aShouldHide) { + DWORD_PTR tempStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE); + DWORD_PTR tempExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + + style = tempStyle & ~(WS_CAPTION | WS_THICKFRAME); + exStyle = tempExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | + WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); + + mOldStyle = tempStyle; + mOldExStyle = tempExStyle; + } else { + if (!mOldStyle || !mOldExStyle) { + mOldStyle = ::GetWindowLongPtrW(hwnd, GWL_STYLE); + mOldExStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE); + } + + style = mOldStyle; + exStyle = mOldExStyle; + if (mFutureMarginsToUse) { + SetNonClientMargins(mFutureMarginsOnceChromeShows); + } + } + + VERIFY_WINDOW_STYLE(style); + ::SetWindowLongPtrW(hwnd, GWL_STYLE, style); + ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle); +} + +/************************************************************** + * + * SECTION: nsWindow::Invalidate + * + * Invalidate an area of the client for painting. + * + **************************************************************/ + +// Invalidate this component visible area +void nsWindow::Invalidate(bool aEraseBackground, bool aUpdateNCArea, + bool aIncludeChildren) { + if (!mWnd) { + return; + } + +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpInvalidate(stdout, this, nullptr, "noname", (int32_t)mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + DWORD flags = RDW_INVALIDATE; + if (aEraseBackground) { + flags |= RDW_ERASE; + } + if (aUpdateNCArea) { + flags |= RDW_FRAME; + } + if (aIncludeChildren) { + flags |= RDW_ALLCHILDREN; + } + + VERIFY(::RedrawWindow(mWnd, nullptr, nullptr, flags)); +} + +// Invalidate this component visible area +void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) { + if (mWnd) { +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpInvalidate(stdout, this, &aRect, "noname", (int32_t)mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + RECT rect; + + rect.left = aRect.X(); + rect.top = aRect.Y(); + rect.right = aRect.XMost(); + rect.bottom = aRect.YMost(); + + VERIFY(::InvalidateRect(mWnd, &rect, FALSE)); + } +} + +static LRESULT CALLBACK FullscreenTransitionWindowProc(HWND hWnd, UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + switch (uMsg) { + case WM_FULLSCREEN_TRANSITION_BEFORE: + case WM_FULLSCREEN_TRANSITION_AFTER: { + DWORD duration = (DWORD)lParam; + DWORD flags = AW_BLEND; + if (uMsg == WM_FULLSCREEN_TRANSITION_AFTER) { + flags |= AW_HIDE; + } + ::AnimateWindow(hWnd, duration, flags); + // The message sender should have added ref for us. + NS_DispatchToMainThread( + already_AddRefed<nsIRunnable>((nsIRunnable*)wParam)); + break; + } + case WM_DESTROY: + ::PostQuitMessage(0); + break; + default: + return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + return 0; +} + +struct FullscreenTransitionInitData { + 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 (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + 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.SetRect(NSToIntRound(x * scale), NSToIntRound(y * scale), + NSToIntRound(width * scale), + 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); +} + +/* virtual */ +void nsWindow::CleanupFullscreenTransition() { + MOZ_ASSERT(NS_IsMainThread(), + "CleanupFullscreenTransition " + "should only run on the main thread"); + + mTransitionWnd = nullptr; +} + +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); + + if (mWidgetListener) { + mWidgetListener->FullscreenWillChange(aFullScreen); + } + + 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); + } + + OnSizeModeChange(mSizeMode); + + if (mWidgetListener) { + mWidgetListener->FullscreenChanged(aFullScreen); + } + + return NS_OK; +} + +/************************************************************** + * + * SECTION: Native data storage + * + * nsIWidget::GetNativeData + * nsIWidget::FreeNativeData + * + * Set or clear native data based on a constant. + * + **************************************************************/ + +// Return some native data according to aDataType +void* nsWindow::GetNativeData(uint32_t aDataType) { + switch (aDataType) { + case NS_NATIVE_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: + case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID: + 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; + } + [[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: + case NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW: { + HWND childHwnd = reinterpret_cast<HWND>(aVal); + DWORD childProc = 0; + GetWindowThreadProcessId(childHwnd, &childProc); + if (!PluginProcessParent::IsPluginProcessId( + static_cast<base::ProcessId>(childProc))) { + MOZ_ASSERT_UNREACHABLE( + "SetNativeData window origin was not a plugin process."); + break; + } + HWND parentHwnd = aDataType == NS_NATIVE_CHILD_WINDOW + ? mWnd + : WinUtils::GetTopLevelHWND(mWnd); + SetChildStyleAndParent(childHwnd, parentHwnd); + RecreateDirectManipulationIfNeeded(); + 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. + * + **************************************************************/ + +nsresult nsWindow::SetTitle(const nsAString& aTitle) { + const nsString& strTitle = PromiseFlatString(aTitle); + AutoRestore<bool> sendingText(mSendingSetText); + mSendingSetText = true; + ::SendMessageW(mWnd, WM_SETTEXT, (WPARAM)0, (LPARAM)(LPCWSTR)strTitle.get()); + return NS_OK; +} + +/************************************************************** + * + * SECTION: nsIWidget::SetIcon + * + * Set the main windows icon. + * + **************************************************************/ + +void nsWindow::SetBigIcon(HICON aIcon) { + HICON icon = + (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)aIcon); + if (icon) { + ::DestroyIcon(icon); + } + + mIconBig = aIcon; +} + +void nsWindow::SetSmallIcon(HICON aIcon) { + HICON icon = (HICON)::SendMessageW(mWnd, WM_SETICON, (WPARAM)ICON_SMALL, + (LPARAM)aIcon); + if (icon) { + ::DestroyIcon(icon); + } + + mIconSmall = aIcon; +} + +void nsWindow::SetIcon(const nsAString& aIconSpec) { + // Assume the given string is a local identifier for an icon file. + + nsCOMPtr<nsIFile> iconFile; + ResolveIconName(aIconSpec, u".ico"_ns, getter_AddRefs(iconFile)); + if (!iconFile) return; + + nsAutoString iconPath; + iconFile->GetPath(iconPath); + + // XXX this should use MZLU (see bug 239279) + + ::SetLastError(0); + + HICON bigIcon = + (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON, + ::GetSystemMetrics(SM_CXICON), + ::GetSystemMetrics(SM_CYICON), LR_LOADFROMFILE); + HICON smallIcon = + (HICON)::LoadImageW(nullptr, (LPCWSTR)iconPath.get(), IMAGE_ICON, + ::GetSystemMetrics(SM_CXSMICON), + ::GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE); + + if (bigIcon) { + SetBigIcon(bigIcon); + } +#ifdef DEBUG_SetIcon + else { + NS_LossyConvertUTF16toASCII cPath(iconPath); + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("\nIcon load error; icon=%s, rc=0x%08X\n\n", cPath.get(), + ::GetLastError())); + } +#endif + if (smallIcon) { + SetSmallIcon(smallIcon); + } +#ifdef DEBUG_SetIcon + else { + NS_LossyConvertUTF16toASCII cPath(iconPath); + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("\nSmall icon load error; icon=%s, rc=0x%08X\n\n", cPath.get(), + ::GetLastError())); + } +#endif +} + +/************************************************************** + * + * 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) { + if (!mWnd) { + // Return early if the window already closed + return; + } + + 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. +nsresult nsWindow::GetAttention(int32_t aCycleCount) { + // Got window? + if (!mWnd) return NS_ERROR_NOT_INITIALIZED; + + HWND flashWnd = WinUtils::GetTopLevelHWND(mWnd, false, false); + HWND fgWnd = ::GetForegroundWindow(); + // Don't flash if the flash count is 0 or if the foreground window is our + // window handle or that of our owned-most window. + if (aCycleCount == 0 || flashWnd == fgWnd || + flashWnd == WinUtils::GetTopLevelHWND(fgWnd, false, false)) { + return NS_OK; + } + + DWORD defaultCycleCount = 0; + ::SystemParametersInfo(SPI_GETFOREGROUNDFLASHCOUNT, 0, &defaultCycleCount, 0); + + FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_ALL, + aCycleCount > 0 ? aCycleCount : defaultCycleCount, 0}; + ::FlashWindowEx(&flashInfo); + + return NS_OK; +} + +void nsWindow::StopFlashing() { + HWND flashWnd = mWnd; + while (HWND ownerWnd = ::GetWindow(flashWnd, GW_OWNER)) { + flashWnd = ownerWnd; + } + + FLASHWINFO flashInfo = {sizeof(FLASHWINFO), flashWnd, FLASHW_STOP, 0, 0}; + ::FlashWindowEx(&flashInfo); +} + +/************************************************************** + * + * SECTION: nsIWidget::HasPendingInputEvent + * + * Ask whether there user input events pending. All input events are + * included, including those not targeted at this nsIwidget instance. + * + **************************************************************/ + +bool nsWindow::HasPendingInputEvent() { + // If there is pending input or the user is currently + // moving the window then return true. + // Note: When the user is moving the window WIN32 spins + // a separate event loop and input events are not + // reported to the application. + if (HIWORD(GetQueueStatus(QS_INPUT))) return true; + GUITHREADINFO guiInfo; + guiInfo.cbSize = sizeof(GUITHREADINFO); + if (!GetGUIThreadInfo(GetCurrentThreadId(), &guiInfo)) return false; + return GUI_INMOVESIZE == (guiInfo.flags & GUI_INMOVESIZE); +} + +/************************************************************** + * + * SECTION: nsIWidget::GetLayerManager + * + * Get the layer manager associated with this widget. + * + **************************************************************/ + +LayerManager* nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, + LayersBackend aBackendHint, + LayerManagerPersistence aPersistence) { + if (mLayerManager) { + return mLayerManager; + } + + 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 + // BrowserChild 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. + WinCompositorWidgetInitData initData( + reinterpret_cast<uintptr_t>(mWnd), + reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)), + mTransparencyMode, mSizeMode); + // If we're not using the compositor, the options don't actually matter. + CompositorOptions options(false, false); + mBasicLayersSurface = + new InProcessWinCompositorWidget(initData, options, this); + mCompositorWidgetDelegate = mBasicLayersSurface; + mLayerManager = CreateBasicLayerManager(); + } + + NS_ASSERTION(mLayerManager, "Couldn't provide a valid layer manager."); + + if (mLayerManager) { + // Update the size constraints now that the layer manager has been + // created. + KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor(); + if (knowsCompositor) { + SizeConstraints c = mSizeConstraints; + mMaxTextureSize = knowsCompositor->GetMaxTextureSize(); + c.mMaxSize.width = std::min(c.mMaxSize.width, mMaxTextureSize); + c.mMaxSize.height = std::min(c.mMaxSize.height, mMaxTextureSize); + nsBaseWidget::SetSizeConstraints(c); + } + } + + return mLayerManager; +} + +/************************************************************** + * + * SECTION: nsBaseWidget::SetCompositorWidgetDelegate + * + * Called to connect the nsWindow to the delegate providing + * platform compositing API access. + * + **************************************************************/ + +void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) { + if (delegate) { + mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate(); + MOZ_ASSERT(mCompositorWidgetDelegate, + "nsWindow::SetCompositorWidgetDelegate called with a " + "non-PlatformCompositorWidgetDelegate"); + } else { + mCompositorWidgetDelegate = nullptr; + } +} + +/************************************************************** + * + * SECTION: nsIWidget::OnDefaultButtonLoaded + * + * Called after the dialog is loaded and it has a default button. + * + **************************************************************/ + +nsresult nsWindow::OnDefaultButtonLoaded( + const LayoutDeviceIntRect& aButtonRect) { + if (aButtonRect.IsEmpty()) return NS_OK; + + // Don't snap when we are not active. + HWND activeWnd = ::GetActiveWindow(); + if (activeWnd != ::GetForegroundWindow() || + WinUtils::GetTopLevelHWND(mWnd, true) != + WinUtils::GetTopLevelHWND(activeWnd, true)) { + return NS_OK; + } + + bool isAlwaysSnapCursor = + Preferences::GetBool("ui.cursor_snapping.always_enabled", false); + + if (!isAlwaysSnapCursor) { + BOOL snapDefaultButton; + if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapDefaultButton, + 0) || + !snapDefaultButton) + return NS_OK; + } + + LayoutDeviceIntRect widgetRect = GetScreenBounds(); + LayoutDeviceIntRect buttonRect(aButtonRect + widgetRect.TopLeft()); + + LayoutDeviceIntPoint centerOfButton(buttonRect.X() + buttonRect.Width() / 2, + buttonRect.Y() + buttonRect.Height() / 2); + // The center of the button can be outside of the widget. + // E.g., it could be hidden by scrolling. + if (!widgetRect.Contains(centerOfButton)) { + return NS_OK; + } + + if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) { + NS_ERROR("SetCursorPos failed"); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void nsWindow::UpdateThemeGeometries( + const nsTArray<ThemeGeometry>& aThemeGeometries) { + RefPtr<LayerManager> layerManager = GetLayerManager(); + if (!layerManager) { + return; + } + + nsIntRegion clearRegion; + if (!HasGlass() || + !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + // 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)); + } + + mWindowButtonsRect = Nothing(); + + if (!IsWin10OrLater()) { + for (size_t i = 0; i < aThemeGeometries.Length(); i++) { + if (aThemeGeometries[i].mType == + nsNativeThemeWin::eThemeGeometryTypeWindowButtons) { + LayoutDeviceIntRect bounds = aThemeGeometries[i].mRect; + // Extend the bounds by one pixel to the right, because that's how much + // the actual window button shape extends past the client area of the + // window (and overlaps the right window frame). + bounds.SetWidth(bounds.Width() + 1); + if (!mWindowButtonsRect) { + mWindowButtonsRect = Some(bounds); + } + 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() - 2.0, 1.0)); + clearRegion.Or(clearRegion, gfx::IntRect::Truncate( + bounds.X() + 2.0, bounds.YMost() - 1.0, + bounds.Width() - 4.0, 1.0)); + } + } + } + + layerManager->SetRegionToClear(clearRegion); +} + +void nsWindow::AddWindowOverlayWebRenderCommands( + layers::WebRenderBridgeChild* aWrBridge, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources) { + if (mWindowButtonsRect) { + wr::LayoutRect rect = wr::ToLayoutRect(*mWindowButtonsRect); + auto complexRegion = wr::ToComplexClipRegion( + RoundedRect(IntRectToRect(mWindowButtonsRect->ToUnknownRect()), + RectCornerRadii(0, 0, 3, 3))); + aBuilder.PushClearRectWithComplexRegion(rect, complexRegion); + } +} + +uint32_t nsWindow::GetMaxTouchPoints() const { + return WinUtils::GetMaxTouchPoints(); +} + +void nsWindow::SetWindowClass(const nsAString& xulWinType) { + mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank"); +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: Moz Events + ** + ** Moz GUI event management. + ** + ************************************************************** + **************************************************************/ + +/************************************************************** + * + * SECTION: Mozilla event initialization + * + * Helpers for initializing moz events. + * + **************************************************************/ + +// Event initialization +void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) { + if (nullptr == aPoint) { // use the point from the event + // get the message position in client coordinates + if (mWnd != nullptr) { + DWORD pos = ::GetMessagePos(); + POINT cpos; + + cpos.x = GET_X_LPARAM(pos); + cpos.y = GET_Y_LPARAM(pos); + + ::ScreenToClient(mWnd, &cpos); + event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y); + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } + } else { + // use the point override if provided + event.mRefPoint = *aPoint; + } + + event.AssignEventTime(CurrentMessageWidgetEventTime()); +} + +WidgetEventTime nsWindow::CurrentMessageWidgetEventTime() const { + LONG messageTime = ::GetMessageTime(); + return WidgetEventTime(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. +nsresult nsWindow::DispatchEvent(WidgetGUIEvent* event, + nsEventStatus& aStatus) { +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpEvent(stdout, event->mWidget, event, "something", (int32_t)mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + aStatus = nsEventStatus_eIgnore; + + // Top level windows can have a view attached which requires events be sent + // to the underlying base window and the view. Added when we combined the + // base chrome window with the main content child for nc client area (title + // bar) rendering. + if (mAttachedWidgetListener) { + aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents); + } else if (mWidgetListener) { + aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents); + } + + // the window can be destroyed during processing of seemingly innocuous events + // like, say, mousedowns due to the magic of scripting. mousedowns will return + // nsEventStatus_eIgnore, which causes problems with the deleted window. + // therefore: + if (mOnDestroyCalled) aStatus = nsEventStatus_eConsumeNoDefault; + return NS_OK; +} + +bool nsWindow::DispatchStandardEvent(EventMessage aMsg) { + WidgetGUIEvent event(true, aMsg, this); + InitEvent(event); + + bool result = DispatchWindowEvent(&event); + return result; +} + +bool nsWindow::DispatchKeyboardEvent(WidgetKeyboardEvent* event) { + nsEventStatus status = DispatchInputEvent(event); + 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; +} + +void nsWindow::DispatchCustomEvent(const nsString& eventName) { + if (Document* doc = GetDocument()) { + if (nsPIDOMWindowOuter* win = doc->GetWindow()) { + win->DispatchCustomEvent(eventName, ChromeOnlyDispatch::eYes); + } + } +} + +bool nsWindow::TouchEventShouldStartDrag(EventMessage aEventMessage, + LayoutDeviceIntPoint aEventPoint) { + // Allow users to start dragging by double-tapping. + if (aEventMessage == eMouseDoubleClick) { + return true; + } + + // In chrome UI, allow touchdownstartsdrag attributes + // to cause any touchdown event to trigger a drag. + if (aEventMessage == eMouseDown) { + WidgetMouseEvent hittest(true, eMouseHitTest, this, + WidgetMouseEvent::eReal); + hittest.mRefPoint = aEventPoint; + hittest.mIgnoreRootScrollFrame = true; + hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; + DispatchInputEvent(&hittest); + + if (EventTarget* target = hittest.GetDOMEventTarget()) { + if (nsCOMPtr<nsIContent> content = do_QueryInterface(target)) { + // Check if the element or any parent element has the + // attribute we're looking for. + for (Element* element = content->GetAsElementOrParentElement(); element; + element = element->GetParentElement()) { + nsAutoString startDrag; + element->GetAttribute(u"touchdownstartsdrag"_ns, startDrag); + if (!startDrag.IsEmpty()) { + return true; + } + } + } + } + } + + return false; +} + +// Deal with all sort of mouse event +bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam, + LPARAM lParam, bool aIsContextMenuKey, + int16_t aButton, uint16_t aInputSource, + WinPointerInfo* aPointerInfo) { + bool 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::ScalarAdd(Telemetry::ScalarID::BROWSER_INPUT_TOUCH_EVENT_COUNT, + 1); + } + + if (mTouchWindow) { + // If mTouchWindow is true, then we must have APZ enabled and be + // feeding it raw touch events. In that case we only want to + // send touch-generated mouse events to content if they should + // start a touch-based drag-and-drop gesture, such as on + // double-tapping or when tapping elements marked with the + // touchdownstartsdrag attribute in chrome UI. + MOZ_ASSERT(mAPZC); + if (TouchEventShouldStartDrag(aEventMessage, eventPoint)) { + aEventMessage = eMouseTouchDrag; + } else { + return result; + } + } + } + + uint32_t pointerId = + aPointerInfo ? aPointerInfo->pointerId : MOUSE_POINTERID(); + + // Since it is unclear whether a user will use the digitizer, + // Postpone initialization until first PEN message will be found. + if (MouseEvent_Binding::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. + && StaticPrefs::dom_w3c_pointer_events_enabled() && + InkCollector::sInkCollector) { + InkCollector::sInkCollector->SetTarget(mWnd); + InkCollector::sInkCollector->SetPointerId(pointerId); + } + + switch (aEventMessage) { + case eMouseDown: + CaptureMouse(true); + break; + + // eMouseMove and eMouseExitFromWidget are here because we need to make + // sure capture flag isn't left on after a drag where we wouldn't see a + // button up message (see bug 324131). + case eMouseUp: + case eMouseMove: + case eMouseExitFromWidget: + if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) && + sIsInMouseCapture) + CaptureMouse(false); + break; + + default: + break; + + } // switch + + WidgetMouseEvent event(true, aEventMessage, this, WidgetMouseEvent::eReal, + aIsContextMenuKey ? WidgetMouseEvent::eContextMenuKey + : WidgetMouseEvent::eNormal); + if (aEventMessage == eContextMenu && aIsContextMenuKey) { + LayoutDeviceIntPoint zero(0, 0); + InitEvent(event, &zero); + } else { + InitEvent(event, &eventPoint); + } + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + + // eContextMenu with Shift state is special. It won't fire "contextmenu" + // event in the web content for blocking web content to prevent its default. + // However, Shift+F10 is a standard shortcut key on Windows. Therefore, + // this should not block web page to prevent its default. I.e., it should + // behave same as ContextMenu key without Shift key. + // XXX Should we allow to block web page to prevent its default with + // Ctrl+Shift+F10 or Alt+Shift+F10 instead? + if (aEventMessage == eContextMenu && aIsContextMenuKey && event.IsShift() && + NativeKey::LastKeyOrCharMSG().message == WM_SYSKEYDOWN && + NativeKey::LastKeyOrCharMSG().wParam == VK_F10) { + event.mModifiers &= ~MODIFIER_SHIFT; + } + + event.mButton = aButton; + event.mInputSource = aInputSource; + if (aPointerInfo) { + // Mouse events from Windows WM_POINTER*. Fill more information in + // WidgetMouseEvent. + event.AssignPointerHelperData(*aPointerInfo); + event.mPressure = aPointerInfo->mPressure; + event.mButtons = aPointerInfo->mButtons; + } else { + // If we get here the mouse events must be from non-touch sources, so + // convert it to pointer events as well + event.convertToPointer = true; + event.pointerId = pointerId; + } + + 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 MouseButton::ePrimary: + eventButton = VK_LBUTTON; + break; + case MouseButton::eMiddle: + eventButton = VK_MBUTTON; + break; + case MouseButton::eSecondary: + eventButton = VK_RBUTTON; + break; + default: + eventButton = 0; + break; + } + + // Doubleclicks are used to set the click count, then changed to mousedowns + // We're going to time double-clicks from mouse *up* to next mouse *down* + LONG curMsgTime = ::GetMessageTime(); + + switch (aEventMessage) { + case eMouseDoubleClick: + event.mMessage = eMouseDown; + event.mButton = aButton; + sLastClickCount = 2; + sLastMouseDownTime = curMsgTime; + break; + case eMouseUp: + // remember when this happened for the next mouse down + sLastMousePoint.x = eventPoint.x; + sLastMousePoint.y = eventPoint.y; + sLastMouseButton = eventButton; + break; + case eMouseDown: + // now look to see if we want to convert this to a double- or triple-click + if (((curMsgTime - sLastMouseDownTime) < (LONG)::GetDoubleClickTime()) && + insideMovementThreshold && eventButton == sLastMouseButton) { + sLastClickCount++; + } else { + // reset the click count, to count *this* click + sLastClickCount = 1; + } + // Set last Click time on MouseDown only + sLastMouseDownTime = curMsgTime; + break; + case eMouseMove: + if (!insideMovementThreshold) { + sLastClickCount = 0; + } + break; + case eMouseExitFromWidget: + event.mExitFrom = + Some(IsTopLevelMouseExit(mWnd) ? WidgetMouseEvent::ePlatformTopLevel + : WidgetMouseEvent::ePlatformChild); + break; + default: + break; + } + event.mClickCount = sLastClickCount; + +#ifdef NS_DEBUG_XX + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("Msg Time: %d Click Count: %d\n", curMsgTime, event.mClickCount)); +#endif + + NPEvent pluginEvent; + + switch (aEventMessage) { + case eMouseDown: + switch (aButton) { + case MouseButton::ePrimary: + pluginEvent.event = WM_LBUTTONDOWN; + break; + case MouseButton::eMiddle: + pluginEvent.event = WM_MBUTTONDOWN; + break; + case MouseButton::eSecondary: + pluginEvent.event = WM_RBUTTONDOWN; + break; + default: + break; + } + break; + case eMouseUp: + switch (aButton) { + case MouseButton::ePrimary: + pluginEvent.event = WM_LBUTTONUP; + break; + case MouseButton::eMiddle: + pluginEvent.event = WM_MBUTTONUP; + break; + case MouseButton::eSecondary: + pluginEvent.event = WM_RBUTTONUP; + break; + default: + break; + } + break; + case eMouseDoubleClick: + switch (aButton) { + case MouseButton::ePrimary: + pluginEvent.event = WM_LBUTTONDBLCLK; + break; + case MouseButton::eMiddle: + pluginEvent.event = WM_MBUTTONDBLCLK; + break; + case MouseButton::eSecondary: + 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.MoveTo(0, 0); + + if (rect.Contains(event.mRefPoint)) { + if (sCurrentWindow == nullptr || sCurrentWindow != this) { + if ((nullptr != sCurrentWindow) && (!sCurrentWindow->mInDtor)) { + LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); + sCurrentWindow->DispatchMouseEvent( + eMouseExitFromWidget, wParam, pos, false, MouseButton::ePrimary, + aInputSource, aPointerInfo); + } + sCurrentWindow = this; + if (!mInDtor) { + LPARAM pos = sCurrentWindow->lParamToClient(lParamToScreen(lParam)); + sCurrentWindow->DispatchMouseEvent( + eMouseEnterIntoWidget, wParam, pos, false, + MouseButton::ePrimary, aInputSource, aPointerInfo); + } + } + } + } else if (aEventMessage == eMouseExitFromWidget) { + if (sCurrentWindow == this) { + sCurrentWindow = nullptr; + } + } + + 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; +} + +HWND nsWindow::GetTopLevelForFocus(HWND aCurWnd) { + // retrieve the toplevel window or dialogue + HWND toplevelWnd = nullptr; + while (aCurWnd) { + toplevelWnd = aCurWnd; + nsWindow* win = WinUtils::GetNSWindowPtr(aCurWnd); + if (win) { + if (win->mWindowType == eWindowType_toplevel || + win->mWindowType == eWindowType_dialog) { + break; + } + } + + aCurWnd = ::GetParent(aCurWnd); // Parent or owner (if has no parent) + } + return toplevelWnd; +} + +void nsWindow::DispatchFocusToTopLevelWindow(bool aIsActivate) { + if (aIsActivate) { + sJustGotActivate = false; + } + sJustGotDeactivate = false; + mLastKillFocusWindow = nullptr; + + HWND toplevelWnd = GetTopLevelForFocus(mWnd); + + if (toplevelWnd) { + nsWindow* win = WinUtils::GetNSWindowPtr(toplevelWnd); + if (win && win->mWidgetListener) { + if (aIsActivate) { + win->mWidgetListener->WindowActivated(); + } else { + win->mWidgetListener->WindowDeactivated(); + } + } + } +} + +HWND nsWindow::WindowAtMouse() { + DWORD pos = ::GetMessagePos(); + POINT mp; + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + return ::WindowFromPoint(mp); +} + +bool nsWindow::IsTopLevelMouseExit(HWND aWnd) { + HWND mouseWnd = WindowAtMouse(); + + // WinUtils::GetTopLevelHWND() will return a HWND for the window frame + // (which includes the non-client area). If the mouse has moved into + // the non-client area, we should treat it as a top-level exit. + HWND mouseTopLevel = WinUtils::GetTopLevelHWND(mouseWnd); + if (mouseWnd == mouseTopLevel) return true; + + return WinUtils::GetTopLevelHWND(aWnd) != mouseTopLevel; +} + +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; + case nsSizeMode_Invalid: + NS_ASSERTION(false, "Did the argument come from invalid IPC?"); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unhnalded nsSizeMode value detected"); + break; + } + LPARAM cmd = TrackPopupMenu( + hMenu, + (TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN | + (isRtl ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), + x, y, 0, hWnd, nullptr); + if (cmd) { + PostMessage(hWnd, WM_SYSCOMMAND, cmd, 0); + return true; + } + } + return false; +} + +// The WndProc procedure for all nsWindows in this toolkit. This merely catches +// exceptions and passes the real work to WindowProcInternal. See bug 587406 +// and http://msdn.microsoft.com/en-us/library/ms633573%28VS.85%29.aspx +LRESULT CALLBACK nsWindow::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + mozilla::ipc::CancelCPOWs(); + + BackgroundHangMonitor().NotifyActivity(); + + return mozilla::CallWindowProcCrashProtected(WindowProcInternal, hWnd, msg, + wParam, lParam); +} + +LRESULT CALLBACK nsWindow::WindowProcInternal(HWND hWnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + if (::GetWindowLongPtrW(hWnd, GWLP_ID) == eFakeTrackPointScrollableID) { + // This message was sent to the FAKETRACKPOINTSCROLLABLE. + if (msg == WM_HSCROLL) { + // Route WM_HSCROLL messages to the main window. + hWnd = ::GetParent(::GetParent(hWnd)); + } else { + // Handle all other messages with its original window procedure. + WNDPROC prevWindowProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_USERDATA); + return ::CallWindowProcW(prevWindowProc, hWnd, msg, wParam, lParam); + } + } + + if (msg == MOZ_WM_TRACE) { + // This is a tracer event for measuring event loop latency. + // See WidgetTraceEvent.cpp for more details. + mozilla::SignalTracerThread(); + return 0; + } + + // Get the window which caused the event and ask it to process the message + nsWindow* targetWindow = WinUtils::GetNSWindowPtr(hWnd); + NS_ASSERTION(targetWindow, "nsWindow* is null!"); + if (!targetWindow) return ::DefWindowProcW(hWnd, msg, wParam, lParam); + + // Hold the window for the life of this method, in case it gets + // destroyed during processing, unless we're in the dtor already. + nsCOMPtr<nsIWidget> kungFuDeathGrip; + if (!targetWindow->mInDtor) kungFuDeathGrip = targetWindow; + + targetWindow->IPCWindowProcHandler(msg, wParam, lParam); + + // Create this here so that we store the last rolled up popup until after + // the event has been processed. + nsAutoRollup autoRollup; + + LRESULT popupHandlingResult; + if (DealWithPopups(hWnd, msg, wParam, lParam, &popupHandlingResult)) + return popupHandlingResult; + + // Call ProcessMessage + LRESULT retValue; + if (targetWindow->ProcessMessage(msg, wParam, lParam, &retValue)) { + return retValue; + } + + LRESULT res = ::CallWindowProcW(targetWindow->GetPrevWindowProc(), hWnd, msg, + wParam, lParam); + + return res; +} + +const char16_t* GetQuitType() { + if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false)) { + DWORD cchCmdLine = 0; + HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr, + &cchCmdLine, nullptr); + if (rc == S_OK) { + return u"os-restart"; + } + } + return nullptr; +} + +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); +} + +bool nsWindow::ExternalHandlerProcessMessage(UINT aMessage, WPARAM& aWParam, + LPARAM& aLParam, + MSGResult& aResult) { + if (mWindowHook.Notify(mWnd, aMessage, aWParam, aLParam, aResult)) { + return true; + } + + if (IMEHandler::ProcessMessage(this, aMessage, aWParam, aLParam, aResult)) { + return true; + } + + if (MouseScrollHandler::ProcessMessage(this, aMessage, aWParam, aLParam, + aResult)) { + return true; + } + + return false; +} + +// The main windows message processing method. +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 && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled() && + /* We don't do this for win10 glass with a custom titlebar, + * in order to avoid the caption buttons breaking. */ + !(IsWin10OrLater() && HasGlass()) && + DwmDefWindowProc(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); + + const char16_t* quitType = GetQuitType(); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", + quitType); + + bool abortQuit; + cancelQuit->GetData(&abortQuit); + sCanQuit = abortQuit ? TRI_FALSE : TRI_TRUE; + } + *aRetValue = sCanQuit ? TRUE : FALSE; + result = true; + break; + + case MOZ_WM_STARTA11Y: +#if defined(ACCESSIBILITY) + Unused << GetAccessible(); + result = true; +#else + result = false; +#endif + 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(); + const char16_t* context = u"shutdown-persist"; + const char16_t* syncShutdown = u"syncShutdown"; + const char16_t* quitType = GetQuitType(); + + obsServ->NotifyObservers(nullptr, "quit-application-granted", + syncShutdown); + obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr); + obsServ->NotifyObservers(nullptr, "quit-application", quitType); + obsServ->NotifyObservers(nullptr, "profile-change-net-teardown", + context); + obsServ->NotifyObservers(nullptr, "profile-change-teardown", context); + obsServ->NotifyObservers(nullptr, "profile-before-change", context); + obsServ->NotifyObservers(nullptr, "profile-before-change-qm", context); + obsServ->NotifyObservers(nullptr, "profile-before-change-telemetry", + context); + mozilla::AppShutdown::DoImmediateExit(); + } + sCanQuit = TRI_UNKNOWN; + result = true; + break; + + case WM_SYSCOLORCHANGE: + // No need to invalidate layout for system color changes, but we need to + // invalidate style. + NotifyThemeChanged(widget::ThemeChangeKind::Style); + break; + + case WM_THEMECHANGED: { + // Before anything else, push updates to child processes + WinContentSystemParameters::GetSingleton()->OnThemeChanged(); + + // Update non-client margin offsets + UpdateNonClientMargins(); + nsUXThemeData::UpdateNativeThemeInfo(); + + // We assume pretty much everything could've changed here. + NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout); + + // 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 (wParam == SPI_SETCLIENTAREAANIMATION || + // CaretBlinkTime is cached in nsLookAndFeel + wParam == SPI_SETKEYBOARDDELAY) { + // This only affects reduced motion settings and and carent blink time, + // so no need to invalidate style / layout. + NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); + break; + } + if (wParam == SPI_SETFONTSMOOTHING || + wParam == SPI_SETFONTSMOOTHINGTYPE) { + gfxDWriteFont::UpdateSystemTextQuality(); + break; + } + if (lParam) { + auto lParamString = reinterpret_cast<const wchar_t*>(lParam); + if (!wcscmp(lParamString, L"ImmersiveColorSet")) { + // This affects system colors (-moz-win-accentcolor), so gotta pass + // the style flag. + NotifyThemeChanged(widget::ThemeChangeKind::Style); + break; + } + if (IsWin10OrLater() && mWindowType == eWindowType_invisible) { + if (!wcscmp(lParamString, L"UserInteractionMode")) { + nsCOMPtr<nsIWindowsUIUtils> uiUtils( + do_GetService("@mozilla.org/windows-ui-utils;1")); + if (uiUtils) { + uiUtils->UpdateTabletModeState(); + } + } + } + } + } break; + + case WM_DEVICECHANGE: { + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) { + DEV_BROADCAST_HDR* hdr = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam); + // Check dbch_devicetype explicitly since we will get other device types + // (e.g. DBT_DEVTYP_VOLUME) for some reasons even if we specify + // DBT_DEVTYP_DEVICEINTERFACE in the filter for + // RegisterDeviceNotification. + if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + // This can only change media queries (any-hover/any-pointer). + NotifyThemeChanged(widget::ThemeChangeKind::MediaQueriesOnly); + } + } + } break; + + case WM_NCCALCSIZE: { + // NOTE: the following block is mirrored in PreXULSkeletonUI.cpp, and + // will need to be kept in sync. + if (mCustomNonClient) { + // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains + // the proposed window rectangle for our window. During our + // processing of the `WM_NCCALCSIZE` message, we are expected to + // modify the `RECT` that `lParam` points to, so that its value upon + // our return is the new client area. We must return 0 if `wParam` + // is `FALSE`. + // + // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS` + // struct. This struct contains an array of 3 `RECT`s, the first of + // which has the exact same meaning as the `RECT` that is pointed to + // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in + // conjunction with our return value, can + // be used to specify portions of the source and destination window + // rectangles that are valid and should be preserved. We opt not to + // implement an elaborate client-area preservation technique, and + // simply return 0, which means "preserve the entire old client area + // and align it with the upper-left corner of our new client area". + RECT* clientRect = + wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0] + : (reinterpret_cast<RECT*>(lParam)); + clientRect->top += mCaptionHeight - mNonClientOffset.top; + clientRect->left += mHorResizeMargin - mNonClientOffset.left; + clientRect->right -= mHorResizeMargin - mNonClientOffset.right; + clientRect->bottom -= mVertResizeMargin - mNonClientOffset.bottom; + // Make client rect's width and height more than 0 to + // avoid problems of webrender and angle. + clientRect->right = std::max(clientRect->right, clientRect->left + 1); + clientRect->bottom = std::max(clientRect->bottom, clientRect->top + 1); + + result = true; + *aRetValue = 0; + } + break; + } + + case WM_NCHITTEST: { + if (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 && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) || + !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( + "nsWindow::ForcePresent", this, &nsWindow::ForcePresent)); + } + + // let the dwm handle nc painting on glass + // Never allow native painting if we are on fullscreen + if (mSizeMode != nsSizeMode_Fullscreen && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) + break; + + if (wParam == TRUE) { + // going active + *aRetValue = FALSE; // ignored + result = true; + // invalidate to trigger a paint + InvalidateNonClientRegion(); + break; + } else { + // going inactive + *aRetValue = TRUE; // go ahead and deactive + result = true; + // invalidate to trigger a paint + InvalidateNonClientRegion(); + break; + } + } + + case WM_NCPAINT: { + /* + * ClearType changes often don't send a WM_SETTINGCHANGE message. But they + * do seem to always send a WM_NCPAINT message, so let's update on that. + */ + gfxDWriteFont::UpdateSystemTextQuality(); + + /* + * 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 (gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) 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. + DestroyLayerManager(); + OnDestroy(); + result = true; + break; + + case WM_PAINT: + *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: { + LPARAM lParamScreen = lParamToScreen(lParam); + mMouseInDraggableArea = WithinDraggableRegion(GET_X_LPARAM(lParamScreen), + GET_Y_LPARAM(lParamScreen)); + + if (!mMousePresent && !sIsInMouseCapture) { + // First MOUSEMOVE over the client area. Ask for MOUSELEAVE + TRACKMOUSEEVENT mTrack; + mTrack.cbSize = sizeof(TRACKMOUSEEVENT); + mTrack.dwFlags = TME_LEAVE; + mTrack.dwHoverTime = 0; + mTrack.hwndTrack = mWnd; + TrackMouseEvent(&mTrack); + } + mMousePresent = true; + + // Suppress dispatch of pending events + // when mouse moves are generated by widget + // creation instead of user input. + POINT mp; + mp.x = GET_X_LPARAM(lParamScreen); + mp.y = GET_Y_LPARAM(lParamScreen); + bool userMovedMouse = false; + if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) { + userMovedMouse = true; + } + + result = + DispatchMouseEvent(eMouseMove, wParam, lParam, false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE(), + mPointerEvents.GetCachedPointerInfo(msg, wParam)); + if (userMovedMouse) { + DispatchPendingEvents(); + } + } break; + + case WM_NCMOUSEMOVE: { + LPARAM lParamClient = lParamToClient(lParam); + if (WithinDraggableRegion(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) { + if (!sIsInMouseCapture) { + TRACKMOUSEEVENT mTrack; + mTrack.cbSize = sizeof(TRACKMOUSEEVENT); + mTrack.dwFlags = TME_LEAVE | TME_NONCLIENT; + mTrack.dwHoverTime = 0; + mTrack.hwndTrack = mWnd; + TrackMouseEvent(&mTrack); + } + // If we noticed the mouse moving in our draggable region, forward the + // message as a normal WM_MOUSEMOVE. + SendMessage(mWnd, WM_MOUSEMOVE, 0, lParamClient); + } else { + // We've transitioned from a draggable area to somewhere else within + // the non-client area - perhaps one of the edges of the window for + // resizing. + mMouseInDraggableArea = false; + } + + if (mMousePresent && !sIsInMouseCapture && !mMouseInDraggableArea) { + SendMessage(mWnd, WM_MOUSELEAVE, 0, 0); + } + } break; + + case WM_LBUTTONDOWN: { + result = + DispatchMouseEvent(eMouseDown, wParam, lParam, false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE(), + mPointerEvents.GetCachedPointerInfo(msg, wParam)); + DispatchPendingEvents(); + } break; + + case WM_LBUTTONUP: { + result = + DispatchMouseEvent(eMouseUp, wParam, lParam, false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE(), + mPointerEvents.GetCachedPointerInfo(msg, wParam)); + DispatchPendingEvents(); + } break; + + case WM_NCMOUSELEAVE: { + mMouseInDraggableArea = false; + + if (EventIsInsideWindow(this)) { + // If we're handling WM_NCMOUSELEAVE and the mouse is still over the + // window, then by process of elimination, the mouse has moved from the + // non-client to client area, so no need to fall-through to the + // WM_MOUSELEAVE handler. We also need to re-register for the + // WM_MOUSELEAVE message, since according to the documentation at [1], + // all tracking requested via TrackMouseEvent is cleared once + // WM_NCMOUSELEAVE or WM_MOUSELEAVE fires. + // [1]: + // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-trackmouseevent + TRACKMOUSEEVENT mTrack; + mTrack.cbSize = sizeof(TRACKMOUSEEVENT); + mTrack.dwFlags = TME_LEAVE; + mTrack.dwHoverTime = 0; + mTrack.hwndTrack = mWnd; + TrackMouseEvent(&mTrack); + break; + } + // We've transitioned from non-client to outside of the window, so + // fall-through to the WM_MOUSELEAVE handler. + } + case WM_MOUSELEAVE: { + if (!mMousePresent) break; + if (mMouseInDraggableArea) break; + mMousePresent = false; + + // Check if the mouse is over the fullscreen transition window, if so + // clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the + // transition window disappears will not be ignored, even if the mouse + // hasn't moved. + if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) { + sLastMouseMovePoint = {0}; + } + + // We need to check mouse button states and put them in for + // wParam. + WPARAM mouseState = (GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) | + (GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) | + (GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0); + // Synthesize an event position because we don't get one from + // WM_MOUSELEAVE. + LPARAM pos = lParamToClient(::GetMessagePos()); + DispatchMouseEvent(eMouseExitFromWidget, mouseState, pos, false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE()); + } break; + + case MOZ_WM_PEN_LEAVES_HOVER_OF_DIGITIZER: { + LPARAM pos = lParamToClient(::GetMessagePos()); + MOZ_ASSERT(InkCollector::sInkCollector); + uint16_t pointerId = InkCollector::sInkCollector->GetPointerId(); + if (pointerId != 0) { + WinPointerInfo pointerInfo; + pointerInfo.pointerId = pointerId; + DispatchMouseEvent(eMouseExitFromWidget, wParam, pos, false, + MouseButton::ePrimary, + MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo); + 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() == MouseEvent_Binding::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 ? MouseButton::ePrimary : MouseButton::eSecondary, + MOUSE_INPUT_SOURCE()); + if (lParam != -1 && !result && mCustomNonClient && + mDraggableRegion.Contains(GET_X_LPARAM(pos), GET_Y_LPARAM(pos))) { + // Blank area hit, throw up the system menu. + DisplaySystemMenu(mWnd, mSizeMode, mIsRTL, GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam)); + result = true; + } + } break; + + case WM_POINTERLEAVE: + case WM_POINTERDOWN: + case WM_POINTERUP: + case WM_POINTERUPDATE: + result = OnPointerEvents(msg, wParam, lParam); + if (result) { + DispatchPendingEvents(); + } + break; + + case DM_POINTERHITTEST: + if (mDmOwner) { + UINT contactId = GET_POINTERID_WPARAM(wParam); + POINTER_INPUT_TYPE pointerType; + if (mPointerEvents.GetPointerType(contactId, &pointerType) && + pointerType == PT_TOUCHPAD) { + mDmOwner->SetContact(contactId); + } + } + break; + + case WM_LBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, wParam, lParam, false, + MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONUP: + result = DispatchMouseEvent(eMouseUp, wParam, lParam, false, + MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_MBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false, + MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONDOWN: + result = DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false, + MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONUP: + result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false, + MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCMBUTTONDBLCLK: + result = + DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), + false, MouseButton::eMiddle, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_RBUTTONDOWN: + result = + DispatchMouseEvent(eMouseDown, wParam, lParam, false, + MouseButton::eSecondary, MOUSE_INPUT_SOURCE(), + mPointerEvents.GetCachedPointerInfo(msg, wParam)); + DispatchPendingEvents(); + break; + + case WM_RBUTTONUP: + result = + DispatchMouseEvent(eMouseUp, wParam, lParam, false, + MouseButton::eSecondary, MOUSE_INPUT_SOURCE(), + mPointerEvents.GetCachedPointerInfo(msg, wParam)); + DispatchPendingEvents(); + break; + + case WM_RBUTTONDBLCLK: + result = + DispatchMouseEvent(eMouseDoubleClick, wParam, lParam, false, + MouseButton::eSecondary, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONDOWN: + result = + DispatchMouseEvent(eMouseDown, 0, lParamToClient(lParam), false, + MouseButton::eSecondary, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONUP: + result = + DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false, + MouseButton::eSecondary, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCRBUTTONDBLCLK: + result = DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), + false, MouseButton::eSecondary, + MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + // Windows doesn't provide to customize the behavior of 4th nor 5th button + // of mouse. If 5-button mouse works with standard mouse deriver of + // Windows, users cannot disable 4th button (browser back) nor 5th button + // (browser forward). We should allow to do it with our prefs since we can + // prevent Windows to generate WM_APPCOMMAND message if WM_XBUTTONUP + // messages are not sent to DefWindowProc. + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONUP: + *aRetValue = TRUE; + switch (GET_XBUTTON_WPARAM(wParam)) { + case XBUTTON1: + result = !Preferences::GetBool("mousebutton.4th.enabled", true); + break; + case XBUTTON2: + result = !Preferences::GetBool("mousebutton.5th.enabled", true); + break; + default: + break; + } + break; + + case WM_SIZING: { + if (mAspectRatio > 0) { + LPRECT rect = (LPRECT)lParam; + int32_t newWidth, newHeight; + + // The following conditions and switch statement borrow heavily from the + // Chromium source code from + // https://chromium.googlesource.com/chromium/src/+/456d6e533cfb4531995e0ef52c279d4b5aa8a352/ui/views/window/window_resize_utils.cc#45 + if (wParam == WMSZ_LEFT || wParam == WMSZ_RIGHT || + wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT) { + newWidth = rect->right - rect->left; + newHeight = newWidth * mAspectRatio; + } else { + newHeight = rect->bottom - rect->top; + newWidth = newHeight / mAspectRatio; + } + + switch (wParam) { + case WMSZ_RIGHT: + case WMSZ_BOTTOM: + rect->right = newWidth + rect->left; + rect->bottom = rect->top + newHeight; + break; + case WMSZ_TOP: + rect->right = newWidth + rect->left; + rect->top = rect->bottom - newHeight; + break; + case WMSZ_LEFT: + case WMSZ_TOPLEFT: + rect->left = rect->right - newWidth; + rect->top = rect->bottom - newHeight; + break; + case WMSZ_TOPRIGHT: + rect->right = rect->left + newWidth; + rect->top = rect->bottom - newHeight; + break; + case WMSZ_BOTTOMLEFT: + rect->left = rect->right - newWidth; + rect->bottom = rect->top + newHeight; + break; + case WMSZ_BOTTOMRIGHT: + rect->right = rect->left + newWidth; + rect->bottom = rect->top + newHeight; + break; + } + } + + // When we get WM_ENTERSIZEMOVE we don't know yet if we're in a live + // resize or move event. Instead we wait for first VM_SIZING message + // within a ENTERSIZEMOVE to consider this a live resize event. + if (mResizeState == IN_SIZEMOVE) { + mResizeState = RESIZING; + NotifyLiveResizeStarted(); + } + break; + } + + case WM_MOVING: + FinishLiveResizing(MOVING); + if (WinUtils::IsPerMonitorDPIAware()) { + // Sometimes, we appear to miss a WM_DPICHANGED message while moving + // a window around. Therefore, call ChangedDPI and ResetLayout here + // if it appears that the window's scaling is not what we expect. + // This causes the prescontext and appshell window management code to + // check the appUnitsPerDevPixel value and current widget size, and + // refresh them if necessary. If nothing has changed, these calls will + // return without actually triggering any extra reflow or painting. + if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) { + ChangedDPI(); + ResetLayout(); + if (mWidgetListener) { + mWidgetListener->UIResolutionChanged(); + } + } + } + break; + + case WM_ENTERSIZEMOVE: { + if (mResizeState == NOT_RESIZING) { + mResizeState = IN_SIZEMOVE; + } + break; + } + + case WM_EXITSIZEMOVE: { + FinishLiveResizing(NOT_RESIZING); + + if (!sIsInMouseCapture) { + NotifySizeMoveDone(); + } + + break; + } + + case WM_DISPLAYCHANGE: { + ScreenHelperWin::RefreshScreens(); + if (mWidgetListener) { + mWidgetListener->UIResolutionChanged(); + } + break; + } + + case WM_NCLBUTTONDBLCLK: + DispatchMouseEvent(eMouseDoubleClick, 0, lParamToClient(lParam), false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE()); + result = DispatchMouseEvent(eMouseUp, 0, lParamToClient(lParam), false, + MouseButton::ePrimary, MOUSE_INPUT_SOURCE()); + DispatchPendingEvents(); + break; + + case WM_NCLBUTTONDOWN: { + // Dispatch a custom event when this happens in the draggable region, so + // that non-popup-based panels can react to it. This doesn't send an + // actual mousedown event because that would break dragging or interfere + // with other mousedown handling in the caption area. + if (WithinDraggableRegion(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) { + DispatchCustomEvent(u"draggableregionleftmousedown"_ns); + } + 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. + // This can also happen when a modal system dialog is opened, so check + // if the last window to receive the WM_KILLFOCUS message was this one + // or a child of this one. + if (HIWORD(wParam) || + (mLastKillFocusWindow && + (GetTopLevelForFocus(mLastKillFocusWindow) == mWnd))) { + DispatchFocusToTopLevelWindow(false); + } else { + sJustGotDeactivate = true; + } + if (mIsTopWidgetWindow) { + mLastKeyboardLayout = KeyboardLayout::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); + } else { + mLastKillFocusWindow = mWnd; + } + 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 + RefPtr<IAccessible> root( + a11y::LazyInstantiator::GetRootAccessible(mWnd)); + if (root) { + *aRetValue = LresultFromObject(IID_IAccessible, wParam, root); + a11y::LazyInstantiator::EnableBlindAggregation(mWnd); + result = true; + } + } + } break; +#endif + + case WM_SYSCOMMAND: { + WPARAM filteredWParam = (wParam & 0xFFF0); + 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: + // Every window will get this message, but gfxVars only broadcasts + // updates when the value actually changes + if (XRE_IsParentProcess()) { + BOOL dwmEnabled = FALSE; + if (FAILED(::DwmIsCompositionEnabled(&dwmEnabled)) || !dwmEnabled) { + gfxVars::SetDwmCompositionEnabled(false); + } else { + gfxVars::SetDwmCompositionEnabled(true); + } + } + + UpdateNonClientMargins(); + BroadcastMsg(mWnd, WM_DWMCOMPOSITIONCHANGED); + // TODO: Why is NotifyThemeChanged needed, what does it affect? And can we + // make it more granular by tweaking the ChangeKind we pass? + NotifyThemeChanged(widget::ThemeChangeKind::StyleAndLayout); + 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 showFocusRings = UIStateChangeType_NoChange; + if (flags & UISF_HIDEFOCUS) + showFocusRings = (action == UIS_SET) ? UIStateChangeType_Clear + : UIStateChangeType_Set; + NotifyUIStateChanged(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) { + NotifyLiveResizeStopped(); + } + 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; + } + + mCachedHitTestPoint = {pt.x, pt.y}; + mCachedHitTestTime = TimeStamp::Now(); + + if (mDraggableRegion.Contains(pt.x, pt.y)) { + testResult = HTCAPTION; + } else { + testResult = HTCLIENT; + } + mCachedHitTestResult = testResult; + } + + return testResult; +} + +bool nsWindow::WithinDraggableRegion(int32_t screenX, int32_t screenY) { + return ClientMarginHitTestPoint(screenX, screenY) == HTCAPTION; +} + +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); + bool result = nativeKey.HandleKeyUpMessage(aEventDispatched); + if (aMsg.wParam == VK_F10) { + // Bug 1382199: Windows default behavior will trigger the System menu bar + // when F10 is released. Among other things, this causes the System menu bar + // to appear when a web page overrides the contextmenu event. We *never* + // want this default behavior, so eat this key (never pass it to Windows). + return true; + } + return result; +} + +LRESULT nsWindow::ProcessKeyDownMessage(const MSG& aMsg, + bool* aEventDispatched) { + // If this method doesn't call NativeKey::HandleKeyDownMessage(), this method + // must clean up the redirected message information itself. For more + // information, see above comment of + // RedirectedKeyDownMessageManager::AutoFlusher class definition in + // KeyboardLayout.h. + RedirectedKeyDownMessageManager::AutoFlusher redirectedMsgFlusher(this, aMsg); + + ModifierKeyState modKeyState; + + NativeKey nativeKey(this, aMsg, modKeyState); + LRESULT result = + static_cast<LRESULT>(nativeKey.HandleKeyDownMessage(aEventDispatched)); + // HandleKeyDownMessage cleaned up the redirected message information + // itself, so, we should do nothing. + redirectedMsgFlusher.Cancel(); + + if (aMsg.wParam == VK_MENU || + (aMsg.wParam == VK_F10 && !modKeyState.IsShift())) { + // We need to let Windows handle this keypress, + // by returning false, if there's a native menu + // bar somewhere in our containing window hierarchy. + // Otherwise we handle the keypress and don't pass + // it on to Windows, by returning true. + bool hasNativeMenu = false; + HWND hWnd = mWnd; + while (hWnd) { + if (::GetMenu(hWnd)) { + hasNativeMenu = true; + break; + } + hWnd = ::GetParent(hWnd); + } + result = !hasNativeMenu; + } + + return result; +} + +nsresult nsWindow::SynthesizeNativeKeyEvent( + int32_t aNativeKeyboardLayout, int32_t aNativeKeyCode, + uint32_t aModifierFlags, const nsAString& aCharacters, + const nsAString& aUnmodifiedCharacters, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "keyevent"); + + KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance(); + return keyboardLayout->SynthesizeNativeKeyEvent( + this, aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters, + aUnmodifiedCharacters); +} + +nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + 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; + +#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 (mSizeMode != previousSizeMode) OnSizeModeChange(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.MoveTo(wp->x, wp->y); + NotifyWindowMoved(wp->x, wp->y); + } + + // Handle window size changes + if (!(wp->flags & SWP_NOSIZE)) { + RECT r; + int32_t newWidth, newHeight; + + ::GetWindowRect(mWnd, &r); + + newWidth = r.right - r.left; + newHeight = r.bottom - r.top; + + if (newWidth > mLastSize.width) { + RECT drect; + + // getting wider + drect.left = wp->x + mLastSize.width; + drect.top = wp->y; + drect.right = drect.left + (newWidth - mLastSize.width); + drect.bottom = drect.top + newHeight; + + ::RedrawWindow(mWnd, &drect, nullptr, + RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT | + RDW_ERASENOW | RDW_ALLCHILDREN); + } + if (newHeight > mLastSize.height) { + RECT drect; + + // getting taller + drect.left = wp->x; + drect.top = wp->y + mLastSize.height; + drect.right = drect.left + newWidth; + drect.bottom = drect.top + (newHeight - mLastSize.height); + + ::RedrawWindow(mWnd, &drect, nullptr, + RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT | + RDW_ERASENOW | RDW_ALLCHILDREN); + } + + mBounds.SizeTo(newWidth, newHeight); + mLastSize.width = newWidth; + mLastSize.height = newHeight; + +#ifdef WINSTATE_DEBUG_OUTPUT + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("*** Resize window: %d x %d x %d x %d\n", wp->x, wp->y, newWidth, + newHeight)); +#endif + + if (mAspectRatio > 0) { + // It's possible (via Windows Aero Snap) that the size of the window + // has changed such that it violates the aspect ratio constraint. If so, + // queue up an event to enforce the aspect ratio constraint and repaint. + // When resized with Windows Aero Snap, we are in the NOT_RESIZING state. + float newAspectRatio = (float)newHeight / newWidth; + if (mResizeState == NOT_RESIZING && mAspectRatio != newAspectRatio) { + // Hold a reference to self alive and pass it into the lambda to make + // sure this nsIWidget stays alive long enough to run this function. + nsCOMPtr<nsIWidget> self(this); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "EnforceAspectRatio", [self, this, newWidth]() -> void { + if (mWnd) { + Resize(newWidth, newWidth * mAspectRatio, true); + } + })); + } + } + + // If a maximized window is resized, recalculate the non-client margins. + if (mSizeMode == nsSizeMode_Maximized) { + if (UpdateNonClientMargins(nsSizeMode_Maximized, true)) { + // gecko resize event already sent by UpdateNonClientMargins. + return; + } + } + } + + // Notify the widget listener for size change of client area for gecko + // events. This needs to be done when either window size is changed, + // or window frame is changed. They may not happen together. + // However, we don't invoke that for popup when window frame changes, + // because popups may trigger frame change before size change via + // {Set,Clear}ThemeRegion they invoke in Resize. That would make the + // code below call OnResize with a wrong client size first, which can + // lead to flickerling for some popups. + if (!(wp->flags & SWP_NOSIZE) || + ((wp->flags & SWP_FRAMECHANGED) && !IsPopup())) { + RECT r; + LayoutDeviceIntSize clientSize; + if (::GetClientRect(mWnd, &r)) { + clientSize = WinUtils::ToIntRect(r).Size(); + } else { + clientSize = mBounds.Size(); + } + // Send a gecko resize event + OnResize(clientSize); + } +} + +void nsWindow::OnWindowPosChanging(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; + + OnSizeModeChange(sizeMode); + + UpdateNonClientMargins(sizeMode, false); + } + + // Force fullscreen. This works around a bug in Windows 10 1809 where + // using fullscreen when a window is "snapped" causes a spurious resize + // smaller than the full screen, see bug 1482920. + if (mSizeMode == nsSizeMode_Fullscreen && !(info->flags & SWP_NOMOVE) && + !(info->flags & SWP_NOSIZE)) { + nsCOMPtr<nsIScreenManager> screenmgr = + do_GetService(sScreenManagerContractID); + if (screenmgr) { + LayoutDeviceIntRect bounds(info->x, info->y, info->cx, info->cy); + DesktopIntRect deskBounds = + RoundedToInt(bounds / GetDesktopToDeviceScale()); + nsCOMPtr<nsIScreen> screen; + screenmgr->ScreenForRect(deskBounds.X(), deskBounds.Y(), + deskBounds.Width(), deskBounds.Height(), + getter_AddRefs(screen)); + + if (screen) { + int32_t x, y, width, height; + screen->GetRect(&x, &y, &width, &height); + + info->x = x; + info->y = y; + info->cx = width; + info->cy = height; + } + } + } + + // enforce local z-order rules + if (!(info->flags & SWP_NOZORDER)) { + HWND hwndAfter = info->hwndInsertAfter; + + nsWindow* aboveWindow = 0; + nsWindowZ placement; + + if (hwndAfter == HWND_BOTTOM) + placement = nsWindowZBottom; + else if (hwndAfter == HWND_TOP || hwndAfter == HWND_TOPMOST || + hwndAfter == HWND_NOTOPMOST) + placement = nsWindowZTop; + else { + placement = nsWindowZRelative; + aboveWindow = WinUtils::GetNSWindowPtr(hwndAfter); + } + + if (mWidgetListener) { + nsCOMPtr<nsIWidget> actualBelow = nullptr; + if (mWidgetListener->ZLevelChanged(false, &placement, aboveWindow, + getter_AddRefs(actualBelow))) { + if (placement == nsWindowZBottom) + info->hwndInsertAfter = HWND_BOTTOM; + else if (placement == nsWindowZTop) + info->hwndInsertAfter = HWND_TOP; + else { + info->hwndInsertAfter = + (HWND)actualBelow->GetNativeData(NS_NATIVE_WINDOW); + } + } + } + } + // prevent rude external programs from making hidden window visible + if (mWindowType == eWindowType_invisible) info->flags &= ~SWP_SHOWWINDOW; + + // When waking from sleep or switching out of tablet mode, Windows 10 + // Version 1809 will reopen popup windows that should be hidden. Detect + // this case and refuse to show the window. + static bool sDWMUnhidesPopups = IsWin10Sep2018UpdateOrLater(); + if (sDWMUnhidesPopups && (info->flags & SWP_SHOWWINDOW) && + mWindowType == eWindowType_popup && mWidgetListener && + mWidgetListener->ShouldNotBeVisible()) { + info->flags &= ~SWP_SHOWWINDOW; + } +} + +void nsWindow::UserActivity() { + // Check if we have the idle service, if not we try to get it. + if (!mIdleService) { + mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1"); + } + + // Check that we now have the idle service. + if (mIdleService) { + mIdleService->ResetIdleTimeOut(0); + } +} + +nsIntPoint nsWindow::GetTouchCoordinates(WPARAM wParam, LPARAM lParam) { + nsIntPoint ret; + uint32_t cInputs = LOWORD(wParam); + if (cInputs != 1) { + // Just return 0,0 if there isn't exactly one touch point active + return ret; + } + PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs]; + if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, + sizeof(TOUCHINPUT))) { + ret.x = TOUCH_COORD_TO_PIXEL(pInputs[0].x); + ret.y = TOUCH_COORD_TO_PIXEL(pInputs[0].y); + } + delete[] pInputs; + // Note that we don't call CloseTouchInputHandle here because we need + // to read the touch input info again in OnTouch later. + return ret; +} + +// Determine if the touch device that originated |aOSEvent| needs to have +// touch events representing a two-finger gesture converted to pan +// gesture events. +// We only do this for touch devices with a specific name and identifiers. +bool TouchDeviceNeedsPanGestureConversion(PTOUCHINPUT aOSEvent, + uint32_t aTouchCount) { + if (aTouchCount == 0) { + return false; + } + HANDLE source = aOSEvent[0].hSource; + std::string deviceName; + UINT dataSize = 0; + // The first call just queries how long the name string will be. + GetRawInputDeviceInfoA(source, RIDI_DEVICENAME, nullptr, &dataSize); + if (!dataSize || dataSize > 0x10000) { + return false; + } + deviceName.resize(dataSize); + // The second call actually populates the string. + UINT result = GetRawInputDeviceInfoA(source, RIDI_DEVICENAME, &deviceName[0], + &dataSize); + if (result == UINT_MAX) { + return false; + } + // The affected device name is "\\?\VIRTUAL_DIGITIZER", but each backslash + // needs to be escaped with another one. + std::string expectedDeviceName = "\\\\?\\VIRTUAL_DIGITIZER"; + // For some reason, the dataSize returned by the first call is double the + // actual length of the device name (as if it were returning the size of a + // wide-character string in bytes) even though we are using the narrow + // version of the API. For the comparison against the expected device name + // to pass, we truncate the buffer to be no longer tha the expected device + // name. + if (deviceName.substr(0, expectedDeviceName.length()) != expectedDeviceName) { + return false; + } + + RID_DEVICE_INFO deviceInfo; + deviceInfo.cbSize = sizeof(deviceInfo); + dataSize = sizeof(deviceInfo); + result = + GetRawInputDeviceInfoA(source, RIDI_DEVICEINFO, &deviceInfo, &dataSize); + if (result == UINT_MAX) { + return false; + } + // The device identifiers that we check for here come from bug 1355162 + // comment 1 (see also bug 1511901 comment 35). + return deviceInfo.dwType == RIM_TYPEHID && deviceInfo.hid.dwVendorId == 0 && + deviceInfo.hid.dwProductId == 0 && + deviceInfo.hid.dwVersionNumber == 1 && + deviceInfo.hid.usUsagePage == 13 && deviceInfo.hid.usUsage == 4; +} + +Maybe<PanGestureInput> nsWindow::ConvertTouchToPanGesture( + const MultiTouchInput& aTouchInput, PTOUCHINPUT aOSEvent) { + // The first time this function is called, perform some checks on the + // touch device that originated the touch event, to see if it's a device + // for which we want to convert the touch events to pang gesture events. + static bool shouldConvert = TouchDeviceNeedsPanGestureConversion( + aOSEvent, aTouchInput.mTouches.Length()); + if (!shouldConvert) { + return Nothing(); + } + + // Only two-finger gestures need conversion. + if (aTouchInput.mTouches.Length() != 2) { + return Nothing(); + } + + PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN; + if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_START) { + eventType = PanGestureInput::PANGESTURE_START; + } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_END) { + eventType = PanGestureInput::PANGESTURE_END; + } else if (aTouchInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) { + eventType = PanGestureInput::PANGESTURE_CANCELLED; + } + + // Use the midpoint of the two touches as the start point of the pan gesture. + ScreenPoint focusPoint = (aTouchInput.mTouches[0].mScreenPoint + + aTouchInput.mTouches[1].mScreenPoint) / + 2; + // To compute the displacement of the pan gesture, we keep track of the + // location of the previous event. + ScreenPoint displacement = (eventType == PanGestureInput::PANGESTURE_START) + ? ScreenPoint(0, 0) + : (focusPoint - mLastPanGestureFocus); + mLastPanGestureFocus = focusPoint; + + // We need to negate the displacement because for a touch event, moving the + // fingers down results in scrolling up, but for a touchpad gesture, we want + // moving the fingers down to result in scrolling down. + PanGestureInput result(eventType, aTouchInput.mTime, aTouchInput.mTimeStamp, + focusPoint, -displacement, aTouchInput.modifiers); + result.mSimulateMomentum = true; + + return Some(result); +} + +// Dispatch an event that originated as an OS touch event. +// Usually, we want to dispatch it as a touch event, but some touchpads +// produce touch events for two-finger scrolling, which need to be converted +// to pan gesture events for correct behaviour. +void nsWindow::DispatchTouchOrPanGestureInput(MultiTouchInput& aTouchInput, + PTOUCHINPUT aOSEvent) { + if (Maybe<PanGestureInput> panInput = + ConvertTouchToPanGesture(aTouchInput, aOSEvent)) { + DispatchPanGestureInput(*panInput); + return; + } + + DispatchTouchInput(aTouchInput); +} + +bool nsWindow::OnTouch(WPARAM wParam, LPARAM lParam) { + uint32_t cInputs = LOWORD(wParam); + PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs]; + + if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, + sizeof(TOUCHINPUT))) { + MultiTouchInput touchInput, touchEndInput; + + // Walk across the touch point array processing each contact point. + for (uint32_t i = 0; i < cInputs; i++) { + bool addToEvent = false, addToEndEvent = false; + + // N.B.: According with MS documentation + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317334(v=vs.85).aspx + // TOUCHEVENTF_DOWN cannot be combined with TOUCHEVENTF_MOVE or + // TOUCHEVENTF_UP. Possibly, it means that TOUCHEVENTF_MOVE and + // TOUCHEVENTF_UP can be combined together. + + if (pInputs[i].dwFlags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE)) { + if (touchInput.mTimeStamp.IsNull()) { + // Initialize a touch event to send. + touchInput.mType = MultiTouchInput::MULTITOUCH_MOVE; + touchInput.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].dwMask & 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()) { + DispatchTouchOrPanGestureInput(touchInput, pInputs); + } + // Dispatch touch end event if we have one. + if (!touchEndInput.mTimeStamp.IsNull()) { + DispatchTouchOrPanGestureInput(touchEndInput, pInputs); + } + } + + delete[] pInputs; + CloseTouchInputHandle((HTOUCHINPUT)lParam); + return true; +} + +// Gesture event processing. Handles WM_GESTURE events. +bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam) { + // Treatment for pan events which translate into scroll events: + if (mGesture.IsPanEvent(lParam)) { + if (!mGesture.ProcessPanMessage(mWnd, wParam, lParam)) + return false; // ignore + + nsEventStatus status; + + WidgetWheelEvent wheelEvent(true, eWheel, this); + + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(wheelEvent); + + wheelEvent.mButton = 0; + wheelEvent.mTime = ::GetMessageTime(); + wheelEvent.mTimeStamp = GetMessageTimeStamp(wheelEvent.mTime); + wheelEvent.mInputSource = MouseEvent_Binding::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); + } + + CloseGestureInfoHandle((HGESTUREINFO)lParam); + + return true; + } + + // Other gestures translate into simple gesture events: + WidgetSimpleGestureEvent event(true, eVoidEvent, this); + if (!mGesture.ProcessGestureMessage(mWnd, wParam, lParam, event)) { + return false; // fall through to DefWndProc + } + + // Polish up and send off the new event + ModifierKeyState modifierKeyState; + modifierKeyState.InitInputEvent(event); + event.mButton = 0; + event.mTime = ::GetMessageTime(); + event.mTimeStamp = GetMessageTimeStamp(event.mTime); + event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; + + nsEventStatus status; + DispatchEvent(&event, status); + if (status == nsEventStatus_eIgnore) { + return false; // Ignored, fall through + } + + // Only close this if we process and return true. + CloseGestureInfoHandle((HGESTUREINFO)lParam); + + return true; // Handled +} + +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; + + DestroyDirectManipulation(); + + if (mWnd == mLastKillFocusWindow) { + mLastKillFocusWindow = 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 == eCursorInvalid) { + SetCursor(eCursor_standard, nullptr, 0, 0); + } + + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->OnDestroyWindow(); + } + mBasicLayersSurface = nullptr; + + // Finalize panning feedback to possibly restore window displacement + mGesture.PanFeedbackFinalize(mWnd, true); + + // Clear the main HWND. + mWnd = nullptr; +} + +// Send a resize message to the listener +bool nsWindow::OnResize(const LayoutDeviceIntSize& aSize) { + if (mCompositorWidgetDelegate && + !mCompositorWidgetDelegate->OnWindowResize(aSize)) { + return false; + } + + bool result = false; + if (mWidgetListener) { + result = mWidgetListener->WindowResized(this, aSize.width, aSize.height); + } + + // If there is an attached view, inform it as well as the normal widget + // listener. + if (mAttachedWidgetListener) { + return mAttachedWidgetListener->WindowResized(this, aSize.width, + aSize.height); + } + + return result; +} + +void nsWindow::OnSizeModeChange(nsSizeMode aSizeMode) { + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->OnWindowModeChange(aSizeMode); + } + + if (mWidgetListener) { + mWidgetListener->SizeModeChanged(aSizeMode); + } +} + +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() { + if (IsSmallPopup()) { + 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(gfx::Feature::HW_COMPOSITING) && + !gfxConfig::IsEnabled(gfx::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(gfx::Feature::HW_COMPOSITING)) { + sHasBogusPopupsDropShadowOnMultiMonitor = TRI_TRUE; + } + } + } + } + } + } + return !!sHasBogusPopupsDropShadowOnMultiMonitor; +} + +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 (StaticPrefs::layout_css_devPixelsPerPx() > 0.0) { + return; + } + mDefaultScale = -1.0; // force recomputation of scale factor + + 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); + } + UpdateNonClientMargins(); + ChangedDPI(); + ResetLayout(); +} + +/************************************************************** + ************************************************************** + ** + ** BLOCK: IME management and accessibility + ** + ** Handles managing IME input and accessibility. + ** + ************************************************************** + **************************************************************/ + +void nsWindow::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) { + InputContext newInputContext = aContext; + IMEHandler::SetInputContext(this, newInputContext, aAction); + mInputContext = newInputContext; +} + +InputContext nsWindow::GetInputContext() { + mInputContext.mIMEState.mOpen = IMEState::CLOSED; + if (WinUtils::IsIMEEnabled(mInputContext) && IMEHandler::GetOpenState(this)) { + mInputContext.mIMEState.mOpen = IMEState::OPEN; + } else { + mInputContext.mIMEState.mOpen = IMEState::CLOSED; + } + return mInputContext; +} + +TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() { + return IMEHandler::GetNativeTextEventDispatcherListener(); +} + +#ifdef ACCESSIBILITY +# ifdef DEBUG +# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) \ + if (a11y::logging::IsEnabled(a11y::logging::ePlatforms)) { \ + printf( \ + "Get the window:\n {\n HWND: %p, parent HWND: %p, wndobj: " \ + "%p,\n", \ + aHwnd, ::GetParent(aHwnd), aWnd); \ + printf(" acc: %p", aAcc); \ + if (aAcc) { \ + nsAutoString name; \ + aAcc->Name(name); \ + printf(", accname: %s", NS_ConvertUTF16toUTF8(name).get()); \ + } \ + printf("\n }\n"); \ + } + +# else +# define NS_LOG_WMGETOBJECT(aWnd, aHwnd, aAcc) +# endif + +a11y::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->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(); + + // Clear window by transparent black when compositor window is used in GPU + // process and non-client area rendering by DWM is enabled. + // It is for showing non-client area rendering. See nsWindow::UpdateGlass(). + if (HasGlass() && GetLayerManager()->AsKnowsCompositor() && + GetLayerManager()->AsKnowsCompositor()->GetUseCompositorWnd()) { + HDC hdc; + RECT rect; + hdc = ::GetWindowDC(mWnd); + ::GetWindowRect(mWnd, &rect); + ::MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2); + ::FillRect(hdc, &rect, + reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))); + ReleaseDC(mWnd, hdc); + } + + // Disable double buffering with D3D compositor for disabling compositor + // window usage. + if (HasGlass() && !gfxVars::UseWebRender() && + gfxVars::UseDoubleBufferingWithCompositor()) { + gfxVars::SetUseDoubleBufferingWithCompositor(false); + GPUProcessManager::Get()->ResetCompositors(); + } +} + +#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 + DebugOnly<BOOL> status = ::KillTimer(nullptr, sHookTimerId); + NS_ASSERTION(status, "Hook Timer was not killed."); + sHookTimerId = 0; + } + + if (sRollupMsgId != 0) { + // Note: DealWithPopups does the check to make sure that the rollup widget + // is set. + LRESULT popupHandlingResult; + nsAutoRollup autoRollup; + DealWithPopups(sRollupMsgWnd, sRollupMsgId, 0, 0, &popupHandlingResult); + sRollupMsgId = 0; + sRollupMsgWnd = nullptr; + } +} + +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, + Maybe<POINT> aEventPoint) { + RECT r; + ::GetWindowRect(aWindow->mWnd, &r); + POINT mp; + if (aEventPoint) { + mp = *aEventPoint; + } else { + DWORD pos = ::GetMessagePos(); + mp.x = GET_X_LPARAM(pos); + mp.y = GET_Y_LPARAM(pos); + } + + // was the event inside this window? + return static_cast<bool>(::PtInRect(&r, mp)); +} + +// static +bool nsWindow::GetPopupsToRollup(nsIRollupListener* aRollupListener, + uint32_t* aPopupsToRollup, + Maybe<POINT> aEventPoint) { + // If we're dealing with menus, we probably have submenus and we don't want + // to rollup some of them if the click is in a parent menu of the current + // submenu. + *aPopupsToRollup = UINT32_MAX; + AutoTArray<nsIWidget*, 5> widgetChain; + uint32_t sameTypeCount = aRollupListener->GetSubmenuWidgetChain(&widgetChain); + for (uint32_t i = 0; i < widgetChain.Length(); ++i) { + nsIWidget* widget = widgetChain[i]; + if (EventIsInsideWindow(static_cast<nsWindow*>(widget), aEventPoint)) { + // Don't roll up if the mouse event occurred within a menu of the + // same type. If the mouse event occurred in a menu higher than that, + // roll up, but pass the number of popups to Rollup so that only those + // of the same type close up. + if (i < sameTypeCount) { + return false; + } + + *aPopupsToRollup = sameTypeCount; + break; + } + } + return true; +} + +// static +bool nsWindow::NeedsToHandleNCActivateDelayed(HWND aWnd) { + // While popup is open, popup window might be activated by other application. + // At this time, we need to take back focus to the previous window but it + // causes flickering its nonclient area because WM_NCACTIVATE comes before + // WM_ACTIVATE and we cannot know which window will take focus at receiving + // WM_NCACTIVATE. Therefore, we need a hack for preventing the flickerling. + // + // If non-popup window receives WM_NCACTIVATE at deactivating, default + // wndproc shouldn't handle it as deactivating. Instead, at receiving + // WM_ACTIVIATE after that, WM_NCACTIVATE should be sent again manually. + // This returns true if the window needs to handle WM_NCACTIVATE later. + + nsWindow* window = WinUtils::GetNSWindowPtr(aWnd); + return window && !window->IsPopup(); +} + +static bool IsTouchSupportEnabled(HWND aWnd) { + nsWindow* topWindow = + WinUtils::GetNSWindowPtr(WinUtils::GetTopLevelHWND(aWnd, true)); + return topWindow ? topWindow->IsTouchWindow() : false; +} + +// static +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; + } + [[fallthrough]]; + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_NCLBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCMBUTTONDOWN: + if (nativeMessage != WM_TOUCH && IsTouchSupportEnabled(aWnd) && + MOUSE_INPUT_SOURCE() == MouseEvent_Binding::MOZ_SOURCE_TOUCH) { + // If any of these mouse events are really compatibility events that + // Windows is sending for touch inputs, then don't allow them to dismiss + // popups when APZ is enabled (instead we do the dismissing as part of + // WM_TOUCH handling which is more correct). + // If we don't do this, then when the user lifts their finger after a + // long-press, the WM_RBUTTONDOWN compatibility event that Windows sends + // us will dismiss the contextmenu popup that we displayed as part of + // handling the long-tap-up. + return false; + } + if (!EventIsInsideWindow(popupWindow) && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + break; + } + return false; + case WM_POINTERDOWN: { + WinPointerEvents pointerEvents; + if (!pointerEvents.ShouldRollupOnPointerEvent(nativeMessage, aWParam)) { + return false; + } + POINT pt; + pt.x = GET_X_LPARAM(aLParam); + pt.y = GET_Y_LPARAM(aLParam); + if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) { + return false; + } + if (EventIsInsideWindow(popupWindow, Some(pt))) { + // Don't roll up if the event is inside the popup window. + return false; + } + } break; + case MOZ_WM_DMANIP: { + POINT pt; + ::GetCursorPos(&pt); + if (!GetPopupsToRollup(rollupListener, &popupsToRollup, Some(pt))) { + return false; + } + if (EventIsInsideWindow(popupWindow, Some(pt))) { + // Don't roll up if the event is inside the popup window + return false; + } + } break; + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + // We need to check if the popup thinks that it should cause closing + // itself when mouse wheel events are fired outside the rollup widget. + if (!EventIsInsideWindow(popupWindow)) { + // Check if we should consume this event even if we don't roll-up: + consumeRollupEvent = rollupListener->ShouldConsumeOnMouseWheelEvent(); + *aResult = MA_ACTIVATE; + if (rollupListener->ShouldRollupOnMouseWheelEvent() && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + break; + } + } + return consumeRollupEvent; + + case WM_ACTIVATEAPP: + 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()) { + // Consume this message here since previous window must not have + // been inactivated since we've already stopped accepting the + // inactivation below. + return true; + } + } else if (LOWORD(aWParam) == WA_INACTIVE) { + nsWindow* activeWindow = + WinUtils::GetNSWindowPtr(reinterpret_cast<HWND>(aLParam)); + if (sPendingNCACTIVATE && NeedsToHandleNCActivateDelayed(aWnd)) { + // If focus moves to non-popup widget or focusable popup, the window + // needs to update its nonclient area. + if (!activeWindow || !activeWindow->IsPopup()) { + sSendingNCACTIVATE = true; + ::SendMessageW(aWnd, WM_NCACTIVATE, false, 0); + sSendingNCACTIVATE = false; + } + sPendingNCACTIVATE = false; + } + // If focus moves from/to popup, we don't need to rollup the popup + // because such case is caused by strange mouse drivers. And in + // such case, we should consume the message here since we need to + // hide this odd focus move from our content. (If we didn't consume + // the message here, ProcessMessage() will notify widget listener of + // inactivation and that causes unnecessary reflow for supporting + // -moz-window-inactive pseudo class. + if (activeWindow) { + if (activeWindow->IsPopup()) { + return true; + } + nsWindow* deactiveWindow = WinUtils::GetNSWindowPtr(aWnd); + if (deactiveWindow && deactiveWindow->IsPopup()) { + return true; + } + } + } else if (LOWORD(aWParam) == WA_CLICKACTIVE) { + // If the WM_ACTIVATE message is caused by a click in a popup, + // we should not rollup any popups. + nsWindow* window = WinUtils::GetNSWindowPtr(aWnd); + if ((window && window->IsPopup()) || + !GetPopupsToRollup(rollupListener, &popupsToRollup)) { + return false; + } + } + break; + + case MOZ_WM_REACTIVATE: + // The previous active window should take back focus. + if (::IsWindow(reinterpret_cast<HWND>(aLParam))) { + // FYI: Even without this API call, you see expected result (e.g., the + // owner window of the popup keeps active without flickering + // the non-client area). And also this causes initializing + // TSF and it causes using CPU time a lot. However, even if we + // consume WM_ACTIVE messages, native focus change has already + // been occurred. I.e., a popup window is active now. Therefore, + // you'll see some odd behavior if we don't reactivate the owner + // window here. For example, if you do: + // 1. Turn wheel on a bookmark panel. + // 2. Turn wheel on another window. + // then, you'll see that the another window becomes active but the + // owner window of the bookmark panel looks still active and the + // bookmark panel keeps open. The reason is that the first wheel + // operation gives focus to the bookmark panel. Therefore, when + // the next operation gives focus to the another window, previous + // focus window is the bookmark panel (i.e., a popup window). + // So, in this case, our hack around here prevents to inactivate + // the owner window and roll up the bookmark panel. + ::SetForegroundWindow(reinterpret_cast<HWND>(aLParam)); + } + return true; + + case WM_NCACTIVATE: + if (!aWParam && !sSendingNCACTIVATE && + NeedsToHandleNCActivateDelayed(aWnd)) { + // Don't just consume WM_NCACTIVATE. It doesn't handle only the + // nonclient area state change. + ::DefWindowProcW(aWnd, aMessage, TRUE, aLParam); + // Accept the deactivating because it's necessary to receive following + // WM_ACTIVATE. + *aResult = TRUE; + sPendingNCACTIVATE = true; + return true; + } + return false; + + case WM_MOUSEACTIVATE: + if (!EventIsInsideWindow(popupWindow) && + GetPopupsToRollup(rollupListener, &popupsToRollup)) { + // WM_MOUSEACTIVATE may be caused by moving the mouse (e.g., X-mouse + // of TweakUI is enabled. Then, check if the popup should be rolled up + // with rollup listener. If not, just consume the message. + if (HIWORD(aLParam) == WM_MOUSEMOVE && + !rollupListener->ShouldRollupOnMouseActivate()) { + return true; + } + // Otherwise, it should be handled by wndproc. + return false; + } + + // Prevent the click inside the popup from causing a change in window + // activation. Since the popup is shown non-activated, we need to eat any + // requests to activate the window while it is displayed. Windows will + // automatically activate the popup on the mousedown otherwise. + return true; + + case WM_SHOWWINDOW: + // If the window is being minimized, close popups. + if (aLParam == SW_PARENTCLOSING) { + 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(!nsAutoRollup::GetLastRollup(), "last rollup is null"); + + if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN || + nativeMessage == WM_POINTERDOWN) { + nsIntPoint pos; + if (nativeMessage == WM_TOUCH) { + if (nsWindow* win = WinUtils::GetNSWindowPtr(aWnd)) { + pos = win->GetTouchCoordinates(aWParam, aLParam); + } + } else { + POINT pt; + pt.x = GET_X_LPARAM(aLParam); + pt.y = GET_Y_LPARAM(aLParam); + ::ClientToScreen(aWnd, &pt); + pos = nsIntPoint(pt.x, pt.y); + } + + nsIContent* lastRollup; + consumeRollupEvent = + rollupListener->Rollup(popupsToRollup, true, &pos, &lastRollup); + nsAutoRollup::SetLastRollup(lastRollup); + } 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; + } +} + +// Set a flag if hwnd is a (non-popup) visible window from this process, +// and bail out of the enumeration. Otherwise leave the flag unmodified +// and continue the enumeration. +// lParam must be a bool* pointing at the flag to be set. +static BOOL CALLBACK EnumVisibleWindowsProc(HWND hwnd, LPARAM lParam) { + DWORD pid; + ::GetWindowThreadProcessId(hwnd, &pid); + if (pid == ::GetCurrentProcessId() && ::IsWindowVisible(hwnd)) { + // Don't count popups as visible windows, since they don't take focus, + // in case we only have a popup visible (see bug 1554490 where the gfx + // test window is an offscreen popup). + nsWindow* window = WinUtils::GetNSWindowPtr(hwnd); + if (!window || !window->IsPopup()) { + bool* windowsVisible = reinterpret_cast<bool*>(lParam); + *windowsVisible = true; + return FALSE; + } + } + return TRUE; +} + +// Determine if it would be ok to activate a window, taking focus. +// We want to avoid stealing focus from another app (bug 225305). +bool nsWindow::CanTakeFocus() { + HWND fgWnd = ::GetForegroundWindow(); + if (!fgWnd) { + // There is no foreground window, so don't worry about stealing focus. + return true; + } + // We can take focus if the current foreground window is already from + // this process. + DWORD pid; + ::GetWindowThreadProcessId(fgWnd, &pid); + if (pid == ::GetCurrentProcessId()) { + return true; + } + + bool windowsVisible = false; + ::EnumWindows(EnumVisibleWindowsProc, + reinterpret_cast<LPARAM>(&windowsVisible)); + + if (!windowsVisible) { + // We're probably creating our first visible window, allow that to + // take focus. + return true; + } + return false; +} + +/* static */ const wchar_t* nsWindow::GetMainWindowClass() { + static const wchar_t* sMainWindowClass = nullptr; + if (!sMainWindowClass) { + nsAutoString className; + Preferences::GetString("ui.window_class_override", className); + if (!className.IsEmpty()) { + sMainWindowClass = wcsdup(className.get()); + } else { + sMainWindowClass = kClassNameGeneral; + } + } + return sMainWindowClass; +} + +LPARAM nsWindow::lParamToScreen(LPARAM lParam) { + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::ClientToScreen(mWnd, &pt); + return MAKELPARAM(pt.x, pt.y); +} + +LPARAM nsWindow::lParamToClient(LPARAM lParam) { + POINT pt; + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ::ScreenToClient(mWnd, &pt); + return MAKELPARAM(pt.x, pt.y); +} + +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()); +} + +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; + } +} + +bool nsWindow::OnPointerEvents(UINT msg, WPARAM aWParam, LPARAM aLParam) { + if (!mPointerEvents.ShouldHandleWinPointerMessages(msg, aWParam)) { + return false; + } + if (!mPointerEvents.ShouldFirePointerEventByWinPointerMessages()) { + // We have to handle WM_POINTER* to fetch and cache pen related information + // and fire WidgetMouseEvent with the cached information the WM_*BUTTONDOWN + // handler. This is because Windows doesn't support ::DoDragDrop in the + // touch or pen message handlers. + mPointerEvents.ConvertAndCachePointerInfo(msg, aWParam); + // Don't consume the Windows WM_POINTER* messages + return false; + } + // When dispatching mouse events with pen, there may be some + // WM_POINTERUPDATE messages between WM_POINTERDOWN and WM_POINTERUP with + // small movements. Those events will reset sLastMousePoint and reset + // sLastClickCount. To prevent that, we keep the last pen down position + // and compare it with the subsequent WM_POINTERUPDATE. If the movement is + // smaller than GetSystemMetrics(SM_CXDRAG), then we suppress firing + // eMouseMove for WM_POINTERUPDATE. + static POINT sLastPointerDownPoint = {0}; + + // We don't support chorded buttons for pen. Keep the button at + // WM_POINTERDOWN. + static mozilla::MouseButton sLastPenDownButton = MouseButton::ePrimary; + static bool sPointerDown = false; + + EventMessage message; + mozilla::MouseButton button = MouseButton::ePrimary; + switch (msg) { + case WM_POINTERDOWN: { + LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam), + GET_Y_LPARAM(aLParam)); + sLastPointerDownPoint.x = eventPoint.x; + sLastPointerDownPoint.y = eventPoint.y; + message = eMouseDown; + button = IS_POINTER_SECONDBUTTON_WPARAM(aWParam) ? MouseButton::eSecondary + : MouseButton::ePrimary; + sLastPenDownButton = button; + sPointerDown = true; + } break; + case WM_POINTERUP: + message = eMouseUp; + MOZ_ASSERT(sPointerDown, "receive WM_POINTERUP w/o WM_POINTERDOWN"); + button = sPointerDown ? sLastPenDownButton : MouseButton::ePrimary; + sPointerDown = false; + break; + case WM_POINTERUPDATE: + message = eMouseMove; + if (sPointerDown) { + LayoutDeviceIntPoint eventPoint(GET_X_LPARAM(aLParam), + GET_Y_LPARAM(aLParam)); + int32_t movementX = sLastPointerDownPoint.x > eventPoint.x + ? sLastPointerDownPoint.x - eventPoint.x + : eventPoint.x - sLastPointerDownPoint.x; + int32_t movementY = sLastPointerDownPoint.y > eventPoint.y + ? sLastPointerDownPoint.y - eventPoint.y + : eventPoint.y - sLastPointerDownPoint.y; + bool insideMovementThreshold = + movementX < (int32_t)::GetSystemMetrics(SM_CXDRAG) && + movementY < (int32_t)::GetSystemMetrics(SM_CYDRAG); + + if (insideMovementThreshold) { + // Suppress firing eMouseMove for WM_POINTERUPDATE if the movement + // from last WM_POINTERDOWN is smaller than SM_CXDRAG / SM_CYDRAG + return false; + } + button = sLastPenDownButton; + } + break; + case WM_POINTERLEAVE: + message = eMouseExitFromWidget; + break; + default: + return false; + } + uint32_t pointerId = mPointerEvents.GetPointerId(aWParam); + POINTER_PEN_INFO penInfo; + mPointerEvents.GetPointerPenInfo(pointerId, &penInfo); + + // Windows defines the pen pressure is normalized to a range between 0 and + // 1024. Convert it to float. + float pressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0; + int16_t buttons = sPointerDown ? button == MouseButton::ePrimary + ? MouseButtonsFlag::ePrimaryFlag + : MouseButtonsFlag::eSecondaryFlag + : MouseButtonsFlag::eNoButtons; + WinPointerInfo pointerInfo(pointerId, penInfo.tiltX, penInfo.tiltY, pressure, + buttons); + + // The aLParam of WM_POINTER* is the screen location. Convert it to client + // location + LPARAM newLParam = lParamToClient(aLParam); + DispatchMouseEvent(message, aWParam, newLParam, false, button, + MouseEvent_Binding::MOZ_SOURCE_PEN, &pointerInfo); + // Consume WM_POINTER* to stop Windows fires WM_*BUTTONDOWN / WM_*BUTTONUP + // WM_MOUSEMOVE. + return true; +} + +void nsWindow::GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) { + *aInitData = WinCompositorWidgetInitData( + reinterpret_cast<uintptr_t>(mWnd), + reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this)), + mTransparencyMode, mSizeMode); +} + +bool nsWindow::SynchronouslyRepaintOnResize() { + return !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled(); +} + +void nsWindow::MaybeDispatchInitialFocusEvent() { + if (mIsShowingPreXULSkeletonUI && ::GetActiveWindow() == mWnd) { + DispatchFocusToTopLevelWindow(true); + } +} + +already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() { + nsCOMPtr<nsIWidget> window = new nsWindow(); + return window.forget(); +} + +already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() { + nsCOMPtr<nsIWidget> window = new nsWindow(true); + return window.forget(); +} diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h new file mode 100644 index 0000000000..20e8447c0a --- /dev/null +++ b/widget/windows/nsWindow.h @@ -0,0 +1,747 @@ +/* -*- 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 "CompositorWidget.h" +#include "nsWindowBase.h" +#include "nsdefs.h" +#include "nsUserIdleService.h" +#include "nsToolkit.h" +#include "nsString.h" +#include "nsTArray.h" +#include "gfxWindowsPlatform.h" +#include "gfxWindowsSurface.h" +#include "nsWindowDbg.h" +#include "cairo.h" +#include "nsRegion.h" +#include "mozilla/EventForwards.h" +#include "mozilla/Maybe.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/UniquePtr.h" +#include "nsMargin.h" +#include "nsRegionFwd.h" + +#include "nsWinGesture.h" +#include "WinPointerEvents.h" +#include "WinUtils.h" +#include "WindowHook.h" +#include "TaskbarWindowPreview.h" + +#ifdef ACCESSIBILITY +# include "oleacc.h" +# include "mozilla/a11y/Accessible.h" +#endif + +#include "nsUXThemeData.h" +#include "nsIUserIdleServiceInternal.h" + +#include "IMMHandler.h" + +/** + * Forward class definitions + */ + +class nsNativeDragTarget; +class nsIRollupListener; +class imgIContainer; + +namespace mozilla { +namespace widget { +class NativeKey; +class InProcessWinCompositorWidget; +struct MSGResult; +class DirectManipulationOwner; +} // namespace widget +} // namespace mozilla + +/** + * Forward Windows-internal definitions of otherwise incomplete ones provided by + * the SDK. + */ +const CLSID CLSID_ImmersiveShell = { + 0xC2F03A33, + 0x21F5, + 0x47FA, + {0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39}}; + +// Virtual Desktop. + +EXTERN_C const IID IID_IVirtualDesktopManager; +MIDL_INTERFACE("a5cd92ff-29be-454c-8d04-d82879fb3f1b") +IVirtualDesktopManager : public IUnknown { + public: + virtual HRESULT STDMETHODCALLTYPE GetWindowDesktopId( + __RPC__in HWND topLevelWindow, __RPC__out GUID * desktopId) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveWindowToDesktop( + __RPC__in HWND topLevelWindow, __RPC__in REFGUID desktopId) = 0; +}; + +/** + * Native WIN32 window wrapper. + */ + +class nsWindow final : 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; + typedef mozilla::widget::PlatformCompositorWidgetDelegate + PlatformCompositorWidgetDelegate; + + public: + explicit nsWindow(bool aIsChildWindow = false); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsWindowBase) + + friend class nsWindowGfx; + + void SendAnAPZEvent(mozilla::InputData& aEvent); + + // 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 + [[nodiscard]] virtual nsresult Create( + nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData = nullptr) override; + virtual void Destroy() override; + virtual void 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()); + } + } + + virtual void Show(bool aState) 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 void LockAspectRatio(bool aShouldLock) override; + virtual const SizeConstraints GetSizeConstraints() override; + virtual void SetWindowMouseTransparent(bool aIsTransparent) override; + virtual void Move(double aX, double aY) override; + virtual void Resize(double aWidth, double aHeight, bool aRepaint) override; + virtual void Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) override; + virtual mozilla::Maybe<bool> IsResizingNativeWidget() override; + [[nodiscard]] virtual nsresult 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; + virtual void GetWorkspaceID(nsAString& workspaceID) override; + virtual void MoveToWorkspace(const nsAString& workspaceID) override; + virtual void SuppressAnimation(bool aSuppress) override; + virtual void Enable(bool aState) override; + virtual bool IsEnabled() const override; + virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override; + virtual LayoutDeviceIntRect GetBounds() override; + virtual LayoutDeviceIntRect GetScreenBounds() override; + [[nodiscard]] virtual nsresult GetRestoredBounds( + LayoutDeviceIntRect& aRect) override; + virtual LayoutDeviceIntRect GetClientBounds() override; + virtual LayoutDeviceIntPoint GetClientOffset() override; + void SetBackgroundColor(const nscolor& aColor) override; + virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage, + uint32_t aHotspotX, uint32_t aHotspotY) 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 void CleanupFullscreenTransition() override; + virtual nsresult MakeFullScreen(bool aFullScreen, + nsIScreen* aScreen = nullptr) override; + virtual void HideWindowChrome(bool aShouldHide) override; + virtual void Invalidate(bool aEraseBackground = false, + bool aUpdateNCArea = false, + bool aIncludeChildren = false); + virtual void 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; + virtual nsresult SetTitle(const nsAString& aTitle) override; + virtual void SetIcon(const nsAString& aIconSpec) override; + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override; + virtual LayoutDeviceIntSize ClientToWindowSize( + const LayoutDeviceIntSize& aClientSize) override; + virtual nsresult 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; + [[nodiscard]] virtual nsresult 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; + void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override; + [[nodiscard]] virtual nsresult 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; + virtual void SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override; + virtual InputContext GetInputContext() override; + virtual 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 nsresult 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; + virtual void SetWindowClass(const nsAString& xulWinType) override; + + /** + * Event helpers + */ + virtual bool DispatchMouseEvent( + mozilla::EventMessage aEventMessage, WPARAM wParam, LPARAM lParam, + bool aIsContextMenuKey = false, + int16_t aButton = mozilla::MouseButton::ePrimary, + uint16_t aInputSource = + mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE, + WinPointerInfo* aPointerInfo = nullptr); + virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus); + void DispatchPendingEvents(); + bool DispatchPluginEvent(UINT aMessage, WPARAM aWParam, LPARAM aLParam, + bool aDispatchPendingEvents); + void DispatchCustomEvent(const nsString& eventName); + +#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. + static nsTArray<nsWindow*> EnumAllWindows(); + + /** + * Misc. + */ + virtual bool AutoErase(HDC dc); + bool WidgetTypeSupportsAcceleration() override; + + void ForcePresent(); + bool TouchEventShouldStartDrag(mozilla::EventMessage aEventMessage, + LayoutDeviceIntPoint aEventPoint); + + void SetSmallIcon(HICON aIcon); + void SetBigIcon(HICON aIcon); + + static void SetIsRestoringSession(const bool aIsRestoringSession) { + sIsRestoringSession = aIsRestoringSession; + } + + /** + * 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 nsresult OnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, + nsIKeyEventInPluginCallback* aCallback) override; + + void GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) override; + bool IsTouchWindow() const { return mTouchWindow; } + bool SynchronouslyRepaintOnResize() override; + virtual void MaybeDispatchInitialFocusEvent() override; + + 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 + */ + HWND GetTopLevelForFocus(HWND aCurWnd); + 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); + LRESULT ProcessCharMessage(const MSG& aMsg, bool* aEventDispatched); + LRESULT ProcessKeyUpMessage(const MSG& aMsg, bool* aEventDispatched); + LRESULT ProcessKeyDownMessage(const MSG& aMsg, bool* aEventDispatched); + static bool EventIsInsideWindow( + nsWindow* aWindow, + mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing()); + // 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); + nsIntPoint GetTouchCoordinates(WPARAM wParam, LPARAM lParam); + mozilla::Maybe<mozilla::PanGestureInput> ConvertTouchToPanGesture( + const mozilla::MultiTouchInput& aTouchInput, PTOUCHINPUT aOriginalEvent); + void DispatchTouchOrPanGestureInput(mozilla::MultiTouchInput& aTouchInput, + PTOUCHINPUT aOSEvent); + + /** + * Event handlers + */ + virtual void OnDestroy() override; + bool OnResize(const LayoutDeviceIntSize& aSize); + void OnSizeModeChange(nsSizeMode aSizeMode); + 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); + bool OnPointerEvents(UINT msg, WPARAM wParam, LPARAM lParam); + + /** + * Function that registers when the user has been active (used for detecting + * when the user is idle). + */ + void UserActivity(); + + int32_t GetHeight(int32_t aProposedHeight); + 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, + mozilla::Maybe<POINT> aEventPoint = mozilla::Nothing()); + static bool NeedsToHandleNCActivateDelayed(HWND aWnd); + static bool DealWithPopups(HWND inWnd, UINT inMsg, WPARAM inWParam, + LPARAM inLParam, LRESULT* outResult); + + /** + * Window transparency helpers + */ +#ifdef MOZ_XUL + private: + void SetWindowTranslucencyInner(nsTransparencyMode aMode); + nsTransparencyMode GetWindowTranslucencyInner() const { + return mTransparencyMode; + } + void UpdateGlass(); + bool WithinDraggableRegion(int32_t clientX, int32_t clientY); + + protected: +#endif // MOZ_XUL + + static bool IsAsyncResponseEvent(UINT aMsg, LRESULT& aResult); + void IPCWindowProcHandler(UINT& msg, WPARAM& wParam, LPARAM& lParam); + + /** + * Misc. + */ + void StopFlashing(); + static HWND WindowAtMouse(); + static bool IsTopLevelMouseExit(HWND aWnd); + virtual nsresult SetWindowClipRegion( + const nsTArray<LayoutDeviceIntRect>& aRects, + bool aIntersectWithExisting) override; + LayoutDeviceIntRegion GetRegionToPaint(bool aForceFullRepaint, PAINTSTRUCT ps, + HDC aDC); + void ClearCachedResources(); + nsIWidgetListener* GetPaintListener(); + + virtual void AddWindowOverlayWebRenderCommands( + mozilla::layers::WebRenderBridgeChild* aWrBridge, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResourceUpdates) override; + + 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); + + void CreateCompositor() override; + void RequestFxrOutput(); + + void RecreateDirectManipulationIfNeeded(); + void ResizeDirectManipulationViewport(); + void DestroyDirectManipulation(); + + protected: + nsCOMPtr<nsIWidget> mParent; + nsIntSize mLastSize; + nsIntPoint mLastPoint; + HWND mWnd; + HWND mTransitionWnd; + WNDPROC mPrevWndProc; + HBRUSH mBrush; + IMEContext mDefaultIMC; + HDEVNOTIFY mDeviceNotifyHandle; + bool mIsTopWidgetWindow; + bool mInDtor; + bool mIsVisible; + bool mPainting; + bool mTouchWindow; + bool mDisplayPanFeedback; + bool mHideChrome; + bool mIsRTL; + bool mFullscreenMode; + bool mMousePresent; + bool mMouseInDraggableArea; + bool mDestroyCalled; + bool mOpeningAnimationSuppressed; + bool mAlwaysOnTop; + bool mIsEarlyBlankWindow; + bool mIsShowingPreXULSkeletonUI; + bool mResizable; + DWORD_PTR mOldStyle; + DWORD_PTR mOldExStyle; + nsNativeDragTarget* mNativeDragTarget; + HKL mLastKeyboardLayout; + nsSizeMode mOldSizeMode; + nsSizeMode mLastSizeMode; + WindowHook mWindowHook; + uint32_t mPickerDisplayCount; + HICON mIconSmall; + HICON mIconBig; + HWND mLastKillFocusWindow; + 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 bool sHaveInitializedPrefs; + static bool sIsRestoringSession; + + PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate; + + // 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; + + float mAspectRatio; + + nsCOMPtr<nsIUserIdleServiceInternal> 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; + + // Whether we we're created as a child window (aka ChildWindow) or not. + bool mIsChildWindow : 1; + + int32_t mCachedHitTestResult; + + // 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; + + // The location of the window buttons in the window. + mozilla::Maybe<LayoutDeviceIntRect> mWindowButtonsRect; + + // Caching for hit test results + POINT mCachedHitTestPoint; + TimeStamp mCachedHitTestTime; + + RefPtr<mozilla::widget::InProcessWinCompositorWidget> mBasicLayersSurface; + + static bool sNeedsToInitMouseWheelSettings; + static void InitMouseWheelScrollData(); + + double mSizeConstraintsScale; // scale in effect when setting constraints + int32_t mMaxTextureSize; + + // Pointer events processing and management + WinPointerEvents mPointerEvents; + + ScreenPoint mLastPanGestureFocus; + + // When true, used to indicate an async call to RequestFxrOutput to the GPU + // process after the Compositor is created + bool mRequestFxrOutputPending; + + mozilla::UniquePtr<mozilla::widget::DirectManipulationOwner> mDmOwner; +}; + +#endif // Window_h__ diff --git a/widget/windows/nsWindowBase.cpp b/widget/windows/nsWindowBase.cpp new file mode 100644 index 0000000000..a9c9d2b53e --- /dev/null +++ b/widget/windows/nsWindowBase.cpp @@ -0,0 +1,224 @@ +/* -*- 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 "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_apz.h" +#include "KeyboardLayout.h" +#include "WinUtils.h" +#include "npapi.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) { return false; } + +// 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; + + for (int i = 0; i < 3; i++) { + if (sInjectTouchFuncPtr(1, &info)) { + break; + } + DWORD error = GetLastError(); + if (error == ERROR_NOT_READY && i < 2) { + // We sent it too quickly after the previous injection (see bug 1535140 + // comment 10). On the first loop iteration we just yield (via Sleep(0)) + // and try again. If it happens again on the second loop iteration we + // explicitly Sleep(1) and try again. If that doesn't work either we just + // error out. + ::Sleep(i); + continue; + } + WinUtils::Log("InjectTouchInput failure. GetLastError=%d", error); + return false; + } + return true; +} + +void nsWindowBase::ChangedDPI() { + if (mWidgetListener) { + if (PresShell* presShell = mWidgetListener->GetPresShell()) { + presShell->BackingScaleFactorChanged(); + } + } +} + +nsresult nsWindowBase::SynthesizeNativeTouchPoint( + uint32_t aPointerId, nsIWidget::TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, double aPointerPressure, + uint32_t aPointerOrientation, nsIObserver* aObserver) { + AutoObserverNotifier notifier(aObserver, "touchpoint"); + + if (StaticPrefs::apz_test_fails_with_native_injection() || + !InitTouchInjection()) { + // If we don't have touch injection from the OS, or if we are running a test + // that cannot properly inject events to satisfy the OS requirements (see + // bug 1313170) we can just fake it and synthesize the events from here. + MOZ_ASSERT(NS_IsMainThread()); + if (aPointerState == TOUCH_HOVER) { + return NS_ERROR_UNEXPECTED; + } + + if (!mSynthesizedTouchInput) { + mSynthesizedTouchInput = MakeUnique<MultiTouchInput>(); + } + + WidgetEventTime time = CurrentMessageWidgetEventTime(); + LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset(); + MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState( + mSynthesizedTouchInput.get(), time.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()) { + auto info = iter.UserData(); + InjectTouchPoint(info->mPointerId, info->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..02a91b0b7c --- /dev/null +++ b/widget/windows/nsWindowBase.h @@ -0,0 +1,139 @@ +/* -*- 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. + */ + HWND GetWindowHandle() { + 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); + + /* + * 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); + + const InputContext& InputContextRef() const { return mInputContext; } + + 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..c0880817d0 --- /dev/null +++ b/widget/windows/nsWindowDbg.cpp @@ -0,0 +1,60 @@ +/* -*- 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; +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..c303b06855 --- /dev/null +++ b/widget/windows/nsWindowDbg.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 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..3e4c9d6dcb --- /dev/null +++ b/widget/windows/nsWindowDefs.h @@ -0,0 +1,137 @@ +/* -*- 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) {} + KeyPair(uint8_t aGeneral, uint8_t aSpecific, uint16_t aScanCode) + : mGeneral(aGeneral), mSpecific(aSpecific), mScanCode(aScanCode) {} +}; + +#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; + + explicit MSGResult(LRESULT* aResult = nullptr) + : mResult(aResult ? *aResult : mDefaultResult), mConsumed(false) {} + + private: + LRESULT mDefaultResult; +}; + +} // namespace widget +} // namespace mozilla + +/************************************************************** + * + * SECTION: macros + * + **************************************************************/ + +#define NSRGB_2_COLOREF(color) \ + RGB(NS_GET_R(color), NS_GET_G(color), NS_GET_B(color)) +#define COLOREF_2_NSRGB(color) \ + NS_RGB(GetRValue(color), GetGValue(color), GetBValue(color)) + +#define VERIFY_WINDOW_STYLE(s) \ + NS_ASSERTION(((s) & (WS_CHILD | WS_POPUP)) != (WS_CHILD | WS_POPUP), \ + "WS_POPUP and WS_CHILD are mutually exclusive") + +#endif /* WindowDefs_h__ */ diff --git a/widget/windows/nsWindowGfx.cpp b/widget/windows/nsWindowGfx.cpp new file mode 100644 index 0000000000..672345edb5 --- /dev/null +++ b/widget/windows/nsWindowGfx.cpp @@ -0,0 +1,693 @@ +/* -*- 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 "gfxDWriteFonts.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGfxCIID.h" +#include "gfxContext.h" +#include "WinUtils.h" +#include "nsIWidgetListener.h" +#include "mozilla/Unused.h" +#include "nsDebug.h" + +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "ClientLayerManager.h" +#include "InProcessWinCompositorWidget.h" + +#include "nsUXThemeData.h" +#include "nsUXThemeConstants.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::plugins; + +/************************************************************** + ************************************************************** + ** + ** 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 +LayoutDeviceIntRegion nsWindow::GetRegionToPaint(bool aForceFullRepaint, + PAINTSTRUCT ps, HDC aDC) { + if (aForceFullRepaint) { + RECT paintRect; + ::GetClientRect(mWnd, &paintRect); + return LayoutDeviceIntRegion(WinUtils::ToIntRect(paintRect)); + } + + HRGN paintRgn = ::CreateRectRgn(0, 0, 0, 0); + if (paintRgn != nullptr) { + int result = GetRandomRgn(aDC, paintRgn, SYSRGN); + if (result == 1) { + POINT pt = {0, 0}; + ::MapWindowPoints(nullptr, mWnd, &pt, 1); + ::OffsetRgn(paintRgn, pt.x, pt.y); + } + LayoutDeviceIntRegion rgn(WinUtils::ConvertHRGNToRegion(paintRgn)); + ::DeleteObject(paintRgn); + return rgn; + } + return LayoutDeviceIntRegion(WinUtils::ToIntRect(ps.rcPaint)); +} + +nsIWidgetListener* nsWindow::GetPaintListener() { + if (mDestroyCalled) return nullptr; + return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener; +} + +void nsWindow::ForcePresent() { + if (mResizeState != RESIZING) { + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + remoteRenderer->SendForcePresent(); + } + } +} + +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(); + + bool guilty; + switch (resetReason) { + case DeviceResetReason::HUNG: + case DeviceResetReason::RESET: + case DeviceResetReason::INVALID_CALL: + guilty = true; + break; + default: + guilty = false; + break; + } + + GPUProcessManager::Get()->OnInProcessDeviceReset(guilty); + + gfxCriticalNote << "(nsWindow) Finished device reset."; + return false; + } + + // 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; + } + + PAINTSTRUCT ps; + + // Avoid starting the GPU process for the initial navigator:blank window. + if (mIsEarlyBlankWindow) { + // Call BeginPaint/EndPaint or Windows will keep sending us messages. + ::BeginPaint(mWnd, &ps); + ::EndPaint(mWnd, &ps); + return true; + } + + // Clear window by transparent black when compositor window is used in GPU + // process and non-client area rendering by DWM is enabled. + // It is for showing non-client area rendering. See nsWindow::UpdateGlass(). + if (HasGlass() && GetLayerManager()->AsKnowsCompositor() && + GetLayerManager()->AsKnowsCompositor()->GetUseCompositorWnd()) { + HDC hdc; + RECT rect; + hdc = ::GetWindowDC(mWnd); + ::GetWindowRect(mWnd, &rect); + ::MapWindowPoints(nullptr, mWnd, (LPPOINT)&rect, 2); + ::FillRect(hdc, &rect, + reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))); + ReleaseDC(mWnd, hdc); + } + + if (GetLayerManager()->AsKnowsCompositor() && + !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. + GetLayerManager()->ScheduleComposite(); + } + mLastPaintBounds = mBounds; + +#ifdef MOZ_XUL + if (!aDC && + (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) && + (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 = mBasicLayersSurface->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 + LayoutDeviceIntRegion region = GetRegionToPaint(forceRepaint, ps, hDC); + + if (GetLayerManager()->AsKnowsCompositor()) { + // 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. + GetLayerManager()->SetNeedsComposite(true); + GetLayerManager()->SendInvalidRegion(region.ToUnknownRegion()); + } + + RefPtr<nsWindow> strongThis(this); + + nsIWidgetListener* listener = GetPaintListener(); + if (listener) { + listener->WillPaintWindow(this); + } + // Re-get the listener since the will paint notification may have killed it. + listener = GetPaintListener(); + if (!listener) { + return false; + } + + if (GetLayerManager()->AsKnowsCompositor() && + GetLayerManager()->NeedsComposite()) { + GetLayerManager()->ScheduleComposite(); + GetLayerManager()->SetNeedsComposite(false); + } + + bool result = true; + if (!region.IsEmpty() && listener) { + // Should probably pass in a real region here, using GetRandomRgn + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/clipping_4q0e.asp + +#ifdef WIDGET_DEBUG_OUTPUT + debug_DumpPaintEvent(stdout, this, region.ToUnknownRegion(), "noname", + (int32_t)mWnd); +#endif // WIDGET_DEBUG_OUTPUT + + switch (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) { + // This mutex needs to be held when EnsureTransparentSurface is + // called. + MutexAutoLock lock(mBasicLayersSurface->GetTransparentSurfaceLock()); + 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::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, 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: + case LayersBackend::LAYERS_WR: { + result = listener->PaintWindow(this, region); + if (!gfxEnv::DisableForcePresent() && + gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) { + nsCOMPtr<nsIRunnable> event = NewRunnableMethod( + "nsWindow::ForcePresent", 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; +} + +// This override of CreateCompositor is to add support for sending the IPC +// call for RequesetFxrOutput as soon as the compositor for this widget is +// available. +void nsWindow::CreateCompositor() { + nsWindowBase::CreateCompositor(); + + if (mRequestFxrOutputPending) { + GetRemoteRenderer()->SendRequestFxrOutput(); + } +} + +void nsWindow::RequestFxrOutput() { + if (GetRemoteRenderer() != nullptr) { + MOZ_CRASH("RequestFxrOutput should happen before Compositor is created."); + } else { + // The compositor isn't ready, so indicate to make the IPC call when + // it is available. + mRequestFxrOutputPending = true; + } +} + +LayoutDeviceIntSize nsWindowGfx::GetIconMetrics(IconSizeType aSizeType) { + int32_t width = ::GetSystemMetrics(sIconMetrics[aSizeType].xMetric); + int32_t height = ::GetSystemMetrics(sIconMetrics[aSizeType].yMetric); + + if (width == 0 || height == 0) { + width = height = sIconMetrics[aSizeType].defaultSize; + } + + return LayoutDeviceIntSize(width, height); +} + +nsresult nsWindowGfx::CreateIcon(imgIContainer* aContainer, bool aIsCursor, + LayoutDeviceIntPoint aHotspot, + LayoutDeviceIntSize aScaledSize, + HICON* aIcon) { + MOZ_ASSERT(aHotspot.x >= 0 && aHotspot.y >= 0); + MOZ_ASSERT((aScaledSize.width > 0 && aScaledSize.height > 0) || + (aScaledSize.width == 0 && aScaledSize.height == 0)); + + // Get the image data + RefPtr<SourceSurface> surface = aContainer->GetFrame( + imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + NS_ENSURE_TRUE(surface, NS_ERROR_NOT_AVAILABLE); + + IntSize frameSize = surface->GetSize(); + if (frameSize.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + IntSize iconSize(aScaledSize.width, aScaledSize.height); + if (iconSize == IntSize(0, 0)) { // use frame's intrinsic size + iconSize = frameSize; + } + + RefPtr<DataSourceSurface> dataSurface; + bool mappedOK; + DataSourceSurface::MappedSurface map; + + if (iconSize != frameSize) { + // Scale the surface + dataSurface = + Factory::CreateDataSourceSurface(iconSize, SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map); + NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE); + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, + SurfaceFormat::B8G8R8A8); + if (!dt) { + gfxWarning() + << "nsWindowGfx::CreatesIcon failed in CreateDrawTargetForData"; + return NS_ERROR_OUT_OF_MEMORY; + } + dt->DrawSurface(surface, Rect(0, 0, iconSize.width, iconSize.height), + Rect(0, 0, frameSize.width, frameSize.height), + DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + } else if (surface->GetFormat() != SurfaceFormat::B8G8R8A8) { + // Convert format to SurfaceFormat::B8G8R8A8 + dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat( + surface, SurfaceFormat::B8G8R8A8); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); + } else { + dataSurface = surface->GetDataSurface(); + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); + } + NS_ENSURE_TRUE(dataSurface && mappedOK, NS_ERROR_FAILURE); + MOZ_ASSERT(dataSurface->GetFormat() == SurfaceFormat::B8G8R8A8); + + uint8_t* data = nullptr; + UniquePtr<uint8_t[]> autoDeleteArray; + if (map.mStride == BytesPerPixel(dataSurface->GetFormat()) * iconSize.width) { + // Mapped data is already packed + data = map.mData; + } else { + // We can't use map.mData since the pixels are not packed (as required by + // CreateDIBitmap, which is called under the DataToBitmap call below). + // + // We must unmap before calling SurfaceToPackedBGRA because it needs access + // to the pixel data. + dataSurface->Unmap(); + map.mData = nullptr; + + autoDeleteArray = SurfaceToPackedBGRA(dataSurface); + data = autoDeleteArray.get(); + NS_ENSURE_TRUE(data, NS_ERROR_FAILURE); + } + + HBITMAP bmp = DataToBitmap(data, iconSize.width, -iconSize.height, 32); + uint8_t* a1data = Data32BitTo1Bit(data, iconSize.width, iconSize.height); + if (map.mData) { + dataSurface->Unmap(); + } + if (!a1data) { + return NS_ERROR_FAILURE; + } + + HBITMAP mbmp = DataToBitmap(a1data, iconSize.width, -iconSize.height, 1); + free(a1data); + + ICONINFO info = {0}; + info.fIcon = !aIsCursor; + info.xHotspot = aHotspot.x; + info.yHotspot = aHotspot.y; + info.hbmMask = mbmp; + info.hbmColor = bmp; + + HCURSOR icon = ::CreateIconIndirect(&info); + ::DeleteObject(mbmp); + ::DeleteObject(bmp); + if (!icon) return NS_ERROR_FAILURE; + *aIcon = icon; + return NS_OK; +} + +// Adjust cursor image data +uint8_t* nsWindowGfx::Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth, + uint32_t aHeight) { + // We need (aWidth + 7) / 8 bytes plus zero-padding up to a multiple of + // 4 bytes for each row (HBITMAP requirement). Bug 353553. + uint32_t outBpr = ((aWidth + 31) / 8) & ~3; + + // Allocate and clear mask buffer + uint8_t* outData = (uint8_t*)calloc(outBpr, aHeight); + if (!outData) return nullptr; + + int32_t* imageRow = (int32_t*)aImageData; + for (uint32_t curRow = 0; curRow < aHeight; curRow++) { + uint8_t* outRow = outData + curRow * outBpr; + uint8_t mask = 0x80; + for (uint32_t curCol = 0; curCol < aWidth; curCol++) { + // Use sign bit to test for transparency, as alpha byte is highest byte + if (*imageRow++ < 0) *outRow |= mask; + + mask >>= 1; + if (!mask) { + outRow++; + mask = 0x80; + } + } + } + + return outData; +} + +/** + * Convert the given image data to a HBITMAP. If the requested depth is + * 32 bit, a bitmap with an alpha channel will be returned. + * + * @param aImageData The image data to convert. Must use the format accepted + * by CreateDIBitmap. + * @param aWidth With of the bitmap, in pixels. + * @param aHeight Height of the image, in pixels. + * @param aDepth Image depth, in bits. Should be one of 1, 24 and 32. + * + * @return The HBITMAP representing the image. Caller should call + * DeleteObject when done with the bitmap. + * On failure, nullptr will be returned. + */ +HBITMAP nsWindowGfx::DataToBitmap(uint8_t* aImageData, uint32_t aWidth, + uint32_t aHeight, uint32_t aDepth) { + HDC dc = ::GetDC(nullptr); + + if (aDepth == 32) { + // Alpha channel. We need the new header. + BITMAPV4HEADER head = {0}; + head.bV4Size = sizeof(head); + head.bV4Width = aWidth; + head.bV4Height = aHeight; + head.bV4Planes = 1; + head.bV4BitCount = aDepth; + head.bV4V4Compression = BI_BITFIELDS; + head.bV4SizeImage = 0; // Uncompressed + head.bV4XPelsPerMeter = 0; + head.bV4YPelsPerMeter = 0; + head.bV4ClrUsed = 0; + head.bV4ClrImportant = 0; + + head.bV4RedMask = 0x00FF0000; + head.bV4GreenMask = 0x0000FF00; + head.bV4BlueMask = 0x000000FF; + head.bV4AlphaMask = 0xFF000000; + + HBITMAP bmp = ::CreateDIBitmap( + dc, reinterpret_cast<CONST BITMAPINFOHEADER*>(&head), CBM_INIT, + aImageData, reinterpret_cast<CONST BITMAPINFO*>(&head), DIB_RGB_COLORS); + ::ReleaseDC(nullptr, dc); + return bmp; + } + + char reserved_space[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 2]; + BITMAPINFOHEADER& head = *(BITMAPINFOHEADER*)reserved_space; + + head.biSize = sizeof(BITMAPINFOHEADER); + head.biWidth = aWidth; + head.biHeight = aHeight; + head.biPlanes = 1; + head.biBitCount = (WORD)aDepth; + head.biCompression = BI_RGB; + head.biSizeImage = 0; // Uncompressed + head.biXPelsPerMeter = 0; + head.biYPelsPerMeter = 0; + head.biClrUsed = 0; + head.biClrImportant = 0; + + BITMAPINFO& bi = *(BITMAPINFO*)reserved_space; + + if (aDepth == 1) { + RGBQUAD black = {0, 0, 0, 0}; + RGBQUAD white = {255, 255, 255, 0}; + + bi.bmiColors[0] = white; + bi.bmiColors[1] = black; + } + + HBITMAP bmp = + ::CreateDIBitmap(dc, &head, CBM_INIT, aImageData, &bi, DIB_RGB_COLORS); + ::ReleaseDC(nullptr, dc); + return bmp; +} diff --git a/widget/windows/nsWindowGfx.h b/widget/windows/nsWindowGfx.h new file mode 100644 index 0000000000..0d5fd9e01a --- /dev/null +++ b/widget/windows/nsWindowGfx.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WindowGfx_h__ +#define WindowGfx_h__ + +/* + * nsWindowGfx - Painting and aceleration. + */ + +#include "nsWindow.h" +#include <imgIContainer.h> + +class nsWindowGfx { + public: + enum IconSizeType { kSmallIcon, kRegularIcon }; + static mozilla::LayoutDeviceIntSize GetIconMetrics(IconSizeType aSizeType); + static nsresult CreateIcon(imgIContainer* aContainer, bool aIsCursor, + mozilla::LayoutDeviceIntPoint aHotspot, + mozilla::LayoutDeviceIntSize aScaledSize, + HICON* aIcon); + + private: + /** + * Cursor helpers + */ + static uint8_t* Data32BitTo1Bit(uint8_t* aImageData, uint32_t aWidth, + uint32_t aHeight); + static HBITMAP DataToBitmap(uint8_t* aImageData, uint32_t aWidth, + uint32_t aHeight, uint32_t aDepth); +}; + +#endif // WindowGfx_h__ diff --git a/widget/windows/nsdefs.h b/widget/windows/nsdefs.h new file mode 100644 index 0000000000..5c4134c7fd --- /dev/null +++ b/widget/windows/nsdefs.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef 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 Binary files differnew file mode 100644 index 0000000000..8d9ac9478c --- /dev/null +++ b/widget/windows/res/aliasb.cur diff --git a/widget/windows/res/cell.cur b/widget/windows/res/cell.cur Binary files differnew file mode 100644 index 0000000000..decfbdcac5 --- /dev/null +++ b/widget/windows/res/cell.cur diff --git a/widget/windows/res/col_resize.cur b/widget/windows/res/col_resize.cur Binary files differnew file mode 100644 index 0000000000..8f7f675122 --- /dev/null +++ b/widget/windows/res/col_resize.cur diff --git a/widget/windows/res/copy.cur b/widget/windows/res/copy.cur Binary files differnew file mode 100644 index 0000000000..87f1519cd1 --- /dev/null +++ b/widget/windows/res/copy.cur diff --git a/widget/windows/res/grab.cur b/widget/windows/res/grab.cur Binary files differnew file mode 100644 index 0000000000..db7ad5aed3 --- /dev/null +++ b/widget/windows/res/grab.cur diff --git a/widget/windows/res/grabbing.cur b/widget/windows/res/grabbing.cur Binary files differnew file mode 100644 index 0000000000..e0dfd04e4d --- /dev/null +++ b/widget/windows/res/grabbing.cur diff --git a/widget/windows/res/none.cur b/widget/windows/res/none.cur Binary files differnew file mode 100644 index 0000000000..2114dfaee3 --- /dev/null +++ b/widget/windows/res/none.cur diff --git a/widget/windows/res/row_resize.cur b/widget/windows/res/row_resize.cur Binary files differnew file mode 100644 index 0000000000..a7369d32d1 --- /dev/null +++ b/widget/windows/res/row_resize.cur diff --git a/widget/windows/res/select.cur b/widget/windows/res/select.cur Binary files differnew file mode 100644 index 0000000000..5a88b3707e --- /dev/null +++ b/widget/windows/res/select.cur diff --git a/widget/windows/res/vertical_text.cur b/widget/windows/res/vertical_text.cur Binary files differnew file mode 100644 index 0000000000..3de04ebec3 --- /dev/null +++ b/widget/windows/res/vertical_text.cur diff --git a/widget/windows/res/zoom_in.cur b/widget/windows/res/zoom_in.cur Binary files differnew file mode 100644 index 0000000000..b594d79271 --- /dev/null +++ b/widget/windows/res/zoom_in.cur diff --git a/widget/windows/res/zoom_out.cur b/widget/windows/res/zoom_out.cur Binary files differnew file mode 100644 index 0000000000..7e495fbaa4 --- /dev/null +++ b/widget/windows/res/zoom_out.cur diff --git a/widget/windows/resource.h b/widget/windows/resource.h new file mode 100644 index 0000000000..a367e9f3e9 --- /dev/null +++ b/widget/windows/resource.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#define IDC_GRAB 4101 +#define IDC_GRABBING 4102 +#define IDC_CELL 4103 +#define IDC_COPY 4104 +#define IDC_ALIAS 4105 +#define IDC_ZOOMIN 4106 +#define IDC_ZOOMOUT 4107 +#define IDC_COLRESIZE 4108 +#define IDC_ROWRESIZE 4109 +#define IDC_VERTICALTEXT 4110 +#define IDC_DUMMY_CE_MENUBAR 4111 +#define IDC_NONE 4112 diff --git a/widget/windows/tests/TestUriValidation.cpp b/widget/windows/tests/TestUriValidation.cpp new file mode 100644 index 0000000000..d8a0ca09ce --- /dev/null +++ b/widget/windows/tests/TestUriValidation.cpp @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#define MOZ_USE_LAUNCHER_ERROR + +#define UNICODE +#include "mozilla/UrlmonHeaderOnlyUtils.h" +#include "TestUrisToValidate.h" + +#include <urlmon.h> + +using namespace mozilla; + +static LauncherResult<_bstr_t> ShellValidateUri(const wchar_t* aUri) { + LauncherResult<UniqueAbsolutePidl> pidlResult = ShellParseDisplayName(aUri); + if (pidlResult.isErr()) { + return pidlResult.propagateErr(); + } + UniqueAbsolutePidl pidl = pidlResult.unwrap(); + + // |pidl| is an absolute path. IShellFolder::GetDisplayNameOf requires a + // valid child ID, so the first thing we need to resolve is the IShellFolder + // for |pidl|'s parent, as well as the childId that represents |pidl|. + // Fortunately SHBindToParent does exactly that! + PCUITEMID_CHILD childId = nullptr; + RefPtr<IShellFolder> parentFolder; + HRESULT hr = SHBindToParent(pidl.get(), IID_IShellFolder, + getter_AddRefs(parentFolder), &childId); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // Now we retrieve the display name of |childId|, telling the shell that we + // plan to have the string parsed. + STRRET strret; + hr = parentFolder->GetDisplayNameOf(childId, SHGDN_FORPARSING, &strret); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + // StrRetToBSTR automatically takes care of freeing any dynamically + // allocated memory in |strret|. + _bstr_t bstrUri; + hr = StrRetToBSTR(&strret, nullptr, bstrUri.GetAddress()); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + return bstrUri; +} + +static LauncherResult<_bstr_t> GetFragment(const wchar_t* aUri) { + constexpr DWORD flags = + Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE | + Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI | + Uri_CREATE_IE_SETTINGS; + RefPtr<IUri> uri; + HRESULT hr = CreateUri(aUri, flags, 0, getter_AddRefs(uri)); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + + _bstr_t bstrFragment; + hr = uri->GetFragment(bstrFragment.GetAddress()); + if (FAILED(hr)) { + return LAUNCHER_ERROR_FROM_HRESULT(hr); + } + return bstrFragment; +} + +static bool RunSingleTest(const wchar_t* aUri) { + LauncherResult<_bstr_t> uriOld = ShellValidateUri(aUri), + uriNew = UrlmonValidateUri(aUri); + if (uriOld.isErr() != uriNew.isErr()) { + printf("TEST-FAILED | UriValidation | Validation result mismatch on %S\n", + aUri); + return false; + } + + if (uriOld.isErr()) { + if (uriOld.unwrapErr().mError != uriNew.unwrapErr().mError) { + printf("TEST-FAILED | UriValidation | Error code mismatch on %S\n", aUri); + return false; + } + return true; + } + + LauncherResult<_bstr_t> bstrFragment = GetFragment(aUri); + if (bstrFragment.isErr()) { + printf("TEST-FAILED | UriValidation | Failed to get a fragment from %S\n", + aUri); + return false; + } + + // We validate a uri with two logics: the current one UrlmonValidateUri and + // the older one ShellValidateUri, to make sure the same validation result. + // We introduced UrlmonValidateUri because ShellValidateUri drops a fragment + // in a uri due to the design of Windows. To bypass the fragment issue, we + // extract a fragment and appends it into the validated string, and compare. + _bstr_t bstrUriOldCorrected = uriOld.unwrap() + bstrFragment.unwrap(); + const _bstr_t& bstrUriNew = uriNew.unwrap(); + if (bstrUriOldCorrected != bstrUriNew) { + printf("TEST-FAILED | UriValidation | %S %S %S\n", aUri, + static_cast<const wchar_t*>(bstrUriOldCorrected), + static_cast<const wchar_t*>(bstrUriNew)); + return false; + } + + return true; +} + +int wmain(int argc, wchar_t* argv[]) { + HRESULT hr = CoInitialize(nullptr); + if (FAILED(hr)) { + return 1; + } + + bool isOk = true; + + if (argc == 2) { + isOk = RunSingleTest(argv[1]); + } else { + for (const wchar_t*& testUri : kTestUris) { + if (!RunSingleTest(testUri)) { + isOk = false; + } + } + } + + CoUninitialize(); + return isOk ? 0 : 1; +} diff --git a/widget/windows/tests/TestUrisToValidate.h b/widget/windows/tests/TestUrisToValidate.h new file mode 100644 index 0000000000..cb00366d1e --- /dev/null +++ b/widget/windows/tests/TestUrisToValidate.h @@ -0,0 +1,471 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TestUrisToValidate_h
+#define mozilla_TestUrisToValidate_h
+
+const wchar_t* kTestUris[] = {
+ L"callto:%.txt",
+ L"callto:%00.txt",
+ L"callto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"fdaction:%.txt",
+ L"fdaction:%00.txt",
+ L"fdaction:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"feed:%.txt",
+ L"feed:%00.txt",
+ L"feed:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"feeds:%.txt",
+ L"feeds:%00.txt",
+ L"feeds:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"file:///%.txt",
+ L"file:///%00.txt",
+ L"file:///%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"firefox.url:%.txt",
+ L"firefox.url:%00.txt",
+ L"firefox.url:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"firefoxurl:%.txt",
+ L"firefoxurl:%00.txt",
+ L"firefoxurl:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ftp:%.txt",
+ L"ftp:%00.txt",
+ L"ftp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gopher:%.txt",
+ L"gopher:%00.txt",
+ L"gopher:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gtalk:%.txt",
+ L"gtalk:%00.txt",
+ L"gtalk:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"HTTP:%.txt",
+ L"HTTP:%00.txt",
+ L"HTTP:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"http:%.txt",
+ L"http:%00.txt",
+ L"http:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/%.txt",
+ L"https://bug389580.bmoattachments.org/%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"ie.ftp:%.txt",
+ L"ie.ftp:%00.txt",
+ L"ie.ftp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ie.http:%.txt",
+ L"ie.http:%00.txt",
+ L"ie.http:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ie.https:%.txt",
+ L"ie.https:%00.txt",
+ L"ie.https:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"irc:%.txt",
+ L"irc:%00.txt",
+ L"irc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ircs:%.txt",
+ L"ircs:%00.txt",
+ L"ircs:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itms:%.txt",
+ L"itms:%00.txt",
+ L"itms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itmss:%.txt",
+ L"itmss:%00.txt",
+ L"itmss:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itpc:%.txt",
+ L"itpc:%00.txt",
+ L"itpc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itms:%.txt",
+ L"itunes.assocprotocol.itms:%00.txt",
+ L"itunes.assocprotocol.itms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itmss:%.txt",
+ L"itunes.assocprotocol.itmss:%00.txt",
+ L"itunes.assocprotocol.itmss:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itunes.assocprotocol.itpc:%.txt",
+ L"itunes.assocprotocol.itpc:%00.txt",
+ L"itunes.assocprotocol.itpc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ldap:%.txt",
+ L"ldap:%00.txt",
+ L"ldap:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mailto:%.txt",
+ L"mailto:%00.txt",
+ L"mailto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mms:%.txt",
+ L"mms:%00.txt",
+ L"mms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mmst:%.txt",
+ L"mmst:%00.txt",
+ L"mmst:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mmsu:%.txt",
+ L"mmsu:%00.txt",
+ L"mmsu:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%00.txt",
+ L"https://bug389580.bmoattachments.org/"
+ L"Mozilla%20Thunderbird.Url.Mailto:%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+ L"navigatorurl:%.txt",
+ L"navigatorurl:%00.txt",
+ L"navigatorurl:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"news:%.txt",
+ L"news:%00.txt",
+ L"news:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"nntp:%.txt",
+ L"nntp:%00.txt",
+ L"nntp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"oms:%.txt",
+ L"oms:%00.txt",
+ L"oms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlook:%.txt",
+ L"outlook:%00.txt",
+ L"outlook:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlook.url.feed:%.txt",
+ L"outlook.url.feed:%00.txt",
+ L"outlook.url.feed:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlook.url.mailto:%.txt",
+ L"outlook.url.mailto:%00.txt",
+ L"outlook.url.mailto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlook.url.webcal:%.txt",
+ L"outlook.url.webcal:%00.txt",
+ L"outlook.url.webcal:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlookfeed:%.txt",
+ L"outlookfeed:%00.txt",
+ L"outlookfeed:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlookfeeds:%.txt",
+ L"outlookfeeds:%00.txt",
+ L"outlookfeeds:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"pnm:%.txt",
+ L"pnm:%00.txt",
+ L"pnm:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"prls.intappfile.ftp:%.txt",
+ L"prls.intappfile.ftp:%00.txt",
+ L"prls.intappfile.ftp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"prls.intappfile.http:%.txt",
+ L"prls.intappfile.http:%00.txt",
+ L"prls.intappfile.http:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"prls.intappfile.https:%.txt",
+ L"prls.intappfile.https:%00.txt",
+ L"prls.intappfile.https:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"prls.intappfile.mailto:%.txt",
+ L"prls.intappfile.mailto:%00.txt",
+ L"prls.intappfile.mailto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"rlogin:%.txt",
+ L"rlogin:%00.txt",
+ L"rlogin:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"rtsp:%.txt",
+ L"rtsp:%00.txt",
+ L"rtsp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"scp:%.txt",
+ L"scp:%00.txt",
+ L"scp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"sftp:%.txt",
+ L"sftp:%00.txt",
+ L"sftp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"sip:%.txt",
+ L"sip:%00.txt",
+ L"sip:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"skype:%.txt",
+ L"skype:%00.txt",
+ L"skype:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"snews:%.txt",
+ L"snews:%00.txt",
+ L"snews:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"telnet:%.txt",
+ L"telnet:%00.txt",
+ L"telnet:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"thunderbird.url.mailto:%.txt",
+ L"thunderbird.url.mailto:%00.txt",
+ L"thunderbird.url.mailto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"thunderbird.url.news:%.txt",
+ L"thunderbird.url.news:%00.txt",
+ L"thunderbird.url.news:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"tn3270:%.txt",
+ L"tn3270:%00.txt",
+ L"tn3270:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"tscrec4:%.txt",
+ L"tscrec4:%00.txt",
+ L"tscrec4:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"webcal:%.txt",
+ L"webcal:%00.txt",
+ L"webcal:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"webcals:%.txt",
+ L"webcals:%00.txt",
+ L"webcals:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"windowscalendar.urlwebcal.1:%.txt",
+ L"windowscalendar.urlwebcal.1:%00.txt",
+ L"windowscalendar.urlwebcal.1:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"windowsmail.url.mailto:%.txt",
+ L"windowsmail.url.mailto:%00.txt",
+ L"windowsmail.url.mailto:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"windowsmail.url.news:%.txt",
+ L"windowsmail.url.news:%00.txt",
+ L"windowsmail.url.news:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"windowsmail.url.nntp:%.txt",
+ L"windowsmail.url.nntp:%00.txt",
+ L"windowsmail.url.nntp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"windowsmail.url.snews:%.txt",
+ L"windowsmail.url.snews:%00.txt",
+ L"windowsmail.url.snews:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"wmp11.assocprotocol.mms:%.txt",
+ L"wmp11.assocprotocol.mms:%00.txt",
+ L"wmp11.assocprotocol.mms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"wpc:%.txt",
+ L"wpc:%00.txt",
+ L"wpc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ymsgr:%.txt",
+ L"ymsgr:%00.txt",
+ L"ymsgr:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"acrobat:%.txt",
+ L"acrobat:%00.txt",
+ L"acrobat:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"acsui:%.txt",
+ L"acsui:%00.txt",
+ L"acsui:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"aim:%.txt",
+ L"aim:%00.txt",
+ L"aim:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"allc8.commands.2:%.txt",
+ L"allc8.commands.2:%00.txt",
+ L"allc8.commands.2:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"allholdem.commands.2:%.txt",
+ L"allholdem.commands.2:%00.txt",
+ L"allholdem.commands.2:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"allpoker.commands.2:%.txt",
+ L"allpoker.commands.2:%00.txt",
+ L"allpoker.commands.2:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"aolautofix:%.txt",
+ L"aolautofix:%00.txt",
+ L"aolautofix:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"aolds:%.txt",
+ L"aolds:%00.txt",
+ L"aolds:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"bc:%.txt",
+ L"bc:%00.txt",
+ L"bc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"bctp:%.txt",
+ L"bctp:%00.txt",
+ L"bctp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"bittorrent:%.txt",
+ L"bittorrent:%00.txt",
+ L"bittorrent:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"camfrog:%.txt",
+ L"camfrog:%00.txt",
+ L"camfrog:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"csi:%.txt",
+ L"csi:%00.txt",
+ L"csi:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"cvs:%.txt",
+ L"cvs:%00.txt",
+ L"cvs:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"daap:%.txt",
+ L"daap:%00.txt",
+ L"daap:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ed2k:%.txt",
+ L"ed2k:%00.txt",
+ L"ed2k:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"explorer.assocprotocol.search-ms:%.txt",
+ L"explorer.assocprotocol.search-ms:%00.txt",
+ L"explorer.assocprotocol.search-ms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gizmoproject:%.txt",
+ L"gizmoproject:%00.txt",
+ L"gizmoproject:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gnet:%.txt",
+ L"gnet:%00.txt",
+ L"gnet:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gnutella:%.txt",
+ L"gnutella:%00.txt",
+ L"gnutella:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"gsarcade:%.txt",
+ L"gsarcade:%00.txt",
+ L"gsarcade:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"hcp:%.txt",
+ L"hcp:%00.txt",
+ L"hcp:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"icquser:%.txt",
+ L"icquser:%00.txt",
+ L"icquser:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"icy:%.txt",
+ L"icy:%00.txt",
+ L"icy:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"imesync:%.txt",
+ L"imesync:%00.txt",
+ L"imesync:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itunes.assocprotocol.daap:%.txt",
+ L"itunes.assocprotocol.daap:%00.txt",
+ L"itunes.assocprotocol.daap:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"itunes.assocprotocol.pcast:%.txt",
+ L"itunes.assocprotocol.pcast:%00.txt",
+ L"itunes.assocprotocol.pcast:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"joost:%.txt",
+ L"joost:%00.txt",
+ L"joost:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"m4macdrive:%.txt",
+ L"m4macdrive:%00.txt",
+ L"m4macdrive:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"magnet:%.txt",
+ L"magnet:%00.txt",
+ L"magnet:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mapi:%.txt",
+ L"mapi:%00.txt",
+ L"mapi:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mc12:%.txt",
+ L"mc12:%00.txt",
+ L"mc12:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mediajukebox:%.txt",
+ L"mediajukebox:%00.txt",
+ L"mediajukebox:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"morpheus:%.txt",
+ L"morpheus:%00.txt",
+ L"morpheus:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mp2p:%.txt",
+ L"mp2p:%00.txt",
+ L"mp2p:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"mpodcast:%.txt",
+ L"mpodcast:%00.txt",
+ L"mpodcast:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"msbd:%.txt",
+ L"msbd:%00.txt",
+ L"msbd:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"msdigitallocker:%.txt",
+ L"msdigitallocker:%00.txt",
+ L"msdigitallocker:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"outlook.url.stssync:%.txt",
+ L"outlook.url.stssync:%00.txt",
+ L"outlook.url.stssync:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"p2p:%.txt",
+ L"p2p:%00.txt",
+ L"p2p:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"pando:%.txt",
+ L"pando:%00.txt",
+ L"pando:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"pcast:%.txt",
+ L"pcast:%00.txt",
+ L"pcast:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"picasa:%.txt",
+ L"picasa:%00.txt",
+ L"picasa:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"plaxo:%.txt",
+ L"plaxo:%00.txt",
+ L"plaxo:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"play:%.txt",
+ L"play:%00.txt",
+ L"play:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"podcast:%.txt",
+ L"podcast:%00.txt",
+ L"podcast:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ppmate:%.txt",
+ L"ppmate:%00.txt",
+ L"ppmate:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ppmates:%.txt",
+ L"ppmates:%00.txt",
+ L"ppmates:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ppstream:%.txt",
+ L"ppstream:%00.txt",
+ L"ppstream:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"quicktime:%.txt",
+ L"quicktime:%00.txt",
+ L"quicktime:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"realplayer.autoplay.6:%.txt",
+ L"realplayer.autoplay.6:%00.txt",
+ L"realplayer.autoplay.6:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"realplayer.cdburn.6:%.txt",
+ L"realplayer.cdburn.6:%00.txt",
+ L"realplayer.cdburn.6:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"rhap:%.txt",
+ L"rhap:%00.txt",
+ L"rhap:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"sc:%.txt",
+ L"sc:%00.txt",
+ L"sc:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"search-ms:%.txt",
+ L"search-ms:%00.txt",
+ L"search-ms:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"shareaza:%.txt",
+ L"shareaza:%00.txt",
+ L"shareaza:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"shell:%.txt",
+ L"shell:%00.txt",
+ L"shell:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"shout:%.txt",
+ L"shout:%00.txt",
+ L"shout:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"sig2dat:%.txt",
+ L"sig2dat:%00.txt",
+ L"sig2dat:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"sop:%.txt",
+ L"sop:%00.txt",
+ L"sop:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"steam:%.txt",
+ L"steam:%00.txt",
+ L"steam:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"stssync:%.txt",
+ L"stssync:%00.txt",
+ L"stssync:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"svn:%.txt",
+ L"svn:%00.txt",
+ L"svn:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"svn+ssh:%.txt",
+ L"svn+ssh:%00.txt",
+ L"svn+ssh:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"synacast:%.txt",
+ L"synacast:%00.txt",
+ L"synacast:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"torrent:%.txt",
+ L"torrent:%00.txt",
+ L"torrent:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"tsvn:%.txt",
+ L"tsvn:%00.txt",
+ L"tsvn:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"tvants:%.txt",
+ L"tvants:%00.txt",
+ L"tvants:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"tvu:%.txt",
+ L"tvu:%00.txt",
+ L"tvu:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"unsv:%.txt",
+ L"unsv:%00.txt",
+ L"unsv:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"uvox:%.txt",
+ L"uvox:%00.txt",
+ L"uvox:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"ventrilo:%.txt",
+ L"ventrilo:%00.txt",
+ L"ventrilo:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"vs:%.txt",
+ L"vs:%00.txt",
+ L"vs:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"zune:%.txt",
+ L"zune:%00.txt",
+ L"zune:%41%2D%31%5Ftest"ing?%41%31%00.txt",
+ L"https://example.com/?a=123&b=456",
+ L"https://example.com/#123?a=123&b=456",
+ L"https://example.com/?#123a=123&b=456",
+ L"https://example.com/?a=123&b=456#123",
+ L"mailto:%41%42%23%31",
+ L"mailto:%41%42%23%31#fragment",
+ L"news:%41%42%23%31",
+ L"news:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31",
+ L"microsoft-edge:%41%42%23%31#fragment",
+ L"microsoft-edge:%41%42%23%31#fragment#",
+ L"microsoft-edge:%41%42%23%31####",
+ L"something-unknown:",
+ L"something-unknown:x=123",
+ L"something-unknown:?=123",
+ L"something-unknown:#code=0123456789%200123456789&x=01234567890123456789",
+};
+
+#endif // mozilla_TestUrisToValidate_h
diff --git a/widget/windows/tests/TestWinDND.cpp b/widget/windows/tests/TestWinDND.cpp new file mode 100644 index 0000000000..51e43cf19e --- /dev/null +++ b/widget/windows/tests/TestWinDND.cpp @@ -0,0 +1,696 @@ +/* -*- 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 "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; + constexpr auto mozString = u"Mozilla can drag and drop"_ns; + nsCOMPtr<nsISupportsString> xferString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); + rv = xferString->SetData(mozString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("text/unicode", genericWrapper, + mozString.Length() * sizeof(char16_t)); + return rv; +} + +nsresult GetTransferableTextTwo(nsCOMPtr<nsITransferable>& pTransferable) { + nsresult rv; + constexpr auto mozString = u" twice over"_ns; + nsCOMPtr<nsISupportsString> xferString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); + rv = xferString->SetData(mozString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> genericWrapper = do_QueryInterface(xferString); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + pTransferable->Init(nullptr); + rv = pTransferable->SetTransferData("text/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..8d6c88c5e2 --- /dev/null +++ b/widget/windows/tests/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +GeckoCppUnitTests( + [ + "TestUriValidation", + ], + linkage=None, +) + +LOCAL_INCLUDES += [] + +OS_LIBS += [ + "oleaut32", + "ole32", + "shell32", + "shlwapi", + "urlmon", +] + +if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] in ("gcc", "clang"): + # This allows us to use wmain as the entry point on mingw + LDFLAGS += [ + "-municode", + ] diff --git a/widget/windows/touchinjection_sdk80.h b/widget/windows/touchinjection_sdk80.h new file mode 100644 index 0000000000..e2eeceb15b --- /dev/null +++ b/widget/windows/touchinjection_sdk80.h @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + PT_TOUCHPAD = 0x00000005, // Touch pad +}; + +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 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 |